diff options
-rw-r--r-- | ChangeLog.txt | 3 | ||||
-rw-r--r-- | pseudo.1 | 85 | ||||
-rw-r--r-- | pseudo.c | 219 | ||||
-rw-r--r-- | pseudo_db.c | 94 | ||||
-rw-r--r-- | pseudo_db.h | 6 |
5 files changed, 359 insertions, 48 deletions
diff --git a/ChangeLog.txt b/ChangeLog.txt index e2df11e..0c35d84 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,6 @@ +2010-11-16: + * (seebs) database move functionality (first pass) + 2010-10-25: * (seebs) various makewrappers cleanups (pylint, mostly) @@ -28,7 +28,13 @@ .PP .B pseudo .RB [ \-P\ prefix ] -.B \-C +.RB [ \-BC ] +.BR \-i\ path +.PP +.B pseudo +.RB [ \-P\ prefix ] +.RB [ \-BC ] +.BR \-m\ from\ \-M\ to .PP .B pseudo .RB [ \-P\ prefix ] @@ -74,6 +80,19 @@ The command can be invoked in one of several possible modes: .TP 8 +.B \-B +The +.B \-B +option causes +.I pseudo +to scan its database, as with the +.B \-C +option, but instead of reporting mismatches, +.I pseudo +attempts to repair them. Specifically, device and inode number mismatches +are corrected, and symlink or directory mismatches result in deletion of +database entries. +.TP 8 .B \-C The .B \-C @@ -89,6 +108,46 @@ option causes .I pseudo to print a usage message and exit. .TP 8 +.B \-i +The +.B \-i +option causes +.I pseudo +to attempt to correct device number mismatches by +checking inodes; if +.I path +has the same inode number as recorded in the database, but a different +device number, all instances of the device number recorded in the database +are updated to the device number in the live filesystem for +.IR path . +This is intended to handle the mismatches that can occur when remounting +an NFS filesystem. The +.B \-i +option implies the +.B \-C +option. You can also specify the +.B \-B +option to request that the database be rebuilt. +.TP 8 +.B \-m +The +.B \-m +and +.B \-M +options cause +.I pseudo +to rename files, replacing the string +.I from +with the string +.I to. +The +.B \-m +option pair implies the +.B \-C +option. You can also specify the +.B \-B +option to request that the database be rebuilt. +.TP 8 .B \-V The .B \-V @@ -234,6 +293,30 @@ separately invoke the .I pseudo daemon; the client library starts it as needed. +If you have moved a directory which +.I pseudo +was tracking, you may be able to get the database reattached using the +.B \-m +option. A typical usage might be: + +.sp +$ +.I /path/to/pseudo +.B \-B \-m +.I oldpath +.B \-M +.I newpath +.br + +This requests that +.I pseudo +replace the string +.I oldpath +with the string +.I newpath +at the beginnings of filenames, then regenerate the database, correcting any +device/inode numbers. + .SH DIAGNOSTICS Depending on invocation, diagnostic messages usually go either to standard error or to the file @@ -38,22 +38,28 @@ #include "pseudo_server.h" #include "pseudo_db.h" +int opt_B = 0; int opt_C = 0; int opt_d = 0; int opt_f = 0; +char *opt_i = NULL; int opt_l = 0; +char *opt_m = NULL; +char *opt_M = NULL; long opt_p = 0; char *opt_r = NULL; int opt_S = 0; static int pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag); -static int pseudo_db_check(void); +static int pseudo_db_check(int fix); void usage(int status) { FILE *f = status ? stderr : stdout; fputs("Usage: pseudo [-dflv] [-P prefix] [-rR root] [-t timeout] [command]\n", f); fputs(" pseudo -h\n", f); + fputs(" pseudo [-dflv] [-P prefix] [-BC] -i path\n", f); + fputs(" pseudo [-dflv] [-P prefix] [-BC] -m from -M to\n", f); fputs(" pseudo [-dflv] [-P prefix] -C\n", f); fputs(" pseudo [-dflv] [-P prefix] -S\n", f); fputs(" pseudo [-dflv] [-P prefix] -V\n", f); @@ -107,53 +113,84 @@ main(int argc, char *argv[]) { * wrong. The + suppresses this annoying behavior, but may not * be compatible with sane option libraries. */ - while ((o = getopt(argc, argv, "+Cdfhlp:P:r:R:St:vV")) != -1) { + while ((o = getopt(argc, argv, "+BCdfhi:lm:M:p:P:r:R:St:vV")) != -1) { switch (o) { - case 'C': - /* check database */ + case 'B': /* rebuild database */ + opt_B = 1; opt_C = 1; break; - case 'd': - /* run as daemon */ + case 'C': /* check database */ + opt_C = 1; + break; + case 'd': /* run as daemon */ opt_d = 1; break; - case 'f': - /* run foregrounded */ + case 'f': /* run foregrounded */ opt_f = 1; break; - case 'h': + case 'h': /* help */ usage(0); break; - case 'l': + case 'i': /* renumber devices, assuming stable inodes */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0); + if (!s) { + pseudo_diag("Can't resolve path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + opt_i = s; + break; + case 'l': /* log */ optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts), "%s-l", optptr > opts ? " " : ""); opt_l = 1; break; - case 'p': + case 'm': /* move from... (see also 'M') */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0); + if (!s) { + pseudo_diag("Can't resolve move-from path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + opt_m = s; + break; + case 'M': /* move to... (see also 'm') */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0); + if (!s) { + pseudo_diag("Can't resolve move-to path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + opt_M = s; + break; + case 'p': /* passwd file path */ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); - if (!s) + if (!s) { pseudo_diag("Can't resolve passwd path '%s'\n", optarg); + usage(EXIT_FAILURE); + } pseudo_set_value("PSEUDO_PASSWD", s); break; - case 'P': + case 'P': /* prefix */ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); - if (!s) + if (!s) { pseudo_diag("Can't resolve prefix path '%s'\n", optarg); + usage(EXIT_FAILURE); + } pseudo_set_value("PSEUDO_PREFIX", s); break; - case 'r': /* FALLTHROUGH */ - case 'R': + case 'r': /* chroot to... (fallthrough) */ + case 'R': /* pseudo root path */ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); - if (!s) + if (!s) { pseudo_diag("Can't resolve root path '%s'\n", optarg); + usage(EXIT_FAILURE); + } pseudo_set_value("PSEUDO_CHROOT", s); if (o == 'r') opt_r = s; break; - case 'S': + case 'S': /* stop */ opt_S = 1; break; - case 't': + case 't': /* timeout */ pseudo_server_timeout = strtol(optarg, &s, 10); if (*s && !isspace(*s)) { pseudo_diag("Timeout must be an integer value.\n"); @@ -163,10 +200,10 @@ main(int argc, char *argv[]) { "%s-t %d", optptr > opts ? " " : "", pseudo_server_timeout); break; - case 'v': + case 'v': /* verbosity */ pseudo_debug_verbose(); break; - case 'V': + case 'V': /* version info */ printf("pseudo version %s\n", pseudo_version ? pseudo_version : "<undefined>"); printf("pseudo configuration:\n prefix: %s\n", PSEUDO_PREFIX); @@ -188,8 +225,75 @@ main(int argc, char *argv[]) { exit(EXIT_FAILURE); } + /* move database */ + if (opt_m || opt_M) { + struct stat buf; + pseudo_msg_t *msg; + int rc; + if (!(opt_m && opt_M)) { + pseudo_diag("You cannot move the database without specifying from and to.\n"); + exit(EXIT_FAILURE); + } + if (stat(opt_M, &buf) < 0) { + pseudo_diag("stat of '%s' failed: %s\n", + opt_M, strerror(errno)); + pseudo_diag("The directory the database is being moved to must exist.\n"); + exit(EXIT_FAILURE); + } + msg = pseudo_msg_new(0, opt_M); + if (!msg) { + pseudo_diag("Can't allocate message structure.\n"); + exit(EXIT_FAILURE); + } + rc = pdb_rename_file(opt_m, msg); + free(msg); + if (rc < 0) { + pseudo_diag("Warning: Database move may have failed.\n"); + pseudo_diag("To try to restore, you can reverse the move.\n"); + pseudo_diag("To commit to this anyway, run pseudo -C to check the database.\n"); + exit(EXIT_FAILURE); + } + pseudo_diag("Rename looked okay, running database sanity check.\n"); + opt_C = 1; + } + + if (opt_i) { + int rc; + struct stat buf; + pseudo_msg_t *msg; + if (stat(opt_i, &buf) < 0) { + pseudo_diag("stat of '%s' failed: %s\n", + opt_i, strerror(errno)); + pseudo_diag("The file used to renumber the database must exist.\n"); + exit(EXIT_FAILURE); + } + msg = pseudo_msg_new(0, opt_i); + if (!msg) { + pseudo_diag("Couldn't allocate data structure for path.\n"); + exit(EXIT_FAILURE); + } + if (pdb_find_file_path(msg)) { + pseudo_diag("Couldn't find a database entry for '%s'.\n", opt_i); + exit(EXIT_FAILURE); + } + if (buf.st_ino != msg->ino) { + pseudo_diag("The database inode entry for '%s' doesn't match; you must use -b.\n", + opt_i); + exit(EXIT_FAILURE); + } + rc = pdb_renumber_all(msg->dev, buf.st_dev); + free(msg); + if (rc < 0) { + pseudo_diag("Warning: Database renumber failed.\n"); + exit(EXIT_FAILURE); + } + pseudo_diag("Renumber looked okay, running database sanity check.\n"); + opt_C = 1; + } + if (opt_C) { - return pseudo_db_check(); + /* if opt_B is set, try to fix database */ + return pseudo_db_check(opt_B); } if (opt_S) { @@ -414,7 +518,7 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag) { if (by_path.deleting != 0) { pseudo_debug(1, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n", msg->path); - pdb_did_unlink_file(msg->path); + pdb_did_unlink_file(msg->path, by_path.deleting); } else { pseudo_diag("inode mismatch: '%s' ino %llu in db, %llu in request.\n", msg->path, @@ -503,7 +607,7 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag) { if (by_ino.deleting != 0) { pseudo_debug(1, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n", msg->path); - pdb_did_unlink_file(path_by_ino); + pdb_did_unlink_file(path_by_ino, by_ino.deleting); } else { pseudo_diag("path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n", msg->nlink, @@ -700,14 +804,14 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag) { pdb_update_inode(msg); break; case OP_MAY_UNLINK: - if (pdb_may_unlink_file(msg)) { + if (pdb_may_unlink_file(msg, msg->client)) { /* harmless, but client wants to know so it knows * whether to follow up... */ msg->result = RESULT_FAIL; } break; case OP_DID_UNLINK: - pdb_did_unlink_file(msg->path); + pdb_did_unlink_file(msg->path, msg->client); break; case OP_CANCEL_UNLINK: pdb_cancel_unlink_file(msg); @@ -793,11 +897,15 @@ pseudo_server_response(pseudo_msg_t *msg, const char *program, const char *tag) } int -pseudo_db_check(void) { +pseudo_db_check(int fix) { struct stat64 buf; pseudo_msg_t *m; pdb_file_list l; int errors = 0; + int delete_some = 0; + /* magic cookie used to show who's deleting the files */ + int magic_cookie = (int) getpid(); + int rc; l = pdb_files(); if (!l) { @@ -810,46 +918,79 @@ pseudo_db_check(void) { m ? (int) m->pathlen : -1, m ? m->path : "<n/a>"); if (m->pathlen > 0) { + int fixup_needed = 0; pseudo_debug(1, "Checking <%s>\n", m->path); if (lstat64(m->path, &buf)) { errors = EXIT_FAILURE; pseudo_diag("can't stat <%s>\n", m->path); continue; } + /* can't check for device type mismatches, uid/gid, or + * permissions, because those are the very things we + * can't really set. + */ if (buf.st_ino != m->ino) { - pseudo_diag("ino mismatch <%s>: ino %llu, db %llu\n", + pseudo_debug(fix, "ino mismatch <%s>: ino %llu, db %llu\n", m->path, (unsigned long long) buf.st_ino, (unsigned long long) m->ino); - errors = EXIT_FAILURE; + m->ino = buf.st_ino; + fixup_needed = 1; } if (buf.st_dev != m->dev) { - pseudo_diag("dev mismatch <%s>: dev %llu, db %llu\n", + pseudo_debug(fix, "dev mismatch <%s>: dev %llu, db %llu\n", m->path, (unsigned long long) buf.st_dev, (unsigned long long) m->dev); - errors = EXIT_FAILURE; + m->dev = buf.st_dev; + fixup_needed = 1; } if (S_ISLNK(buf.st_mode) != S_ISLNK(m->mode)) { - pseudo_diag("symlink mismatch <%s>: file %d, db %d\n", + pseudo_debug(fix, "symlink mismatch <%s>: file %d, db %d\n", m->path, S_ISLNK(buf.st_mode), S_ISLNK(m->mode)); - errors = EXIT_FAILURE; + fixup_needed = 2; } if (S_ISDIR(buf.st_mode) != S_ISDIR(m->mode)) { - pseudo_diag("symlink mismatch <%s>: file %d, db %d\n", + pseudo_debug(fix, "symlink mismatch <%s>: file %d, db %d\n", m->path, S_ISDIR(buf.st_mode), S_ISDIR(m->mode)); - errors = EXIT_FAILURE; + fixup_needed = 2; + } + if (fixup_needed) { + /* in fixup mode, either delete (mismatches) or + * correct (dev/ino). + */ + if (fix) { + if (fixup_needed == 1) { + rc = pdb_update_inode(m); + } else if (fixup_needed == 2) { + /* mark for deletion */ + delete_some = 1; + rc = pdb_may_unlink_file(m, magic_cookie); + } + if (rc) { + pseudo_diag("error updating file %s\n", + m->path); + errors = EXIT_FAILURE; + } + } else { + errors = EXIT_FAILURE; + } } - /* can't check for device type mismatches, uid/gid, or - * permissions, because those are the very things we - * can't really set. - */ } } pdb_files_done(l); + /* and now delete files marked for deletion */ + if (delete_some) { + rc = pdb_did_unlink_files(magic_cookie); + if (rc) { + pseudo_diag("error nuking mismatched files.\n"); + pseudo_diag("database may not be fixed.\n"); + errors = EXIT_FAILURE; + } + } return errors; } diff --git a/pseudo_db.c b/pseudo_db.c index 62f2322..245f4e6 100644 --- a/pseudo_db.c +++ b/pseudo_db.c @@ -1392,12 +1392,12 @@ pdb_update_file_path(pseudo_msg_t *msg) { return rc != SQLITE_DONE; } -/* mark a file for pending deletion */ +/* mark a file for pending deletion by a given client */ int -pdb_may_unlink_file(pseudo_msg_t *msg) { +pdb_may_unlink_file(pseudo_msg_t *msg, int deleting) { static sqlite3_stmt *mark_file; int rc, exact; - char *sql_mark_file = "UPDATE files SET deleting = 1 WHERE path = ?;"; + char *sql_mark_file = "UPDATE files SET deleting = ? WHERE path = ?;"; if (!file_db && get_db(&file_db)) { pseudo_diag("database error.\n"); @@ -1414,7 +1414,8 @@ pdb_may_unlink_file(pseudo_msg_t *msg) { return 1; } if (msg->pathlen) { - sqlite3_bind_text(mark_file, 1, msg->path, -1, SQLITE_STATIC); + sqlite3_bind_int(mark_file, 1, deleting); + sqlite3_bind_text(mark_file, 2, msg->path, -1, SQLITE_STATIC); } else { pseudo_debug(1, "cannot mark a file for pending deletion without a path."); return 1; @@ -1473,11 +1474,48 @@ pdb_cancel_unlink_file(pseudo_msg_t *msg) { return rc != SQLITE_DONE; } +/* delete all files attached to a given cookie; + * used for database fixup passes. + */ +int +pdb_did_unlink_files(int deleting) { + static sqlite3_stmt *delete_exact; + int rc, exact; + char *sql_delete_exact = "DELETE FROM files WHERE deleting = ?;"; + + if (!file_db && get_db(&file_db)) { + pseudo_diag("database error.\n"); + return 0; + } + if (!delete_exact) { + rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (deleting == 0) { + pseudo_diag("did_unlink_files: deleting must be non-zero.\n"); + return 0; + } + sqlite3_bind_int(delete_exact, 1, deleting); + rc = sqlite3_step(delete_exact); + if (rc != SQLITE_DONE) { + dberr(file_db, "cleanup of files marked for deletion may have failed"); + } + exact = sqlite3_changes(file_db); + pseudo_debug(3, "(exact %d)\n", exact); + sqlite3_reset(delete_exact); + sqlite3_clear_bindings(delete_exact); + return rc != SQLITE_DONE; +} + +/* confirm deletion of a specific file by a given client */ int -pdb_did_unlink_file(char *path) { +pdb_did_unlink_file(char *path, int deleting) { static sqlite3_stmt *delete_exact; int rc, exact; - char *sql_delete_exact = "DELETE FROM files WHERE path = ? AND deleting = 1;"; + char *sql_delete_exact = "DELETE FROM files WHERE path = ? AND deleting = ?;"; if (!file_db && get_db(&file_db)) { pseudo_diag("database error.\n"); @@ -1495,6 +1533,7 @@ pdb_did_unlink_file(char *path) { return 1; } sqlite3_bind_text(delete_exact, 1, path, -1, SQLITE_STATIC); + sqlite3_bind_int(delete_exact, 2, deleting); rc = sqlite3_step(delete_exact); if (rc != SQLITE_DONE) { dberr(file_db, "cleanup of file marked for deletion may have failed"); @@ -1659,6 +1698,49 @@ pdb_rename_file(const char *oldpath, pseudo_msg_t *msg) { return rc != SQLITE_DONE; } +/* renumber device only. + * this is used if the filesystem moves to a new device, without changing + * inode allocations. + */ +int +pdb_renumber_all(dev_t from, dev_t to) { + static sqlite3_stmt *update; + int rc; + char *sql = "UPDATE files " + " SET dev = ? " + " WHERE dev = ?;"; + + if (!file_db && get_db(&file_db)) { + pseudo_diag("database error.\n"); + return 0; + } + if (!update) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + rc = sqlite3_bind_int(update, 1, to); + if (rc) { + dberr(file_db, "error binding device numbers to update"); + } + rc = sqlite3_bind_int(update, 2, from); + if (rc) { + dberr(file_db, "error binding device numbers to update"); + } + + rc = sqlite3_step(update); + if (rc != SQLITE_DONE) { + dberr(file_db, "update may have failed: rc %d", rc); + } + sqlite3_reset(update); + sqlite3_clear_bindings(update); + pseudo_debug(2, "updating device dev %llu to %llu\n", + (unsigned long long) from, (unsigned long long) to); + return rc != SQLITE_DONE; +} + /* change dev/inode for a given path -- used only by RENAME for now. */ int diff --git a/pseudo_db.h b/pseudo_db.h index c9eb228..bd09c89 100644 --- a/pseudo_db.h +++ b/pseudo_db.h @@ -38,9 +38,10 @@ typedef struct { } log_entry; extern int pdb_cancel_unlink_file(pseudo_msg_t *msg); -extern int pdb_did_unlink_file(char *path); +extern int pdb_did_unlink_file(char *path, int deleting); +extern int pdb_did_unlink_files(int deleting); extern int pdb_link_file(pseudo_msg_t *msg); -extern int pdb_may_unlink_file(pseudo_msg_t *msg); +extern int pdb_may_unlink_file(pseudo_msg_t *msg, int deleting); extern int pdb_unlink_file(pseudo_msg_t *msg); extern int pdb_unlink_file_dev(pseudo_msg_t *msg); extern int pdb_update_file(pseudo_msg_t *msg); @@ -48,6 +49,7 @@ extern int pdb_update_file_path(pseudo_msg_t *msg); extern int pdb_update_inode(pseudo_msg_t *msg); extern int pdb_unlink_contents(pseudo_msg_t *msg); extern int pdb_rename_file(const char *oldpath, pseudo_msg_t *msg); +extern int pdb_renumber_all(dev_t from, dev_t to); extern int pdb_find_file_exact(pseudo_msg_t *msg); extern int pdb_find_file_path(pseudo_msg_t *msg); extern int pdb_find_file_dev(pseudo_msg_t *msg); |