static void edit_cmd(void) {
	char n[MAX_FNAME], q[MAX_TEMPSTR];
	const char *editor;
	FILE *f;
	int ch = '\0', t;
	struct stat statbuf;
	struct utimbuf utimebuf;
	WAIT_T waiter;
	PID_T pid, xpid;

	log_it(RealUser, Pid, "BEGIN EDIT", User, 0);
	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
		fprintf(stderr, "path too long\n");
		exit(ERROR_EXIT);
	}
	if (!(f = fopen(n, "r"))) {
		if (errno != ENOENT) {
			perror(n);
			exit(ERROR_EXIT);
		}
		fprintf(stderr, "no crontab for %s - using an empty one\n", User);
		if (!(f = fopen(_PATH_DEVNULL, "r"))) {
			perror(_PATH_DEVNULL);
			exit(ERROR_EXIT);
		}
	}

	/* Turn off signals. */
	(void) signal(SIGHUP, SIG_IGN);
	(void) signal(SIGINT, SIG_IGN);
	(void) signal(SIGQUIT, SIG_IGN);

	if (!glue_strings(Filename, sizeof Filename, tmp_path(),
			"crontab.XXXXXX", '/')) {
		fprintf(stderr, "path too long\n");
		exit(ERROR_EXIT);
	}
	if (swap_uids() == -1) {
		perror("swapping uids");
		exit(ERROR_EXIT);
	}
	if (-1 == (t = mkstemp(Filename))) {
		perror(Filename);
		goto fatal;
	}

	if (swap_uids_back() == -1) {
		perror("swapping uids back");
		goto fatal;
	}
	if (!(NewCrontab = fdopen(t, "r+"))) {
		perror("fdopen");
		goto fatal;
	}

	Set_LineNum(1)
		/* 
		 * NHEADER_LINES processing removed for clarity
		 * (NHEADER_LINES == 0 in all Red Hat crontabs)
		 */
		/* copy the rest of the crontab (if any) to the temp file.
		 */
		if (EOF != ch)
		while (EOF != (ch = get_char(f)))
			putc(ch, NewCrontab);

#ifdef WITH_SELINUX
	if (selinux_context) {
		context_t ccon = NULL;
		const char *level = NULL;

		if (!(ccon = context_new(selinux_context))) {
			fprintf(stderr, "context_new failed\n");
			goto fatal;
		}

		if (!(level = context_range_get(ccon))) {
			fprintf(stderr, "context_range failed\n");
			goto fatal;
		}

		fprintf(NewCrontab, "MLS_LEVEL=%s\n", level);
		context_free(ccon);
		freecon(selinux_context);
		selinux_context = NULL;
	}
#endif

	fclose(f);
	if (fflush(NewCrontab) < OK) {
		perror(Filename);
		exit(ERROR_EXIT);
	}
	if (swap_uids() == -1) {
		perror("swapping uids");
		exit(ERROR_EXIT);
	}
	/* Set it to 1970 */
	utimebuf.actime = 0;
	utimebuf.modtime = 0;
	utime(Filename, &utimebuf);
	if (swap_uids_back() == -1) {
		perror("swapping uids");
		exit(ERROR_EXIT);
	}
  again:
	rewind(NewCrontab);
	if (ferror(NewCrontab)) {
		fprintf(stderr, "%s: error while writing new crontab to %s\n",
			ProgramName, Filename);
	  fatal:
		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:
		perror("fork");
		goto fatal;
	case 0:
		/* child */
		if (setgid(MY_GID(pw)) < 0) {
			perror("setgid(getgid())");
			exit(ERROR_EXIT);
		}
		if (setuid(MY_UID(pw)) < 0) {
			perror("setuid(getuid())");
			exit(ERROR_EXIT);
		}
		if (!glue_strings(q, sizeof q, editor, Filename, ' ')) {
			fprintf(stderr, "%s: editor command line too long\n", ProgramName);
			exit(ERROR_EXIT);
		}
		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *) 0);
		perror(editor);
		exit(ERROR_EXIT);
	 /*NOTREACHED*/ default:
		/* parent */
		break;
	}

	/* parent */
	for (;;) {
		xpid = waitpid(pid, &waiter, 0);
		if (xpid == -1) {
			if (errno != EINTR)
				fprintf(stderr,
					"%s: waitpid() failed waiting for PID %ld from \"%s\": %s\n",
					ProgramName, (long) pid, editor, strerror(errno));
		}
		else if (xpid != pid) {
			fprintf(stderr, "%s: wrong PID (%ld != %ld) from \"%s\"\n",
				ProgramName, (long) xpid, (long) pid, editor);
			goto fatal;
		}
		else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
			fprintf(stderr, "%s: \"%s\" exited with status %d\n",
				ProgramName, editor, WEXITSTATUS(waiter));
			goto fatal;
		}
		else if (WIFSIGNALED(waiter)) {
			fprintf(stderr,
				"%s: \"%s\" killed; signal %d (%score dumped)\n",
				ProgramName, editor, WTERMSIG(waiter),
				WCOREDUMP(waiter) ? "" : "no ");
			goto fatal;
		}
		else
			break;
	}
	(void) signal(SIGHUP, SIG_DFL);
	(void) signal(SIGINT, SIG_DFL);
	(void) signal(SIGQUIT, SIG_DFL);

	/* lstat doesn't make any harm, because 
	 * the file is stat'ed only when crontab is touched
	 */
	if (lstat(Filename, &statbuf) < 0) {
		perror("lstat");
		goto fatal;
	}

	if (!S_ISREG(statbuf.st_mode)) {
		fprintf(stderr, "%s: illegal crontab\n", ProgramName);
		goto remove;
	}

	if (statbuf.st_mtime == 0) {
		fprintf(stderr, "%s: no changes made to crontab\n", ProgramName);
		goto remove;
	}

	fprintf(stderr, "%s: installing new crontab\n", ProgramName);
	fclose(NewCrontab);
	if (swap_uids() < OK) {
		perror("swapping uids");
		goto remove;
	}
	if (!(NewCrontab = fopen(Filename, "r+"))) {
		perror("cannot read new crontab");
		goto remove;
	}
	if (swap_uids_back() < OK) {
		perror("swapping uids back");
		exit(ERROR_EXIT);
	}
	if (NewCrontab == 0L) {
		perror("fopen");
		goto fatal;
	}
	switch (replace_cmd()) {
	case 0:
		break;
	case -1:
		for (;;) {
			printf("Do you want to retry the same edit? ");
			fflush(stdout);
			q[0] = '\0';
			if (fgets(q, sizeof q, stdin) == 0L)
				continue;
			switch (q[0]) {
			case 'y':
			case 'Y':
				goto again;
			case 'n':
			case 'N':
				goto abandon;
			default:
				fprintf(stderr, "Enter Y or N\n");
			}
		}
	 /*NOTREACHED*/ case -2:
	  abandon:
		fprintf(stderr, "%s: edits left in %s\n", ProgramName, Filename);
		goto done;
	default:
		fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n",
			ProgramName);
		goto fatal;
	}
  remove:
	unlink(Filename);
  done:
	log_it(RealUser, Pid, "END EDIT", User, 0);
}