diff options
-rw-r--r-- | guts/rename.c | 41 | ||||
-rw-r--r-- | pseudo.c | 29 | ||||
-rw-r--r-- | pseudo_db.c | 47 | ||||
-rw-r--r-- | pseudo_db.h | 1 |
4 files changed, 98 insertions, 20 deletions
diff --git a/guts/rename.c b/guts/rename.c index 9a27538..973b164 100644 --- a/guts/rename.c +++ b/guts/rename.c @@ -31,18 +31,21 @@ /* as with unlink, we have to do the remove before the operation */ msg = pseudo_client_op(OP_UNLINK, 0, -1, -1, newpath, newrc ? NULL : &newbuf); + /* stash the server's old data */ rc = real_rename(oldpath, newpath); save_errno = errno; if (rc == -1) { - newbuf.st_uid = msg->uid; - newbuf.st_gid = msg->uid; - newbuf.st_mode = msg->mode; - newbuf.st_dev = msg->dev; - newbuf.st_ino = msg->ino; - /* since we failed, that wasn't really unlinked -- put - * it back. - */ - pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &newbuf); + if (msg && msg->result == RESULT_SUCCEED) { + newbuf.st_uid = msg->uid; + newbuf.st_gid = msg->uid; + newbuf.st_mode = msg->mode; + newbuf.st_dev = msg->dev; + newbuf.st_ino = msg->ino; + /* since we failed, that wasn't really unlinked -- put + * it back. + */ + pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &newbuf); + } /* and we're done. */ errno = save_errno; return rc; @@ -70,17 +73,31 @@ * theory rename can never destroy a directory tree. */ - /* fill in "correct" details from server */ - msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, &oldbuf); + /* re-stat the new file. Why? Because if something got moved + * across device boundaries, its dev/ino changed! + */ + newrc = real___lxstat64(_STAT_VER, newpath, &newbuf); if (msg && msg->result == RESULT_SUCCEED) { pseudo_stat_msg(&oldbuf, msg); + if (newrc == 0) { + if (newbuf.st_dev != oldbuf.st_dev) { + oldbuf.st_dev = newbuf.st_dev; + oldbuf.st_ino = newbuf.st_ino; + } + } pseudo_debug(1, "renaming %s, got old mode of 0%o\n", oldpath, (int) msg->mode); } else { /* create an entry under the old name, which will then be * renamed; this way, children would get renamed too, if there * were any. */ - pseudo_debug(1, "renaming new '%s' [%llu]\n", + if (newrc == 0) { + if (newbuf.st_dev != oldbuf.st_dev) { + oldbuf.st_dev = newbuf.st_dev; + oldbuf.st_ino = newbuf.st_ino; + } + } + pseudo_debug(1, "creating new '%s' [%llu] to rename\n", oldpath, (unsigned long long) oldbuf.st_ino); pseudo_client_op(OP_LINK, 0, -1, -1, oldpath, &oldbuf); } @@ -365,12 +365,24 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag) { /* This is a bad sign. We should never have a different entry * for the inode... */ - if (by_path.ino != msg_header.ino) { - pseudo_diag("inode mismatch: '%s' ino %llu in db, %llu in request.\n", - msg->path, - (unsigned long long) by_path.ino, - (unsigned long long) msg_header.ino); - + if (by_path.ino != msg_header.ino && msg_header.ino != 0) { + switch (msg->op) { + case OP_EXEC: /* FALLTHROUGH */ + case OP_RENAME: + /* A rename that crossed a filesystem can change the inode + * number legitimately. + */ + pseudo_debug(2, "inode changed for '%s': %llu in db, %llu in request.\n", + msg->path, + (unsigned long long) by_path.ino, + (unsigned long long) msg_header.ino); + break; + default: + pseudo_diag("inode mismatch: '%s' ino %llu in db, %llu in request.\n", + msg->path, + (unsigned long long) by_path.ino, + (unsigned long long) msg_header.ino); + } } /* If the database entry disagrees on S_ISDIR, it's just * plain wrong. We remove the database entry, because it @@ -512,7 +524,7 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag) { */ pseudo_diag("creat for '%s' replaces existing %llu ['%s'].\n", msg->pathlen ? msg->path : "no path", - (unsigned long long) msg_header.ino, + (unsigned long long) by_ino.ino, path_by_ino ? path_by_ino : "no path"); pdb_unlink_file_dev(&by_ino); } @@ -632,9 +644,10 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag) { break; case OP_RENAME: /* a rename implies renaming an existing entry... and every - * database entry rooted in it. + * database entry rooted in it, if it's a directory. */ pdb_rename_file(oldpath, msg); + pdb_update_inode(msg); break; case OP_UNLINK: /* this removes any entries with the given path from the diff --git a/pseudo_db.c b/pseudo_db.c index 5211948..0af38eb 100644 --- a/pseudo_db.c +++ b/pseudo_db.c @@ -1485,6 +1485,53 @@ pdb_rename_file(const char *oldpath, pseudo_msg_t *msg) { return rc != SQLITE_DONE; } +/* change dev/inode for a given path -- used only by RENAME for now. + */ +int +pdb_update_inode(pseudo_msg_t *msg) { + static sqlite3_stmt *update; + int rc; + char *sql = "UPDATE files " + " SET dev = ?, ino = ? " + " WHERE path = ?;"; + + 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; + } + } + if (!msg) { + return 1; + } + if (!msg->pathlen) { + pseudo_diag("Can't update the inode of a file without its path.\n"); + return 1; + } + sqlite3_bind_int(update, 1, msg->dev); + sqlite3_bind_int(update, 2, msg->ino); + rc = sqlite3_bind_text(update, 3, msg->path, -1, SQLITE_STATIC); + if (rc) { + dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>"); + } + + 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 path %s to dev %llu, ino %llu\n", + msg->path, + (unsigned long long) msg->dev, (unsigned long long) msg->ino); + return rc != SQLITE_DONE; +} + /* change uid/gid/mode/rdev in any existing entries matching a given * dev/inode pair. */ diff --git a/pseudo_db.h b/pseudo_db.h index 2760278..f8f5e3c 100644 --- a/pseudo_db.h +++ b/pseudo_db.h @@ -42,6 +42,7 @@ 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); extern int pdb_update_file_path(pseudo_msg_t *msg); +extern int pdb_update_inode(pseudo_msg_t *msg); extern int pdb_rename_file(const char *oldpath, pseudo_msg_t *msg); extern int pdb_find_file_exact(pseudo_msg_t *msg); extern int pdb_find_file_path(pseudo_msg_t *msg); |