77// SPDX-License-Identifier: MIT
88//
99
10- pub mod job;
11-
12- use crate :: job:: Database ;
10+ use cron:: job:: Database ;
1311use chrono:: Local ;
1412use gettextrs:: { bind_textdomain_codeset, setlocale, textdomain, LocaleCategory } ;
1513use std:: env;
1614use std:: error:: Error ;
17- use std:: fs;
1815use std:: str:: FromStr ;
16+ use std:: fs;
17+ use std:: time:: UNIX_EPOCH ;
18+ use std:: fmt;
19+ use std:: sync:: Mutex ;
1920
20- fn parse_cronfile ( username : & str ) -> Result < Database , Box < dyn Error > > {
21- #[ cfg( target_os = "linux" ) ]
22- let file = format ! ( "/var/spool/cron/{username}" ) ;
23- #[ cfg( target_os = "macos" ) ]
24- let file = format ! ( "/var/at/tabs/{username}" ) ;
25- let s = fs:: read_to_string ( & file) ?;
26- Ok ( s. lines ( )
27- . filter_map ( |x| Database :: from_str ( x) . ok ( ) )
28- . fold ( Database ( vec ! [ ] ) , |acc, next| acc. merge ( next) ) )
21+ static CRONTAB : Mutex < Option < Database > > = Mutex :: new ( None ) ;
22+ static LAST_MODIFIED : Mutex < Option < u64 > > = Mutex :: new ( None ) ;
23+
24+ #[ derive( Debug ) ]
25+ enum CronError {
26+ Fork ,
27+ NoLogname ,
28+ NoCrontab
2929}
3030
31- fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
32- setlocale ( LocaleCategory :: LcAll , "" ) ;
33- textdomain ( "posixutils-rs" ) ?;
34- bind_textdomain_codeset ( "posixutils-rs" , "UTF-8" ) ?;
31+ impl Error for CronError { }
32+
33+ impl fmt:: Display for CronError {
34+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
35+ match self {
36+ Self :: NoLogname => write ! ( f, "Could not obtain the user's logname" ) ,
37+ Self :: NoCrontab => write ! ( f, "Could not format database" ) ,
38+ Self :: Fork => write ! ( f, "Could not create child process" )
39+ }
40+ }
41+ }
42+
43+ /// Check if logname file is changed
44+ fn is_file_changed ( filepath : & str ) -> Result < bool , Box < dyn Error > > {
45+ let last_modified = fs:: metadata ( filepath) ?
46+ . modified ( ) ?
47+ . duration_since ( UNIX_EPOCH ) ?
48+ . as_secs ( ) ;
49+
50+ let Some ( last_checked) = * LAST_MODIFIED . lock ( ) . unwrap ( ) else {
51+ * LAST_MODIFIED . lock ( ) . unwrap ( ) = Some ( last_modified) ;
52+ return Ok ( true ) ;
53+ } ;
54+ if last_checked <= last_modified{
55+ * LAST_MODIFIED . lock ( ) . unwrap ( ) = Some ( last_modified) ;
56+ Ok ( true )
57+ } else {
58+ Ok ( false )
59+ }
60+ }
3561
62+ /// Update [`CRONTAB`] if logname file is changed
63+ fn sync_cronfile ( ) -> Result < ( ) , Box < dyn Error > > {
3664 let Ok ( logname) = env:: var ( "LOGNAME" ) else {
37- panic ! ( "Could not obtain the user's logname." )
65+ return Err ( Box :: new ( CronError :: NoLogname ) ) ;
3866 } ;
67+ #[ cfg( target_os = "linux" ) ]
68+ let file = format ! ( "/var/spool/cron/{logname}" ) ;
69+ #[ cfg( target_os = "macos" ) ]
70+ let file = format ! ( "/var/at/tabs/{logname}" ) ;
71+ if ( * CRONTAB . lock ( ) . unwrap ( ) ) . is_none ( ) || is_file_changed ( & file) ?{
72+ let s = fs:: read_to_string ( & file) ?;
73+ let crontab = s. lines ( )
74+ . filter_map ( |x| Database :: from_str ( x) . ok ( ) )
75+ . fold ( Database ( vec ! [ ] ) , |acc, next| acc. merge ( next) ) ;
76+ * CRONTAB . lock ( ) . unwrap ( ) = Some ( crontab) ;
77+ }
78+ Ok ( ( ) )
79+ }
3980
40- // Daemon setup
81+ /// Create new daemon process of crond
82+ fn setup ( ) -> i32 {
4183 unsafe {
4284 use libc:: * ;
4385
4486 let pid = fork ( ) ;
45- if pid > 0 {
46- return Ok ( ( ) ) ;
87+ if pid != 0 {
88+ return pid ;
4789 }
4890
4991 setsid ( ) ;
@@ -52,12 +94,28 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
5294 close ( STDIN_FILENO ) ;
5395 close ( STDOUT_FILENO ) ;
5496 close ( STDERR_FILENO ) ;
97+
98+ return pid;
5599 }
100+ }
56101
57- // Daemon code
102+ /// Handles incoming signals
103+ fn handle_signals ( signal_code : libc:: c_int ) {
104+ if signal_code == libc:: SIGHUP {
105+ if let Err ( err) = sync_cronfile ( ) {
106+ eprintln ! ( "{err}" ) ;
107+ std:: process:: exit ( 1 ) ;
108+ }
109+ }
110+ }
58111
112+ /// Daemon loop
113+ fn daemon_loop ( ) -> Result < ( ) , Box < dyn Error > > {
59114 loop {
60- let db = parse_cronfile ( & logname) ?;
115+ sync_cronfile ( ) ?;
116+ let Some ( db) = CRONTAB . lock ( ) . unwrap ( ) . clone ( ) else {
117+ return Err ( Box :: new ( CronError :: NoCrontab ) ) ;
118+ } ;
61119 let Some ( x) = db. nearest_job ( ) else {
62120 sleep ( 60 ) ;
63121 continue ;
@@ -79,6 +137,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
79137 }
80138}
81139
140+ fn main ( ) -> Result < ( ) , Box < dyn Error > > {
141+ setlocale ( LocaleCategory :: LcAll , "" ) ;
142+ textdomain ( "posixutils-rs" ) ?;
143+ bind_textdomain_codeset ( "posixutils-rs" , "UTF-8" ) ?;
144+
145+ let pid = setup ( ) ;
146+
147+ if pid < 0 {
148+ return Err ( Box :: new ( CronError :: Fork ) ) ;
149+ } else if pid > 0 {
150+ return Ok ( ( ) ) ;
151+ }
152+
153+ unsafe {
154+ libc:: signal ( libc:: SIGHUP , handle_signals as usize ) ;
155+ }
156+
157+ daemon_loop ( )
158+ }
159+
82160fn sleep ( target : u32 ) {
83161 unsafe { libc:: sleep ( target) } ;
84- }
162+ }
0 commit comments