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