/* $Id: util.c 6138 2003-01-19 04:13:51Z rra $ ** ** Various miscellaneous utility functions for innd internal use. */ #include "config.h" #include "clibrary.h" #include "inn/innconf.h" #include "libinn.h" #include "innd.h" /* ** Sprintf a long into a buffer with enough leading zero's so that it ** takes up width characters. Don't add trailing NUL. Return true ** if it fit. Used for updating high-water marks in the active file ** in-place. */ bool FormatLong(char *p, unsigned long value, int width) { for (p += width - 1; width-- > 0; ) { *p-- = (int)(value % 10) + '0'; value /= 10; } return value == 0; } /* ** Glue a string, a char, and a string together. Useful for making ** filenames. */ void FileGlue(char *p, const char *n1, char c, const char *n2) { p += strlen(strcpy(p, n1)); *p++ = c; strcpy(p, n2); } /* ** Turn any \r or \n in text into spaces. Used to splice back multi-line ** headers into a single line. */ static char * Join(char *text) { char *p; for (p = text; *p; p++) if (*p == '\n' || *p == '\r') *p = ' '; return text; } /* ** Return a short name that won't overrun our bufer or syslog's buffer. ** q should either be p, or point into p where the "interesting" part is. */ char * MaxLength(const char *p, const char *q) { static char buff[80]; unsigned int i; /* Already short enough? */ i = strlen(p); if (i < sizeof buff - 1) { strlcpy(buff, p, sizeof(buff)); return Join(buff); } /* Simple case of just want the begining? */ if ((unsigned)(q - p) < sizeof buff - 4) { strlcpy(buff, p, sizeof(buff) - 3); strlcat(buff, "...", sizeof(buff)); } /* Is getting last 10 characters good enough? */ else if ((p + i) - q < 10) { strlcpy(buff, p, sizeof(buff) - 13); strlcat(buff, "...", sizeof(buff) - 10); strlcat(buff, &p[i - 10], sizeof(buff)); } else { /* Not in last 10 bytes, so use double elipses. */ strlcpy(buff, p, sizeof(buff) - 16); strlcat(buff, "...", sizeof(buff) - 13); strlcat(buff, &q[-5], sizeof(buff) - 3); strlcat(buff, "...", sizeof(buff)); } return Join(buff); } /* ** Split text into comma-separated fields. Return an allocated ** NULL-terminated array of the fields within the modified argument that ** the caller is expected to save or free. We don't use strchr() since ** the text is expected to be either relatively short or "comma-dense." */ char ** CommaSplit(char *text) { int i; char *p; char **av; char **save; /* How much space do we need? */ for (i = 2, p = text; *p; p++) if (*p == ',') i++; for (av = save = xmalloc(i * sizeof(char *)), *av++ = p = text; *p; ) if (*p == ',') { *p++ = '\0'; *av++ = p; } else p++; *av = NULL; return save; } /* ** Set up LISTBUFFER so that data will be put into array ** it allocates buffer and array for data if needed, otherwise use already ** allocated one */ void SetupListBuffer(int size, LISTBUFFER *list) { /* get space for data to be splitted */ if (list->Data == NULL) { list->DataLength = size; list->Data = xmalloc(list->DataLength + 1); } else if (list->DataLength < size) { list->DataLength = size; list->Data = xrealloc(list->Data, list->DataLength + 1); } /* get an array of character pointers. */ if (list->List == NULL) { list->ListLength = DEFAULTNGBOXSIZE; list->List = xmalloc(list->ListLength * sizeof(char *)); } } /* ** Do we need a shell for the command? If not, av is filled in with ** the individual words of the command and the command is modified to ** have NUL's inserted. */ bool NeedShell(char *p, const char **av, const char **end) { static const char Metachars[] = ";<>|*?[]{}()#$&=`'\"\\~\n"; const char *q; /* We don't use execvp(); works for users, fails out of /etc/rc. */ if (*p != '/') return true; for (q = p; *q; q++) if (strchr(Metachars, *q) != NULL) return true; for (end--; av < end; ) { /* Mark this word, check for shell meta-characters. */ for (*av++ = p; *p && !ISWHITE(*p); p++) continue; /* If end of list, we're done. */ if (*p == '\0') { *av = NULL; return false; } /* Skip whitespace, find next word. */ for (*p++ = '\0'; ISWHITE(*p); p++) continue; if (*p == '\0') { *av = NULL; return false; } } /* Didn't fit. */ return true; } /* ** Spawn a process, with I/O redirected as needed. Return the PID or -1 ** (and a syslog'd message) on error. */ pid_t Spawn(int niceval, int fd0, int fd1, int fd2, char * const av[]) { static char NOCLOSE[] = "%s cant close %d in %s %m"; static char NODUP2[] = "%s cant dup2 %d to %d in %s %m"; pid_t i; /* Fork; on error, give up. If not using the patched dbz, make * this call fork! */ i = fork(); if (i == -1) { syslog(L_ERROR, "%s cant fork %s %m", LogName, av[0]); return -1; } /* If parent, do nothing. */ if (i > 0) return i; /* Child -- do any I/O redirection. */ if (fd0 != 0) { if (dup2(fd0, 0) < 0) { syslog(L_FATAL, NODUP2, LogName, fd0, 0, av[0]); _exit(1); } if (fd0 != fd1 && fd0 != fd2 && close(fd0) < 0) syslog(L_ERROR, NOCLOSE, LogName, fd0, av[0]); } if (fd1 != 1) { if (dup2(fd1, 1) < 0) { syslog(L_FATAL, NODUP2, LogName, fd1, 1, av[0]); _exit(1); } if (fd1 != fd2 && close(fd1) < 0) syslog(L_ERROR, NOCLOSE, LogName, fd1, av[0]); } if (fd2 != 2) { if (dup2(fd2, 2) < 0) { syslog(L_FATAL, NODUP2, LogName, fd2, 2, av[0]); _exit(1); } if (close(fd2) < 0) syslog(L_ERROR, NOCLOSE, LogName, fd2, av[0]); } close_on_exec(0, false); close_on_exec(1, false); close_on_exec(2, false); /* Nice our child if we're supposed to. */ if (niceval != 0 && nice(niceval) == -1) syslog(L_ERROR, "SERVER cant nice child to %d: %m", niceval); /* Start the desired process (finally!). */ execv(av[0], av); syslog(L_FATAL, "%s cant exec in %s %m", LogName, av[0]); _exit(1); /* Not reached. */ return -1; } /* ** We ran out of space or other I/O error, throttle ourselves. */ void ThrottleIOError(const char *when) { char buff[SMBUF]; const char * p; int oerrno; if (Mode == OMrunning) { oerrno = errno; if (Reservation) { free(Reservation); Reservation = NULL; } snprintf(buff, sizeof(buff), "%s writing %s file -- throttling", strerror(oerrno), when); if ((p = CCblock(OMthrottled, buff)) != NULL) syslog(L_ERROR, "%s cant throttle %s", LogName, p); syslog(L_FATAL, "%s throttle %s", LogName, buff); errno = oerrno; ThrottledbyIOError = true; } } /* ** No matching storage.conf, throttle ourselves. */ void ThrottleNoMatchError(void) { char buff[SMBUF]; const char *p; if (Mode == OMrunning) { if (Reservation) { free(Reservation); Reservation = NULL; } snprintf(buff, sizeof(buff), "%s storing article -- throttling", SMerrorstr); if ((p = CCblock(OMthrottled, buff)) != NULL) syslog(L_ERROR, "%s cant throttle %s", LogName, p); syslog(L_FATAL, "%s throttle %s", LogName, buff); ThrottledbyIOError = true; } } void InndHisOpen(void) { char *histpath; int flags; size_t synccount; histpath = concatpath(innconf->pathdb, _PATH_HISTORY); if (innconf->hismethod == NULL) { sysdie("hismethod is not defined"); /*NOTREACHED*/ } flags = HIS_RDWR | (INND_DBZINCORE ? HIS_MMAP : HIS_ONDISK); History = HISopen(histpath, innconf->hismethod, flags); if (!History) { sysdie("SERVER can't open history %s", histpath); /*NOTREACHED*/ } free(histpath); HISsetcache(History, 1024 * innconf->hiscachesize); synccount = innconf->icdsynccount; HISctl(History, HISCTLS_SYNCCOUNT, &synccount); } void InndHisClose(void) { if (History == NULL) return; if (!HISclose(History)) { char *histpath; histpath = concatpath(innconf->pathdb, _PATH_HISTORY); sysdie("SERVER can't close history %s", histpath); free(histpath); } History = NULL; } bool InndHisWrite(const char *key, time_t arrived, time_t posted, time_t expires, TOKEN *token) { bool r = HISwrite(History, key, arrived, posted, expires, token); if (r != true) IOError("history write", errno); return r; } bool InndHisRemember(const char *key) { bool r = HISremember(History, key, Now.time); if (r != true) IOError("history remember", errno); return r; } void InndHisLogStats(void) { struct histstats stats = HISstats(History); syslog(L_NOTICE, "ME HISstats %d hitpos %d hitneg %d missed %d dne", stats.hitpos, stats.hitneg, stats.misses, stats.dne); }