2 * Copyright (c) 1997-2006 Erez Zadok
3 * Copyright (c) 1990 Jan-Simon Pendry
4 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
5 * Copyright (c) 1990 The Regents of the University of California.
8 * This code is derived from software contributed to Berkeley by
9 * Jan-Simon Pendry at Imperial College, London.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgment:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * File: am-utils/libamu/xutil.c
45 * Miscellaneous Utilities: Logging, TTY, timers, signals, RPC, memory, etc.
50 #endif /* HAVE_CONFIG_H */
55 * Logfp is the default logging device, and is initialized to stderr by
56 * default in dplog/plog below, and in
57 * amd/amfs_program.c:amfs_program_exec().
61 static char *am_progname = "unknown"; /* "amd" */
62 static char am_hostname[MAXHOSTNAMELEN] = "unknown"; /* Hostname */
63 pid_t am_mypid = -1; /* process ID */
64 serv_state amd_state; /* amd's state */
65 int foreground = 1; /* 1 == this is the top-level server */
70 #endif /* HAVE_SYSLOG */
71 int xlog_level = XLOG_ALL & ~XLOG_MAP & ~XLOG_STATS;
72 int xlog_level_init = ~0;
73 static int amd_program_number = AMQ_PROGRAM;
76 # if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
78 static int orig_mem_bytes;
79 # endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
80 #endif /* DEBUG_MEM */
82 /* forward definitions */
83 /* for GCC format string auditing */
84 static void real_plog(int lvl, const char *fmt, va_list vargs)
85 __attribute__((__format__(__printf__, 2, 0)));
90 * List of debug options.
92 struct opt_tab dbg_opt[] =
94 {"all", D_ALL}, /* All non-disruptive options */
95 {"amq", D_AMQ}, /* Don't register for AMQ program */
96 {"daemon", D_DAEMON}, /* Don't enter daemon mode */
97 {"fork", D_FORK}, /* Don't fork server */
98 {"full", D_FULL}, /* Program trace */
99 #ifdef HAVE_CLOCK_GETTIME
100 {"hrtime", D_HRTIME}, /* Print high resolution time stamps */
101 #endif /* HAVE_CLOCK_GETTIME */
102 /* info service specific debugging (hesiod, nis, etc) */
104 {"mem", D_MEM}, /* Trace memory allocations */
105 {"mtab", D_MTAB}, /* Use local mtab file */
106 {"readdir", D_READDIR}, /* Check on browsable_dirs progress */
107 {"str", D_STR}, /* Debug string munging */
108 {"test", D_TEST}, /* Full debug - no daemon, no amq, local mtab */
109 {"trace", D_TRACE}, /* Protocol trace */
110 {"xdrtrace", D_XDRTRACE}, /* Trace xdr routines */
116 * List of log options
118 struct opt_tab xlog_opt[] =
120 {"all", XLOG_ALL}, /* All messages */
122 {"debug", XLOG_DEBUG}, /* Debug messages */
123 #endif /* DEBUG */ /* DEBUG */
124 {"error", XLOG_ERROR}, /* Non-fatal system errors */
125 {"fatal", XLOG_FATAL}, /* Fatal errors */
126 {"info", XLOG_INFO}, /* Information */
127 {"map", XLOG_MAP}, /* Map errors */
128 {"stats", XLOG_STATS}, /* Additional statistical information */
129 {"user", XLOG_USER}, /* Non-fatal user errors */
130 {"warn", XLOG_WARNING}, /* Warnings */
131 {"warning", XLOG_WARNING}, /* Warnings */
137 am_set_progname(char *pn)
144 am_get_progname(void)
151 am_set_hostname(char *hn)
153 xstrlcpy(am_hostname, hn, MAXHOSTNAMELEN);
158 am_get_hostname(void)
175 return (long) (foreground ? am_mypid : getppid());
186 * Avoid malloc's which return NULL for malloc(0)
192 p = (voidp) malloc((unsigned) len);
195 plog(XLOG_DEBUG, "Allocated size %d; block %p", len, p);
199 plog(XLOG_ERROR, "Retrying memory allocation");
204 plog(XLOG_FATAL, "Out of memory");
213 /* like xmalloc, but zeros out the bytes */
217 voidp p = xmalloc(len);
226 xrealloc(voidp ptr, int len)
229 plog(XLOG_DEBUG, "Reallocated size %d; block %p", len, ptr);
235 ptr = (voidp) realloc(ptr, (unsigned) len);
237 ptr = (voidp) xmalloc((unsigned) len);
240 plog(XLOG_FATAL, "Out of memory in realloc");
250 dxfree(char *file, int line, voidp ptr)
253 plog(XLOG_DEBUG, "Free in %s:%d: block %p", file, line, ptr);
254 /* this is the only place that must NOT use XFREE()!!! */
256 ptr = NULL; /* paranoid */
260 # if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
264 struct mallinfo mi = mallinfo();
265 u_long uordbytes = mi.uordblks * 4096;
267 if (mem_bytes != uordbytes) {
268 if (orig_mem_bytes == 0)
269 mem_bytes = orig_mem_bytes = uordbytes;
271 fprintf(logfp, "%s[%ld]: ", am_get_progname(), (long) am_mypid);
272 if (mem_bytes < uordbytes) {
273 fprintf(logfp, "ALLOC: %ld bytes", uordbytes - mem_bytes);
275 fprintf(logfp, "FREE: %ld bytes", mem_bytes - uordbytes);
277 mem_bytes = uordbytes;
278 fprintf(logfp, ", making %d missing\n", mem_bytes - orig_mem_bytes);
283 # endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
284 #endif /* DEBUG_MEM */
288 * Take a log format string and expand occurrences of %m
289 * with the current error code taken from errno. Make sure
290 * 'e' never gets longer than maxlen characters.
293 expand_error(const char *f, char *e, size_t maxlen)
300 for (p = f, q = e; (*q = *p) && (size_t) len < maxlen; len++, q++, p++) {
301 if (p[0] == '%' && p[1] == 'm') {
302 xstrlcpy(q, strerror(error), maxlen);
303 len += strlen(q) - 1;
308 e[maxlen-1] = '\0'; /* null terminate, to be sure */
314 * Output the time of day and hostname to the logfile
317 show_time_host_and_name(int lvl)
319 static time_t last_t = 0;
320 static char *last_ctime = 0;
322 #if defined(HAVE_CLOCK_GETTIME) && defined(DEBUG)
324 #endif /* defined(HAVE_CLOCK_GETTIME) && defined(DEBUG) */
325 char nsecs[11]; /* '.' + 9 digits + '\0' */
330 #if defined(HAVE_CLOCK_GETTIME) && defined(DEBUG)
332 * Some systems (AIX 4.3) seem to implement clock_gettime() as stub
335 if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
337 if (amuDebug(D_HRTIME))
338 xsnprintf(nsecs, sizeof(nsecs), ".%09ld", ts.tv_nsec);
341 #endif /* defined(HAVE_CLOCK_GETTIME) && defined(DEBUG) */
345 last_ctime = ctime(&t);
378 fprintf(logfp, "%15.15s%s %s %s[%ld]/%s ",
379 last_ctime + 4, nsecs, am_get_hostname(),
388 * Switch on/off debug options
391 debug_option(char *opt)
393 return cmdoption(opt, dbg_opt, &debug_flags);
398 dplog(const char *fmt, ...)
403 logfp = stderr; /* initialize before possible first use */
406 real_plog(XLOG_DEBUG, fmt, ap);
413 plog(int lvl, const char *fmt, ...)
418 logfp = stderr; /* initialize before possible first use */
421 real_plog(lvl, fmt, ap);
427 real_plog(int lvl, const char *fmt, va_list vargs)
432 static char last_msg[1024];
433 static int last_count = 0, last_lvl = 0;
435 if (!(xlog_level & lvl))
439 # if defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY)
441 # endif /* not defined(HAVE_MALLINFO) && defined(HAVE_MALLOC_VERIFY) */
442 #endif /* DEBUG_MEM */
445 * Note: xvsnprintf() may call plog() if a truncation happened, but the
446 * latter has some code to break out of an infinite loop. See comment in
449 xvsnprintf(ptr, 1023, expand_error(fmt, efmt, 1024), vargs);
452 if (*(ptr-1) == '\n')
457 switch (lvl) { /* from mike <mcooper@usc.edu> */
486 syslog(lvl, "%s", msg);
489 #endif /* HAVE_SYSLOG */
495 * mimic syslog behavior: only write repeated strings if they differ
497 switch (last_count) {
498 case 0: /* never printed at all */
500 if (strlcpy(last_msg, msg, 1024) >= 1024) /* don't use xstrlcpy here (recursive!) */
501 fprintf(stderr, "real_plog: string \"%s\" truncated to \"%s\"\n", last_msg, msg);
503 show_time_host_and_name(lvl); /* mimic syslog header */
504 fwrite(msg, ptr - msg, 1, logfp);
508 case 1: /* item printed once, if same, don't repeat */
509 if (STREQ(last_msg, msg)) {
511 } else { /* last msg printed once, new one differs */
512 /* last_count remains at 1 */
513 if (strlcpy(last_msg, msg, 1024) >= 1024) /* don't use xstrlcpy here (recursive!) */
514 fprintf(stderr, "real_plog: string \"%s\" truncated to \"%s\"\n", last_msg, msg);
516 show_time_host_and_name(lvl); /* mimic syslog header */
517 fwrite(msg, ptr - msg, 1, logfp);
524 * Don't allow repetitions longer than 100, so you can see when something
527 show_time_host_and_name(last_lvl);
528 xsnprintf(last_msg, sizeof(last_msg),
529 "last message repeated %d times\n", last_count);
530 fwrite(last_msg, strlen(last_msg), 1, logfp);
532 last_count = 0; /* start from scratch */
535 default: /* item repeated multiple times */
536 if (STREQ(last_msg, msg)) {
538 } else { /* last msg repeated+skipped, new one differs */
539 show_time_host_and_name(last_lvl);
540 xsnprintf(last_msg, sizeof(last_msg),
541 "last message repeated %d times\n", last_count);
542 fwrite(last_msg, strlen(last_msg), 1, logfp);
543 if (strlcpy(last_msg, msg, 1024) >= 1024) /* don't use xstrlcpy here (recursive!) */
544 fprintf(stderr, "real_plog: string \"%s\" truncated to \"%s\"\n", last_msg, msg);
547 show_time_host_and_name(lvl); /* mimic syslog header */
548 fwrite(msg, ptr - msg, 1, logfp);
558 * Display current debug options
561 show_opts(int ch, struct opt_tab *opts)
566 fprintf(stderr, "\t[-%c {no}", ch);
567 for (i = 0; opts[i].opt; i++) {
568 fprintf(stderr, "%c%s", s, opts[i].opt);
571 fputs("}]\n", stderr);
576 cmdoption(char *s, struct opt_tab *optb, int *flags)
584 struct opt_tab *dp, *dpn = 0;
591 /* check for "no" prefix to options */
592 if (s[0] == 'n' && s[1] == 'o') {
601 * Scan the array of debug options to find the
602 * corresponding flag value. If it is found
603 * then set (or clear) the flag (depending on
604 * whether the option was prefixed with "no").
606 for (dp = optb; dp->opt; dp++) {
607 if (STREQ(opt, dp->opt))
609 if (opt != s && !dpn && STREQ(s, dp->opt))
613 if (dp->opt || dpn) {
624 * This will log to stderr when parsing the command line
625 * since any -l option will not yet have taken effect.
627 plog(XLOG_USER, "option \"%s\" not recognized", s);
643 * Switch on/off logging options
646 switch_option(char *opt)
649 int rc = cmdoption(opt, xlog_opt, &xl);
655 * Keep track of initial log level, and
656 * don't allow options to be turned off.
658 if (xlog_level_init == ~0)
659 xlog_level_init = xl;
661 xl |= xlog_level_init;
670 * get syslog facility to use.
671 * logfile can be "syslog", "syslog:daemon", "syslog:local7", etc.
674 get_syslog_facility(const char *logfile)
678 /* parse facility string */
679 facstr = strchr(logfile, ':');
680 if (!facstr) /* log file was "syslog" */
683 if (!facstr || facstr[0] == '\0') { /* log file was "syslog:" */
684 plog(XLOG_WARNING, "null syslog facility, using LOG_DAEMON");
689 if (STREQ(facstr, "kern"))
691 #endif /* not LOG_KERN */
693 if (STREQ(facstr, "user"))
695 #endif /* not LOG_USER */
697 if (STREQ(facstr, "mail"))
699 #endif /* not LOG_MAIL */
701 if (STREQ(facstr, "daemon"))
705 if (STREQ(facstr, "auth"))
707 #endif /* not LOG_AUTH */
709 if (STREQ(facstr, "syslog"))
711 #endif /* not LOG_SYSLOG */
713 if (STREQ(facstr, "lpr"))
715 #endif /* not LOG_LPR */
717 if (STREQ(facstr, "news"))
719 #endif /* not LOG_NEWS */
721 if (STREQ(facstr, "uucp"))
723 #endif /* not LOG_UUCP */
725 if (STREQ(facstr, "cron"))
727 #endif /* not LOG_CRON */
729 if (STREQ(facstr, "local0"))
731 #endif /* not LOG_LOCAL0 */
733 if (STREQ(facstr, "local1"))
735 #endif /* not LOG_LOCAL1 */
737 if (STREQ(facstr, "local2"))
739 #endif /* not LOG_LOCAL2 */
741 if (STREQ(facstr, "local3"))
743 #endif /* not LOG_LOCAL3 */
745 if (STREQ(facstr, "local4"))
747 #endif /* not LOG_LOCAL4 */
749 if (STREQ(facstr, "local5"))
751 #endif /* not LOG_LOCAL5 */
753 if (STREQ(facstr, "local6"))
755 #endif /* not LOG_LOCAL6 */
757 if (STREQ(facstr, "local7"))
759 #endif /* not LOG_LOCAL7 */
761 /* didn't match anything else */
762 plog(XLOG_WARNING, "unknown syslog facility \"%s\", using LOG_DAEMON", facstr);
765 #endif /* not LOG_DAEMON */
769 * Change current logfile
772 switch_to_logfile(char *logfile, int old_umask, int truncate_log)
774 FILE *new_logfp = stderr;
779 #endif /* HAVE_SYSLOG */
781 if (STREQ(logfile, "/dev/stderr"))
783 else if (NSTREQ(logfile, "syslog", strlen("syslog"))) {
788 openlog(am_get_progname(),
792 # endif /* LOG_NOWAIT */
794 , get_syslog_facility(logfile)
795 # endif /* LOG_DAEMON */
797 #else /* not HAVE_SYSLOG */
798 plog(XLOG_WARNING, "syslog option not supported, logging unchanged");
799 #endif /* not HAVE_SYSLOG */
801 } else { /* regular log file */
802 (void) umask(old_umask);
804 truncate(logfile, 0);
805 new_logfp = fopen(logfile, "a");
811 * If we couldn't open a new file, then continue using the old.
813 if (!new_logfp && logfile) {
814 plog(XLOG_USER, "%s: Can't open logfile: %m", logfile);
819 * Close the previous file
821 if (logfp && logfp != stderr)
822 (void) fclose(logfp);
826 plog(XLOG_INFO, "switched to logfile \"%s\"", logfile);
828 plog(XLOG_INFO, "no logfile defined; using stderr");
837 if (!amuDebug(D_AMQ)) {
838 /* find which instance of amd to unregister */
839 u_long amd_prognum = get_amd_program_number();
841 if (pmap_unset(amd_prognum, AMQ_VERSION) != 1)
842 dlog("failed to de-register Amd program %lu, version %lu",
843 amd_prognum, AMQ_VERSION);
852 if (amd_state != Start) {
853 if (amd_state != Done)
859 #ifdef MOUNT_TABLE_ON_FILE
861 * Call unlock_mntlist to free any important resources such as an on-disk
862 * lock file (/etc/mtab~).
865 #endif /* MOUNT_TABLE_ON_FILE */
868 plog(XLOG_INFO, "Finishing with status %d", rc);
870 dlog("background process exiting with status %d", rc);
877 /* return the rpc program number under which amd was used */
879 get_amd_program_number(void)
881 return amd_program_number;
885 /* set the rpc program number used for amd */
887 set_amd_program_number(int program)
889 amd_program_number = program;
894 * Release the controlling tty of the process pid.
896 * Algorithm: try these in order, if available, until one of them
897 * succeeds: setsid(), ioctl(fd, TIOCNOTTY, 0).
898 * Do not use setpgid(): on some OSs it may release the controlling tty,
899 * even if the man page does not mention it, but on other OSs it does not.
900 * Also avoid setpgrp(): it works on some systems, and on others it is
901 * identical to setpgid().
904 amu_release_controlling_tty(void)
909 * In daemon mode, leaving open file descriptors to terminals or pipes
910 * can be a really bad idea.
911 * Case in point: the redhat startup script calls us through their 'initlog'
912 * program, which exits as soon as the original amd process exits. If,
913 * at some point, a misbehaved library function decides to print something
914 * to the screen, we get a SIGPIPE and die.
915 * And guess what: NIS glibc functions will attempt to print to stderr
916 * "YPBINDPROC_DOMAIN: Domain not bound" if ypbind is running but can't find
919 * So we close all of our "terminal" filedescriptors, i.e. 0, 1 and 2, then
920 * reopen them as /dev/null.
922 * XXX We should also probably set the SIGPIPE handler to SIG_IGN.
924 fd = open("/dev/null", O_RDWR);
926 plog(XLOG_WARNING, "Could not open /dev/null for rw: %m");
928 fflush(stdin); close(0); dup2(fd, 0);
929 fflush(stdout); close(1); dup2(fd, 1);
930 fflush(stderr); close(2); dup2(fd, 2);
935 /* XXX: one day maybe use vhangup(2) */
937 plog(XLOG_WARNING, "Could not release controlling tty using setsid(): %m");
939 plog(XLOG_INFO, "released controlling tty using setsid()");
942 #endif /* HAVE_SETSID */
945 fd = open("/dev/tty", O_RDWR);
947 /* not an error if already no controlling tty */
949 plog(XLOG_WARNING, "Could not open controlling tty: %m");
951 if (ioctl(fd, TIOCNOTTY, 0) < 0 && errno != ENOTTY)
952 plog(XLOG_WARNING, "Could not disassociate tty (TIOCNOTTY): %m");
954 plog(XLOG_INFO, "released controlling tty using ioctl(TIOCNOTTY)");
958 #endif /* not TIOCNOTTY */
960 plog(XLOG_ERROR, "unable to release controlling tty");
964 /* setup a single signal handler */
966 setup_sighandler(int signum, void (*handler)(int))
968 #ifdef HAVE_SIGACTION
970 memset(&sa, 0, sizeof(sa));
971 sa.sa_flags = 0; /* unnecessary */
972 sa.sa_handler = handler;
973 sigemptyset(&(sa.sa_mask)); /* probably unnecessary too */
974 sigaddset(&(sa.sa_mask), signum);
975 sigaction(signum, &sa, NULL);
976 #else /* not HAVE_SIGACTION */
977 (void) signal(signum, handler);
978 #endif /* not HAVE_SIGACTION */
983 * Return current time in seconds. If passed a non-null argyument, then
984 * fill it in with the current time in seconds and microseconds (useful
985 * for mtime updates).
988 clocktime(nfstime *nt)
990 static struct timeval now; /* keep last time, as default */
992 if (gettimeofday(&now, NULL) < 0) {
993 plog(XLOG_ERROR, "clocktime: gettimeofday: %m");
994 /* hack: force time to have incremented by at least 1 second */
997 /* copy seconds and microseconds. may demote a long to an int */
999 nt->nt_seconds = (u_int) now.tv_sec;
1000 nt->nt_useconds = (u_int) now.tv_usec;
1002 return (time_t) now.tv_sec;
1007 * Make all the directories in the path.
1010 mkdirs(char *path, int mode)
1013 * take a copy in case path is in readonly store
1015 char *p2 = strdup(path);
1018 int error_so_far = 0;
1021 * Skip through the string make the directories.
1022 * Mostly ignore errors - the result is tested at the end.
1024 * This assumes we are root so that we can do mkdir in a
1025 * mode 555 directory...
1027 while ((sp = strchr(sp + 1, '/'))) {
1029 if (mkdir(p2, mode) < 0) {
1030 error_so_far = errno;
1032 dlog("mkdir(%s)", p2);
1037 if (mkdir(p2, mode) < 0) {
1038 error_so_far = errno;
1040 dlog("mkdir(%s)", p2);
1045 return stat(path, &stb) == 0 &&
1046 (stb.st_mode & S_IFMT) == S_IFDIR ? 0 : error_so_far;
1051 * Remove as many directories in the path as possible.
1052 * Give up if the directory doesn't appear to have
1053 * been created by Amd (not mode dr-x) or an rmdir
1054 * fails for any reason.
1059 char *xdp = strdup(dir);
1065 * Try to find out whether this was
1066 * created by amd. Do this by checking
1067 * for owner write permission.
1069 if (stat(xdp, &stb) == 0 && (stb.st_mode & 0200) == 0) {
1070 if (rmdir(xdp) < 0) {
1071 if (errno != ENOTEMPTY &&
1076 plog(XLOG_ERROR, "rmdir(%s): %m", xdp);
1079 dlog("rmdir(%s)", xdp);
1085 dp = strrchr(xdp, '/');
1088 } while (dp && dp > xdp);