Exemple #1
0
/*
| 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;
}
Exemple #2
0
/*
| 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;
}
Exemple #3
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);
}
Exemple #4
0
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;
				}
			}
		}
	}
}
Exemple #5
0
/*
| 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 );
}
Exemple #6
0
/*
| 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 );
}
Exemple #7
0
/*
| 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 );
}
Exemple #8
0
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);
}
Exemple #9
0
/*
 * 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);
}
Exemple #10
0
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);
}
Exemple #11
0
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);
}
Exemple #12
0
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;
	}
}
Exemple #13
0
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 */
	}
}
Exemple #14
0
/*
| 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;
}
Exemple #15
0
/*
| 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;
}
Exemple #16
0
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;
}
Exemple #17
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 */
}
Exemple #18
0
/*
| 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;
}
Exemple #19
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 );
		}
	}
}
Exemple #20
0
/*
| 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;
}
Exemple #21
0
/*
| 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;
}