1 /*
2  * various and sundry little helper functions, some of them to replace standard
3  * library functions that should be but aren't available on all systems. Some
4  * are stubs for the grok program; several grok source files (g_*.c) are linked
5  * into plan to be able to read grok databases.
6  *
7  *	resolve_tilde(path)		return path with ~ replaced with home
8  *					directory as found in $HOME
9  *	find_file(buf, name, exec)	find file <name> and store the path
10  *					in <buf>. Return FALSE if not found.
11  *	startup_lock(fname, force)	make sure program runs only once
12  *					at any time
13  *	lockfile(fp, lock)		lock or unlock the database to
14  *					prevent simultaneous access
15  *	fatal(fmt, ...)			print fatal error message and exit
16  *	mystrdup(s)			local version of strdup
17  *	mystrcasecmp(a,b)		local version of strcasecmp
18  *	exit(ret)			local version of exit that writes back
19  *	to_octal(n)			convert ascii to octal string (grok)
20  *	to_ascii(str, def)		convert octal string to ascii (grok)
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <time.h>
28 #include <errno.h>
29 #include <sys/types.h>
30 #include <stdarg.h>
31 #if defined(IBM) || defined(ULTRIX)
32 #include <fcntl.h>
33 #else
34 #include <sys/fcntl.h>
35 #endif
36 #include <pwd.h>
37 #if !defined(NOLOCK) && defined(LOCKF) || defined(linux)
38 #include <sys/file.h>
39 #endif
40 #if !defined(NOLOCK) && defined(LOCKF)
41 #include <sys/file.h>
42 #endif
43 #if defined(IBM) && !defined(NOLOCK)
44 #include <sys/lockf.h>
45 #endif
46 #include <sys/signal.h>
47 #include <sys/stat.h>
48 #include <Xm/Xm.h>
49 #include "config.h"
50 #include "cal.h"
51 
52 #ifdef MIPS
53 extern struct passwd *getpwnam();
54 #endif
55 
56 extern char		*progname;	/* argv[0] */
57 extern struct plist	*mainlist;	/* list of all schedule entries */
58 char			lockpath[256];	/* lockfile path */
59 
60 
61 /*
62  * If <path> begins with a tilde, replace the tilde with $HOME. This is used
63  * for the database files, and the holiday file (see holiday.c).
64  */
65 
resolve_tilde(char * path)66 char *resolve_tilde(
67 	char		*path)			/* path with ~ */
68 {
69 	struct passwd	*pw;			/* for searching home dirs */
70 	static char	pathbuf[512];		/* path with ~ expanded */
71 	char		*p, *q;			/* username copy pointers */
72 	char		*home = 0;		/* home dir (if ~ in path) */
73 
74 	if (*path != '~')
75 		return(path);
76 
77 	if (!path[1] || path[1] == '/') {
78 		*pathbuf = 0;
79 		if (!(home = getenv("HOME")) &&
80 		    !(home = getenv("home")))
81 			fprintf(stderr, _("%s: $HOME and $home not set,\n"),
82 								progname);
83 	} else {
84 		for (p=path+1, q=pathbuf; *p && *p != '/'; p++, q++)
85 			*q = *p;
86 		*q = 0;
87 		if ((pw = getpwnam(pathbuf))) {
88 			home = pw->pw_dir;
89 			path = p-1;
90 		}
91 	}
92 	if (!home) {
93 		fprintf(stderr, _("%s: can't evaluate ~%s in %s, using .\n"),
94 						progname, pathbuf, path);
95 		home = ".";
96 	}
97 	sprintf(pathbuf, "%s/%s", home, path+1);
98 	return(pathbuf);
99 }
100 
101 
102 /*
103  * locate a program or file, and return its complete path. This is used by
104  * the daemon to locate notifier and user programs, and by plan to locate
105  * pland and plan.help. Assume that <buf> has space for 1024 chars. PATH
106  * is a macro defined by the Makefile.
107  */
108 
109 #ifndef PATH
110 #define PATH 0
111 #endif
112 
find_file(char * buf,char * name,BOOL exec)113 BOOL find_file(
114 	char		*buf,		/* buffer for returned path */
115 	char		*name,		/* file name to locate */
116 	BOOL		exec)		/* must be executable? */
117 {
118 	int		method;		/* search path counter */
119 	char		*path;		/* $PATH or DEFAULTPATH */
120 	int		namelen;	/* len of tail of name */
121 	char		*p, *q;		/* string copy pointers */
122 
123 	if (*name == '/') {				/* begins with / */
124 		strcpy(buf, name);
125 		return(TRUE);
126 	}
127 	if (*name == '~') { 				/* begins with ~ */
128 		strcpy(buf, resolve_tilde(name));
129 		return(TRUE);
130 	}
131 	namelen = strlen(name);
132 	for (method=0; ; method++) {
133 		switch(method) {
134 		  case 0:   path = PATH;		break;
135 		  case 1:   path = getenv("PLAN_PATH");	break;
136 		  case 2:   path = getenv("PATH");	break;
137 		  case 3:   path = DEFAULTPATH;		break;
138 		  default:  return(FALSE);
139 		}
140 		if (!path)
141 			continue;
142 		do {
143 			q = buf;
144 			p = path;
145 			while (*p && *p != ':' && q < buf + 1021 - namelen)
146 				*q++ = *p++;
147 			*q++ = '/';
148 			strcpy(q, name);
149 			if (!access(buf, exec ? X_OK : R_OK)) {
150 				strcpy(q = buf + strlen(buf), "/..");
151 				if (access(buf, X_OK)) {
152 					*q = 0;
153 					return(TRUE);
154 				}
155 			}
156 			*buf = 0;
157 			path = p+1;
158 		} while (*p);
159 	}
160 	/*NOTREACHED*/
161 }
162 
163 
164 /*
165  * Make sure that the program is running only once, by creating a special
166  * lockfile that contains our pid. If such a lockfile already exists, see
167  * if the process that created it exists; if no, ignore it. If <force> is
168  * TRUE, try to kill it. Otherwise, return FALSE.
169  */
170 
171 static BOOL check_process(BOOL, PID_T);
172 
startup_lock(BOOL pland,BOOL force)173 BOOL startup_lock(
174 	BOOL			pland,		/* is this plan or pland? */
175 	BOOL			force)		/* kill competitor */
176 {
177 	char			*pathtmp;	/* name of lockfile */
178 	char			buf[2048];	/* lockfile contents */
179 	int			lockfd;		/* lock/pid file */
180 	PID_T			pid;		/* pid in lockfile */
181 	int			retry = 5;	/* try to kill daemon 5 times*/
182 
183 	pathtmp = resolve_tilde(pland ? PLANDLOCK : PLANLOCK);
184 	sprintf(buf, pathtmp, (int)getuid());
185 	strncpy(lockpath, resolve_tilde(buf), sizeof(lockpath)-1);
186 	lockpath[sizeof(lockpath)-1] = 0;
187 	while ((lockfd = open(lockpath, O_WRONLY|O_EXCL|O_CREAT, 0644)) < 0) {
188 		if ((lockfd = open(lockpath, O_RDONLY)) < 0) {
189 			int err = errno;
190 			fprintf(stderr, _("%s: cannot open lockfile "),
191 								progname);
192 			errno = err;
193 			perror(lockpath);
194 			_exit(1);
195 		}
196 		if (read(lockfd, buf, 10) < 5) {
197 			int err = errno;
198 			fprintf(stderr, _("%s: cannot read lockfile "),
199 								progname);
200 			errno = err;
201 			perror(lockpath);
202 			_exit(1);
203 		}
204 		buf[10] = 0;
205 		pid = atoi(buf);
206 		close(lockfd);
207 		if (pid == getpid())
208 			break;
209 		if (!retry--)
210 			fprintf(stderr,
211 			_("%s: failed to kill process %d owning lockfile %s\n"),
212 						progname, pid, lockpath);
213 #		ifndef NOKILL0
214 		if (kill(pid, 0) && errno == ESRCH) {
215 			if (unlink(lockpath) && errno != ENOENT) {
216 				int err = errno;
217 				fprintf(stderr, _("%s: cannot unlink lockfile "
218 						), progname);
219 				errno = err;
220 				perror(lockpath);
221 				return(FALSE);
222 			}
223 			continue;
224 		}
225 #		endif
226 		if (!force)
227 			return(FALSE);
228 
229 		/* if check_process is functional (works on current process),
230 		 * check whether the pid in the lockfile is really plan/pland.
231 		 * if yes (or if check_process is broken), kill the old pid. */
232 		if (check_process(pland, getpid()) &&
233 		   !check_process(pland, pid)) {
234 			fprintf(stderr, _("%s: process %d owning lockfile %s "
235 				"no longer exists, not killing"),
236 				progname, pid, pathtmp);
237 			unlink(pathtmp);
238 			break;
239 		}
240 		(void)kill(pid, retry == 4 ? SIGINT : SIGTERM);
241 		sleep(1);
242 	}
243 	sprintf(buf, _("%5d      (pid of lockfile for %s, for user %d)\n"),
244 				(int)getpid(), progname, (int)getuid());
245 	if (write(lockfd, buf, (int)strlen(buf)) != (int)strlen(buf)) {
246 		int err = errno;
247 		fprintf(stderr, _("%s: cannot write lockfile "), progname);
248 		errno = err;
249 		perror(lockpath);
250 		_exit(1);
251 	}
252 	close(lockfd);
253 	return(TRUE);
254 }
255 
256 
257 /*
258  * given a pid (read from the lockfile), make sure it really belongs to a
259  * process called "plan" or "pland". The expected process might have died
260  * and the pid might have been reused by some other process, which we do
261  * not want to kill. Return TRUE if the process is still plan or pland.
262  */
263 
check_process(BOOL pland,PID_T pid)264 static BOOL check_process(
265 	BOOL			pland,		/* false: plan, true: pland */
266 	PID_T			pid)		/* check this process ID */
267 {
268 #ifdef LINUX
269 	struct stat		statbuf;	/* for checking ownership */
270 	char			buf[1024];	/* lockfile contents */
271 	int			fd;		/* /proc/%d/... file */
272 	char			*p;		/* for mangling the prog name*/
273 	int			i;
274 
275 	sprintf(buf, "/proc/%u/cmdline", pid);
276 	if (stat(buf, &statbuf) < 0)			/* read file owner */
277 		return(FALSE);
278 	if (statbuf.st_uid != getuid())			/* is it us? */
279 		return(FALSE);
280 	while ((fd = open(buf, O_RDONLY)) < 0)		/* open /proc file */
281 		return(FALSE);
282 	i = read(fd, buf, sizeof(buf)-1);		/* get cmdline */
283 	close(fd);
284 	if (i < 1)					/* none found? */
285 		return(FALSE);
286 	buf[i] = 0;
287 	if ((p = strchr(buf, ' ')))			/* isolate first word*/
288 		*p = 0;
289 	p = strrchr(buf, '/');				/* cut off /path/ */
290 	p = p ? p+1 : buf;
291 	return(!strcmp(p, pland ? "pland" : "plan"));	/* compare prog name */
292 #else
293 	(void)pland;
294 	(void)pid;
295 	return(FALSE);
296 #endif
297 }
298 
299 
300 /*
301  * Create new lock file when it has been deleted (after cleanup of /tmp)
302  */
303 
refresh_lock(char * pathtmp)304 BOOL refresh_lock(
305 	char		*pathtmp)	/* PLANDLOCK or PLANLOCK */
306 {
307 	char		buf[80];	/* lockfile contents */
308 	int		lockfd;		/* lock/pid file */
309 
310 	sprintf(lockpath, pathtmp, (int)getuid());
311 	if ((lockfd = open(lockpath, O_RDONLY)) >= 0) {
312 		close(lockfd);
313 		return(TRUE);
314 	}
315 	lockfd = creat(lockpath,0644);
316 	if (lockfd < 0) {
317 		int err = errno;
318 		fprintf(stderr, _("%s: cannot create lockfile "), progname);
319 		errno = err;
320 		perror(lockpath);
321 		_exit(1);
322 	}
323 	sprintf(buf, _("%5d      (pid of lockfile for %s, for user %d)\n"),
324 				(int)getpid(), progname, (int)getuid());
325 	if (write(lockfd, buf, (int)strlen(buf)) != (int)strlen(buf)) {
326 		int err = errno;
327 		fprintf(stderr, _("%s: cannot write lockfile "), progname);
328 		errno = err;
329 		perror(lockpath);
330 		_exit(1);
331 	}
332 	close(lockfd);
333 	return(TRUE);
334 }
335 
336 
337 /*
338  * try to lock or unlock the database file. There is actually little risk
339  * that two plan programs try to access simultaneously, unless someone adds
340  * an appointment graphically, and then another one on the command line
341  * within 10 seconds. If people add two at the same time, locking doesn't
342  * help at all. As a side effect, the file is rewound.
343  */
344 
345 #ifndef NOLOCK
346 static BOOL got_alarm;
347 static void (*old_alarm)();
348 
349 /*ARGSUSED*/
alarmhand(int sig)350 static void alarmhand(
351 	int sig)
352 {
353 	got_alarm = TRUE;
354 	signal(SIGALRM, old_alarm);
355 }
356 #endif
357 
358 
359 /* the two UNUSED's below are wrong but they shut up stupid gcc warnings */
lockfile(UNUSED FILE * fp,UNUSED BOOL lock)360 void lockfile(
361 	UNUSED FILE	*fp,		/* file to lock */
362 	UNUSED BOOL	lock)		/* TRUE=lock, FALSE=unlock */
363 {
364 #ifndef NOLOCK
365 	if (lock) {
366 		got_alarm = FALSE;
367 		old_alarm = signal(SIGALRM, alarmhand);
368 		alarm(3);
369 		(void)rewind(fp);
370 		(void)lseek(fileno(fp), 0, 0);
371 #ifdef FLOCK
372 		if (flock(fileno(fp), LOCK_EX | LOCK_NB) || got_alarm) {
373 #else
374 		if (lockf(fileno(fp), F_LOCK, 0) || got_alarm) {
375 #endif
376 			perror(progname);
377 			fprintf(stderr,
378 _("%s: failed to lock database after 3 seconds, accessing anyway\n"), progname);
379 		}
380 		alarm(0);
381 	} else {
382 		(void)rewind(fp);
383 		(void)lseek(fileno(fp), 0, 0);
384 #ifdef FLOCK
385 		(void)flock(fileno(fp), LOCK_UN);
386 #else
387 		(void)lockf(fileno(fp), F_ULOCK, 0);
388 #endif
389 		return;
390 	}
391 #endif /* NOLOCK */
392 }
393 
394 
395 /*
396  * whenever something goes seriously wrong, this routine is called. It makes
397  * code easier to read. fatal() never returns.
398  */
399 
400 void fatal(
401 	char		*fmt, ...)
402 {
403 	va_list		parm;
404 
405 	va_start(parm, fmt);
406 	fprintf(stderr, "%s: ", progname);
407 	vfprintf(stderr, fmt, parm);
408 	va_end(parm);
409 	putc('\n', stderr);
410 	exit(1);
411 }
412 
413 
414 /*
415  * Ultrix doesn't have strdup, so we'll need to define one locally.
416  */
417 
418 char *mystrdup(
419 	char		*s)
420 {
421 	char		*p = NULL;
422 
423 	if (s && (p = (char *)malloc(strlen(s)+1)))
424 		strcpy(p, s);
425 	return(p);
426 }
427 
428 
429 /*
430  * Sinix doesn't have strcasecmp, so here is my own. Not as efficient as
431  * the canonical implementation, but short, and it's not time-critical.
432  */
433 
434 int mystrcasecmp(
435 	char		*a,
436 	char		*b)
437 {
438 	char		ac, bc;
439 
440 	for (;;) {
441 		ac = *a++;
442 		bc = *b++;
443 		if (!ac || !bc)
444 			break;
445 		if (ac <= 'Z' && ac >= 'A')	ac += 'a' - 'A';
446 		if (bc <= 'Z' && bc >= 'A')	bc += 'a' - 'A';
447 		if (ac != bc)
448 			break;
449 	}
450 	return(ac - bc);
451 }
452 
453 
454 #ifdef PLANGROK
455 
456 /*
457  * convert ascii code to and from a string representation. This is used for
458  * form files in grok format. Only compiled into plan versions with grok
459  * support (the real name is xmbase-grok; you need version 1.4 or up).
460  */
461 
462 char *to_octal(
463 	int		n)		/* ascii to convert to string */
464 {
465 	static char	buf[8];
466 
467 	if (n == '\t')			return("\\t");
468 	if (n == '\n')			return("\\n");
469 	if (n <= 0x20 || n >= 0x7f)	{ sprintf(buf, "\\%03o", n); }
470 	else				{ buf[0] = n; buf[1] = 0;    }
471 	return(buf);
472 }
473 
474 char to_ascii(
475 	char		*str,		/* string to convert to ascii */
476 	int		def)		/* default if string is empty */
477 {
478 	int		n;
479 	char		*p = str;
480 
481 	if (!p)
482 		return(def);
483 	while (*p == ' ' || *p == '\t') p++;
484 	if (!*p)
485 		return(def);
486 	if (*p == '\\') {
487 		if (p[1] == 't')	return('\t');
488 		if (p[1] == 'n')	return('\n');
489 		if (p[1] >= '0' &&
490 		    p[1] <= '7')	{ sscanf(p+1, "%o", &n); return(n); }
491 		if (p[1] == 'x')	{ sscanf(p+2, "%x", &n); return(n); }
492 		return('\\');
493 	} else
494 		return(*p);
495 }
496 
497 
498 /*
499  * some systems use mybzero (BSD), others memset (SysV), I'll roll my own...
500  */
501 
502 void mybzero(
503 	void		*p,
504 	int		n)
505 {
506 	char		*q = p;
507 	while (n--) *q++ = 0;
508 }
509 
510 
511 /*
512  * dummies for grok, called by g_*.c.
513  */
514 
515 void print_info_line(void) {}
516 
517 #endif
518