Jelajahi Sumber

Added support for renaming dirs/files

Christopher Haster 8 tahun lalu
induk
melakukan
bd817abb00
3 mengubah file dengan 278 tambahan dan 2 penghapusan
  1. 174 1
      lfs.c
  2. 1 0
      lfs.h
  3. 103 1
      tests/test_dirs.sh

+ 174 - 1
lfs.c

@@ -1337,7 +1337,7 @@ int lfs_remove(lfs_t *lfs, const char *path) {
     cwd.d.size -= entry.d.len;
 
     // either shift out the one entry or remove the whole dir block
-    if (cwd.d.size == sizeof(dir.d)) {
+    if (cwd.d.size == sizeof(cwd.d)) {
         lfs_dir_t pdir;
         int err = lfs_dir_fetch(lfs, &pdir, lfs->cwd);
         if (err) {
@@ -1418,6 +1418,179 @@ int lfs_remove(lfs_t *lfs, const char *path) {
     return 0;
 }
 
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
+    // Find old entry
+    lfs_dir_t oldcwd;
+    int err = lfs_dir_fetch(lfs, &oldcwd, lfs->cwd);
+    if (err) {
+        return err;
+    }
+
+    lfs_entry_t oldentry;
+    err = lfs_dir_find(lfs, &oldcwd, &oldpath, &oldentry);
+    if (err) {
+        return err;
+    }
+
+    // Allocate new entry
+    lfs_dir_t newcwd;
+    err = lfs_dir_fetch(lfs, &newcwd, lfs->cwd);
+    if (err) {
+        return err;
+    }
+
+    lfs_entry_t preventry;
+    err = lfs_dir_append(lfs, &newcwd, &newpath, &preventry);
+    if (err && err != LFS_ERROR_EXISTS) {
+        return err;
+    }
+    bool prevexists = (err == LFS_ERROR_EXISTS);
+
+    // must have same type
+    if (prevexists && preventry.d.type != oldentry.d.type) {
+        return LFS_ERROR_INVALID;
+    }
+
+    lfs_dir_t dir;
+    if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
+        // must be empty before removal, checking size
+        // without masking top bit checks for any case where
+        // dir is not empty
+        int err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
+        if (err) {
+            return err;
+        } else if (dir.d.size != sizeof(dir.d)) {
+            return LFS_ERROR_INVALID;
+        }
+    }
+
+    // Move to new location
+    lfs_entry_t newentry = preventry;
+    newentry.d = oldentry.d;
+    newentry.d.len = sizeof(newentry.d) + strlen(newpath);
+
+    newcwd.d.rev += 1;
+    if (!prevexists) {
+        newcwd.d.size += newentry.d.len;
+    }
+
+    err = lfs_pair_commit(lfs, newentry.dir,
+        3, (struct lfs_commit_region[3]) {
+            {0, sizeof(newcwd.d), &newcwd.d},
+            {newentry.off,
+             sizeof(newentry.d),
+             &newentry.d},
+            {newentry.off+sizeof(newentry.d),
+             newentry.d.len - sizeof(newentry.d),
+             newpath}
+        });
+    if (err) {
+        return err;
+    }
+
+    // fetch again in case newcwd == oldcwd
+    // TODO handle this better?
+    err = lfs_dir_fetch(lfs, &oldcwd, oldcwd.pair);
+    if (err) {
+        return err;
+    }
+
+    err = lfs_dir_find(lfs, &oldcwd, &oldpath, &oldentry);
+    if (err) {
+        return err;
+    }
+
+    // Remove from old location
+    // TODO abstract this out for rename + remove?
+    oldcwd.d.rev += 1;
+    oldcwd.d.size -= oldentry.d.len;
+
+    // either shift out the one entry or remove the whole dir block
+    if (oldcwd.d.size == sizeof(oldcwd.d)) {
+        lfs_dir_t pdir;
+        int err = lfs_dir_fetch(lfs, &pdir, lfs->cwd);
+        if (err) {
+            return err;
+        }
+
+        while (lfs_paircmp(pdir.d.tail, oldcwd.pair) != 0) {
+            int err = lfs_dir_fetch(lfs, &pdir, pdir.d.tail);
+            if (err) {
+                return err;
+            }
+        }
+
+        // TODO easier check for head block? (common case)
+        if (!(pdir.d.size & 0x80000000)) {
+            int err = lfs_pair_shift(lfs, oldentry.dir,
+                1, (struct lfs_commit_region[]) {
+                    {0, sizeof(oldcwd.d), &oldcwd.d},
+                },
+                oldentry.off, oldentry.d.len);
+            if (err) {
+                return err;
+            }
+        } else {
+            pdir.d.tail[0] = oldcwd.d.tail[0];
+            pdir.d.tail[1] = oldcwd.d.tail[1];
+            pdir.d.rev += 1;
+
+            err = lfs_pair_commit(lfs, pdir.pair,
+                1, (struct lfs_commit_region[]) {
+                    {0, sizeof(pdir.d), &pdir.d},
+                });
+            if (err) {
+                return err;
+            }
+        }
+    } else {
+        int err = lfs_pair_shift(lfs, oldentry.dir,
+            1, (struct lfs_commit_region[]) {
+                {0, sizeof(oldcwd.d), &oldcwd.d},
+            },
+            oldentry.off, oldentry.d.len);
+        if (err) {
+            return err;
+        }
+    }
+
+    // TODO abstract this out for rename + remove?
+    if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
+        // remove dest from the dir list
+        // this may create an orphan, which must be deorphaned
+        lfs_dir_t pdir;
+        int err = lfs_dir_fetch(lfs, &pdir, lfs->root);
+        if (err) {
+            return err;
+        }
+
+        while (pdir.d.tail[0]) {
+            if (lfs_paircmp(pdir.d.tail, preventry.d.u.dir) == 0) {
+                pdir.d.tail[0] = dir.d.tail[0];
+                pdir.d.tail[1] = dir.d.tail[1];
+                pdir.d.rev += 1;
+
+                int err = lfs_pair_commit(lfs, pdir.pair,
+                    1, (struct lfs_commit_region[]) {
+                        {0, sizeof(pdir.d), &pdir.d},
+                    });
+                if (err) {
+                    return err;
+                }
+
+                break;
+            }
+
+            int err = lfs_dir_fetch(lfs, &pdir, pdir.d.tail);
+            if (err) {
+                return err;
+            }
+        }
+    }
+
+    return 0;
+}
+
 int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
     lfs_dir_t cwd;
     int err = lfs_dir_fetch(lfs, &cwd, lfs->cwd);

+ 1 - 0
lfs.h

@@ -138,6 +138,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
 int lfs_unmount(lfs_t *lfs);
 
 int lfs_remove(lfs_t *lfs, const char *path);
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
 int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
 
 int lfs_mkdir(lfs_t *lfs, const char *path);

+ 103 - 1
tests/test_dirs.sh

@@ -123,7 +123,7 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
 TEST
 
-echo "--- Directory deletion ---"
+echo "--- Directory remove ---"
 tests/test.py << TEST
     lfs_mount(&lfs, &config) => 0;
     lfs_remove(&lfs, "potato") => LFS_ERROR_INVALID;
@@ -180,5 +180,107 @@ tests/test.py << TEST
     lfs_unmount(&lfs) => 0;
 TEST
 
+echo "--- Directory rename ---"
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_mkdir(&lfs, "coldpotato") => 0;
+    lfs_mkdir(&lfs, "coldpotato/baked") => 0;
+    lfs_mkdir(&lfs, "coldpotato/sweet") => 0;
+    lfs_mkdir(&lfs, "coldpotato/fried") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_rename(&lfs, "coldpotato", "hotpotato") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_dir_open(&lfs, &dir[0], "hotpotato") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "baked") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "sweet") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "fried") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_dir_close(&lfs, &dir[0]) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_mkdir(&lfs, "warmpotato") => 0;
+    lfs_mkdir(&lfs, "warmpotato/mushy") => 0;
+    lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERROR_INVALID;
+
+    lfs_remove(&lfs, "warmpotato/mushy") => 0;
+    lfs_rename(&lfs, "hotpotato", "warmpotato") => 0;
+
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_dir_open(&lfs, &dir[0], "warmpotato") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "baked") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "sweet") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "fried") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_dir_close(&lfs, &dir[0]) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_mkdir(&lfs, "coldpotato") => 0;
+    lfs_rename(&lfs, "warmpotato/baked", "coldpotato/baked") => 0;
+    lfs_rename(&lfs, "warmpotato/sweet", "coldpotato/sweet") => 0;
+    lfs_rename(&lfs, "warmpotato/fried", "coldpotato/fried") => 0;
+    lfs_remove(&lfs, "coldpotato") => LFS_ERROR_INVALID;
+    lfs_remove(&lfs, "warmpotato") => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    lfs_mount(&lfs, &config) => 0;
+    lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, ".") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "..") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "baked") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "sweet") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 1;
+    strcmp(info.name, "fried") => 0;
+    info.type => LFS_TYPE_DIR;
+    lfs_dir_read(&lfs, &dir[0], &info) => 0;
+    lfs_dir_close(&lfs, &dir[0]) => 0;
+    lfs_unmount(&lfs) => 0;
+TEST
+
 echo "--- Results ---"
 tests/stats.py