/* $Id: vidb.C,v 1.5 2002/11/28 07:49:48 dm Exp $ */ /* * * Copyright (C) 2001 David Mazieres (dm@uun.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * */ #include "authdb.h" #include "sfsmisc.h" #include "sfskeymisc.h" #include "init.h" str db; mode_t dbmode; uid_t dbuid; str editor; ptr dblock; str dottmp; str tmppath; EXITFN (cleanup); static void cleanup () { if (tmppath) unlink (tmppath); } static void lockit (str file, bool wait) { dblock = lockfile::alloc (file << ".lock"); if (!dblock) { if (!wait) fatal << file << ": could not lock file\n"; warn << ": waiting for lock on " << file << "..."; dblock = lockfile::alloc (file << ".lock", true); warnx << "\n"; } // In case we die but not editor, best NOT to close on exec fcntl (dblock->fd, F_SETFD, 0); dottmp = file << ".tmp"; } static void copyit (str file) { int rfd = open (file, O_RDONLY); struct stat sb; if (rfd < 0 || fstat (rfd, &sb) < 0) fatal << file << ": " << strerror (errno) << "\n"; dbmode = sb.st_mode & 0666; dbuid = sb.st_uid; int wfd = open (dottmp, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0600); if (wfd < 0) { if (errno == EEXIST) fatal << dottmp << " exists, please remove file or use -r flag\n"; fatal << dottmp << ": " << strerror (errno) << "\n"; } #ifdef HAVE_FCHOWN fchown (wfd, (uid_t) -1, sb.st_gid); #else /* !HAVE_FCHOWN */ chown (dottmp, (uid_t) -1, sb.st_gid); #endif /* !HAVE_FCHOWN */ tmppath = dottmp; char buf[8192]; int n; while ((n = read (rfd, buf, sizeof (buf))) > 0) if (write (wfd, buf, n) != n) fatal << tmppath << ": " << strerror (errno) << "\n"; if (n < 0) fatal << file << ": " << strerror (errno) << "\n"; close (rfd); close (wfd); } static void finish (int status, bool commit) { if (status) fatal << editor << ": abnormal exit\n"; int wfd = open (tmppath, O_WRONLY); if (wfd < 0 || fsync (wfd) < 0) fatal << tmppath << ": " << strerror (errno) << "\n"; #ifdef HAVE_FCHMOD if (fchmod (wfd, dbmode) < 0) #else /* !HAVE_FCHMOD */ if (chmod (tmppath, dbmode) < 0) #endif /* !HAVE_FCHMOD */ fatal << tmppath << ": " << strerror (errno) << "\n"; #ifdef HAVE_FCHOWN fchown (wfd, dbuid, (gid_t) -1); // Errors okay #else /* !HAVE_FCHOWN */ chown (tmppath, dbuid, (gid_t) -1); #endif /* !HAVE_FCHOWN */ if (close (wfd) < 0) fatal << tmppath << ": " << strerror (errno) << "\n"; if (!commit) { unlink (tmppath); exit (1); } if (!dblock->ok ()) { str target = db << ".bak"; if (rename (dottmp, target) < 0) fatal << dottmp << ": " << strerror (errno) << "\n"; dblock = NULL; tmppath = NULL; fatal << "lock was stolen -- file saved in " << target << "\n"; } if (rename (tmppath, db) < 0) fatal << db << ": " << strerror (errno) << "\n"; exit (0); } static void usage () { warnx << "usage: " << progname << " [-r] [-w] [-e editor] dbfile\n"; exit (1); } static bool get_yesno (const str &prompt) { bool retry = true; while (retry) { str r = getline (prompt, NULL); if (!r || r.len () == 0) return false; const char *cp = r.cstr (); if (*cp == 'Y' || *cp == 'y') return true; if (*cp == 'N' || *cp == 'n') return false; } return false; } int main (int argc, char **argv) { setprogname (argv[0]); editor = getenv ("EDITOR"); bool opt_recover = false, opt_wait = false; int ch; while ((ch = getopt (argc, argv, "rwe:")) != -1) switch (ch) { case 'r': opt_recover = true; break; case 'w': opt_wait = true; break; case 'e': editor = optarg; break; default: usage (); } if (optind + 1 != argc) usage (); db = argv[optind]; if (!editor) editor = "vi"; if (str edpath = find_program (editor)) editor = edpath; else fatal << editor << ": " << strerror (errno) << "\n"; lockit (db, opt_wait); if (opt_recover) { str target = db << ".bak"; if (rename (dottmp, target) == 0) warn << "saved old " << dottmp << " in " << target << "\n"; } copyit (db); const char *av[] = { editor, tmppath, NULL, NULL }; bool retry = true; int status = -1; bool commit; while (retry) { make_sync (0); make_sync (1); pid_t pid = spawn (editor, av); if (pid == -1) fatal << editor << ": " << strerror (errno) << "\n"; waitpid (pid, &status, 0); if (status) fatal << editor << ": abnormal exit\n"; ptr db = New refcounted (tmppath); ptr ac = db->open (false); if (!ac || !ac->validate ()) { warn << "Database update refused.\n"; commit = false; retry = get_yesno ("Retry database edit [y/N]? "); } else { commit = true; retry = false; } } if (!commit) warn << "Database update aborted.\n"; finish (status, commit); return 0; }