/* | Converts a relative directory path to an absolute. | | Params on Lua stack: | 1: a relative path to directory | | Returns on Lua stack: | The absolute path of directory */ static int l_realdir( lua_State *L ) { luaL_Buffer b; const char *rdir = luaL_checkstring(L, 1); char *adir = get_realpath(rdir); if( !adir ) { printlogf( L, "Error", "failure getting absolute path of [%s]", rdir ); return 0; } { // makes sure its a directory struct stat st; if( stat( adir, &st ) ) { printlogf( L, "Error", "cannot get absolute path of dir '%s': %s", rdir, strerror( errno ) ); free( adir ); return 0; } if( !S_ISDIR( st.st_mode ) ) { printlogf( L, "Error", "cannot get absolute path of dir '%s': is not a directory", rdir ); free( adir ); return 0; } } // returns absolute path with a concated '/' luaL_buffinit( L, &b ); luaL_addstring( &b, adir ); luaL_addchar( &b, '/' ); luaL_pushresult( &b ); free( adir ); return 1; }
/* | Dumps the Lua stack. | For debugging purposes. */ int l_stackdump( lua_State * L ) { int i; int top = lua_gettop( L ); printlogf( L, "Debug", "total in stack %d", top ); for( i = 1; i <= top; i++ ) { int t = lua_type( L, i ); switch( t ) { case LUA_TSTRING: printlogf( L, "Debug", "%d string: '%s'", i, lua_tostring( L, i ) ); break; case LUA_TBOOLEAN: printlogf( L, "Debug", "%d boolean %s", i, lua_toboolean( L, i ) ? "true" : "false" ); break; case LUA_TNUMBER: printlogf( L, "Debug", "%d number: %g", i, lua_tonumber( L, i ) ); break; default: printlogf( L, "Debug", "%d %s", i, lua_typename( L, t ) ); break; } } return 0; }
int TestStartupJobs(void) { short nJobs = 0; time_t t1 = time(NULL); CronFile *file; CronLine *line; t1 = t1 - t1 % 60 + 60; for (file = FileBase; file; file = file->cf_Next) { if (DebugOpt) printlogf(LOG_DEBUG, "TestStartup for FILE %s/%s USER %s:\n", file->cf_DPath, file->cf_FileName, file->cf_UserName); for (line = file->cf_LineBase; line; line = line->cl_Next) { struct CronWaiter *waiter; if (DebugOpt) { if (line->cl_JobName) printlogf(LOG_DEBUG, " LINE %s JOB %s\n", line->cl_Shell, line->cl_JobName); else printlogf(LOG_DEBUG, " LINE %s\n", line->cl_Shell); } if (line->cl_Freq == -1) { /* freq is @reboot */ line->cl_Pid = -1; /* if we have any waiters, reset them and arm Pid = -2 */ waiter = line->cl_Waiters; while (waiter != NULL) { waiter->cw_Flag = -1; line->cl_Pid = -2; /* we only arm @noauto jobs we're waiting on, not other @reboot jobs */ if (waiter->cw_NotifLine && waiter->cw_NotifLine->cl_Freq == -2) ArmJob(file, waiter->cw_NotifLine, t1, t1+60); waiter = waiter->cw_Next; } if (line->cl_Pid == -1) { /* job is ready to run */ file->cf_Ready = 1; ++nJobs; if (DebugOpt) printlogf(LOG_DEBUG, " scheduled: %s\n", line->cl_Description); } else if (DebugOpt) printlogf(LOG_DEBUG, " waiting: %s\n", line->cl_Description); } } /* for line */ } return(nJobs); }
void RunJobs(void) { CronFile *file; CronLine *line; for (file = FileBase; file; file = file->cf_Next) { if (file->cf_Ready) { file->cf_Ready = 0; for (line = file->cf_LineBase; line; line = line->cl_Next) { if (line->cl_Pid == -1) { RunJob(file, line); printlogf(LOG_INFO, "FILE %s/%s USER %s PID %3d %s\n", file->cf_DPath, file->cf_FileName, file->cf_UserName, line->cl_Pid, line->cl_Description ); if (line->cl_Pid < 0) /* QUESTION how could this happen? RunJob will leave cl_Pid set to 0 or the actual pid */ file->cf_Ready = 1; else if (line->cl_Pid > 0) file->cf_Running = 1; } } } } }
/* | Writes a pid file. */ static void write_pidfile ( lua_State *L, const char *pidfile ) { pidfile_fd = open( pidfile, O_CREAT | O_RDWR, 0644 ); fcntl( pidfile_fd, F_SETFD, FD_CLOEXEC ); char buf[ 127 ]; if( pidfile_fd < 0 ) { printlogf( L, "Error", "Cannot create pidfile; '%s'", pidfile ); exit( -1 ); } int rc = lockf( pidfile_fd, F_TLOCK, 0 ); if( rc < 0 ) { printlogf( L, "Error", "Cannot lock pidfile; '%s'", pidfile ); exit( -1 ); } snprintf( buf, sizeof( buf ), "%i\n", getpid( ) ); write( pidfile_fd, buf, strlen( buf ) ); //fclose( f ); }
/* | Writes a pid file. */ static void write_pidfile( lua_State *L, const char *pidfile ) { FILE* f = fopen( pidfile, "w" ); if( !f ) { printlogf( L, "Error", "Cannot write pidfile; '%s'", pidfile ) ; exit( -1 ); } fprintf( f, "%i\n", getpid( ) ); fclose( f ); }
/* | Pushes a function from the runner on the stack. | As well as the callError handler. */ extern void load_runner_func( lua_State * L, const char * name ) { printlogf( L, "Call", "%s( )", name ); // pushes the error handler lua_pushlightuserdata( L, (void *) &callError ); lua_gettable( L, LUA_REGISTRYINDEX ); // pushes the function lua_pushlightuserdata( L, (void *) &runner ); lua_gettable( L, LUA_REGISTRYINDEX ); lua_pushstring( L, name ); lua_gettable( L, -2 ); lua_remove( L, -2 ); }
int TestJobs(time_t t1, time_t t2) { short nJobs = 0; time_t t; CronFile *file; CronLine *line; for (file = FileBase; file; file = file->cf_Next) { if (file->cf_Deleted) continue; for (line = file->cf_LineBase; line; line = line->cl_Next) { struct CronWaiter *waiter; if (line->cl_Pid == -2) { /* can job stop waiting? */ int ready = 1; waiter = line->cl_Waiters; while (waiter != NULL) { if (waiter->cw_Flag > 0) { /* notifier exited unsuccessfully */ ready = 2; break; } else if (waiter->cw_Flag < 0) /* still waiting, notifier hasn't run to completion */ ready = 0; waiter = waiter->cw_Next; } if (ready == 2) { if (DebugOpt) printlogf(LOG_DEBUG, "cancelled waiting: user %s %s\n", file->cf_UserName, line->cl_Description); line->cl_Pid = 0; } else if (ready) { if (DebugOpt) printlogf(LOG_DEBUG, "finished waiting: user %s %s\n", file->cf_UserName, line->cl_Description); nJobs += ArmJob(file, line, 0, -1); /* if (line->cl_NotUntil) line->cl_NotUntil = t2; */ } } } } /* * Find jobs > t1 and <= t2 */ for (t = t1 - t1 % 60; t <= t2; t += 60) { if (t > t1) { struct tm *tp = localtime(&t); unsigned short n_wday = (tp->tm_mday - 1)%7 + 1; if (n_wday >= 4) { struct tm tnext = *tp; tnext.tm_mday += 7; if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon) n_wday |= 16; /* last dow in month is always recognized as 5th */ } for (file = FileBase; file; file = file->cf_Next) { if (file->cf_Deleted) continue; for (line = file->cf_LineBase; line; line = line->cl_Next) { if ((line->cl_Pid == -2 || line->cl_Pid == 0) && (line->cl_Freq == 0 || (line->cl_Freq > 0 && t2 >= line->cl_NotUntil))) { /* (re)schedule job? */ if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) && line->cl_Mons[tp->tm_mon] ) { if (line->cl_NotUntil) line->cl_NotUntil = t2 - t2 % 60 + line->cl_Delay; /* save what minute this job was scheduled/started waiting, plus cl_Delay */ nJobs += ArmJob(file, line, t1, t2); } } } } } } return(nJobs); }
/* * Check the cron.update file in the specified directory. If user_override * is NULL then the files in the directory belong to the user whose name is * the file, otherwise they belong to the user_override user. */ void CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2) { FILE *fi; char buf[SMALL_BUFFER]; char *fname, *ptok, *job; char *path; if (!(path = concat(dpath, "/", CRONUPDATE, NULL))) { errno = ENOMEM; perror("CheckUpdates"); exit(1); } if ((fi = fopen(path, "r")) != NULL) { remove(path); printlogf(LOG_INFO, "reading %s/%s\n", dpath, CRONUPDATE); while (fgets(buf, sizeof(buf), fi) != NULL) { /* * if buf has only sep chars, return NULL and point ptok at buf's terminating 0 * else return pointer to first non-sep of buf and * if there's a following sep, overwrite it to 0 and point ptok to next char * else point ptok at buf's terminating 0 */ fname = strtok_r(buf, " \t\n", &ptok); if (user_override) SynchronizeFile(dpath, fname, user_override); else if (!getpwnam(fname)) printlogf(LOG_WARNING, "ignoring %s/%s (non-existent user)\n", dpath, fname); else if (*ptok == 0 || *ptok == '\n') { SynchronizeFile(dpath, fname, fname); ReadTimestamps(fname); } else { /* if fname is followed by whitespace, we prod any following jobs */ CronFile *file = FileBase; while (file) { if (strcmp(file->cf_UserName, fname) == 0) break; file = file->cf_Next; } if (!file) printlogf(LOG_WARNING, "unable to prod for user %s: no crontab\n", fname); else { CronLine *line; /* calling strtok(ptok...) then strtok(NULL) is equiv to calling strtok_r(NULL,..&ptok) */ while ((job = strtok(ptok, " \t\n")) != NULL) { time_t force = t2; ptok = NULL; if (*job == '!') { force = (time_t)-1; ++job; } line = file->cf_LineBase; while (line) { if (line->cl_JobName && strcmp(line->cl_JobName, job) == 0) break; line = line->cl_Next; } if (line) ArmJob(file, line, t1, force); else { printlogf(LOG_WARNING, "unable to prod for user %s: unknown job %s\n", fname, job); /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */ } } } } } fclose(fi); } free(path); }
char * ParseField(char *user, char *ary, int modvalue, int off, int onvalue, const char **names, char *ptr) { char *base = ptr; int n1 = -1; int n2 = -1; if (base == NULL) return (NULL); while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { int skip = 0; /* * Handle numeric digit or symbol or '*' */ if (*ptr == '*') { n1 = 0; /* everything will be filled */ n2 = modvalue - 1; skip = 1; ++ptr; } else if (*ptr >= '0' && *ptr <= '9') { if (n1 < 0) n1 = strtol(ptr, &ptr, 10) + off; else n2 = strtol(ptr, &ptr, 10) + off; skip = 1; } else if (names) { int i; for (i = 0; names[i]; ++i) { if (strncmp(ptr, names[i], strlen(names[i])) == 0) { break; } } if (names[i]) { ptr += strlen(names[i]); if (n1 < 0) n1 = i; else n2 = i; skip = 1; } } /* * handle optional range '-' */ if (skip == 0) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base); return(NULL); } if (*ptr == '-' && n2 < 0) { ++ptr; continue; } /* * collapse single-value ranges, handle skipmark, and fill * in the character array appropriately. */ if (n2 < 0) n2 = n1; n2 = n2 % modvalue; if (*ptr == '/') skip = strtol(ptr + 1, &ptr, 10); /* * fill array, using a failsafe is the easiest way to prevent * an endless loop */ { int s0 = 1; int failsafe = 1024; --n1; do { n1 = (n1 + 1) % modvalue; if (--s0 == 0) { ary[n1] = onvalue; s0 = skip; } } while (n1 != n2 && --failsafe); if (failsafe == 0) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base); return(NULL); } } if (*ptr != ',') break; ++ptr; n1 = -1; n2 = -1; } if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base); return(NULL); } while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') ++ptr; if (DebugOpt) { int i; for (i = 0; i < modvalue; ++i) if (modvalue == 7) printlogf(LOG_DEBUG, "%2x ", ary[i]); else printlogf(LOG_DEBUG, "%d", ary[i]); printlogf(LOG_DEBUG, "\n"); } return(ptr); }
void SynchronizeFile(const char *dpath, const char *fileName, const char *userName) { CronFile **pfile; CronFile *file; int maxEntries; int maxLines; char buf[RW_BUFFER]; /* max length for crontab lines */ char *path; FILE *fi; /* * Limit entries */ if (strcmp(userName, "root") == 0) maxEntries = 65535; else maxEntries = MAXLINES; maxLines = maxEntries * 10; /* * Delete any existing copy of this CronFile */ pfile = &FileBase; while ((file = *pfile) != NULL) { if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0 && strcmp(file->cf_FileName, fileName) == 0 ) { DeleteFile(pfile); } else { pfile = &file->cf_Next; } } if (!(path = concat(dpath, "/", fileName, NULL))) { errno = ENOMEM; perror("SynchronizeFile"); exit(1); } if ((fi = fopen(path, "r")) != NULL) { struct stat sbuf; if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { CronFile *file = calloc(1, sizeof(CronFile)); CronLine **pline; time_t tnow = time(NULL); tnow -= tnow % 60; file->cf_UserName = strdup(userName); file->cf_FileName = strdup(fileName); file->cf_DPath = strdup(dpath); pline = &file->cf_LineBase; /* fgets reads at most size-1 chars until \n or EOF, then adds a\0; \n if present is stored in buf */ while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { CronLine line; char *ptr = buf; int len; while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') ++ptr; len = strlen(ptr); if (len && ptr[len-1] == '\n') ptr[--len] = 0; if (*ptr == 0 || *ptr == '#') continue; if (--maxEntries == 0) break; memset(&line, 0, sizeof(line)); if (DebugOpt) printlogf(LOG_DEBUG, "User %s Entry %s\n", userName, buf); if (*ptr == '@') { /* * parse @hourly, etc */ int j; line.cl_Delay = -1; ptr += 1; for (j = 0; FreqAry[j]; ++j) { if (strncmp(ptr, FreqAry[j], strlen(FreqAry[j])) == 0) { break; } } if (FreqAry[j]) { ptr += strlen(FreqAry[j]); switch(j) { case 0: /* noauto */ line.cl_Freq = -2; line.cl_Delay = 0; break; case 1: /* reboot */ line.cl_Freq = -1; line.cl_Delay = 0; break; case 2: line.cl_Freq = HOURLY_FREQ; break; case 3: line.cl_Freq = DAILY_FREQ; break; case 4: line.cl_Freq = WEEKLY_FREQ; break; case 5: line.cl_Freq = MONTHLY_FREQ; break; case 6: line.cl_Freq = YEARLY_FREQ; break; /* else line.cl_Freq will remain 0 */ } } if (!line.cl_Freq || (*ptr != ' ' && *ptr != '\t')) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, buf); continue; } if (line.cl_Delay < 0) { /* * delays on @daily, @hourly, etc are 1/20 of the frequency * so they don't all start at once * this also affects how they behave when the job returns EAGAIN */ line.cl_Delay = line.cl_Freq / 20; line.cl_Delay -= line.cl_Delay % 60; if (line.cl_Delay == 0) line.cl_Delay = 60; /* all minutes are permitted */ for (j=0; j<60; ++j) line.cl_Mins[j] = 1; for (j=0; j<24; ++j) line.cl_Hrs[j] = 1; for (j=1; j<32; ++j) /* days are numbered 1..31 */ line.cl_Days[j] = 1; for (j=0; j<12; ++j) line.cl_Mons[j] = 1; } while (*ptr == ' ' || *ptr == '\t') ++ptr; } else { /* * parse date ranges */ ptr = ParseField(file->cf_UserName, line.cl_Mins, 60, 0, 1, NULL, ptr); ptr = ParseField(file->cf_UserName, line.cl_Hrs, 24, 0, 1, NULL, ptr); ptr = ParseField(file->cf_UserName, line.cl_Days, 32, 0, 1, NULL, ptr); ptr = ParseField(file->cf_UserName, line.cl_Mons, 12, -1, 1, MonAry, ptr); ptr = ParseField(file->cf_UserName, line.cl_Dow, 7, 0, 31, DowAry, ptr); /* * check failure */ if (ptr == NULL) continue; /* * fix days and dow - if one is not * and the other * is *, the other is set to 0, and vise-versa */ FixDayDow(&line); } /* check for ID=... and AFTER=... and FREQ=... */ do { if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) { if (line.cl_JobName) { /* only assign ID_TAG once */ printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr); ptr = NULL; } else { ptr += strlen(ID_TAG); /* * name = strsep(&ptr, seps): * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char * else set ptr=NULL */ if (!(line.cl_Description = concat("job ", strsep(&ptr, " \t"), NULL))) { errno = ENOMEM; perror("SynchronizeFile"); exit(1); } line.cl_JobName = line.cl_Description + 4; if (!ptr) printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, ID_TAG, line.cl_JobName); } } else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) { if (line.cl_Freq) { /* only assign FREQ_TAG once */ printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr); ptr = NULL; } else { char *base = ptr; ptr += strlen(FREQ_TAG); ptr = ParseInterval(&line.cl_Freq, ptr); if (ptr && *ptr == '/') ptr = ParseInterval(&line.cl_Delay, ++ptr); else line.cl_Delay = line.cl_Freq; if (!ptr) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, base); } else if (*ptr != ' ' && *ptr != '\t') { printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s\n", userName, base); ptr = NULL; } } } else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) { if (line.cl_Waiters) { /* only assign WAIT_TAG once */ printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr); ptr = NULL; } else { short more = 1; char *name; ptr += strlen(WAIT_TAG); do { CronLine *job, **pjob; if (strcspn(ptr,",") < strcspn(ptr," \t")) name = strsep(&ptr, ","); else { more = 0; name = strsep(&ptr, " \t"); } if (!ptr || *ptr == 0) { /* unexpectedly this was the last token in buf; so abort */ printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, WAIT_TAG, name); ptr = NULL; } else { int waitfor = 0; char *w, *wsave; if ((w = strchr(name, '/')) != NULL) { wsave = w++; w = ParseInterval(&waitfor, w); if (!w || *w != 0) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s%s\n", userName, WAIT_TAG, name); ptr = NULL; } else /* truncate name */ *wsave = 0; } if (ptr) { /* look for a matching CronLine */ pjob = &file->cf_LineBase; while ((job = *pjob) != NULL) { if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) { CronWaiter *waiter = malloc(sizeof(CronWaiter)); CronNotifier *notif = malloc(sizeof(CronNotifier)); waiter->cw_Flag = -1; waiter->cw_MaxWait = waitfor; waiter->cw_NotifLine = job; waiter->cw_Notifier = notif; waiter->cw_Next = line.cl_Waiters; /* add to head of line.cl_Waiters */ line.cl_Waiters = waiter; notif->cn_Waiter = waiter; notif->cn_Next = job->cl_Notifs; /* add to head of job->cl_Notifs */ job->cl_Notifs = notif; break; } else pjob = &job->cl_Next; } if (!job) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: unknown job %s\n", userName, name); /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */ } } } } while (ptr && more); } } else break; if (!ptr) break; while (*ptr == ' ' || *ptr == '\t') ++ptr; } while (!line.cl_JobName || !line.cl_Waiters || !line.cl_Freq); if (line.cl_JobName && (!ptr || *line.cl_JobName == 0)) { /* we're aborting, or ID= was empty */ free(line.cl_Description); line.cl_Description = NULL; line.cl_JobName = NULL; } if (ptr && line.cl_Delay > 0 && !line.cl_JobName) { printlogf(LOG_WARNING, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName, ptr); ptr = NULL; } if (!ptr) { /* couldn't parse so we abort; free any cl_Waiters */ if (line.cl_Waiters) { CronWaiter **pwaiters, *waiters; pwaiters = &line.cl_Waiters; while ((waiters = *pwaiters) != NULL) { *pwaiters = waiters->cw_Next; /* leave the Notifier allocated but disabled */ waiters->cw_Notifier->cn_Waiter = NULL; free(waiters); } } continue; } /* now we've added any ID=... or AFTER=... */ /* * copy command string */ line.cl_Shell = strdup(ptr); if (line.cl_Delay > 0) { if (!(line.cl_Timestamp = concat(TSDir, "/", userName, ".", line.cl_JobName, NULL))) { errno = ENOMEM; perror("SynchronizeFile"); exit(1); } line.cl_NotUntil = tnow + line.cl_Delay; } if (line.cl_JobName) { if (DebugOpt) printlogf(LOG_DEBUG, " Command %s Job %s\n", line.cl_Shell, line.cl_JobName); } else { /* when cl_JobName is NULL, we point cl_Description to cl_Shell */ line.cl_Description = line.cl_Shell; if (DebugOpt) printlogf(LOG_DEBUG, " Command %s\n", line.cl_Shell); } *pline = calloc(1, sizeof(CronLine)); /* copy working CronLine to newly allocated one */ **pline = line; pline = &((*pline)->cl_Next); } *pline = NULL; file->cf_Next = FileBase; FileBase = file; if (maxLines == 0 || maxEntries == 0) printlogf(LOG_WARNING, "maximum number of lines reached for user %s\n", userName); } fclose(fi); } free(path); }
void ReadTimestamps(const char *user) { CronFile *file; CronLine *line; FILE *fi; char buf[SMALL_BUFFER]; char *ptr; struct tm tm = {0}; time_t sec, freq; file = FileBase; while (file != NULL) { if (file->cf_Deleted == 0 && (!user || strcmp(user, file->cf_UserName) == 0)) { line = file->cf_LineBase; while (line != NULL) { if (line->cl_Timestamp) { if ((fi = fopen(line->cl_Timestamp, "r")) != NULL) { if (fgets(buf, sizeof(buf), fi) != NULL) { int fake = 0; ptr = buf; if (strncmp(buf, "after ", 6) == 0) { fake = 1; ptr += 6; } sec = (time_t)-1; ptr = strptime(ptr, CRONSTAMP_FMT, &tm); if (ptr && (*ptr == 0 || *ptr == '\n')) /* strptime uses current seconds when seconds not specified? anyway, we don't get round minutes */ tm.tm_sec = 0; tm.tm_isdst = -1; sec = mktime(&tm); if (sec == (time_t)-1) { printlogf(LOG_ERR, "unable to parse timestamp (user %s job %s)\n", file->cf_UserName, line->cl_JobName); /* we continue checking other timestamps in this CronFile */ } else { /* sec -= sec % 60; */ if (fake) { line->cl_NotUntil = sec; } else { line->cl_LastRan = sec; freq = (line->cl_Freq > 0) ? line->cl_Freq : line->cl_Delay; /* if (line->cl_NotUntil < line->cl_LastRan + freq) */ line->cl_NotUntil = line->cl_LastRan + freq; } } } fclose(fi); } else { int succeeded = 0; printlogf(LOG_NOTICE, "no timestamp found (user %s job %s)\n", file->cf_UserName, line->cl_JobName); /* write a fake timestamp file so our initial NotUntil doesn't keep being reset every hour when crond does a SynchronizeDir */ if ((fi = fopen(line->cl_Timestamp, "w")) != NULL) { if (strftime(buf, sizeof(buf), CRONSTAMP_FMT, localtime(&line->cl_NotUntil))) if (fputs("after ", fi) >= 0) if (fputs(buf,fi) >= 0) succeeded = 1; fclose(fi); } if (!succeeded) printlogf(LOG_WARNING, "unable to write timestamp to %s (user %s %s)\n", line->cl_Timestamp, file->cf_UserName, line->cl_Description); } } line = line->cl_Next; } } file = file->cf_Next; } }
void SynchronizeDir(const char *dpath, const char *user_override, int initial_scan) { CronFile **pfile; CronFile *file; struct dirent *den; DIR *dir; char *path; if (DebugOpt) printlogf(LOG_DEBUG, "Synchronizing %s\n", dpath); /* * Delete all database CronFiles for this directory. DeleteFile() will * free *pfile and relink the *pfile pointer, or in the alternative will * mark it as deleted. */ pfile = &FileBase; while ((file = *pfile) != NULL) { if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0) { DeleteFile(pfile); } else { pfile = &file->cf_Next; } } /* * Since we are resynchronizing the entire directory, remove the * the CRONUPDATE file. */ if (!(path = concat(dpath, "/", CRONUPDATE, NULL))) { errno = ENOMEM; perror("SynchronizeDir"); exit(1); } remove(path); free(path); /* * Scan the specified directory */ if ((dir = opendir(dpath)) != NULL) { while ((den = readdir(dir)) != NULL) { if (strchr(den->d_name, '.') != NULL) continue; if (strcmp(den->d_name, CRONUPDATE) == 0) continue; if (user_override) { SynchronizeFile(dpath, den->d_name, user_override); } else if (getpwnam(den->d_name)) { SynchronizeFile(dpath, den->d_name, den->d_name); } else { printlogf(LOG_WARNING, "ignoring %s/%s (non-existent user)\n", dpath, den->d_name); } } closedir(dir); } else { if (initial_scan) printlogf(LOG_ERR, "unable to scan directory %s\n", dpath); /* softerror, do not exit the program */ } }
/* | Executes a subprocess. Does not wait for it to return. | | Params on Lua stack: | | 1: Path to binary to call | 2: List of string as arguments | or "<" in which case the next argument is a string | that will be piped on stdin. | The arguments will follow that one. | | Returns (Lua stack) the pid on success, 0 on failure. */ static int l_exec( lua_State *L ) { // the binary to call const char *binary = luaL_checkstring(L, 1); // number of arguments int argc = lua_gettop( L ) - 1; // the pid spawned pid_t pid; // the arguments position in the lua arguments int li = 1; // the pipe to text char const * pipe_text = NULL; // the pipes length size_t pipe_len = 0; // the arguments char const ** argv; // pipe file descriptors int pipefd[ 2 ]; int i; // expands tables // and removes nils for( i = 1; i <= lua_gettop( L ); i++ ) { if( lua_isnil( L, i ) ) { lua_remove( L, i ); i--; argc--; continue; } if( lua_istable( L, i ) ) { int tlen; int it; lua_checkstack( L, lua_gettop( L ) + lua_objlen( L, i ) + 1 ); // moves table to top of stack lua_pushvalue( L, i ); lua_remove( L, i ); argc--; tlen = lua_objlen( L, -1 ); for( it = 1; it <= tlen; it++ ) { lua_pushinteger( L, it ); lua_gettable( L, -2 ); lua_insert( L, i ); i++; argc++; } i--; lua_pop( L, 1 ); } } // writes a log message (if needed). if( check_logcat( "Exec" ) <= settings.log_level ) { lua_checkstack( L, lua_gettop( L ) + argc * 3 + 2 ); lua_pushvalue( L, 1 ); for( i = 1; i <= argc; i++ ) { lua_pushstring( L, " [" ); lua_pushvalue( L, i + 1 ); lua_pushstring( L, "]" ); } lua_concat( L, 3 * argc + 1 ); // replaces midfile 0 chars by linefeed size_t len = 0; const char * cs = lua_tolstring( L, -1, &len ); char * s = s_calloc( len + 1, sizeof( char ) ); for( i = 0; i < len; i++ ) { s[ i ] = cs[ i ] ? cs[ i ] : '\n'; } logstring0( LOG_DEBUG, "Exec", s ); free( s ); lua_pop( L, 1 ); } if( argc >= 2 && !strcmp( luaL_checkstring( L, 2 ), "<" ) ) { // pipes something into stdin if( !lua_isstring( L, 3 ) ) { logstring( "Error", "in spawn(), expected a string after pipe '<'" ); exit( -1 ); } pipe_text = lua_tolstring( L, 3, &pipe_len ); if( strlen( pipe_text ) > 0 ) { // creates the pipe if( pipe( pipefd ) == -1 ) { logstring( "Error", "cannot create a pipe!" ); exit( -1 ); } // always closes the write end for child processes close_exec_fd( pipefd[ 1 ] ); // sets the write end on non-blocking non_block_fd( pipefd[ 1 ] ); } else { pipe_text = NULL; } argc -= 2; li += 2; } // prepares the arguments argv = s_calloc( argc + 2, sizeof( char * ) ); argv[ 0 ] = binary; for( i = 1; i <= argc; i++ ) { argv[i] = luaL_checkstring( L, i + li ); } argv[ i ] = NULL; // the fork! pid = fork( ); if( pid == 0 ) { // replaces stdin for pipes if( pipe_text ) { dup2( pipefd[ 0 ], STDIN_FILENO ); } // if lsyncd runs as a daemon and has a logfile it will redirect // stdout/stderr of child processes to the logfile. if( is_daemon && settings.log_file ) { if( !freopen( settings.log_file, "a", stdout ) ) { printlogf( L, "Error", "cannot redirect stdout to '%s'.", settings.log_file ); } if( !freopen( settings.log_file, "a", stderr ) ) { printlogf( L, "Error", "cannot redirect stderr to '%s'.", settings.log_file ); } } execv( binary, ( char ** ) argv ); // in a sane world execv does not return! printlogf( L, "Error", "Failed executing [ %s ]!", binary ); exit( -1 ); } if( pipe_text ) { int len; // first closes read-end of pipe, this is for child process only close( pipefd[ 0 ] ); // starts filling the pipe len = write( pipefd[ 1 ], pipe_text, pipe_len ); if( len < 0 ) { logstring( "Normal", "immediatly broken pipe." ); close( pipefd[ 1 ] ); } else if( len == pipe_len ) { // usual and best case, the pipe accepted all input -> close close( pipefd[ 1 ] ); logstring( "Exec", "one-sweeped pipe" ); } else { struct pipemsg *pm; logstring( "Exec", "adding pipe observance" ); pm = s_calloc( 1, sizeof( struct pipemsg ) ); pm->text = s_calloc( pipe_len + 1, sizeof( char ) ); memcpy( pm->text, pipe_text, pipe_len + 1 ); pm->tlen = pipe_len; pm->pos = len; observe_fd( pipefd[ 1 ], NULL, pipe_writey, pipe_tidy, pm ); } } free( argv ); lua_pushnumber( L, pid ); return 1; }
/* | Reads the directories entries. | | Params on Lua stack: | 1: absolute path to directory | | Returns on Lua stack: | a table of directory names. | names are keys | values are boolean true on dirs. */ static int l_readdir( lua_State *L ) { const char * dirname = luaL_checkstring( L, 1 ); DIR *d; d = opendir( dirname ); if( d == NULL ) { printlogf( L, "Error", "cannot open dir [%s].", dirname ); return 0; } lua_newtable( L ); while( !hup && !term ) { struct dirent *de = readdir( d ); bool isdir; if( de == NULL ) // finished { break; } // ignores . and .. if( !strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." ) ) { continue; } if( de->d_type == DT_UNKNOWN ) { // must call stat on some systems :-/ // ( e.g. ReiserFS ) char *entry = s_malloc( strlen( dirname ) + strlen( de->d_name ) + 2 ); struct stat st; strcpy( entry, dirname ); strcat( entry, "/" ); strcat( entry, de->d_name ); lstat( entry, &st ); isdir = S_ISDIR( st.st_mode ); free( entry ); } else { // otherwise readdir can be trusted isdir = de->d_type == DT_DIR; } // adds this entry to the Lua table lua_pushstring( L, de->d_name ); lua_pushboolean( L, isdir ); lua_settable( L, -3 ); } closedir( d ); return 1; }
int ArmJob(CronFile *file, CronLine *line, time_t t1, time_t t2) { struct CronWaiter *waiter; if (line->cl_Pid > 0) { printlogf(LOG_NOTICE, "process already running (%d): user %s %s\n", line->cl_Pid, file->cf_UserName, line->cl_Description ); } else if (t2 == -1 && line->cl_Pid != -1) { line->cl_Pid = -1; file->cf_Ready = 1; return 1; } else if (line->cl_Pid == 0) { /* arming a waiting job (cl_Pid == -2) without forcing has no effect */ line->cl_Pid = -1; /* if we have any waiters, zero them and arm cl_Pid=-2 */ waiter = line->cl_Waiters; while (waiter != NULL) { /* check if notifier will run <= t2 + cw_Max_Wait? */ if (!waiter->cw_NotifLine) /* notifier deleted */ waiter->cw_Flag = 0; else if (waiter->cw_NotifLine->cl_Pid != 0) { /* if notifier is armed, or waiting, or running, we wait for it */ waiter->cw_Flag = -1; line->cl_Pid = -2; } else if (waiter->cw_NotifLine->cl_Freq < 0) { /* arm any @noauto or @reboot jobs we're waiting on */ ArmJob(file, waiter->cw_NotifLine, t1, t2); waiter->cw_Flag = -1; line->cl_Pid = -2; } else { time_t t; if (waiter->cw_MaxWait == 0) /* when no MaxWait interval specified, we always wait */ waiter->cw_Flag = -1; else if (waiter->cw_NotifLine->cl_Freq == 0 || (waiter->cw_NotifLine->cl_Freq > 0 && t2 + waiter->cw_MaxWait >= waiter->cw_NotifLine->cl_NotUntil)) { /* default is don't wait */ waiter->cw_Flag = 0; for (t = t1 - t1 % 60; t <= t2; t += 60) { if (t > t1) { struct tm *tp = localtime(&t); unsigned short n_wday = (tp->tm_mday - 1)%7 + 1; if (n_wday >= 4) { struct tm tnext = *tp; tnext.tm_mday += 7; if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon) n_wday |= 16; /* last dow in month is always recognized as 5th */ } if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) && line->cl_Mons[tp->tm_mon] ) { /* notifier will run soon enough, we wait for it */ waiter->cw_Flag = -1; line->cl_Pid = -2; break; } } } } } waiter = waiter->cw_Next; } if (line->cl_Pid == -1) { /* job is ready to run */ file->cf_Ready = 1; if (DebugOpt) printlogf(LOG_DEBUG, "scheduled: user %s %s\n", file->cf_UserName, line->cl_Description ); return 1; } else if (DebugOpt) printlogf(LOG_DEBUG, "waiting: user %s %s\n", file->cf_UserName, line->cl_Description ); } return 0; }
int main(int ac, char **av) { const char *LevelAry[] = { "emerg", "alert", "crit", "err", "warning", "notice", "info", "debug", "panic", "error", "warn", NULL }; int i; /* * parse options */ DaemonUid = getuid(); opterr = 0; while ((i = getopt(ac,av,"dl:L:fbSc:s:m:M:t:")) != -1) { switch (i) { case 'l': { char *ptr; int j; ptr = optarg; for (j = 0; LevelAry[j]; ++j) { if (strncmp(ptr, LevelAry[j], strlen(LevelAry[j])) == 0) { break; } } switch(j) { case 0: case 8: /* #define LOG_EMERG 0 [* system is unusable *] */ LogLevel = LOG_EMERG; break; case 1: /* #define LOG_ALERT 1 [* action must be taken immediately *] */ LogLevel = LOG_ALERT; break; case 2: /* #define LOG_CRIT 2 [* critical conditions *] */ LogLevel = LOG_CRIT; break; case 3: case 9: /* #define LOG_ERR 3 [* error conditions *] */ LogLevel = LOG_ERR; break; case 4: case 10: /* #define LOG_WARNING 4 [* warning conditions *] */ LogLevel = LOG_WARNING; break; case 5: /* #define LOG_NOTICE 5 [* normal but significant condition *] */ LogLevel = LOG_NOTICE; break; case 6: /* #define LOG_INFO 6 [* informational *] */ LogLevel = LOG_INFO; break; case 7: /* #define LOG_DEBUG 7 [* debug-level messages *] */ LogLevel = LOG_DEBUG; break; default: LogLevel = atoi(optarg); } } break; case 'd': DebugOpt = 1; LogLevel = LOG_DEBUG; /* fall through to include f too */ case 'f': ForegroundOpt = 1; break; case 'b': ForegroundOpt = 0; break; case 'S': /* log through syslog */ SyslogOpt = 1; break; case 'L': /* use internal log formatter */ SyslogOpt = 0; LogFile = optarg; /* if LC_TIME is defined, we use it for logging to file instead of compiled-in TIMESTAMP_FMT */ if (getenv("LC_TIME") != NULL) { LogHeader = LOCALE_LOGHEADER; } break; case 'c': if (*optarg != 0) CDir = optarg; break; case 's': if (*optarg != 0) SCDir = optarg; break; case 't': if (*optarg != 0) TSDir = optarg; break; case 'M': if (*optarg != 0) SendMail = optarg; break; case 'm': if (*optarg != 0) Mailto = optarg; break; default: /* * check for parse error */ printf("dillon's cron daemon " VERSION "\n"); printf("crond [-s dir] [-c dir] [-t dir] [-m user@host] [-M mailer] [-S|-L [file]] [-l level] [-b|-f|-d]\n"); printf("-s directory of system crontabs (defaults to %s)\n", SCRONTABS); printf("-c directory of per-user crontabs (defaults to %s)\n", CRONTABS); printf("-t directory of timestamps (defaults to %s)\n", CRONSTAMPS); printf("-m user@host where should cron output be directed? (defaults to local user)\n"); printf("-M mailer (defaults to %s)\n", SENDMAIL); printf("-S log to syslog using identity '%s' (default)\n", LOG_IDENT); printf("-L file log to specified file instead of syslog\n"); printf("-l loglevel log events <= this level (defaults to %s (level %d))\n", LevelAry[LOG_LEVEL], LOG_LEVEL); printf("-b run in background (default)\n"); printf("-f run in foreground\n"); printf("-d run in debugging mode\n"); exit(2); } } /* * close stdin and stdout. * close unused descriptors - don't need. * optional detach from controlling terminal */ fclose(stdin); fclose(stdout); i = open("/dev/null", O_RDWR); if (i < 0) { perror("open: /dev/null"); exit(1); } dup2(i, 0); dup2(i, 1); /* create tempdir with permissions 0755 for cron output */ TempDir = strdup(TMPDIR "/cron.XXXXXX"); if (mkdtemp(TempDir) == NULL) { perror("mkdtemp"); exit(1); } if (chmod(TempDir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) { perror("chmod"); exit(1); } if (!(TempFileFmt = concat(TempDir, "/cron.%s.%d", NULL))) { errno = ENOMEM; perror("main"); exit(1); } if (ForegroundOpt == 0) { int fd; int pid; if ((pid = fork()) < 0) { /* fork failed */ perror("fork"); exit(1); } else if (pid > 0) { /* parent */ exit(0); } /* child continues */ /* become session leader, detach from terminal */ if (setsid() < 0) perror("setsid"); if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY, 0); close(fd); } /* setup logging for backgrounded daemons */ if (SyslogOpt) { /* start SIGHUP and SIGCHLD handling while stderr still open */ initsignals(); /* 2> /dev/null */ fclose(stderr); dup2(1, 2); /* open syslog */ openlog(LOG_IDENT, LOG_CONS|LOG_PID, LOG_CRON); } else { /* open logfile */ if ((fd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) { /* start SIGHUP ignoring, SIGCHLD handling while stderr still open */ initsignals(); /* 2> LogFile */ fclose(stderr); dup2(fd, 2); } else { int n = errno; fdprintf(2, "failed to open logfile '%s', reason: %s", LogFile, strerror(n)); exit(n); } } } else { /* daemon in foreground */ /* stay in existing session, but start a new process group */ if (setpgid(0,0)) { perror("setpgid"); exit(1); } /* stderr stays open, start SIGHUP ignoring, SIGCHLD handling */ initsignals(); } /* close all other fds, including the ones we opened as /dev/null and LogFile */ for (i = 3; i < MAXOPEN; ++i) { close(i); } /* * main loop - synchronize to 1 second after the minute, minimum sleep * of 1 second. */ printlogf(LOG_NOTICE,"%s " VERSION " dillon's cron daemon, started with loglevel %s\n", av[0], LevelAry[LogLevel]); SynchronizeDir(CDir, NULL, 1); SynchronizeDir(SCDir, "root", 1); ReadTimestamps(NULL); TestStartupJobs(); /* @startup jobs only run when crond is started, not when their crontab is loaded */ { time_t t1 = time(NULL); time_t t2; long dt; short rescan = 60; short stime = 60; for (;;) { sleep((stime + 1) - (short)(time(NULL) % stime)); t2 = time(NULL); dt = t2 - t1; /* * The file 'cron.update' is checked to determine new cron * jobs. The directory is rescanned once an hour to deal * with any screwups. * * check for disparity. Disparities over an hour either way * result in resynchronization. A reverse-indexed disparity * less then an hour causes us to effectively sleep until we * match the original time (i.e. no re-execution of jobs that * have just been run). A forward-indexed disparity less then * an hour causes intermediate jobs to be run, but only once * in the worst case. * * when running jobs, the inequality used is greater but not * equal to t1, and less then or equal to t2. */ if (--rescan == 0) { /* * If we resynchronize while jobs are running, we'll clobber * the job pids, so we won't know what's already running. */ if (CheckJobs() > 0) { rescan = 1; } else { rescan = 60; SynchronizeDir(CDir, NULL, 0); SynchronizeDir(SCDir, "root", 0); ReadTimestamps(NULL); } } else { CheckUpdates(CDir, NULL, t1, t2); CheckUpdates(SCDir, "root", t1, t2); } if (DebugOpt) printlogf(LOG_DEBUG, "Wakeup dt=%d\n", dt); if (dt < -60*60 || dt > 60*60) { t1 = t2; printlogf(LOG_NOTICE,"time disparity of %d minutes detected\n", dt / 60); } else if (dt > 0) { TestJobs(t1, t2); RunJobs(); sleep(5); if (CheckJobs() > 0) stime = 10; else stime = 60; t1 = t2; } } } /* not reached */ }
/* | The effective main for one run. | | HUP signals may cause several runs of the one main. */ int main1( int argc, char *argv[] ) { // the Lua interpreter lua_State * L; // the runner file char * lsyncd_runner_file = NULL; int argp = 1; // load Lua L = luaL_newstate( ); luaL_openlibs( L ); { // checks the lua version const char * version; int major, minor; lua_getglobal( L, "_VERSION" ); version = luaL_checkstring( L, -1 ); if( sscanf( version, "Lua %d.%d", &major, &minor ) != 2 ) { fprintf( stderr, "cannot parse lua library version!\n" ); exit (-1 ); } if( major < 5 || (major == 5 && minor < 1) ) { fprintf( stderr, "Lua library is too old. Needs 5.1 at least" ); exit( -1 ); } lua_pop( L, 1 ); } { // logging is prepared quite early int i = 1; add_logcat( "Normal", LOG_NOTICE ); add_logcat( "Warn", LOG_WARNING ); add_logcat( "Error", LOG_ERR ); while( i < argc ) { if( strcmp( argv[ i ], "-log" ) && strcmp( argv[ i ], "--log" ) ) { // arg is neither -log or --log i++; continue; } if( ++i >= argc ) { // -(-)log was last argument break; } if( !add_logcat( argv[ i ], LOG_NOTICE ) ) { printlogf( L, "Error", "'%s' is not a valid logging category", argv[ i ] ); exit( -1 ); } } } // registers Lsycnd's core library register_lsyncd( L ); if( check_logcat( "Debug" ) <= settings.log_level ) { // printlogf doesnt support %ld :-( printf( "kernels clocks_per_sec=%ld\n", clocks_per_sec ); } // checks if the user overrode the default runner file if( argp < argc && !strcmp( argv[ argp ], "--runner" ) ) { if (argp + 1 >= argc) { logstring( "Error", "Lsyncd Lua-runner file missing after --runner " ); exit( -1 ); } lsyncd_runner_file = argv[ argp + 1 ]; argp += 2; } if( lsyncd_runner_file ) { // checks if the runner file exists struct stat st; if( stat( lsyncd_runner_file, &st ) ) { printlogf( L, "Error", "Cannot see a runner at '%s'.", lsyncd_runner_file ); exit( -1 ); } // loads the runner file if( luaL_loadfile(L, lsyncd_runner_file ) ) { printlogf( L, "Error", "error loading '%s': %s", lsyncd_runner_file, lua_tostring( L, -1 ) ); exit( -1 ); } } else { // loads the runner from binary if( luaL_loadbuffer( L, runner_out, runner_size, "runner" ) ) { printlogf( L, "Error", "error loading precompiled runner: %s", lua_tostring( L, -1 ) ); exit( -1 ); } } // prepares the runner executing the script { if( lua_pcall( L, 0, LUA_MULTRET, 0 ) ) { printlogf( L, "Error", "preparing runner: %s", lua_tostring( L, -1 ) ); exit( -1 ); } lua_pushlightuserdata( L, (void *) & runner ); // switches the value ( result of preparing ) and the key &runner lua_insert( L, 1 ); // saves the table of the runners functions in the lua registry lua_settable( L, LUA_REGISTRYINDEX ); // saves the error function extras // &callError is the key lua_pushlightuserdata ( L, (void *) &callError ); // &runner[ callError ] the value lua_pushlightuserdata ( L, (void *) &runner ); lua_gettable ( L, LUA_REGISTRYINDEX ); lua_pushstring ( L, "callError" ); lua_gettable ( L, -2 ); lua_remove ( L, -2 ); lua_settable ( L, LUA_REGISTRYINDEX ); } // asserts the Lsyncd's version matches // between runner and core { const char *lversion; lua_getglobal( L, "lsyncd_version" ); lversion = luaL_checkstring( L, -1 ); if( strcmp( lversion, PACKAGE_VERSION ) ) { printlogf( L, "Error", "Version mismatch '%s' is '%s', but core is '%s'", lsyncd_runner_file ? lsyncd_runner_file : "( internal runner )", lversion, PACKAGE_VERSION ); exit( -1 ); } lua_pop( L, 1 ); } // loads the defaults from binary { if( luaL_loadbuffer( L, defaults_out, defaults_size, "defaults" ) ) { printlogf( L, "Error", "loading defaults: %s", lua_tostring( L, -1 ) ); exit( -1 ); } // prepares the defaults if( lua_pcall( L, 0, 0, 0 ) ) { printlogf( L, "Error", "preparing defaults: %s", lua_tostring( L, -1 ) ); exit( -1 ); } } // checks if there is a "-help" or "--help" { int i; for( i = argp; i < argc; i++ ) { if ( !strcmp( argv[ i ], "-help" ) || !strcmp( argv[ i ], "--help" ) ) { load_runner_func( L, "help" ); if( lua_pcall( L, 0, 0, -2 ) ) { exit( -1 ); } lua_pop( L, 1 ); exit( 0 ); } } } // starts the option parser in Lua script { int idx = 1; const char *s; // creates a table with all remaining argv option arguments load_runner_func( L, "configure" ); lua_newtable( L ); while( argp < argc ) { lua_pushnumber ( L, idx++ ); lua_pushstring ( L, argv[ argp++ ] ); lua_settable ( L, -3 ); } // creates a table with the cores event monitor interfaces idx = 0; lua_newtable( L ); while( monitors[ idx ] ) { lua_pushnumber ( L, idx + 1 ); lua_pushstring ( L, monitors[ idx++ ] ); lua_settable ( L, -3 ); } if( lua_pcall( L, 2, 1, -4 ) ) { exit( -1 ); } if( first_time ) { // If not first time, simply retains the config file given s = lua_tostring(L, -1); if( s ) { lsyncd_config_file = s_strdup( s ); } } lua_pop( L, 2 ); } // checks existence of the config file if( lsyncd_config_file ) { struct stat st; // gets the absolute path to the config file // so in case of HUPing the daemon, it finds it again char * apath = get_realpath( lsyncd_config_file ); if( !apath ) { printlogf( L, "Error", "Cannot find config file at '%s'.", lsyncd_config_file ); exit( -1 ); } free( lsyncd_config_file ); lsyncd_config_file = apath; if( stat( lsyncd_config_file, &st ) ) { printlogf( L, "Error", "Cannot find config file at '%s'.", lsyncd_config_file ); exit( -1 ); } // loads and executes the config file if( luaL_loadfile( L, lsyncd_config_file ) ) { printlogf( L, "Error", "error loading %s: %s", lsyncd_config_file, lua_tostring( L, -1 ) ); exit( -1 ); } if( lua_pcall( L, 0, LUA_MULTRET, 0) ) { printlogf( L, "Error", "error preparing %s: %s", lsyncd_config_file, lua_tostring( L, -1 ) ); exit( -1 ); } } #ifdef WITH_INOTIFY open_inotify( L ); #endif #ifdef WITH_FSEVENTS open_fsevents( L ); #endif // adds signal handlers // listens to SIGCHLD, but blocks it until pselect( ) // opens the signal handler up { sigset_t set; sigemptyset( &set ); sigaddset( &set, SIGCHLD ); signal( SIGCHLD, sig_child ); sigprocmask( SIG_BLOCK, &set, NULL ); signal( SIGHUP, sig_handler ); signal( SIGTERM, sig_handler ); signal( SIGINT, sig_handler ); } // runs initializations from runner // it will set the configuration and add watches { load_runner_func( L, "initialize" ); lua_pushboolean( L, first_time ); if( lua_pcall( L, 1, 0, -3 ) ) { exit( -1 ); } lua_pop( L, 1 ); } // // enters the master loop // masterloop( L ); // // cleanup // // tidies up all observances { int i; for( i = 0; i < observances_len; i++ ) { struct observance *obs = observances + i; obs->tidy( obs ); } observances_len = 0; nonobservances_len = 0; } // frees logging categories { int ci; struct logcat *lc; for( ci = 'A'; ci <= 'Z'; ci++ ) { for( lc = logcats[ ci - 'A' ]; lc && lc->name; lc++) { free( lc->name ); lc->name = NULL; } if( logcats[ci - 'A' ] ) { free( logcats[ ci - 'A' ] ); logcats[ ci - 'A' ] = NULL; } } } lua_close( L ); return 0; }
/* | Normal operation happens in here. */ static void masterloop(lua_State *L) { while( true ) { bool have_alarm; bool force_alarm = false; clock_t now = times( dummy_tms ); clock_t alarm_time = 0; // memory usage debugging // lua_gc( L, LUA_GCCOLLECT, 0 ); // printf( // "gccount: %d\n", // lua_gc( L, LUA_GCCOUNT, 0 ) * 1024 + lua_gc( L, LUA_GCCOUNTB, 0 ) ); // // queries the runner about the soonest alarm // load_runner_func( L, "getAlarm" ); if( lua_pcall( L, 0, 1, -2 ) ) { exit( -1 ); } if( lua_type( L, -1 ) == LUA_TBOOLEAN) { have_alarm = false; force_alarm = lua_toboolean( L, -1 ); } else { have_alarm = true; alarm_time = *( ( clock_t * ) luaL_checkudata( L, -1, "Lsyncd.jiffies" ) ); } lua_pop( L, 2 ); if( force_alarm || ( have_alarm && time_before_eq( alarm_time, now ) ) ) { // there is a delay that wants to be handled already thus instead // of reading/writing from observances it jumps directly to // handling // TODO: Actually it might be smarter to handler observances // eitherway. since event queues might overflow. logstring( "Masterloop", "immediately handling delays." ); } else { // uses select( ) to determine what happens next: // a) a new event on an observance // b) an alarm on timeout // c) the return of a child process struct timespec tv; if( have_alarm ) { // TODO use trunc instead of long converstions double d = ( (double )( alarm_time - now ) ) / clocks_per_sec; tv.tv_sec = d; tv.tv_nsec = ( (d - ( long ) d) ) * 1000000000.0; printlogf( L, "Masterloop", "going into select ( timeout %f seconds )", d ); } else { logstring( "Masterloop", "going into select ( no timeout )" ); } // time for Lsyncd to try to put itself to rest into the big select( ) // this configures: // timeouts, // filedescriptors and // signals // that will wake Lsyncd { fd_set rfds; fd_set wfds; sigset_t sigset; int pi, pr; sigemptyset( &sigset ); FD_ZERO( &rfds ); FD_ZERO( &wfds ); for( pi = 0; pi < observances_len; pi++ ) { struct observance *obs = observances + pi; if ( obs->ready ) { FD_SET( obs->fd, &rfds ); } if ( obs->writey ) { FD_SET( obs->fd, &wfds ); } } if( !observances_len ) { logstring( "Error", "Internal fail, no observances, no monitor!" ); exit( -1 ); } // the great select, this is the very heart beat of Lsyncd // that puts Lsyncd to sleep until anything worth noticing // happens pr = pselect( observances[ observances_len - 1 ].fd + 1, &rfds, &wfds, NULL, have_alarm ? &tv : NULL, &sigset ); // something happened! if (pr >= 0) { // walks through the observances calling ready/writey observance_action = true; for( pi = 0; pi < observances_len; pi++ ) { struct observance *obs = observances + pi; // Checks for signals if( hup || term ) { break; } // a file descriptor became read-ready if( obs->ready && FD_ISSET( obs->fd, &rfds ) ) { obs->ready(L, obs); } // Checks for signals, again, better safe than sorry if ( hup || term ) { break; } // FIXME breaks on multiple nonobservances in one beat if( nonobservances_len > 0 && nonobservances[ nonobservances_len - 1 ] == obs->fd ) { continue; } // a file descriptor became write-ready if( obs->writey && FD_ISSET( obs->fd, &wfds ) ) { obs->writey( L, obs ); } } observance_action = false; // works through delayed nonobserve_fd() calls for (pi = 0; pi < nonobservances_len; pi++) { nonobserve_fd( nonobservances[ pi ] ); } nonobservances_len = 0; } } } // collects zombified child processes while( 1 ) { int status; pid_t pid = waitpid( 0, &status, WNOHANG ); if (pid <= 0) { // no more zombies break; } // calls the runner to handle the collection load_runner_func( L, "collectProcess" ); lua_pushinteger( L, pid ); lua_pushinteger( L, WEXITSTATUS( status ) ); if ( lua_pcall( L, 2, 0, -4 ) ) { exit(-1); } lua_pop( L, 1 ); } // reacts on HUP signals if( hup ) { load_runner_func( L, "hup" ); if( lua_pcall( L, 0, 0, -2 ) ) { exit( -1 ); } lua_pop( L, 1 ); hup = 0; } // reacts on TERM and INT signals if( term == 1 ) { load_runner_func( L, "term" ); lua_pushnumber( L, sigcode ); if( lua_pcall( L, 1, 0, -3 ) ) { exit( -1 ); } lua_pop( L, 1 ); term = 2; } // lets the runner do stuff every cycle, // like starting new processes, writing the statusfile etc. load_runner_func( L, "cycle" ); l_now( L ); if( lua_pcall( L, 1, 1, -3 ) ) { exit( -1 ); } if( !lua_toboolean( L, -1 ) ) { // cycle told core to break mainloop lua_pop( L, 2 ); return; } lua_pop( L, 2 ); if( lua_gettop( L ) ) { logstring( "Error", "internal, stack is dirty." ); l_stackdump( L ); exit( -1 ); } } }
/* | Daemonizes. | | Lsyncds own implementation over daemon(0, 0) since | a) OSX keeps bugging about it being deprecated | b) for a reason, since blindly closing stdin/out/err | is unsafe, since they might not have existed and | might actually close the monitors fd! */ static void daemonize( lua_State *L, // the lua state const char *pidfile // if not NULL write pidfile ) { pid_t pid, sid; pid = fork( ); if( pid < 0 ) { printlogf( L, "Error", "Failure in daemonize at fork: %s", strerror( errno ) ); exit( -1 ); } if( pid > 0 ) { // parent process returns to shell exit( 0 ); } if( pidfile ) { write_pidfile( L, pidfile ); } // detaches the new process from the parent process sid = setsid( ); if( sid < 0 ) { printlogf( L, "Error", "Failure in daemonize at setsid: %s", strerror( errno ) ); exit( -1 ); } // goes to root dir if( chdir( "/" ) < 0 ) { printlogf( L, "Error", "Failure in daemonize at chdir( \"/\" ): %s", strerror( errno ) ); exit( -1 ); } // does what clibs daemon( 0, 0 ) cannot do, // checks if there were no stdstreams and it might close used fds if( observances_len && observances->fd < 3 ) { printlogf( L, "Normal", "daemonize not closing stdin/out/err, since there seem to none." ); return; } // disconnects stdstreams if ( !freopen( "/dev/null", "r", stdin ) || !freopen( "/dev/null", "w", stdout ) || !freopen( "/dev/null", "w", stderr ) ) { printlogf( L, "Error", "Failure in daemonize at freopen( /dev/null, std[in|out|err] )" ); } is_daemon = true; }
/* | Configures core parameters. | | Params on Lua stack: | 1: a string, configure option | 2: depends on Param 1 */ static int l_configure( lua_State *L ) { const char * command = luaL_checkstring( L, 1 ); if( !strcmp( command, "running" ) ) { // set by runner after first initialize // from this on log to configurated log end instead of // stdout/stderr first_time = false; if( !settings.nodaemon && !settings.log_file ) { settings.log_syslog = true; const char * log_ident = settings.log_ident ? settings.log_ident : "lsyncd"; openlog( log_ident, 0, settings.log_facility ); } if( !settings.nodaemon && !is_daemon ) { logstring( "Normal", "--- Startup, daemonizing ---" ); daemonize( L, settings.pidfile ); } else { logstring( "Normal", "--- Startup ---" ); } } else if( !strcmp( command, "nodaemon" ) ) { settings.nodaemon = true; } else if( !strcmp( command, "logfile" ) ) { const char * file = luaL_checkstring( L, 2 ); if( settings.log_file ) { free( settings.log_file ); } settings.log_file = s_strdup( file ); } else if( !strcmp( command, "pidfile" ) ) { const char * file = luaL_checkstring( L, 2 ); if( settings.pidfile ) { free( settings.pidfile ); } settings.pidfile = s_strdup( file ); } else if( !strcmp( command, "logfacility" ) ) { if( lua_isstring( L, 2 ) ) { const char * fname = luaL_checkstring( L, 2 ); int i; for( i = 0; facilitynames[ i ].c_name; i++ ) { if( !strcasecmp( fname, facilitynames[ i ].c_name ) ) { break; } } if( !facilitynames[ i ].c_name ) { printlogf( L, "Error", "Logging facility '%s' unknown.", fname ); exit( -1 ); } settings.log_facility = facilitynames[ i ].c_val; } else if (lua_isnumber(L, 2)) { settings.log_facility = luaL_checknumber(L, 2); } else { printlogf( L, "Error", "Logging facility must be a number or string" ); exit( -1 ); } } else if( !strcmp( command, "logident" ) ) { const char * ident = luaL_checkstring( L, 2 ); if( settings.log_ident ) { free( settings.log_ident ); } settings.log_ident = s_strdup( ident ); } else { printlogf( L, "Error", "Internal error, unknown parameter in l_configure( %s )", command ); exit( -1 ); } return 0; }