* Fixed two bugs in rootfs_rename(): the check of the result of

rootfs_find_in_dir() was wrong, leading to never be able to find the fromName
  in the directory. Furthermore, the parent of the root directory is itself, but
  the check to see whether or not the target is valid did not take this into
  account, and therefore ran into an endless loop. This fixes bug #3864.
* Rearranged rootfs_rename() to be clearer.
* Style cleanup.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30633 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2009-05-05 11:53:46 +00:00
parent a5dbd78b7a
commit c24d3c0d98

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Copyright 2002-2009, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
* *
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
@ -82,7 +82,7 @@ struct rootfs {
struct rootfs_dir_cookie { struct rootfs_dir_cookie {
struct list_link link; struct list_link link;
struct rootfs_vnode* current; struct rootfs_vnode* current;
int32 state; // iteration state int32 iteration_state;
}; };
// directory iteration states // directory iteration states
@ -180,8 +180,7 @@ rootfs_delete_vnode(struct rootfs *fs, struct rootfs_vnode *v, bool force_delete
} }
/* makes sure none of the dircookies point to the vnode passed in */ /*! Makes sure none of the dircookies point to the vnode passed in. */
static void static void
update_dir_cookies(struct rootfs_vnode* dir, struct rootfs_vnode* vnode) update_dir_cookies(struct rootfs_vnode* dir, struct rootfs_vnode* vnode)
{ {
@ -206,7 +205,7 @@ rootfs_find_in_dir(struct rootfs_vnode *dir, const char *path)
return dir->parent; return dir->parent;
for (vnode = dir->stream.dir.dir_head; vnode; vnode = vnode->dir_next) { for (vnode = dir->stream.dir.dir_head; vnode; vnode = vnode->dir_next) {
if (strcmp(vnode->name, path) == 0) if (!strcmp(vnode->name, path))
return vnode; return vnode;
} }
return NULL; return NULL;
@ -219,8 +218,9 @@ rootfs_insert_in_dir(struct rootfs *fs, struct rootfs_vnode *dir,
{ {
// make sure the directory stays sorted alphabetically // make sure the directory stays sorted alphabetically
struct rootfs_vnode *node = dir->stream.dir.dir_head, *last = NULL; struct rootfs_vnode* node = dir->stream.dir.dir_head;
while (node && strcmp(node->name, vnode->name) < 0) { struct rootfs_vnode* last = NULL;
while (node != NULL && strcmp(node->name, vnode->name) < 0) {
last = node; last = node;
node = node->dir_next; node = node->dir_next;
} }
@ -244,21 +244,22 @@ rootfs_insert_in_dir(struct rootfs *fs, struct rootfs_vnode *dir,
static status_t static status_t
rootfs_remove_from_dir(struct rootfs* fs, struct rootfs_vnode* dir, rootfs_remove_from_dir(struct rootfs* fs, struct rootfs_vnode* dir,
struct rootfs_vnode *findit) struct rootfs_vnode* removeVnode)
{ {
struct rootfs_vnode *v; struct rootfs_vnode* vnode;
struct rootfs_vnode *last_v; struct rootfs_vnode* lastVnode;
for (v = dir->stream.dir.dir_head, last_v = NULL; v; last_v = v, v = v->dir_next) { for (vnode = dir->stream.dir.dir_head, lastVnode = NULL; vnode != NULL;
if (v == findit) { lastVnode = vnode, vnode = vnode->dir_next) {
/* make sure all dircookies dont point to this vnode */ if (vnode == removeVnode) {
update_dir_cookies(dir, v); // make sure all dircookies dont point to this vnode
update_dir_cookies(dir, vnode);
if (last_v) if (lastVnode)
last_v->dir_next = v->dir_next; lastVnode->dir_next = vnode->dir_next;
else else
dir->stream.dir.dir_head = v->dir_next; dir->stream.dir.dir_head = vnode->dir_next;
v->dir_next = NULL; vnode->dir_next = NULL;
dir->modification_time = time(NULL); dir->modification_time = time(NULL);
notify_stat_changed(fs->id, dir->id, B_STAT_MODIFICATION_TIME); notify_stat_changed(fs->id, dir->id, B_STAT_MODIFICATION_TIME);
@ -276,8 +277,7 @@ rootfs_is_dir_empty(struct rootfs_vnode *dir)
} }
/** You must hold the FS lock when calling this function */ /*! You must hold the FS lock when calling this function */
static status_t static status_t
remove_node(struct rootfs* fs, struct rootfs_vnode* directory, remove_node(struct rootfs* fs, struct rootfs_vnode* directory,
struct rootfs_vnode* vnode) struct rootfs_vnode* vnode)
@ -303,7 +303,8 @@ remove_node(struct rootfs *fs, struct rootfs_vnode *directory,
static status_t static status_t
rootfs_remove(struct rootfs *fs, struct rootfs_vnode *dir, const char *name, bool isDirectory) rootfs_remove(struct rootfs* fs, struct rootfs_vnode* dir, const char* name,
bool isDirectory)
{ {
struct rootfs_vnode* vnode; struct rootfs_vnode* vnode;
status_t status = B_OK; status_t status = B_OK;
@ -337,7 +338,7 @@ err:
static status_t static status_t
rootfs_mount(fs_volume* volume, const char* device, uint32 flags, rootfs_mount(fs_volume* volume, const char* device, uint32 flags,
const char *args, ino_t *root_vnid) const char* args, ino_t* _rootID)
{ {
struct rootfs* fs; struct rootfs* fs;
struct rootfs_vnode* vnode; struct rootfs_vnode* vnode;
@ -357,8 +358,9 @@ rootfs_mount(fs_volume *volume, const char *device, uint32 flags,
mutex_init(&fs->lock, "rootfs_mutex"); mutex_init(&fs->lock, "rootfs_mutex");
fs->vnode_list_hash = hash_init(ROOTFS_HASH_SIZE, (addr_t)&vnode->all_next - (addr_t)vnode, fs->vnode_list_hash = hash_init(ROOTFS_HASH_SIZE,
&rootfs_vnode_compare_func, &rootfs_vnode_hash_func); (addr_t)&vnode->all_next - (addr_t)vnode, &rootfs_vnode_compare_func,
&rootfs_vnode_hash_func);
if (fs->vnode_list_hash == NULL) { if (fs->vnode_list_hash == NULL) {
err = B_NO_MEMORY; err = B_NO_MEMORY;
goto err2; goto err2;
@ -376,7 +378,7 @@ rootfs_mount(fs_volume *volume, const char *device, uint32 flags,
hash_insert(fs->vnode_list_hash, vnode); hash_insert(fs->vnode_list_hash, vnode);
publish_vnode(volume, vnode->id, vnode, &sVnodeOps, vnode->stream.type, 0); publish_vnode(volume, vnode->id, vnode, &sVnodeOps, vnode->stream.type, 0);
*root_vnid = vnode->id; *_rootID = vnode->id;
return B_OK; return B_OK;
@ -394,8 +396,6 @@ static status_t
rootfs_unmount(fs_volume* _volume) rootfs_unmount(fs_volume* _volume)
{ {
struct rootfs* fs = (struct rootfs*)_volume->private_volume; struct rootfs* fs = (struct rootfs*)_volume->private_volume;
struct rootfs_vnode *v;
struct hash_iterator i;
TRACE(("rootfs_unmount: entry fs = %p\n", fs)); TRACE(("rootfs_unmount: entry fs = %p\n", fs));
@ -403,10 +403,14 @@ rootfs_unmount(fs_volume *_volume)
put_vnode(fs->volume, fs->root_vnode->id); put_vnode(fs->volume, fs->root_vnode->id);
// delete all of the vnodes // delete all of the vnodes
struct hash_iterator i;
hash_open(fs->vnode_list_hash, &i); hash_open(fs->vnode_list_hash, &i);
while ((v = (struct rootfs_vnode *)hash_next(fs->vnode_list_hash, &i)) != NULL) {
rootfs_delete_vnode(fs, v, true); while (struct rootfs_vnode* vnode = (struct rootfs_vnode*)
hash_next(fs->vnode_list_hash, &i)) {
rootfs_delete_vnode(fs, vnode, true);
} }
hash_close(fs->vnode_list_hash, &i, false); hash_close(fs->vnode_list_hash, &i, false);
hash_uninit(fs->vnode_list_hash); hash_uninit(fs->vnode_list_hash);
@ -466,7 +470,8 @@ rootfs_get_vnode_name(fs_volume *_volume, fs_vnode *_vnode, char *buffer,
{ {
struct rootfs_vnode* vnode = (struct rootfs_vnode*)_vnode->private_node; struct rootfs_vnode* vnode = (struct rootfs_vnode*)_vnode->private_node;
TRACE(("rootfs_get_vnode_name: vnode = %p (name = %s)\n", vnode, vnode->name)); TRACE(("rootfs_get_vnode_name: vnode = %p (name = %s)\n", vnode,
vnode->name));
strlcpy(buffer, vnode->name, bufferSize); strlcpy(buffer, vnode->name, bufferSize);
return B_OK; return B_OK;
@ -490,7 +495,7 @@ rootfs_get_vnode(fs_volume *_volume, ino_t id, fs_vnode *_vnode, int *_type,
if (!reenter) if (!reenter)
mutex_unlock(&fs->lock); mutex_unlock(&fs->lock);
TRACE(("rootfs_getnvnode: looked it up at %p\n", *_vnode)); TRACE(("rootfs_getnvnode: looked it up at %p\n", vnode));
if (vnode == NULL) if (vnode == NULL)
return B_ENTRY_NOT_FOUND; return B_ENTRY_NOT_FOUND;
@ -522,14 +527,16 @@ rootfs_remove_vnode(fs_volume *_volume, fs_vnode *_vnode, bool reenter)
struct rootfs* fs = (struct rootfs*)_volume->private_volume; struct rootfs* fs = (struct rootfs*)_volume->private_volume;
struct rootfs_vnode* vnode = (struct rootfs_vnode*)_vnode->private_node; struct rootfs_vnode* vnode = (struct rootfs_vnode*)_vnode->private_node;
TRACE(("rootfs_remove_vnode: remove %p (0x%Lx), r %d\n", vnode, vnode->id, reenter)); TRACE(("rootfs_remove_vnode: remove %p (0x%Lx), r %d\n", vnode, vnode->id,
reenter));
if (!reenter) if (!reenter)
mutex_lock(&fs->lock); mutex_lock(&fs->lock);
if (vnode->dir_next) { if (vnode->dir_next) {
// can't remove node if it's linked to the dir // can't remove node if it's linked to the dir
panic("rootfs_remove_vnode: vnode %p asked to be removed is present in dir\n", vnode); panic("rootfs_remove_vnode: vnode %p asked to be removed is present in "
"dir\n", vnode);
} }
rootfs_delete_vnode(fs, vnode, false); rootfs_delete_vnode(fs, vnode, false);
@ -560,14 +567,10 @@ rootfs_open(fs_volume *_volume, fs_vnode *_v, int oflags, void **_cookie)
static status_t static status_t
rootfs_close(fs_volume *_volume, fs_vnode *_v, void *_cookie) rootfs_close(fs_volume* _volume, fs_vnode* _vnode, void* _cookie)
{ {
#ifdef TRACE_ROOTFS TRACE(("rootfs_close: entry vnode %p, cookie %p\n", _vnode->private_node,
struct rootfs_vnode *v = _v->private_node; _cookie));
struct rootvoid **cookie = _cookie;
TRACE(("rootfs_close: entry vnode %p, cookie %p\n", v, cookie));
#endif
return B_OK; return B_OK;
} }
@ -598,8 +601,8 @@ static status_t
rootfs_write(fs_volume* _volume, fs_vnode* vnode, void* cookie, rootfs_write(fs_volume* _volume, fs_vnode* vnode, void* cookie,
off_t pos, const void* buffer, size_t* _length) off_t pos, const void* buffer, size_t* _length)
{ {
TRACE(("rootfs_write: vnode %p, cookie %p, pos 0x%Lx , len 0x%lx\n", TRACE(("rootfs_write: vnode %p, cookie %p, pos 0x%Lx , len %#x\n",
vnode, cookie, pos, *_length)); vnode, cookie, pos, (int)*_length));
return EPERM; return EPERM;
} }
@ -653,7 +656,8 @@ rootfs_remove_dir(fs_volume *_volume, fs_vnode *_dir, const char *name)
struct rootfs* fs = (rootfs*)_volume->private_volume; struct rootfs* fs = (rootfs*)_volume->private_volume;
struct rootfs_vnode* dir = (rootfs_vnode*)_dir->private_node; struct rootfs_vnode* dir = (rootfs_vnode*)_dir->private_node;
TRACE(("rootfs_remove_dir: dir %p (0x%Lx), name '%s'\n", dir, dir->id, name)); TRACE(("rootfs_remove_dir: dir %p (0x%Lx), name '%s'\n", dir, dir->id,
name));
return rootfs_remove(fs, dir, name, true); return rootfs_remove(fs, dir, name, true);
} }
@ -678,7 +682,7 @@ rootfs_open_dir(fs_volume *_volume, fs_vnode *_v, void **_cookie)
mutex_lock(&fs->lock); mutex_lock(&fs->lock);
cookie->current = vnode->stream.dir.dir_head; cookie->current = vnode->stream.dir.dir_head;
cookie->state = ITERATION_STATE_BEGIN; cookie->iteration_state = ITERATION_STATE_BEGIN;
list_add_item(&vnode->stream.dir.cookies, cookie); list_add_item(&vnode->stream.dir.cookies, cookie);
*_cookie = cookie; *_cookie = cookie;
@ -716,24 +720,25 @@ rootfs_read_dir(fs_volume *_volume, fs_vnode *_vnode, void *_cookie,
struct rootfs_vnode* childNode = NULL; struct rootfs_vnode* childNode = NULL;
const char* name = NULL; const char* name = NULL;
struct rootfs_vnode* nextChildNode = NULL; struct rootfs_vnode* nextChildNode = NULL;
int nextState = cookie->state; int nextState = cookie->iteration_state;
TRACE(("rootfs_read_dir: vnode %p, cookie %p, buffer = %p, bufferSize = %ld, num = %p\n", _vnode, cookie, dirent, bufferSize,_num)); TRACE(("rootfs_read_dir: vnode %p, cookie %p, buffer = %p, bufferSize = %d, "
"num = %p\n", _vnode, cookie, dirent, (int)bufferSize, _num));
mutex_lock(&fs->lock); mutex_lock(&fs->lock);
switch (cookie->state) { switch (cookie->iteration_state) {
case ITERATION_STATE_DOT: case ITERATION_STATE_DOT:
childNode = vnode; childNode = vnode;
name = "."; name = ".";
nextChildNode = vnode->stream.dir.dir_head; nextChildNode = vnode->stream.dir.dir_head;
nextState = cookie->state + 1; nextState = cookie->iteration_state + 1;
break; break;
case ITERATION_STATE_DOT_DOT: case ITERATION_STATE_DOT_DOT:
childNode = vnode->parent; childNode = vnode->parent;
name = ".."; name = "..";
nextChildNode = vnode->stream.dir.dir_head; nextChildNode = vnode->stream.dir.dir_head;
nextState = cookie->state + 1; nextState = cookie->iteration_state + 1;
break; break;
default: default:
childNode = cookie->current; childNode = cookie->current;
@ -765,7 +770,7 @@ rootfs_read_dir(fs_volume *_volume, fs_vnode *_vnode, void *_cookie,
goto err; goto err;
cookie->current = nextChildNode; cookie->current = nextChildNode;
cookie->state = nextState; cookie->iteration_state = nextState;
*_num = 1; *_num = 1;
status = B_OK; status = B_OK;
@ -786,7 +791,7 @@ rootfs_rewind_dir(fs_volume *_volume, fs_vnode *_vnode, void *_cookie)
mutex_lock(&fs->lock); mutex_lock(&fs->lock);
cookie->current = vnode->stream.dir.dir_head; cookie->current = vnode->stream.dir.dir_head;
cookie->state = ITERATION_STATE_BEGIN; cookie->iteration_state = ITERATION_STATE_BEGIN;
mutex_unlock(&fs->lock); mutex_unlock(&fs->lock);
return B_OK; return B_OK;
@ -795,11 +800,12 @@ rootfs_rewind_dir(fs_volume *_volume, fs_vnode *_vnode, void *_cookie)
static status_t static status_t
rootfs_ioctl(fs_volume* _volume, fs_vnode* _v, void* _cookie, ulong op, rootfs_ioctl(fs_volume* _volume, fs_vnode* _v, void* _cookie, ulong op,
void *buf, size_t len) void* buffer, size_t length)
{ {
TRACE(("rootfs_ioctl: vnode %p, cookie %p, op %ld, buf %p, len %ld\n", _v, _cookie, op, buf, len)); TRACE(("rootfs_ioctl: vnode %p, cookie %p, op %d, buf %p, length %d\n",
_volume, _cookie, (int)op, buffer, (int)length));
return EINVAL; return B_BAD_VALUE;
} }
@ -911,60 +917,48 @@ rootfs_rename(fs_volume *_volume, fs_vnode *_fromDir, const char *fromName,
struct rootfs* fs = (rootfs*)_volume->private_volume; struct rootfs* fs = (rootfs*)_volume->private_volume;
struct rootfs_vnode* fromDirectory = (rootfs_vnode*)_fromDir->private_node; struct rootfs_vnode* fromDirectory = (rootfs_vnode*)_fromDir->private_node;
struct rootfs_vnode* toDirectory = (rootfs_vnode*)_toDir->private_node; struct rootfs_vnode* toDirectory = (rootfs_vnode*)_toDir->private_node;
struct rootfs_vnode *vnode, *targetVnode, *parent;
status_t status;
char *nameBuffer = NULL;
TRACE(("rootfs_rename: from %p (0x%Lx), fromName '%s', to %p (0x%Lx), toName '%s'\n", TRACE(("rootfs_rename: from %p (0x%Lx), fromName '%s', to %p (0x%Lx), "
fromDirectory, fromDirectory->id, fromName, toDirectory, toDirectory->id, toName)); "toName '%s'\n", fromDirectory, fromDirectory->id, fromName, toDirectory,
toDirectory->id, toName));
mutex_lock(&fs->lock); MutexLocker _(&fs->lock);
vnode = rootfs_find_in_dir(fromDirectory, fromName); struct rootfs_vnode* vnode = rootfs_find_in_dir(fromDirectory, fromName);
if (vnode != NULL) { if (vnode == NULL)
status = B_ENTRY_NOT_FOUND; return B_ENTRY_NOT_FOUND;
goto err;
}
// make sure the target not a subdirectory of us // make sure the target is not a subdirectory of us
parent = toDirectory->parent; struct rootfs_vnode* parent = toDirectory->parent;
while (parent != NULL) { while (parent != NULL && parent != parent->parent) {
if (parent == vnode) { if (parent == vnode)
status = B_BAD_VALUE; return B_BAD_VALUE;
goto err;
}
parent = parent->parent; parent = parent->parent;
} }
// we'll reuse the name buffer if possible struct rootfs_vnode* targetVnode = rootfs_find_in_dir(toDirectory, toName);
if (strlen(fromName) >= strlen(toName)) {
nameBuffer = strdup(toName);
if (nameBuffer == NULL) {
status = B_NO_MEMORY;
goto err;
}
}
targetVnode = rootfs_find_in_dir(toDirectory, toName);
if (targetVnode != NULL) { if (targetVnode != NULL) {
// target node exists, let's see if it is an empty directory // target node exists, let's see if it is an empty directory
if (S_ISDIR(targetVnode->stream.type) && !rootfs_is_dir_empty(targetVnode)) { if (S_ISDIR(targetVnode->stream.type)
status = B_NAME_IN_USE; && !rootfs_is_dir_empty(targetVnode))
goto err; return B_NAME_IN_USE;
}
// so we can cleanly remove it // so we can cleanly remove it
remove_node(fs, toDirectory, targetVnode); remove_node(fs, toDirectory, targetVnode);
} }
// change the name on this node // we try to reuse the existing name buffer if possible
if (nameBuffer == NULL) { if (strlen(fromName) >= strlen(toName)) {
// we can just copy it char* nameBuffer = strdup(toName);
strcpy(vnode->name, toName); if (nameBuffer == NULL)
} else { return B_NO_MEMORY;
free(vnode->name); free(vnode->name);
vnode->name = nameBuffer; vnode->name = nameBuffer;
} else {
// we can just copy it
strcpy(vnode->name, toName);
} }
// remove it from the dir // remove it from the dir
@ -975,16 +969,10 @@ rootfs_rename(fs_volume *_volume, fs_vnode *_fromDir, const char *fromName,
// so that it keeps sorted correctly. // so that it keeps sorted correctly.
rootfs_insert_in_dir(fs, toDirectory, vnode); rootfs_insert_in_dir(fs, toDirectory, vnode);
notify_entry_moved(fs->id, fromDirectory->id, fromName, toDirectory->id, toName, vnode->id); notify_entry_moved(fs->id, fromDirectory->id, fromName, toDirectory->id,
status = B_OK; toName, vnode->id);
err: return B_OK;
if (status != B_OK)
free(nameBuffer);
mutex_unlock(&fs->lock);
return status;
} }
@ -994,9 +982,11 @@ rootfs_read_stat(fs_volume *_volume, fs_vnode *_v, struct stat *stat)
struct rootfs* fs = (rootfs*)_volume->private_volume; struct rootfs* fs = (rootfs*)_volume->private_volume;
struct rootfs_vnode* vnode = (rootfs_vnode*)_v->private_node; struct rootfs_vnode* vnode = (rootfs_vnode*)_v->private_node;
TRACE(("rootfs_read_stat: vnode %p (0x%Lx), stat %p\n", vnode, vnode->id, stat)); TRACE(("rootfs_read_stat: vnode %p (0x%Lx), stat %p\n", vnode, vnode->id,
stat));
// stream exists, but we know to return size 0, since we can only hold directories // stream exists, but we know to return size 0, since we can only hold
// directories
stat->st_dev = fs->id; stat->st_dev = fs->id;
stat->st_ino = vnode->id; stat->st_ino = vnode->id;
if (S_ISLNK(vnode->stream.type)) if (S_ISLNK(vnode->stream.type))
@ -1027,7 +1017,8 @@ rootfs_write_stat(fs_volume *_volume, fs_vnode *_vnode, const struct stat *stat,
struct rootfs* fs = (rootfs*)_volume->private_volume; struct rootfs* fs = (rootfs*)_volume->private_volume;
struct rootfs_vnode* vnode = (rootfs_vnode*)_vnode->private_node; struct rootfs_vnode* vnode = (rootfs_vnode*)_vnode->private_node;
TRACE(("rootfs_write_stat: vnode %p (0x%Lx), stat %p\n", vnode, vnode->id, stat)); TRACE(("rootfs_write_stat: vnode %p (0x%Lx), stat %p\n", vnode, vnode->id,
stat));
// we cannot change the size of anything // we cannot change the size of anything
if (statMask & B_STAT_SIZE) if (statMask & B_STAT_SIZE)
@ -1035,17 +1026,19 @@ rootfs_write_stat(fs_volume *_volume, fs_vnode *_vnode, const struct stat *stat,
mutex_lock(&fs->lock); mutex_lock(&fs->lock);
if (statMask & B_STAT_MODE) if ((statMask & B_STAT_MODE) != 0) {
vnode->stream.type = (vnode->stream.type & ~S_IUMSK) | (stat->st_mode & S_IUMSK); vnode->stream.type = (vnode->stream.type & ~S_IUMSK)
| (stat->st_mode & S_IUMSK);
}
if (statMask & B_STAT_UID) if ((statMask & B_STAT_UID) != 0)
vnode->uid = stat->st_uid; vnode->uid = stat->st_uid;
if (statMask & B_STAT_GID) if ((statMask & B_STAT_GID) != 0)
vnode->gid = stat->st_gid; vnode->gid = stat->st_gid;
if (statMask & B_STAT_MODIFICATION_TIME) if ((statMask & B_STAT_MODIFICATION_TIME) != 0)
vnode->modification_time = stat->st_mtime; vnode->modification_time = stat->st_mtime;
if (statMask & B_STAT_CREATION_TIME) if ((statMask & B_STAT_CREATION_TIME) != 0)
vnode->creation_time = stat->st_crtime; vnode->creation_time = stat->st_crtime;
mutex_unlock(&fs->lock); mutex_unlock(&fs->lock);