aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Seebach <peter.seebach@windriver.com>2010-04-26 16:40:55 -0700
committerPeter Seebach <peter.seebach@windriver.com>2010-04-26 17:13:45 -0700
commitfbaffe9e22c6eac916f2323b5c2940f76862f9cb (patch)
tree95edeb9f97f5e17fca8081864523aa3b31dd78d0
parent69de8f0dfdd47fb70487f0d8213e5659e9b7e2c6 (diff)
downloadpseudo-fbaffe9e22c6eac916f2323b5c2940f76862f9cb.tar.gz
pseudo-fbaffe9e22c6eac916f2323b5c2940f76862f9cb.tar.bz2
pseudo-fbaffe9e22c6eac916f2323b5c2940f76862f9cb.zip
Handle rename(3) across devices.
When you rename across devices, inode can change. Until now, pseudo had no tools for handling a change in inode, but this is clearly a legitimate case.
-rw-r--r--guts/rename.c41
-rw-r--r--pseudo.c29
-rw-r--r--pseudo_db.c47
-rw-r--r--pseudo_db.h1
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);
}
diff --git a/pseudo.c b/pseudo.c
index bedce6b..a90cc1a 100644
--- a/pseudo.c
+++ b/pseudo.c
@@ -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);