/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "los_mux.h" #include "fs/dirent_fs.h" #include "path_cache.h" #include "vnode.h" #include "los_process.h" #include "los_process_pri.h" LIST_HEAD g_vnodeFreeList; /* free vnodes list */ LIST_HEAD g_vnodeVirtualList; /* dev vnodes list */ LIST_HEAD g_vnodeActiveList; /* inuse vnodes list */ static int g_freeVnodeSize = 0; /* system free vnodes size */ static int g_totalVnodeSize = 0; /* total vnode size */ static LosMux g_vnodeMux; static struct Vnode *g_rootVnode = NULL; static struct VnodeOps g_devfsOps; #define ENTRY_TO_VNODE(ptr) LOS_DL_LIST_ENTRY(ptr, struct Vnode, actFreeEntry) #define VNODE_LRU_COUNT 10 #define DEV_VNODE_MODE 0755 int VnodesInit(void) { int retval = LOS_MuxInit(&g_vnodeMux, NULL); if (retval != LOS_OK) { PRINT_ERR("Create mutex for vnode fail, status: %d", retval); return retval; } LOS_ListInit(&g_vnodeFreeList); LOS_ListInit(&g_vnodeVirtualList); LOS_ListInit(&g_vnodeActiveList); retval = VnodeAlloc(NULL, &g_rootVnode); if (retval != LOS_OK) { PRINT_ERR("VnodeInit failed error %d\n", retval); return retval; } g_rootVnode->mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR; g_rootVnode->type = VNODE_TYPE_DIR; g_rootVnode->filePath = "/"; #ifdef LOSCFG_CHROOT LosProcessCB *processCB = OsGetKernelInitProcess(); if (processCB->files != NULL) { g_rootVnode->useCount++; processCB->files->rootVnode = g_rootVnode; } #endif return LOS_OK; } static struct Vnode *GetFromFreeList(void) { if (g_freeVnodeSize <= 0) { return NULL; } struct Vnode *vnode = NULL; if (LOS_ListEmpty(&g_vnodeFreeList)) { PRINT_ERR("get vnode from free list failed, list empty but g_freeVnodeSize = %d!\n", g_freeVnodeSize); g_freeVnodeSize = 0; return NULL; } vnode = ENTRY_TO_VNODE(LOS_DL_LIST_FIRST(&g_vnodeFreeList)); LOS_ListDelete(&vnode->actFreeEntry); g_freeVnodeSize--; return vnode; } struct Vnode *VnodeReclaimLru(void) { struct Vnode *item = NULL; struct Vnode *nextItem = NULL; int releaseCount = 0; LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, nextItem, &g_vnodeActiveList, struct Vnode, actFreeEntry) { if ((item->useCount > 0) || (item->flag & VNODE_FLAG_MOUNT_ORIGIN) || (item->flag & VNODE_FLAG_MOUNT_NEW)) { continue; } if (VnodeFree(item) == LOS_OK) { releaseCount++; } if (releaseCount >= VNODE_LRU_COUNT) { break; } } if (releaseCount == 0) { PRINT_ERR("VnodeAlloc failed, vnode size hit max but can't reclaim anymore!\n"); return NULL; } item = GetFromFreeList(); if (item == NULL) { PRINT_ERR("VnodeAlloc failed, reclaim and get from free list failed!\n"); } return item; } int VnodeAlloc(struct VnodeOps *vop, struct Vnode **newVnode) { struct Vnode* vnode = NULL; VnodeHold(); vnode = GetFromFreeList(); if ((vnode == NULL) && g_totalVnodeSize < LOSCFG_MAX_VNODE_SIZE) { vnode = (struct Vnode*)zalloc(sizeof(struct Vnode)); g_totalVnodeSize++; } if (vnode == NULL) { vnode = VnodeReclaimLru(); } if (vnode == NULL) { *newVnode = NULL; VnodeDrop(); return -ENOMEM; } vnode->type = VNODE_TYPE_UNKNOWN; LOS_ListInit((&(vnode->parentPathCaches))); LOS_ListInit((&(vnode->childPathCaches))); LOS_ListInit((&(vnode->hashEntry))); LOS_ListInit((&(vnode->actFreeEntry))); if (vop == NULL) { LOS_ListAdd(&g_vnodeVirtualList, &(vnode->actFreeEntry)); vnode->vop = &g_devfsOps; } else { LOS_ListTailInsert(&g_vnodeActiveList, &(vnode->actFreeEntry)); vnode->vop = vop; } LOS_ListInit(&vnode->mapping.page_list); LOS_SpinInit(&vnode->mapping.list_lock); (VOID)LOS_MuxInit(&vnode->mapping.mux_lock, NULL); vnode->mapping.host = vnode; VnodeDrop(); *newVnode = vnode; return LOS_OK; } int VnodeFree(struct Vnode *vnode) { if (vnode == NULL) { return LOS_OK; } VnodeHold(); if (vnode->useCount > 0) { VnodeDrop(); return -EBUSY; } VnodePathCacheFree(vnode); LOS_ListDelete(&(vnode->hashEntry)); LOS_ListDelete(&vnode->actFreeEntry); if (vnode->vop->Reclaim) { vnode->vop->Reclaim(vnode); } if (vnode->filePath) { free(vnode->filePath); } if (vnode->vop == &g_devfsOps) { /* for dev vnode, just free it */ free(vnode->data); free(vnode); g_totalVnodeSize--; } else { /* for normal vnode, reclaim it to g_VnodeFreeList */ (void)memset_s(vnode, sizeof(struct Vnode), 0, sizeof(struct Vnode)); LOS_ListAdd(&g_vnodeFreeList, &vnode->actFreeEntry); g_freeVnodeSize++; } VnodeDrop(); return LOS_OK; } int VnodeFreeAll(const struct Mount *mount) { struct Vnode *vnode = NULL; struct Vnode *nextVnode = NULL; int ret; LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(vnode, nextVnode, &g_vnodeActiveList, struct Vnode, actFreeEntry) { if ((vnode->originMount == mount) && !(vnode->flag & VNODE_FLAG_MOUNT_NEW)) { ret = VnodeFree(vnode); if (ret != LOS_OK) { return ret; } } } return LOS_OK; } BOOL VnodeInUseIter(const struct Mount *mount) { struct Vnode *vnode = NULL; LOS_DL_LIST_FOR_EACH_ENTRY(vnode, &g_vnodeActiveList, struct Vnode, actFreeEntry) { if (vnode->originMount == mount) { if ((vnode->useCount > 0) || (vnode->flag & VNODE_FLAG_MOUNT_ORIGIN)) { return TRUE; } } } return FALSE; } int VnodeHold(void) { int ret = LOS_MuxLock(&g_vnodeMux, LOS_WAIT_FOREVER); if (ret != LOS_OK) { PRINT_ERR("VnodeHold lock failed !\n"); } return ret; } int VnodeDrop(void) { int ret = LOS_MuxUnlock(&g_vnodeMux); if (ret != LOS_OK) { PRINT_ERR("VnodeDrop unlock failed !\n"); } return ret; } static char *NextName(char *pos, uint8_t *len) { char *name = NULL; while (*pos != 0 && *pos == '/') { pos++; } if (*pos == '\0') { return NULL; } name = (char *)pos; while (*pos != '\0' && *pos != '/') { pos++; } *len = pos - name; return name; } static int PreProcess(const char *originPath, struct Vnode **startVnode, char **path) { int ret; char *absolutePath = NULL; ret = vfs_normalize_path(NULL, originPath, &absolutePath); if (ret == LOS_OK) { *startVnode = GetCurrRootVnode(); *path = absolutePath; } return ret; } static struct Vnode *ConvertVnodeIfMounted(struct Vnode *vnode) { if ((vnode == NULL) || !(vnode->flag & VNODE_FLAG_MOUNT_ORIGIN)) { return vnode; } #ifdef LOSCFG_MNT_CONTAINER LIST_HEAD *mntList = GetMountList(); struct Mount *mnt = NULL; LOS_DL_LIST_FOR_EACH_ENTRY(mnt, mntList, struct Mount, mountList) { if ((mnt != NULL) && (mnt->vnodeBeCovered == vnode)) { return mnt->vnodeCovered; } } if (strcmp(vnode->filePath, "/dev") == 0) { return vnode->newMount->vnodeCovered; } return vnode; #else return vnode->newMount->vnodeCovered; #endif } static void RefreshLRU(struct Vnode *vnode) { if (vnode == NULL || (vnode->type != VNODE_TYPE_REG && vnode->type != VNODE_TYPE_DIR) || vnode->vop == &g_devfsOps || vnode->vop == NULL) { return; } LOS_ListDelete(&(vnode->actFreeEntry)); LOS_ListTailInsert(&g_vnodeActiveList, &(vnode->actFreeEntry)); } static int ProcessVirtualVnode(struct Vnode *parent, uint32_t flags, struct Vnode **vnode) { int ret = -ENOENT; if (flags & V_CREATE) { // only create /dev/ vnode ret = VnodeAlloc(NULL, vnode); } if (ret == LOS_OK) { (*vnode)->parent = parent; } return ret; } static int Step(char **currentDir, struct Vnode **currentVnode, uint32_t flags) { int ret; uint8_t len = 0; struct Vnode *nextVnode = NULL; char *nextDir = NULL; if ((*currentVnode)->type != VNODE_TYPE_DIR) { return -ENOTDIR; } nextDir = NextName(*currentDir, &len); if (nextDir == NULL) { // there is '/' at the end of the *currentDir. *currentDir = NULL; return LOS_OK; } ret = PathCacheLookup(*currentVnode, nextDir, len, &nextVnode); if (ret == LOS_OK) { goto STEP_FINISH; } (*currentVnode)->useCount++; if (flags & V_DUMMY) { ret = ProcessVirtualVnode(*currentVnode, flags, &nextVnode); } else { if ((*currentVnode)->vop != NULL && (*currentVnode)->vop->Lookup != NULL) { ret = (*currentVnode)->vop->Lookup(*currentVnode, nextDir, len, &nextVnode); } else { ret = -ENOSYS; } } (*currentVnode)->useCount--; if (ret == LOS_OK) { (void)PathCacheAlloc((*currentVnode), nextVnode, nextDir, len); } STEP_FINISH: nextVnode = ConvertVnodeIfMounted(nextVnode); RefreshLRU(nextVnode); *currentDir = nextDir + len; if (ret == LOS_OK) { *currentVnode = nextVnode; } return ret; } int VnodeLookupAt(const char *path, struct Vnode **result, uint32_t flags, struct Vnode *orgVnode) { int ret; int vnodePathLen; char *vnodePath = NULL; struct Vnode *startVnode = NULL; char *normalizedPath = NULL; if (orgVnode != NULL) { startVnode = orgVnode; normalizedPath = strdup(path); if (normalizedPath == NULL) { PRINT_ERR("[VFS]lookup failed, strdup err\n"); ret = -EINVAL; goto OUT_FREE_PATH; } } else { ret = PreProcess(path, &startVnode, &normalizedPath); if (ret != LOS_OK) { PRINT_ERR("[VFS]lookup failed, invalid path err = %d\n", ret); goto OUT_FREE_PATH; } } if (normalizedPath[1] == '\0' && normalizedPath[0] == '/') { *result = GetCurrRootVnode(); free(normalizedPath); return LOS_OK; } char *currentDir = normalizedPath; struct Vnode *currentVnode = startVnode; while (*currentDir != '\0') { ret = Step(¤tDir, ¤tVnode, flags); if (currentDir == NULL || *currentDir == '\0') { // return target or parent vnode as result *result = currentVnode; if (currentVnode->filePath == NULL) { currentVnode->filePath = normalizedPath; } else { free(normalizedPath); } return ret; } else if (VfsVnodePermissionCheck(currentVnode, EXEC_OP)) { ret = -EACCES; goto OUT_FREE_PATH; } if (ret != LOS_OK) { // no such file, lookup failed goto OUT_FREE_PATH; } if (currentVnode->filePath == NULL) { vnodePathLen = currentDir - normalizedPath; vnodePath = malloc(vnodePathLen + 1); if (vnodePath == NULL) { ret = -ENOMEM; goto OUT_FREE_PATH; } ret = strncpy_s(vnodePath, vnodePathLen + 1, normalizedPath, vnodePathLen); if (ret != EOK) { ret = -ENAMETOOLONG; free(vnodePath); goto OUT_FREE_PATH; } currentVnode->filePath = vnodePath; currentVnode->filePath[vnodePathLen] = 0; } } OUT_FREE_PATH: if (normalizedPath) { free(normalizedPath); } return ret; } int VnodeLookup(const char *path, struct Vnode **vnode, uint32_t flags) { return VnodeLookupAt(path, vnode, flags, NULL); } int VnodeLookupFullpath(const char *fullpath, struct Vnode **vnode, uint32_t flags) { return VnodeLookupAt(fullpath, vnode, flags, GetCurrRootVnode()); } static void ChangeRootInternal(struct Vnode *rootOld, char *dirname) { int ret; struct Mount *mnt = NULL; char *name = NULL; struct Vnode *node = NULL; struct Vnode *nodeInFs = NULL; struct PathCache *item = NULL; struct PathCache *nextItem = NULL; LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, nextItem, &rootOld->childPathCaches, struct PathCache, childEntry) { name = item->name; node = item->childVnode; if (strcmp(name, dirname)) { continue; } PathCacheFree(item); ret = VnodeLookup(dirname, &nodeInFs, 0); if (ret) { PRINTK("%s-%d %s NOT exist in rootfs\n", __FUNCTION__, __LINE__, dirname); break; } mnt = node->newMount; mnt->vnodeBeCovered = nodeInFs; nodeInFs->newMount = mnt; nodeInFs->flag |= VNODE_FLAG_MOUNT_ORIGIN; break; } } void ChangeRoot(struct Vnode *rootNew) { struct Vnode *rootOld = g_rootVnode; g_rootVnode = rootNew; #ifdef LOSCFG_CHROOT LosProcessCB *curr = OsCurrProcessGet(); if ((curr->files != NULL) && (curr->files->rootVnode != NULL) && (curr->files->rootVnode->useCount > 0)) { curr->files->rootVnode->useCount--; } rootNew->useCount++; curr->files->rootVnode = rootNew; #endif ChangeRootInternal(rootOld, "proc"); ChangeRootInternal(rootOld, "dev"); } static int VnodeReaddir(struct Vnode *vp, struct fs_dirent_s *dir) { int result; int cnt = 0; off_t i = 0; off_t idx; unsigned int dstNameSize; struct PathCache *item = NULL; struct PathCache *nextItem = NULL; if (dir == NULL) { return -EINVAL; } LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, nextItem, &vp->childPathCaches, struct PathCache, childEntry) { if (i < dir->fd_position) { i++; continue; } idx = i - dir->fd_position; dstNameSize = sizeof(dir->fd_dir[idx].d_name); result = strncpy_s(dir->fd_dir[idx].d_name, dstNameSize, item->name, item->nameLen); if (result != EOK) { return -ENAMETOOLONG; } dir->fd_dir[idx].d_off = i; dir->fd_dir[idx].d_reclen = (uint16_t)sizeof(struct dirent); i++; if (++cnt >= dir->read_cnt) { break; } } dir->fd_position = i; return cnt; } int VnodeOpendir(struct Vnode *vnode, struct fs_dirent_s *dir) { (void)vnode; (void)dir; return LOS_OK; } int VnodeClosedir(struct Vnode *vnode, struct fs_dirent_s *dir) { (void)vnode; (void)dir; return LOS_OK; } int VnodeCreate(struct Vnode *parent, const char *name, int mode, struct Vnode **vnode) { int ret; struct Vnode *newVnode = NULL; ret = VnodeAlloc(NULL, &newVnode); if (ret != 0) { return -ENOMEM; } newVnode->type = VNODE_TYPE_CHR; newVnode->vop = parent->vop; newVnode->fop = parent->fop; newVnode->data = NULL; newVnode->parent = parent; newVnode->originMount = parent->originMount; newVnode->uid = parent->uid; newVnode->gid = parent->gid; newVnode->mode = mode; /* The 'name' here is not full path, but for device we don't depend on this path, it's just a name for DFx. When we have devfs, we can get a fullpath. */ newVnode->filePath = strdup(name); *vnode = newVnode; return 0; } int VnodeDevInit(void) { struct Vnode *devNode = NULL; struct Mount *devMount = NULL; int retval = VnodeLookup("/dev", &devNode, V_CREATE | V_DUMMY); if (retval != LOS_OK) { PRINT_ERR("VnodeDevInit failed error %d\n", retval); return retval; } devNode->mode = DEV_VNODE_MODE | S_IFDIR; devNode->type = VNODE_TYPE_DIR; devMount = MountAlloc(devNode, NULL); if (devMount == NULL) { PRINT_ERR("VnodeDevInit failed mount point alloc failed.\n"); return -ENOMEM; } devMount->vnodeCovered = devNode; devMount->vnodeBeCovered->flag |= VNODE_FLAG_MOUNT_ORIGIN; return LOS_OK; } int VnodeGetattr(struct Vnode *vnode, struct stat *buf) { (void)memset_s(buf, sizeof(struct stat), 0, sizeof(struct stat)); buf->st_mode = vnode->mode; buf->st_uid = vnode->uid; buf->st_gid = vnode->gid; return LOS_OK; } struct Vnode *VnodeGetRoot(void) { return g_rootVnode; } static int VnodeChattr(struct Vnode *vnode, struct IATTR *attr) { mode_t tmpMode; if (vnode == NULL || attr == NULL) { return -EINVAL; } if (attr->attr_chg_valid & CHG_MODE) { tmpMode = attr->attr_chg_mode; tmpMode &= ~S_IFMT; vnode->mode &= S_IFMT; vnode->mode = tmpMode | vnode->mode; } if (attr->attr_chg_valid & CHG_UID) { vnode->uid = attr->attr_chg_uid; } if (attr->attr_chg_valid & CHG_GID) { vnode->gid = attr->attr_chg_gid; } return LOS_OK; } int VnodeDevLookup(struct Vnode *parentVnode, const char *path, int len, struct Vnode **vnode) { (void)parentVnode; (void)path; (void)len; (void)vnode; /* dev node must in pathCache. */ return -ENOENT; } static struct VnodeOps g_devfsOps = { .Lookup = VnodeDevLookup, .Getattr = VnodeGetattr, .Readdir = VnodeReaddir, .Opendir = VnodeOpendir, .Closedir = VnodeClosedir, .Create = VnodeCreate, .Chattr = VnodeChattr, }; void VnodeMemoryDump(void) { struct Vnode *item = NULL; struct Vnode *nextItem = NULL; int vnodeCount = 0; LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, nextItem, &g_vnodeActiveList, struct Vnode, actFreeEntry) { if ((item->useCount > 0) || (item->flag & VNODE_FLAG_MOUNT_ORIGIN) || (item->flag & VNODE_FLAG_MOUNT_NEW)) { continue; } vnodeCount++; } PRINTK("Vnode number = %d\n", vnodeCount); PRINTK("Vnode memory size = %d(B)\n", vnodeCount * sizeof(struct Vnode)); } struct Vnode *GetVnode(INT32 fd) { INT32 sysFd; if (fd < 0) { PRINT_ERR("Error. fd is invalid as %d\n", fd); return NULL; } /* Process fd convert to system global fd */ sysFd = GetAssociatedSystemFd(fd); if (sysFd < 0) { PRINT_ERR("Error. sysFd is invalid as %d\n", sysFd); return NULL; } return files_get_openfile((int)sysFd); } LIST_HEAD* GetVnodeFreeList() { return &g_vnodeFreeList; } LIST_HEAD* GetVnodeVirtualList() { return &g_vnodeVirtualList; } LIST_HEAD* GetVnodeActiveList() { return &g_vnodeActiveList; } int VnodeClearCache(void) { struct Vnode *item = NULL; struct Vnode *nextItem = NULL; int count = 0; VnodeHold(); LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, nextItem, &g_vnodeActiveList, struct Vnode, actFreeEntry) { if ((item->useCount > 0) || (item->flag & VNODE_FLAG_MOUNT_ORIGIN) || (item->flag & VNODE_FLAG_MOUNT_NEW)) { continue; } if (VnodeFree(item) == LOS_OK) { count++; } } VnodeDrop(); return count; } struct Vnode *GetCurrRootVnode(void) { #ifdef LOSCFG_CHROOT LosProcessCB *curr = OsCurrProcessGet(); return curr->files->rootVnode; #else return g_rootVnode; #endif }