# # Copyright (C) 2010 Alexandros Stergiakis <alsterg@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # #//# # The "ntp" module defines commands for clock configuration, calendar, and Network Time Protocol (NTP) Client Support. #//# module require base 1.0 module provide ntp 1.0 namespace eval ::module::ntp { namespace import ::helper::* ::module::api::* proc description {} { return "Clock, Calendar & NTP Support" } proc version {} { } proc check {} { foreach feature [list \ CONFIG_HWCLOCK \ CONFIG_DATE \ CONFIG_FEATURE_DATE_ISOFMT \ CONFIG_RDATE] { if {! [::helper::busybox_has $feature]} { ;# Can return error. error "Busybox doesn't have support for $feature." } } } proc reset {} { variable TZ_FILE # Default timezone: UTC fileutil::writeFile $TZ_FILE "UTC" # A list that holds the timezone information configure via the # 'clock timezone' command. If empty, no timezone is configured. variable time_zone {} # A list that holds the summer time information configure via the # 'clock summer-time' command. If empty, no summer-time is configured. variable summer_time {} variable ntp_server {} } proc constructor {} { log::Info "Loading \"ntp\" module: [description]" Global ETC_DIR variable TZ_FILE [file join $ETC_DIR TZ] check reset # Finally load Command Specs sysconf loadspecs "modules/ntp/ntp.specs" # Set system clock from hardware clock. # If hardware clock not available or not set, ignore. exec hwclock -l -s } proc destructor {} { # First unload Command Specs sysconf remove "ntp" reset } ################ # Handlers ################ ################################ # Clock & Calendar ################################ # This is the local time, after any timezone. # @todo after 'clock set' is entered for execution, some extra new lines are printed. command ClockSet {cmdline argstart sid out no arguments args} { set time [dict get $arguments TIME] if {[catch {clock scan $time -format %T} errMsg]} { ferror "erroneous time" [getpos TIME] } set day [dict get $arguments DAY] if {[catch {clock scan $day -format %d} errMsg]} { ferror "erroneous day" [getpos DAY] } set month [dict get $arguments MONTH] if {[catch {clock scan $month -format %b} errMsg]} { ferror "erroneous month" [getpos MONTH] } set year [dict get $arguments YEAR] if {[catch {clock scan $year -format %Y} errMsg]} { ferror "erroneous year" [getpos YEAR] } set clck [clock scan "$time $day $month $year" -format {%T %d %b %Y}] # Format accepted by 'date': 2003.08.14-19:50:00 set frmt [clock format $clck -format {%Y.%m.%d-%H:%M:%S}] exec date $frmt } # @see http://www.gnu.org/software/libtool/manual/libc/TZ-Variable.html # @todo Timezone abbreviation is not set in TZ variable. # @diff Sign is a separate argument and not embedded in the offsets. command ClockTimezone {cmdline argstart sid out no arguments args} { variable TZ_FILE variable time_zone if {$no} { variable summer_time set ::env(TZ) {} set time_zone {} set summer_time {} return } # @todo Currently the TIMEZONE argument is rather tricky. I don't know which abbreviations # are valid, and will be accepted from uclibc (via the TZ env variable). Also since it is also # mandatory to specify the offset from UTC, it is unclear to me what's the purpuse of # of the timezone name. Currently we accept it, but make no use of it, other than being listed # in the configuration file. The TZ variable will always be set with UTC as the timezone name. set tz [dict get $arguments TIMEZONE] # Check that is recognized by Tcl. if {[catch {clock scan $tz -format %Z} errMsg]} { ferror "erroneous timezone string" [getpos TIMEZONE] } set sign [dict get $arguments SIGN] set hours [dict get $arguments HOURS] if {[dict exists $arguments MINUTES]} { set mins [dict get $arguments MINUTES] } else { set mins 0 } if {! ([string is integer $hours] && (0 <= $hours < 24))} { ferror "must be an integer in the range: 0..23" [getpos HOURS] } if {! ([string is integer $mins] && (0 <= $mins < 60))} { ferror "must be an integer in the range: 0..59" [getpos MINUTES] } set hours [format {%02d} $hours] set mins [format {%02d} $mins] # By setting it here it takes effect imediately in this process, # (all threads and interpreters), as well as any new child process # (with exec or fork). set ::env(TZ) "UTC${sign}${hours}:${mins}" fileutil::writeFile $TZ_FILE $::env(TZ) set time_zone [list $tz $sign $hours $mins] return } proc print_ClockTimezone {} { variable time_zone if {! [lempty $time_zone]} { return "clock timezone $time_zone\n" } return } # Will not take effect until timezone is set first with 'clock timezone' command. # @see a # @limit The 'clock summer-time <zone> date ...' version of the command is not supported. # @diff In the recurring form, the day,week,month,time are mandatory. command ClockSummertime {cmdline argstart sid out no arguments args} { variable TZ_FILE variable summer_time variable time_zone if {$no} { set summer_time {} if {! [lempty $time_zone]} { lassign $time_zone tz sign hours mins set ::env(TZ) "UTC${sign}${hours}:${mins}" } else { set ::env(TZ) {} } return } if {[lempty $time_zone]} { error "please set the timezone first" } set tz [getval TIMEZONE] lassign [getall WEEK] sweek eweek lassign [getall DAY] sday eday lassign [getall MONTH] smonth emonth lassign [getall TIME] stime etime set offset [getval OFFSET {}] # TIMEZONE if {[catch {clock scan $tz -format %Z} errMsg]} { ferror "erroneous timezone" [getpos TIMEZONE] } # START DATE if {! [string is integer $sweek] || ! (1 <= $sweek <= 5)} { ferror "start week must be in the range 1..5" [getpos WEEK] } if {! [string is integer $sday] || ! (0 <= $sday <= 6)} { ferror "start day must be in the range 0..6" [getpos DAY] } if {! [string is integer $smonth] || ! (1 <= $smonth <= 12)} { ferror "start month must be in the range 1..12" [getpos MONTH] } if {[catch { clock scan $stime -format %T }]} { ferror "start time must be in the format HH:MM:SS" [getpos TIME] } # END DATE if {! [string is integer $sweek] || ! (1 <= $eweek <= 5)} { ferror "end week must be in the range 1..5" [getpos WEEK 1] } if {! [string is integer $eday] || ! (0 <= $eday <= 6)} { ferror "end day must be in the range 0..6" [getpos DAY 1] } if {! [string is integer $emonth] || ! (1 <= $emonth <= 12)} { ferror "end month must be in the range 1..12" [getpos MONTH 1] } if {[catch { clock scan $etime -format %T }]} { ferror "end time must be in the format HH:MM:SS" [getpos TIME 1] } # OFFSET if {[lempty $offset]} { set offset 0 } elseif {! [string is integer $offset] || ! (0*60 <= $offset <= 23*60)} { ferror "offset must be in the range 0..23" [getpos OFFSET] } set hours [format {%02d} [expr {$offset / 60}]] set mins [format {%02d} [expr {$offset % 60}]] lassign $time_zone tz sign hrs mns set ::env(TZ) "UTC${sign}${hrs}:${mns}:00${tz}+${hours}:${mins}:00,M${smonth}.${sweek}.${sday}/${stime},M${emonth}.${eweek}.${eday}/${etime}" fileutil::writeFile $TZ_FILE $::env(TZ) set summer_time [list $tz $sweek $sday $smonth $stime $eweek $eday $emonth $etime $offset] sputs $::env(TZ) return } proc print_ClockSummertime {} { variable summer_time if {! [lempty $summer_time]} { lassign $summer_time timezone sweek sday smonth stime eweek eday emonth etime offset return "clock summer-time $timezone recurring $sweek $sday $smonth $stime $eweek $eday $emonth $etime $offset\n" } return } command ClockReadcal {cmdline argstart sid out no arguments args} { # -s : Set system time from hardware clock # -l : Set hardware clock to system time exec hwclock -l -s } # @diff No "calendar set" command. "clock update-calendar" is the only way to write to the calendar. command ClockUpdatecal {cmdline argstart sid out no arguments args} { # -w : Set hardware clock to system time # -l : Set hardware clock to system time exec hwclock -l -w } command ShClock {cmdline argstart sid out no arguments args} { clock format [clock seconds] -format {%a %b %d %H:%M:%S %Z %Y} } command NtpSever {cmdline argstart sid out no arguments args} { variable ntp_server set host [dict get $arguments HOST] if {! [ishostname $host] && ! [isip $host]} { ferror "illegal IP address or hostname" [getpos HOST] } set ntp_server $host exec rdate -s $host } proc print_NtpSever {} { variable ntp_server if {! [lempty $ntp_server]} { return "ntp server $ntp_server\n" } return } } ;# End of Namespace