blob: 0fa4840779fdc1802dd344e7333b8aac7cf7376d [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string.h>
#include "f2fs.h"
#include "dir.h"
namespace f2fs {
zx_status_t Dir::F2fsNewInode(uint32_t mode, fbl::RefPtr<VnodeF2fs> *out) {
struct f2fs_sb_info &sbi = Vfs()->SbInfo();
nid_t ino;
fbl::RefPtr<VnodeF2fs> vnode_refptr;
VnodeF2fs *vnode = nullptr;
int err;
auto cur_time = time(nullptr);
mutex_lock_op(&sbi, NODE_NEW);
if (!Vfs()->Nodemgr().AllocNid(&ino)) {
mutex_unlock_op(&sbi, NODE_NEW);
err = -ENOSPC;
goto fail;
}
mutex_unlock_op(&sbi, NODE_NEW);
VnodeF2fs::Allocate(Vfs(), ino, mode, &vnode_refptr);
vnode = vnode_refptr.get();
vnode->i_uid = current_fsuid();
if (i_mode & S_ISGID) {
vnode->i_gid = i_gid;
if (S_ISDIR(mode))
mode |= S_ISGID;
} else {
vnode->i_gid = current_fsgid();
}
vnode->i_mode = mode;
vnode->i_blocks = 0;
cur_time = time(nullptr);
vnode->i_mtime.tv_sec = cur_time;
vnode->i_mtime.tv_nsec = 0;
vnode->i_atime.tv_sec = cur_time;
vnode->i_atime.tv_nsec = 0;
vnode->i_ctime.tv_sec = cur_time;
vnode->i_ctime.tv_nsec = 0;
vnode->i_generation = sbi.s_next_generation++;
Vfs()->InsertVnode(vnode);
mark_inode_dirty(vnode);
*out = std::move(vnode_refptr);
return ZX_OK;
fail:
iput(vnode);
return err;
}
int Dir::IsMultimediaFile(const char *s, const char *sub) {
int slen = strlen(s);
int sublen = strlen(sub);
int ret;
if (sublen > slen)
return 1;
ret = memcmp(s + slen - sublen, sub, sublen);
if (ret) { /* compare upper case */
int i;
char upper_sub[8];
for (i = 0; i < sublen && i < (int)sizeof(upper_sub); i++)
upper_sub[i] = toupper(sub[i]);
return memcmp(s + slen - sublen, upper_sub, sublen);
}
return ret;
}
/**
* Set multimedia files as cold files for hot/cold data separation
*/
inline void Dir::SetColdFile(const char *name) {
int i;
__u8(*extlist)[8] = Vfs()->RawSb().extension_list;
int count = le32_to_cpu(Vfs()->RawSb().extension_count);
for (i = 0; i < count; i++) {
if (!IsMultimediaFile(name, (const char *)extlist[i])) {
fi.i_advise |= FADVISE_COLD_BIT;
break;
}
}
}
zx_status_t Dir::F2fsCreate(fbl::StringPiece name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out) {
struct f2fs_sb_info &sbi = Vfs()->SbInfo();
fbl::RefPtr<VnodeF2fs> vnode_refptr;
VnodeF2fs *vnode = nullptr;
zx_status_t err;
err = F2fsNewInode(S_IFREG | mode, &vnode_refptr);
if (err)
return err;
vnode = vnode_refptr.get();
vnode->i_name_sp = name;
if (!test_opt(&sbi, DISABLE_EXT_IDENTIFY))
SetColdFile(name.data());
SetInodeFlag(&vnode->fi, FI_INC_LINK);
err = F2fsAddLink(name, vnode);
if (err)
goto out;
Vfs()->Nodemgr().AllocNidDone(vnode->Ino());
#if 0 // porting needed
// if (!sbi.por_doing)
// d_instantiate(dentry, inode);
#endif
unlock_new_inode(vnode);
Vfs()->Segmgr().F2fsBalanceFs();
*out = std::move(vnode_refptr);
return ZX_OK;
out:
vnode->ClearNlink();
unlock_new_inode(vnode);
iput(vnode);
Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
return err;
}
zx_status_t Dir::Link(fbl::StringPiece name, fbl::RefPtr<fs::Vnode> _target) {
VnodeF2fs *target = (VnodeF2fs *)_target.get();
auto cur_time = time(nullptr);
zx_status_t err;
mtx_lock(&i_mutex);
mtx_lock(&target->i_mutex);
target->i_ctime.tv_sec = cur_time;
target->i_ctime.tv_nsec = 0;
#if 0 // porting needed
// atomic_inc(&inode->i_count);
#endif
SetInodeFlag(&target->fi, FI_INC_LINK);
err = F2fsAddLink(name, target);
if (err)
goto out;
#if 0 // porting needed
// d_instantiate(dentry, inode);
#endif
Vfs()->Segmgr().F2fsBalanceFs();
mtx_unlock(&target->i_mutex);
mtx_unlock(&i_mutex);
return ZX_OK;
out:
clear_inode_flag(&target->fi, FI_INC_LINK);
iput(target);
mtx_unlock(&target->i_mutex);
mtx_unlock(&i_mutex);
return err;
}
#if 0 // porting needed
// struct dentry *Dir::F2fsGetParent(struct dentry *child) {
// return nullptr;
// // struct qstr dotdot = QSTR_INIT("..", 2);
// // unsigned long ino = f2fs_inode_by_name(child->d_inode, &dotdot);
// // if (!ino)
// // return ERR_PTR(-ENOENT);
// // return d_obtain_alias(f2fs_iget(child->d_inode->i_sb, ino));
// }
#endif
zx_status_t Dir::F2fsLookup(fbl::StringPiece name, fbl::RefPtr<fs::Vnode> *out) {
fbl::RefPtr<VnodeF2fs> vn;
struct f2fs_dir_entry *de;
Page *page;
zx_status_t ret;
if (name.length() > F2FS_MAX_NAME_LEN)
return ZX_ERR_OUT_OF_RANGE;
de = F2fsFindEntry(name, &page);
if (de) {
nid_t ino = le32_to_cpu(de->ino);
#if 0 // porting needed
// kunmap(page);
#endif
F2fsPutPage(page, 0);
ret = VnodeF2fs::F2fsVget(Vfs(), ino, &vn);
if (ret)
return ret;
*out = std::move(vn);
return ZX_OK;
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t Dir::Lookup(fbl::StringPiece name, fbl::RefPtr<fs::Vnode> *out) {
zx_status_t ret;
mtx_lock(&i_mutex);
ret = F2fsLookup(name, out);
mtx_unlock(&i_mutex);
return ret;
}
zx_status_t Dir::F2fsUnlink(VnodeF2fs *vnode, fbl::StringPiece name) {
struct f2fs_dir_entry *de;
Page *page;
zx_status_t err = ZX_ERR_NOT_FOUND;
de = F2fsFindEntry(name, &page);
if (de == nullptr)
goto fail;
err = Vfs()->CheckOrphanSpace();
if (err) {
#if 0 // porting needed
// kunmap(page);
#endif
F2fsPutPage(page, 0);
goto fail;
}
F2fsDeleteEntry(de, page, vnode);
/* In order to evict this inode, we set it dirty */
mark_inode_dirty(vnode);
Vfs()->Segmgr().F2fsBalanceFs();
fail:
return err;
}
#if 0 // porting needed
// int Dir::F2fsSymlink(struct dentry *dentry, const char *symname) {
// return 0;
// // fbl::RefPtr<VnodeF2fs> vnode_refptr;
// // VnodeF2fs *vnode = nullptr;
// // unsigned symlen = strlen(symname) + 1;
// // int err;
// // err = F2fsNewInode(S_IFLNK | S_IRWXUGO, &vnode_refptr);
// // if (err)
// // return err;
// // vnode = vnode_refptr.get();
// // // inode->i_mapping->a_ops = &f2fs_dblock_aops;
// // // err = F2fsAddLink(dentry, vnode);
// // if (err)
// // goto out;
// // err = page_symlink(vnode, symname, symlen);
// // Vfs()->Nodemgr().AllocNidDone(vnode->Ino());
// // // d_instantiate(dentry, vnode);
// // unlock_new_inode(vnode);
// // Vfs()->Segmgr().F2fsBalanceFs();
// // return err;
// // out:
// // vnode->ClearNlink();
// // unlock_new_inode(vnode);
// // // iput(inode);
// // Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
// // return err;
// }
#endif
zx_status_t Dir::F2fsMkdir(fbl::StringPiece name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out) {
fbl::RefPtr<VnodeF2fs> vnode_refptr;
VnodeF2fs *vnode = nullptr;
zx_status_t err;
err = F2fsNewInode(S_IFDIR | mode, &vnode_refptr);
if (err)
return err;
vnode = vnode_refptr.get();
vnode->i_name_sp = name;
#if 0 // porting needed
// mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS | __GFP_ZERO);
#endif
SetInodeFlag(&vnode->fi, FI_INC_LINK);
err = F2fsAddLink(name, vnode);
if (err)
goto out_fail;
Vfs()->Nodemgr().AllocNidDone(vnode->Ino());
#if 0 // porting needed
// d_instantiate(dentry, inode);
#endif
unlock_new_inode(vnode);
Vfs()->Segmgr().F2fsBalanceFs();
*out = std::move(vnode_refptr);
return ZX_OK;
out_fail:
clear_inode_flag(&vnode->fi, FI_INC_LINK);
vnode->ClearNlink();
unlock_new_inode(vnode);
iput(vnode);
Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
return err;
}
zx_status_t Dir::F2fsRmdir(Dir *vnode, fbl::StringPiece name) {
if (vnode->F2fsEmptyDir())
return F2fsUnlink(vnode, name);
return ZX_ERR_NOT_EMPTY;
}
#if 0 // porting needed
// int Dir::F2fsMknod(struct dentry *dentry, umode_t mode, dev_t rdev) {
// fbl::RefPtr<VnodeF2fs> vnode_refptr;
// VnodeF2fs *vnode = nullptr;
// int err = 0;
// // if (!new_valid_dev(rdev))
// // return -EINVAL;
// err = F2fsNewInode(mode, &vnode_refptr);
// if (err)
// return err;
// vnode = vnode_refptr.get();
// // init_special_inode(inode, inode->i_mode, rdev);
// // inode->i_op = &f2fs_special_inode_operations;
// // err = F2fsAddLink(dentry, vnode);
// if (err)
// goto out;
// Vfs()->Nodemgr().AllocNidDone(vnode->Ino());
// // d_instantiate(dentry, inode);
// unlock_new_inode(vnode);
// Vfs()->Segmgr().F2fsBalanceFs();
// return 0;
// out:
// vnode->ClearNlink();
// unlock_new_inode(vnode);
// iput(vnode);
// Vfs()->Nodemgr().AllocNidFailed(vnode->Ino());
// return err;
// }
#endif
zx_status_t Dir::Rename(fbl::RefPtr<fs::Vnode> _newdir, fbl::StringPiece oldname,
fbl::StringPiece newname, bool src_must_be_dir, bool dst_must_be_dir) {
struct f2fs_sb_info &sbi = Vfs()->SbInfo();
fbl::RefPtr<VnodeF2fs> old_vn_ref;
fbl::RefPtr<VnodeF2fs> new_vn_ref;
Dir *old_dir = this;
Dir *new_dir = (Dir *)_newdir.get();
VnodeF2fs *old_vnode;
nid_t old_ino;
VnodeF2fs *new_vnode;
nid_t new_ino;
Page *old_dir_page;
Page *old_page;
Page *new_page;
struct f2fs_dir_entry *old_dir_entry = nullptr;
struct f2fs_dir_entry *old_entry;
struct f2fs_dir_entry *new_entry;
zx_status_t err = ZX_ERR_NOT_FOUND;
auto cur_time = time(nullptr);
if (old_dir == new_dir && oldname == newname)
return ZX_OK;
mtx_lock(&i_mutex);
old_entry = F2fsFindEntry(oldname, &old_page);
if (!old_entry)
goto out;
old_ino = le32_to_cpu(old_entry->ino);
err = VnodeF2fs::F2fsVget(Vfs(), old_ino, &old_vn_ref);
if (err)
goto out;
old_vnode = old_vn_ref.get();
old_vnode->i_name_sp = oldname;
ZX_ASSERT(!src_must_be_dir || S_ISDIR(old_vnode->i_mode));
if (S_ISDIR(old_vnode->i_mode)) {
err = ZX_ERR_IO;
old_dir_entry = ((Dir *)old_vnode)->F2fsParentDir(&old_dir_page);
if (!old_dir_entry)
goto out_old;
}
mutex_lock_op(&sbi, RENAME);
new_entry = new_dir->F2fsFindEntry(newname, &new_page);
if (new_entry) {
new_ino = le32_to_cpu(new_entry->ino);
err = VnodeF2fs::F2fsVget(Vfs(), new_ino, &new_vn_ref);
if (err)
goto out_dir;
new_vnode = new_vn_ref.get();
new_vnode->i_name_sp = newname;
err = ZX_ERR_NOT_EMPTY;
if (old_dir_entry && (!S_ISDIR(new_vnode->i_mode) || !((Dir *)new_vnode)->F2fsEmptyDir()))
goto out_dir;
new_dir->F2fsSetLink(new_entry, new_page, old_vnode);
cur_time = time(nullptr);
new_vnode->i_ctime.tv_sec = cur_time;
new_vnode->i_ctime.tv_nsec = 0;
if (old_dir_entry)
new_vnode->DropNlink();
new_vnode->DropNlink();
if (!new_vnode->i_nlink)
Vfs()->AddOrphanInode(new_vnode->Ino());
new_vnode->F2fsWriteInode(NULL);
} else {
old_vnode->i_name_sp = newname;
err = new_dir->F2fsAddLink(newname, old_vnode);
if (err)
goto out_dir;
if (old_dir_entry) {
new_dir->IncNlink();
new_dir->F2fsWriteInode(NULL);
}
}
old_vnode->i_ctime.tv_sec = cur_time;
old_vnode->i_ctime.tv_nsec = 0;
SetInodeFlag(&old_vnode->fi, FI_NEED_CP);
mark_inode_dirty(old_vnode);
// TODO(djkim): remove this after pager is available
// If old_dir == new_dir, old_page is not up-to-date after add new entry with newname
// Therefore, old_page should be read again, unless pager is implemented
if (old_dir == new_dir)
old_entry = F2fsFindEntry(oldname, &old_page);
F2fsDeleteEntry(old_entry, old_page, NULL);
if (old_dir_entry) {
if (old_dir != new_dir) {
((Dir *)old_vnode)->F2fsSetLink(old_dir_entry, old_dir_page, new_dir);
} else {
#if 0 // porting needed
// kunmap(old_dir_page);
#endif
F2fsPutPage(old_dir_page, 0);
}
old_dir->DropNlink();
old_dir->F2fsWriteInode(NULL);
}
mutex_unlock_op(&sbi, RENAME);
Vfs()->Segmgr().F2fsBalanceFs();
mtx_unlock(&i_mutex);
return ZX_OK;
out_dir:
if (old_dir_entry) {
#if 0 // porting needed
// kunmap(old_dir_page);
#endif
F2fsPutPage(old_dir_page, 0);
}
mutex_unlock_op(&sbi, RENAME);
out_old:
#if 0 // porting needed
// kunmap(old_page);
#endif
F2fsPutPage(old_page, 0);
out:
mtx_unlock(&i_mutex);
return err;
}
zx_status_t Dir::Create(fbl::StringPiece name, uint32_t mode, fbl::RefPtr<fs::Vnode> *out) {
Page *page;
zx_status_t ret;
mtx_lock(&i_mutex);
if (F2fsFindEntry(name, &page) != nullptr) {
mtx_unlock(&i_mutex);
return ZX_ERR_ALREADY_EXISTS;
}
if (S_ISDIR(mode))
ret = F2fsMkdir(name, mode, out);
else
ret = F2fsCreate(name, mode, out);
mtx_unlock(&i_mutex);
return ret;
}
zx_status_t Dir::Unlink(fbl::StringPiece name, bool must_be_dir) {
fbl::RefPtr<fs::Vnode> vn;
zx_status_t ret;
mtx_lock(&i_mutex);
ret = F2fsLookup(name, &vn);
if (ret) {
mtx_unlock(&i_mutex);
return ZX_ERR_NOT_FOUND;
}
VnodeF2fs *vnode = (VnodeF2fs *)vn.get();
if (S_ISDIR(vnode->i_mode))
ret = F2fsRmdir((Dir *)vnode, name);
else
ret = F2fsUnlink(vnode, name);
mtx_unlock(&i_mutex);
return ret;
}
} // namespace f2fs