static void list_cmd(void) { char n[MAX_FNAME]; FILE *f; int ch; log_it(RealUser, Pid, "LIST", User); if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { errx(ERROR_EXIT, "path too long"); } if (!(f = fopen(n, "r"))) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for `%s'", User); else err(ERROR_EXIT, "Cannot open `%s'", n); } /* file is open. copy to stdout, close. */ Set_LineNum(1); skip_header(&ch, f); for (; EOF != ch; ch = get_char(f)) (void)putchar(ch); (void)fclose(f); }
/* * Glue an array of strings together. Return a BIO and put the string * into |*out| so we can free it. */ static BIO *glue2bio(const char **pem, char **out) { size_t s = 0; *out = glue_strings(pem, &s); return BIO_new_mem_buf(*out, s); }
static void delete_cmd(void) { char n[MAX_FNAME]; log_it(RealUser, Pid, "DELETE", User); if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { errx(ERROR_EXIT, "path too long"); } if (unlink(n) != 0) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for `%s'", User); else err(ERROR_EXIT, "cannot unlink `%s'", n); } poke_daemon(); }
static int expand_single_string(struct token *tok, struct token *next, struct string **string_ret) { int ret; LIST_HEAD(string_list); ret = expand_params_and_word_split(tok, next, &string_list); if (ret) goto out_free_string_list; ret = glue_strings(&string_list); if (ret) goto out_free_string_list; if (!mysh_filename_expansion_disabled) { ret = do_filename_expansion(&string_list); if (ret) goto out_free_string_list; } if (list_empty(&string_list)) *string_ret = NULL; else if (list_is_singular(&string_list)) *string_ret = list_entry(string_list.next, struct string, list); else {
/* return NULL if eof or syntax error occurs; * otherwise return a pointer to a new entry. */ entry * load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, char **envp) { /* this function reads one crontab entry -- the next -- from a file. * it skips any leading blank lines, ignores comments, and returns * NULL if for any reason the entry can't be read and parsed. * * the entry is also parsed here. * * syntax: * user crontab: * minutes hours doms months dows cmd\n * system crontab (/etc/crontab): * minutes hours doms months dows USERNAME cmd\n */ ecode_e ecode = e_none; entry *e; int ch; char cmd[MAX_COMMAND]; char envstr[MAX_ENVSTR]; char **tenvp; Debug(DPARS, ("load_entry()...about to eat comments\n")); skip_comments(file); ch = get_char(file); if (ch == EOF) return (NULL); /* ch is now the first useful character of a useful line. * it may be an @special or it may be the first character * of a list of minutes. */ e = calloc(sizeof(*e), sizeof(char)); if (ch == '@') { /* all of these should be flagged and load-limited; i.e., * instead of @hourly meaning "0 * * * *" it should mean * "close to the front of every hour but not 'til the * system load is low". Problems are: how do you know * what "low" means? (save me from /etc/cron.conf!) and: * how to guarantee low variance (how low is low?), which * means how to we run roughly every hour -- seems like * we need to keep a history or let the first hour set * the schedule, which means we aren't load-limited * anymore. too much for my overloaded brain. (vix, jan90) * HINT */ ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); if (!strcmp("reboot", cmd)) { e->flags |= WHEN_REBOOT; } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ bit_set(e->minute, 0); bit_set(e->hour, 0); bit_set(e->dom, 0); bit_set(e->month, 0); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOW_STAR; } else if (!strcmp("monthly", cmd)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_set(e->dom, 0); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOW_STAR; } else if (!strcmp("weekly", cmd)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_set(e->dow, 0); e->flags |= DOM_STAR; } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { bit_set(e->minute, 0); bit_set(e->hour, 0); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOM_STAR | DOW_STAR; } else if (!strcmp("hourly", cmd)) { bit_set(e->minute, 0); bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOM_STAR | DOW_STAR; } else { ecode = e_timespec; goto eof; } /* Advance past whitespace between shortcut and * username/command. */ Skip_Blanks(ch, file); if (ch == EOF || ch == '\n') { ecode = e_cmd; goto eof; } } else { Debug(DPARS, ("load_entry()...about to parse numerics\n")); if (ch == '*') e->flags |= MIN_STAR; ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file); if (ch == EOF) { ecode = e_minute; goto eof; } /* hours */ if (ch == '*') e->flags |= HR_STAR; ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file); if (ch == EOF) { ecode = e_hour; goto eof; } /* DOM (days of month) */ if (ch == '*') e->flags |= DOM_STAR; ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file); if (ch == EOF) { ecode = e_dom; goto eof; } /* month */ ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, file); if (ch == EOF) { ecode = e_month; goto eof; } /* DOW (days of week) */ if (ch == '*') e->flags |= DOW_STAR; ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file); if (ch == EOF) { ecode = e_dow; goto eof; } } /* make sundays equivalent */ if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { bit_set(e->dow, 0); bit_set(e->dow, 7); } /* check for permature EOL and catch a common typo */ if (ch == '\n' || ch == '*') { ecode = e_cmd; goto eof; } /* ch is the first character of a command, or a username */ unget_char(ch, file); if (!pw) { char *username = cmd; /* temp buffer */ Debug(DPARS, ("load_entry()...about to parse username\n")); ch = get_string(username, MAX_COMMAND, file, " \t\n"); Debug(DPARS, ("load_entry()...got %s\n",username)); if (ch == EOF || ch == '\n' || ch == '*') { ecode = e_cmd; goto eof; } pw = getpwnam(username); if (pw == NULL) { ecode = e_username; goto eof; } Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", (long)pw->pw_uid, (long)pw->pw_gid)); } if ((e->pwd = pw_dup(pw)) == NULL) { ecode = e_memory; goto eof; } (void)memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd)); /* copy and fix up environment. some variables are just defaults and * others are overrides. */ if ((e->envp = env_copy(envp)) == NULL) { ecode = e_memory; goto eof; } if (!env_get("SHELL", e->envp)) { if (glue_strings(envstr, sizeof envstr, "SHELL", _PATH_BSHELL, '=')) { if ((tenvp = env_set(e->envp, envstr)) == NULL) { ecode = e_memory; goto eof; } e->envp = tenvp; } else log_it("CRON", getpid(), "error", "can't set SHELL"); } if (!env_get("HOME", e->envp)) { if (glue_strings(envstr, sizeof envstr, "HOME", pw->pw_dir, '=')) { if ((tenvp = env_set(e->envp, envstr)) == NULL) { ecode = e_memory; goto eof; } e->envp = tenvp; } else log_it("CRON", getpid(), "error", "can't set HOME"); } /* If login.conf is in used we will get the default PATH later. */ if (!env_get("PATH", e->envp)) { if (glue_strings(envstr, sizeof envstr, "PATH", _PATH_DEFPATH, '=')) { if ((tenvp = env_set(e->envp, envstr)) == NULL) { ecode = e_memory; goto eof; } e->envp = tenvp; } else log_it("CRON", getpid(), "error", "can't set PATH"); } if (glue_strings(envstr, sizeof envstr, "LOGNAME", pw->pw_name, '=')) { if ((tenvp = env_set(e->envp, envstr)) == NULL) { ecode = e_memory; goto eof; } e->envp = tenvp; } else log_it("CRON", getpid(), "error", "can't set LOGNAME"); #if defined(BSD) || defined(__linux) if (glue_strings(envstr, sizeof envstr, "USER", pw->pw_name, '=')) { if ((tenvp = env_set(e->envp, envstr)) == NULL) { ecode = e_memory; goto eof; } e->envp = tenvp; } else log_it("CRON", getpid(), "error", "can't set USER"); #endif Debug(DPARS, ("load_entry()...about to parse command\n")); /* If the first character of the command is '-' it is a cron option. */ while ((ch = get_char(file)) == '-') { switch (ch = get_char(file)) { case 'q': e->flags |= DONT_LOG; Skip_Nonblanks(ch, file); break; default: ecode = e_option; goto eof; } Skip_Blanks(ch, file); if (ch == EOF || ch == '\n') { ecode = e_cmd; goto eof; } } unget_char(ch, file); /* Everything up to the next \n or EOF is part of the command... * too bad we don't know in advance how long it will be, since we * need to malloc a string for it... so, we limit it to MAX_COMMAND. */ ch = get_string(cmd, MAX_COMMAND, file, "\n"); /* a file without a \n before the EOF is rude, so we'll complain... */ if (ch == EOF) { ecode = e_cmd; goto eof; } /* got the command in the 'cmd' string; save it in *e. */ if ((e->cmd = strdup(cmd)) == NULL) { ecode = e_memory; goto eof; } Debug(DPARS, ("load_entry()...returning successfully\n")); /* success, fini, return pointer to the entry we just created... */ return (e); eof: if (e->envp) env_free(e->envp); if (e->pwd) free(e->pwd); if (e->cmd) free(e->cmd); free(e); while (ch != '\n' && !feof(file)) ch = get_char(file); if (ecode != e_none && error_func) (*error_func)(ecodes[(int)ecode]); return (NULL); }
static void process_dir(const char *dname, struct stat *st, int sys, cron_db *new_db, cron_db *old_db) { DIR *dir; DIR_T *dp; /* we used to keep this dir open all the time, for the sake of * efficiency. however, we need to close it in every fork, and * we fork a lot more often than the mtime of the dir changes. */ if (!(dir = opendir(dname))) { log_it("CRON", getpid(), "OPENDIR FAILED", dname); (void) exit(ERROR_EXIT); } while (NULL != (dp = readdir(dir))) { char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1]; size_t i, len; /* * Homage to... */ static const char *junk[] = { "rpmsave", "rpmorig", "rpmnew", }; /* avoid file names beginning with ".". this is good * because we would otherwise waste two guaranteed calls * to getpwnam() for . and .., and there shouldn't be * hidden files in here anyway (in the non system case). */ if (dp->d_name[0] == '.') continue; /* ignore files starting with # ... */ if (dp->d_name[0] == '#') continue; len = strlen(dp->d_name); /* ... or too big or to small ... */ if (len == 0 || len >= sizeof(fname)) { log_it(dp->d_name, getpid(), "ORPHAN", "name too short or long"); continue; } /* ... or ending with ~ ... */ if (dp->d_name[len - 1] == '~') continue; (void)strlcpy(fname, dp->d_name, sizeof(fname)); /* ... or look for blacklisted extensions */ for (i = 0; i < __arraycount(junk); i++) { char *p; if ((p = strrchr(fname, '.')) != NULL && strcmp(p + 1, junk[i]) == 0) break; } if (i != __arraycount(junk)) continue; if (!glue_strings(tabname, sizeof tabname, dname, fname, '/')) { log_it(fname, getpid(), "ORPHAN", "could not glue strings"); continue; } process_crontab(sys ? "root" : fname, sys ? "*system*" : fname, tabname, st, new_db, old_db); } (void)closedir(dir); }
/* return ERR = end of file * FALSE = not an env setting (file was repositioned) * TRUE = was an env setting */ int load_env(char *envstr, FILE *f) { long filepos; int fileline; enum env_state state; char name[MAX_ENVSTR], val[MAX_ENVSTR]; char quotechar, *c, *str; filepos = ftell(f); fileline = LineNumber; skip_comments(f); if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n")) return (ERR); Debug(DPARS, ("load_env, read <%s>\n", envstr)); (void)memset(name, 0, sizeof name); (void)memset(val, 0, sizeof val); str = name; state = NAMEI; quotechar = '\0'; c = envstr; while (state != ERROR && *c) { switch (state) { case NAMEI: case VALUEI: if (*c == '\'' || *c == '"') quotechar = *c++; state++; /* FALLTHROUGH */ case NAME: case VALUE: if (quotechar) { if (*c == quotechar) { state++; c++; break; } if (state == NAME && *c == '=') { state = ERROR; break; } } else { if (state == NAME) { if (isspace((unsigned char)*c)) { c++; state++; break; } if (*c == '=') { state++; break; } } } *str++ = *c++; break; case EQ1: if (*c == '=') { state++; str = val; quotechar = '\0'; } else { if (!isspace((unsigned char)*c)) state = ERROR; } c++; break; case EQ2: case FINI: if (isspace((unsigned char)*c)) c++; else state++; break; default: abort(); } } if (state != FINI && !(state == VALUE && !quotechar)) { Debug(DPARS, ("load_env, not an env var, state = %d\n", state)); (void)fseek(f, filepos, 0); Set_LineNum(fileline); return (FALSE); } if (state == VALUE) { /* End of unquoted value: trim trailing whitespace */ c = val + strlen(val); while (c > val && isspace((unsigned char)c[-1])) *(--c) = '\0'; } /* 2 fields from parser; looks like an env setting */ /* * This can't overflow because get_string() limited the size of the * name and val fields. Still, it doesn't hurt to be careful... */ if (!glue_strings(envstr, MAX_ENVSTR, name, val, '=')) return (FALSE); Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr)); return (TRUE); }
/* returns 0 on success * -1 on syntax error * -2 on install error */ static int replace_cmd(void) { char n[MAX_FNAME], n2[MAX_FNAME], envstr[MAX_ENVSTR], lastch; FILE *tmp, *fmaxtabsize; int ch, eof, fd; int error = 0; entry *e; sig_t oint, oabrt, oquit, ohup; uid_t file_owner; time_t now = time(NULL); char **envp = env_init(); size_t maxtabsize; struct stat statbuf; if (envp == NULL) { warn("Cannot allocate memory."); return (-2); } if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, "tmp.XXXXXXXXXX", '/')) { TempFilename[0] = '\0'; warnx("path too long"); return (-2); } if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { warn("cannot create `%s'", TempFilename); if (fd != -1) { (void)close(fd); (void)unlink(TempFilename); } TempFilename[0] = '\0'; return (-2); } ohup = signal(SIGHUP, SIG_IGN); oint = signal(SIGINT, SIG_IGN); oquit = signal(SIGQUIT, SIG_IGN); oabrt = signal(SIGABRT, SIG_IGN); /* Make sure that the crontab is not an unreasonable size. * * XXX This is subject to a race condition--the user could * add stuff to the file after we've checked the size but * before we slurp it in and write it out. We can't just move * the test to test the temp file we later create, because by * that time we've already filled up the crontab disk. Probably * the right thing to do is to do a bytecount in the copy loop * rather than stating the file we're about to read. */ (void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE); if ((fmaxtabsize = fopen(n2, "r")) != NULL) { if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL) { maxtabsize = 0; } else { maxtabsize = atoi(n2); } (void)fclose(fmaxtabsize); } else { maxtabsize = MAXTABSIZE_DEFAULT; } if (fstat(fileno(NewCrontab), &statbuf)) { warn("error stat'ing crontab input"); error = -2; goto done; } if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize) { warnx("%ld bytes is larger than the maximum size of %ld bytes", (long) statbuf.st_size, (long) maxtabsize); error = -2; goto done; } /* write a signature at the top of the file. * * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. */ (void)fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); (void)fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); (void)fprintf(tmp, "# (Cron version %s -- %s)\n", CRON_VERSION, "$NetBSD: crontab.c,v 1.3.8.1 2012/03/07 23:41:17 riz Exp $"); /* copy the crontab to the tmp */ (void)rewind(NewCrontab); Set_LineNum(1); lastch = EOF; while (EOF != (ch = get_char(NewCrontab))) (void)putc(lastch = ch, tmp); if (lastch != (char)EOF && lastch != '\n') { warnx("missing trailing newline in `%s'", Filename); error = -1; goto done; } if (ferror(NewCrontab)) { warn("error while reading `%s'", Filename); error = -2; goto done; } (void)ftruncate(fileno(tmp), ftell(tmp)); /* XXX this should be a NOOP - is */ (void)fflush(tmp); if (ferror(tmp)) { (void)fclose(tmp); warn("error while writing new crontab to `%s'", TempFilename); error = -2; goto done; } /* check the syntax of the file being installed. */ /* BUG: was reporting errors after the EOF if there were any errors * in the file proper -- kludged it by stopping after first error. * vix 31mar87 */ Set_LineNum(1 - NHEADER_LINES); CheckErrorCount = 0; eof = FALSE; while (!CheckErrorCount && !eof) { switch (load_env(envstr, tmp)) { case ERR: /* check for data before the EOF */ if (envstr[0] != '\0') { Set_LineNum(LineNumber + 1); check_error("premature EOF"); } eof = TRUE; break; case FALSE: e = load_entry(tmp, check_error, pw, envp); if (e) free(e); break; case TRUE: break; } } if (CheckErrorCount != 0) { warnx("errors in crontab file, can't install."); (void)fclose(tmp); error = -1; goto done; } file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid; #ifdef HAVE_FCHOWN error = fchown(fileno(tmp), file_owner, (uid_t)-1); #else error = chown(TempFilename, file_owner, (gid_t)-1); #endif if (error < OK) { warn("cannot chown `%s'", TempFilename); (void)fclose(tmp); error = -2; goto done; } if (fclose(tmp) == EOF) { warn("error closing file"); error = -2; goto done; } if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { warnx("path too long"); error = -2; goto done; } if (rename(TempFilename, n)) { warn("error renaming `%s' to `%s'", TempFilename, n); error = -2; goto done; } TempFilename[0] = '\0'; log_it(RealUser, Pid, "REPLACE", User); poke_daemon(); done: (void)signal(SIGHUP, ohup); (void)signal(SIGINT, oint); (void)signal(SIGQUIT, oquit); (void)signal(SIGABRT, oabrt); if (TempFilename[0]) { (void) unlink(TempFilename); TempFilename[0] = '\0'; } return (error); }
static void edit_cmd(void) { char n[MAX_FNAME], q[MAX_TEMPSTR]; const char *editor; FILE *f; int ch, t, x; sig_t oint, oabrt, oquit, ohup; struct stat statbuf; struct utimbuf utimebuf; long mtimensec; WAIT_T waiter; PID_T pid, xpid; log_it(RealUser, Pid, "BEGIN EDIT", User); if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { errx(ERROR_EXIT, "path too long"); } if (!(f = fopen(n, "r"))) { if (errno != ENOENT) { err(ERROR_EXIT, "cannot open `%s'", n); } warnx("no crontab for `%s' - using an empty one", User); if (!(f = fopen(_PATH_DEVNULL, "r"))) { err(ERROR_EXIT, "cannot open `%s'", _PATH_DEVNULL); } } if (fstat(fileno(f), &statbuf) < 0) { warn("cannot stat crontab file"); goto fatal; } utimebuf.actime = statbuf.st_atime; utimebuf.modtime = statbuf.st_mtime; mtimensec = statbuf.st_mtimensec; /* Turn off signals. */ ohup = signal(SIGHUP, SIG_IGN); oint = signal(SIGINT, SIG_IGN); oquit = signal(SIGQUIT, SIG_IGN); oabrt = signal(SIGABRT, SIG_IGN); if (!glue_strings(Filename, sizeof Filename, _PATH_TMP, "crontab.XXXXXXXXXX", '/')) { warnx("path too long"); goto fatal; } if (-1 == (t = mkstemp(Filename))) { warn("cannot create `%s'", Filename); goto fatal; } #ifdef HAS_FCHOWN x = fchown(t, MY_UID(pw), MY_GID(pw)); #else x = chown(Filename, MY_UID(pw), MY_GID(pw)); #endif if (x < 0) { warn("cannot chown `%s'", Filename); goto fatal; } if (!(NewCrontab = fdopen(t, "r+"))) { warn("cannot open fd"); goto fatal; } Set_LineNum(1); skip_header(&ch, f); /* copy the rest of the crontab (if any) to the temp file. */ for (; EOF != ch; ch = get_char(f)) (void)putc(ch, NewCrontab); (void)fclose(f); if (fflush(NewCrontab) < OK) { err(ERROR_EXIT, "cannot flush output for `%s'", Filename); } (void)utime(Filename, &utimebuf); again: rewind(NewCrontab); if (ferror(NewCrontab)) { warn("error while writing new crontab to `%s'", Filename); fatal: (void)unlink(Filename); exit(ERROR_EXIT); } if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') && ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) { editor = EDITOR; } /* we still have the file open. editors will generally rewrite the * original file rather than renaming/unlinking it and starting a * new one; even backup files are supposed to be made by copying * rather than by renaming. if some editor does not support this, * then don't use it. the security problems are more severe if we * close and reopen the file around the edit. */ switch (pid = fork()) { case -1: warn("cannot fork"); goto fatal; case 0: /* child */ if (setgid(MY_GID(pw)) < 0) { err(ERROR_EXIT, "cannot setgid(getgid())"); } if (setuid(MY_UID(pw)) < 0) { err(ERROR_EXIT, "cannot setuid(getuid())"); } if (chdir(_PATH_TMP) < 0) { err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP); } if (!glue_strings(q, sizeof q, editor, Filename, ' ')) { errx(ERROR_EXIT, "editor command line too long"); } (void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0); err(ERROR_EXIT, "cannot start `%s'", editor); /*NOTREACHED*/ default: /* parent */ break; } /* parent */ for (;;) { xpid = waitpid(pid, &waiter, WUNTRACED); if (xpid == -1) { if (errno != EINTR) warn("waitpid() failed waiting for PID %ld " "from `%s'", (long)pid, editor); } else if (xpid != pid) { warnx("wrong PID (%ld != %ld) from `%s'", (long)xpid, (long)pid, editor); goto fatal; } else if (WIFSTOPPED(waiter)) { (void)kill(getpid(), WSTOPSIG(waiter)); } else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { warnx("`%s' exited with status %d\n", editor, WEXITSTATUS(waiter)); goto fatal; } else if (WIFSIGNALED(waiter)) { warnx("`%s' killed; signal %d (%score dumped)", editor, WTERMSIG(waiter), WCOREDUMP(waiter) ? "" : "no "); goto fatal; } else break; } (void)signal(SIGHUP, ohup); (void)signal(SIGINT, oint); (void)signal(SIGQUIT, oquit); (void)signal(SIGABRT, oabrt); if (fstat(t, &statbuf) < 0) { warn("cannot stat `%s'", Filename); goto fatal; } if (utimebuf.modtime == statbuf.st_mtime && mtimensec == statbuf.st_mtimensec) { warnx("no changes made to crontab"); goto remove; } warnx("installing new crontab"); switch (replace_cmd()) { case 0: break; case -1: for (;;) { (void)fpurge(stdin); (void)printf("Do you want to retry the same edit? "); (void)fflush(stdout); q[0] = '\0'; (void) fgets(q, (int)sizeof(q), stdin); switch (q[0]) { case 'y': case 'Y': goto again; case 'n': case 'N': goto abandon; default: (void)printf("Enter Y or N\n"); } } /*NOTREACHED*/ case -2: abandon: warnx("edits left in `%s'", Filename); goto done; default: warnx("panic: bad switch() in replace_cmd()"); goto fatal; } remove: (void)unlink(Filename); done: log_it(RealUser, Pid, "END EDIT", User); }