diff options
Diffstat (limited to 'fs/fat/fat_write.c')
-rw-r--r-- | fs/fat/fat_write.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 86366d03853..d4952e259ff 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1216,6 +1216,28 @@ static void fill_dentry(fsdata *mydata, dir_entry *dentptr, } /** + * fat_itr_parent() - modifies the iterator to the parent directory of the + * current iterator. + * + * @itr: iterator positioned anywhere in a directory + * @Return: 0 if the iterator is in the parent directory, -errno otherwise + */ +static int fat_itr_parent(fat_itr *itr) +{ + int ret; + + if (itr->is_root) + return -EIO; + + /* ensure iterator is at the first directory entry */ + ret = fat_move_to_cluster(itr, itr->start_clust); + if (ret) + return ret; + + return fat_itr_resolve(itr, "..", TYPE_DIR); +} + +/** * create_link() - inserts a directory entry for a file or directory * * @itr: directory iterator @@ -1822,3 +1844,266 @@ exit: free(dotdent); return ret; } + +/** + * check_path_prefix() - ensures one path does not contains another path as a + * prefix. + * + * for example: path foo/bar/baz/qux contains the path prefix foo/bar/baz + * + * note: the iterator may be pointing to any directory entry in the directory + * + * @prefix_clust: start cluster of the final directory in the prefix path + * (the start cluster of 'baz' in the above example) + * @path_itr: iterator of the path to check (an iterator pointing to any + * direntry in 'qux' in the above example) + * Return: -errno on error, 0 if path_itr does not have the directory + * at prefix_clust as an ancestor. + */ +static int check_path_prefix(loff_t prefix_clust, fat_itr *path_itr) +{ + fat_itr itr; + fsdata fsdata = { .fatbuf = NULL, }, *mydata = &fsdata; + int ret; + + /* duplicate fsdata */ + itr = *path_itr; + fsdata = *itr.fsdata; + + /* allocate local fat buffer */ + fsdata.fatbuf = malloc_cache_aligned(FATBUFSIZE); + if (!fsdata.fatbuf) { + log_debug("Error: allocating memory\n"); + ret = -ENOMEM; + goto exit; + } + + fsdata.fatbufnum = -1; + itr.fsdata = &fsdata; + + /* ensure iterator is at the first directory entry */ + ret = fat_move_to_cluster(&itr, itr.start_clust); + if (ret) + goto exit; + + while (1) { + if (prefix_clust == itr.start_clust) { + ret = -EINVAL; + goto exit; + } + + if (itr.is_root) { + ret = 0; + goto exit; + } + + /* Should not occur in a well-formed FAT filesystem besides the root */ + if (fat_itr_parent(&itr)) { + log_debug("FAT filesystem corrupt!\n"); + log_debug("dir @ clust %u has no parent direntry\n", + itr.start_clust); + ret = -EIO; + goto exit; + } + } + +exit: + free(fsdata.fatbuf); + return ret; +} + +/** + * fat_rename - rename/move a file or directory + * + * @old_path: path to the existing file/directory + * @new_path: new path/name for the rename/move + * Return: 0 on success, -errno otherwise + */ +int fat_rename(const char *old_path, const char *new_path) +{ + fat_itr *old_itr = NULL, *new_itr = NULL; + fsdata old_datablock = { .fatbuf = NULL, }; + fsdata new_datablock = { .fatbuf = NULL, }; + /* used for START macro */ + fsdata *mydata = &old_datablock; + int ret = -EIO, is_old_dir; + char *old_path_copy, *old_dirname, *old_basename; + char *new_path_copy, *new_dirname, *new_basename; + char l_new_basename[VFAT_MAXLEN_BYTES]; + __u32 old_clust; + dir_entry *found_existing; + /* only set if found_existing != NULL */ + __u32 new_clust; + + old_path_copy = strdup(old_path); + new_path_copy = strdup(new_path); + old_itr = malloc_cache_aligned(sizeof(fat_itr)); + new_itr = malloc_cache_aligned(sizeof(fat_itr)); + if (!old_path_copy || !new_path_copy || !old_itr || !new_itr) { + log_debug("Error: out of memory\n"); + ret = -ENOMEM; + goto exit; + } + split_filename(old_path_copy, &old_dirname, &old_basename); + split_filename(new_path_copy, &new_dirname, &new_basename); + + if (normalize_longname(l_new_basename, new_basename)) { + log_debug("FAT: illegal filename (%s)\n", new_basename); + ret = -EINVAL; + goto exit; + } + + if (!strcmp(old_basename, ".") || !strcmp(old_basename, "..") || + !strcmp(old_basename, "") || !strcmp(l_new_basename, ".") || + !strcmp(l_new_basename, "..") || !strcmp(l_new_basename, "")) { + ret = -EINVAL; + goto exit; + } + + /* checking for old_path == new_path is deferred until they're resolved */ + + /* resolve old_path */ + ret = fat_itr_root(old_itr, &old_datablock); + if (ret) + goto exit; + + ret = fat_itr_resolve(old_itr, old_dirname, TYPE_DIR); + if (ret) { + log_debug("%s doesn't exist (%d)\n", old_dirname, ret); + ret = -ENOENT; + goto exit; + } + + if (!find_directory_entry(old_itr, old_basename)) { + log_debug("%s doesn't exist (%d)\n", old_basename, -ENOENT); + ret = -ENOENT; + goto exit; + } + + /* store clust old_path points to, to relink later */ + total_sector = old_datablock.total_sect; + old_clust = START(old_itr->dent); + is_old_dir = fat_itr_isdir(old_itr); + + /* resolve new_path*/ + ret = fat_itr_root(new_itr, &new_datablock); + if (ret) + goto exit; + + ret = fat_itr_resolve(new_itr, new_dirname, TYPE_DIR); + if (ret) { + log_debug("%s doesn't exist (%d)\n", new_dirname, ret); + ret = -ENOENT; + goto exit; + } + + found_existing = find_directory_entry(new_itr, l_new_basename); + + if (found_existing) { + /* store cluster of new_path since it may need to be deleted */ + new_clust = START(new_itr->dent); + + /* old_path is new_path, noop */ + if (old_clust == new_clust) { + ret = 0; + goto exit; + } + + if (fat_itr_isdir(new_itr) != is_old_dir) { + if (is_old_dir) + ret = -ENOTDIR; + else + ret = -EISDIR; + goto exit; + } + } + + if (is_old_dir) { + ret = check_path_prefix(old_clust, new_itr); + if (ret) + goto exit; + } + + /* create/update dentry to point to old_path's data cluster */ + if (found_existing) { + struct nameext new_name = new_itr->dent->nameext; + __u8 lcase = new_itr->dent->lcase; + + if (is_old_dir) { + int n_entries = fat_dir_entries(new_itr); + + if (n_entries < 0) { + ret = n_entries; + goto exit; + } + if (n_entries > 2) { + log_debug("Error: directory is not empty: %d\n", + n_entries); + ret = -ENOTEMPTY; + goto exit; + } + } + + *new_itr->dent = *old_itr->dent; + new_itr->dent->nameext = new_name; + new_itr->dent->lcase = lcase; + } else { + /* reset iterator to the start of the directory */ + ret = fat_move_to_cluster(new_itr, new_itr->start_clust); + if (ret) + goto exit; + + ret = create_link(new_itr, l_new_basename, old_clust, + old_itr->dent->size, + old_itr->dent->attr | ATTR_ARCH); + if (ret) + goto exit; + } + + ret = flush_dir(new_itr); + if (ret) + goto exit; + + /* with new_path data cluster unreferenced, clear it */ + if (found_existing) { + ret = clear_fatent(&new_datablock, new_clust); + if (ret) + goto exit; + } + + /* update moved directory so the parent is new_path */ + if (is_old_dir) { + __u32 clust = new_itr->start_clust; + dir_entry *dent; + + fat_itr_child(new_itr, new_itr); + dent = find_directory_entry(new_itr, ".."); + if (!dent) { + log_debug("FAT filesystem corrupt!\n"); + log_debug("dir %s has no parent direntry\n", + l_new_basename); + ret = -EIO; + goto exit; + } + set_start_cluster(&new_datablock, dent, clust); + ret = flush_dir(new_itr); + if (ret) + goto exit; + } + + /* refresh old in case write happened to the same block. */ + ret = fat_move_to_cluster(old_itr, old_itr->dent_clust); + if (ret) + goto exit; + + ret = delete_dentry_link(old_itr); +exit: + free(new_datablock.fatbuf); + free(old_datablock.fatbuf); + free(new_itr); + free(old_itr); + free(new_path_copy); + free(old_path_copy); + + return ret; +} |