aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog.txt3
-rw-r--r--pseudo.185
-rw-r--r--pseudo.c219
-rw-r--r--pseudo_db.c94
-rw-r--r--pseudo_db.h6
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)
diff --git a/pseudo.1 b/pseudo.1
index a7064a2..7002023 100644
--- a/pseudo.1
+++ b/pseudo.1
@@ -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
diff --git a/pseudo.c b/pseudo.c
index 44b746f..b9b05d0 100644
--- a/pseudo.c
+++ b/pseudo.c
@@ -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);