diff options
author | Christopher Haster <chaster@utexas.edu> | 2017-04-15 20:35:20 +0300 |
---|---|---|
committer | Christopher Haster <chaster@utexas.edu> | 2017-04-18 09:44:01 +0300 |
commit | bd817abb00080898357385b5d358418d3072789d (patch) | |
tree | 7e6d98d601d6d4834bb150bb85d4fa7aeadc8a09 /lfs.c | |
parent | 3b1bcbe8517db576030450e0a88ff1b274570f58 (diff) |
Added support for renaming dirs/files
Diffstat (limited to 'lfs.c')
-rw-r--r-- | lfs.c | 175 |
1 files changed, 174 insertions, 1 deletions
@@ -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); |