time.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013 package require Tcl 8.0;
00014 package require log;
00015
00016 namespace ::time {
00017 variable version 1.2.1
00018 variable rcsid {$Id: time.tcl,v 1.19 2006/09/19 23:36:17 andreas_kupries Exp $}
00019
00020 namespace export configure gettime server cleanup
00021
00022 variable options
00023 if {![info exists options]} {
00024 array options = {
00025 -timeserver {}
00026 -port 37
00027 -protocol tcp
00028 -timeout 10000
00029 -command {}
00030 -loglevel warning
00031 }
00032 if {![catch {package require udp}]} {
00033 options = (-protocol) udp
00034 } else {
00035 if {![catch {package require ceptcl}]} {
00036 options = (-protocol) udp
00037 }
00038 }
00039 log::lvSuppressLE emergency 0
00040 log::lvSuppressLE $options(-loglevel) 1
00041 log::lvSuppress $options(-loglevel) 0
00042 }
00043
00044
00045
00046 variable epoch
00047 if {![info exists epoch]} {
00048 array epoch = {
00049 unix 2208988800
00050 }
00051 }
00052
00053
00054 variable uid
00055 if {![info exists uid]} {
00056 uid = 0
00057 }
00058 }
00059
00060
00061
00062
00063
00064
00065 ret ::time::cget (type optionname) {
00066 return [configure $optionname]
00067 }
00068
00069
00070
00071
00072
00073 ret ::time::configure (type args) {
00074 variable options
00075 set r {}
00076 set cget 0
00077
00078 if {[llength $args] < 1} {
00079 foreach opt [lsort [array names options]] {
00080 lappend r $opt $options($opt)
00081 }
00082 return $r
00083 }
00084
00085 if {[llength $args] == 1} {
00086 set cget 1
00087 }
00088
00089 while {[string match -* [set option [lindex $args 0]]]} {
00090 switch -glob -- $option {
00091 -port { set r [SetOrGet -port $cget] }
00092 -timeout { set r [SetOrGet -timeout $cget] }
00093 -protocol { set r [SetOrGet -protocol $cget] }
00094 -command { set r [SetOrGet -command $cget] }
00095 -loglevel {
00096 if {$cget} {
00097 return $options(-loglevel)
00098 } else {
00099 set options(-loglevel) [Pop args 1]
00100 log::lvSuppressLE emergency 0
00101 log::lvSuppressLE $options(-loglevel) 1
00102 log::lvSuppress $options(-loglevel) 0
00103 }
00104 }
00105 -- { Pop args ; break }
00106 default {
00107 set err [join [lsort [array names options -*]] ", "]
00108 return -code error "bad option \"$option\": must be $err"
00109 }
00110 }
00111 Pop args
00112 }
00113
00114 return $r
00115 }
00116
00117
00118 ret ::time::SetOrGet (type option , optional cget =0) {
00119 upvar options options
00120 upvar args args
00121 if {$cget} {
00122 return $options($option)
00123 } else {
00124 set options($option) [Pop args 1]
00125 }
00126 return {}
00127 }
00128
00129
00130
00131 ret ::time::getsntp (type args) {
00132 set token [eval [linsert $args 0 CommonSetup -port 123]]
00133 upvar #0 $token State
00134 set State(rfc) 2030
00135 return [QueryTime $token]
00136 }
00137
00138 ret ::time::gettime (type args) {
00139 set token [eval [linsert $args 0 CommonSetup -port 37]]
00140 upvar #0 $token State
00141 set State(rfc) 868
00142 return [QueryTime $token]
00143 }
00144
00145 ret ::time::CommonSetup (type args) {
00146 variable options
00147 variable uid
00148 set token [namespace current]::[incr uid]
00149 variable $token
00150 upvar 0 $token State
00151
00152 array set State [array get options]
00153 set State(status) unconnected
00154 set State(data) {}
00155
00156 while {[string match -* [set option [lindex $args 0]]]} {
00157 switch -glob -- $option {
00158 -port { set State(-port) [Pop args 1] }
00159 -timeout { set State(-timeout) [Pop args 1] }
00160 -proto* { set State(-protocol) [Pop args 1] }
00161 -command { set State(-command) [Pop args 1] }
00162 -- { Pop args ; break }
00163 default {
00164 set err [join [lsort [array names State -*]] ", "]
00165 return -code error "bad option \"$option\":\
00166 must be $err."
00167 }
00168 }
00169 Pop args
00170 }
00171
00172 set len [llength $args]
00173 if {$len < 1 || $len > 2} {
00174 if {[catch {info level -1} arg0]} {
00175 set arg0 [info level 0]
00176 }
00177 return -code error "wrong # args: should be\
00178 \"[lindex $arg0 0] ?options? timeserver ?port?\""
00179 }
00180
00181 set State(-timeserver) [lindex $args 0]
00182 if {$len == 2} {
00183 set State(-port) [lindex $args 1]
00184 }
00185
00186 return $token
00187 }
00188
00189 ret ::time::QueryTime (type token) {
00190 variable $token
00191 upvar 0 $token State
00192
00193 if {[string equal $State(-protocol) "udp"]} {
00194 if {[llength [package provide ceptcl]] == 0 \
00195 && [llength [package provide udp]] == 0} {
00196 set State(status) error
00197 set State(error) "udp support is not available, \
00198 either ceptcl or tcludp required"
00199 return $token
00200 }
00201 }
00202
00203 if {[catch {
00204 if {[string equal $State(-protocol) "udp"]} {
00205 if {[llength [package provide ceptcl]] > 0} {
00206 # using ceptcl
00207 set State(sock) [cep -type datagram \
00208 $State(-timeserver) $State(-port)]
00209 fconfigure $State(sock) -blocking 0
00210 } else {
00211 # using tcludp
00212 set State(sock) [udp_open]
00213 udp_conf $State(sock) $State(-timeserver) $State(-port)
00214 }
00215 } else {
00216 set State(sock) [socket $State(-timeserver) $State(-port)]
00217 }
00218 } sockerr]} {
00219 set State(status) error
00220 set State(error) $sockerr
00221 return $token
00222 }
00223
00224 # setup the timeout
00225 if {$State(-timeout) > 0} {
00226 set State(after) [after $State(-timeout) \
00227 [list [namespace origin reset] $token timeout]]
00228 }
00229
00230 set State(status) connect
00231 fconfigure $State(sock) -translation binary -buffering none
00232
00233 # SNTP wants a 48 byte request while TIME doesn't care and is happy
00234 # to accept any old rubbish. If protocol is TCP then merely connecting
00235 # is sufficient to elicit a response.
00236 if {[string equal $State(-protocol) "udp"]} {
00237 set len [expr {($State(rfc) == 2030) ? 47 : 3}]
00238 puts -nonewline $State(sock) \x0b[string repeat \0 $len]
00239 }
00240
00241 fileevent $State(sock) readable \
00242 [list [namespace origin ClientReadEvent] $token]
00243
00244 if {$State(-command) == {}} {
00245 wait $token
00246 }
00247
00248 return $token
00249 }
00250
00251 ret ::time::unixtime (optional token ={)} {
00252 variable $token
00253 variable epoch
00254 upvar 0 $token State
00255 if {$State(status) != "ok"} {
00256 return -code error $State(error)
00257 }
00258
00259
00260 if {[string length $State(data)] == 4} {
00261
00262 if {[binary scan $State(data) I r] < 1} {
00263 return -code error "Unable to scan data"
00264 }
00265 return [expr {int($r - $epoch(unix))&0xffffffff}]
00266 } elseif {[string length $State(data)] > 47} {
00267
00268 if {[binary scan $State(data) c40II -> sec frac] < 1} {
00269 return -code error "Failed to decode result"
00270 }
00271 return [expr {int($sec - $epoch(unix))&0xffffffff}]
00272 } else {
00273 return -code error "error: data format not recognised"
00274 }
00275 }
00276
00277 ret ::time::status (type token) {
00278 variable $token
00279 upvar 0 $token State
00280 return $State(status)
00281 }
00282
00283 ret ::time::error (type token) {
00284 variable $token
00285 upvar 0 $token State
00286 set r {}
00287 if {[info exists State(error)]} {
00288 set r $State(error)
00289 }
00290 return $r
00291 }
00292
00293 ret ::time::wait (type token) {
00294 variable $token
00295 upvar 0 $token State
00296
00297 if {$State(status) == "connect"} {
00298 vwait [subst $token](status)
00299 }
00300
00301 return $State(status)
00302 }
00303
00304 ret ::time::reset (type token , optional why =reset) {
00305 variable $token
00306 upvar 0 $token State
00307 set reason {}
00308 set State(status) $why
00309 catch {fileevent $State(sock) readable {}}
00310 if {$why == "timeout"} {
00311 set reason "timeout ocurred"
00312 }
00313 Finish $token $reason
00314 }
00315
00316
00317
00318
00319 ret ::time::cleanup (type token) {
00320 variable $token
00321 upvar 0 $token State
00322 if {[info exists State]} {
00323 unset State
00324 }
00325 }
00326
00327
00328
00329 ret ::time::ClientReadEvent (type token) {
00330 variable $token
00331 upvar 0 $token State
00332
00333 append State(data) [read $State(sock)]
00334 set expected [expr {($State(rfc) == 868) ? 4 : 48}]
00335 if {[string length $State(data)] < $expected} { return }
00336
00337 #FIX ME: acquire peer data?
00338
00339 set State(status) ok
00340 Finish $token
00341 return
00342 }
00343
00344 ret ::time::Finish (type token , optional errormsg ={)} {
00345 variable $token
00346 upvar 0 $token State
00347 global errorInfo errorCode
00348
00349 if {[string length $errormsg] > 0} {
00350 State = (error) $errormsg
00351 State = (status) error
00352 }
00353 catch {close $State(sock)}
00354 catch {after cancel $State(after)}
00355 if {[info exists State(-command)] && $State(-command) != {}} {
00356 if {[catch {eval $State(-command) {$token}} err]} {
00357 if {[string length $errormsg] == 0} {
00358 State = (error) [list $err $errorInfo $errorCode]
00359 State = (status) error
00360 }
00361 }
00362 if {[info exists State(-command)]} {
00363 un State = (-command)
00364 }
00365 }
00366 }
00367
00368
00369
00370
00371
00372 ret ::time::Pop (type varname , optional nth =0) {
00373 upvar $varname args
00374 set r [lindex $args $nth]
00375 set args [lreplace $args $nth $nth]
00376 return $r
00377 }
00378
00379
00380
00381 package provide time $::time::version
00382
00383
00384
00385
00386
00387
00388