/**************************************************************************** * fs/inode/fs_files.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_FDCHECK # include #endif #include "sched/sched.h" #include "inode/inode.h" #include "fs_heap.h" /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: fdlist_get_by_index ****************************************************************************/ static void fdlist_get_by_index(FAR struct fdlist *list, int l1, int l2, FAR struct file **filep, FAR struct fd **fdp) { FAR struct fd *fdp1; irqstate_t flags; flags = spin_lock_irqsave_notrace(&list->fl_lock); fdp1 = &list->fl_fds[l1][l2]; *filep = fdp1->f_file; if (*filep != NULL) { atomic_fetch_add(&(*filep)->f_refs, 1); } spin_unlock_irqrestore_notrace(&list->fl_lock, flags); if (fdp != NULL) { *fdp = fdp1; } } /**************************************************************************** * Name: fdlist_extend ****************************************************************************/ static int fdlist_extend(FAR struct fdlist *list, size_t row) { FAR struct fd **fds; uint8_t orig_rows; FAR void *tmp; int flags; int i; int j; orig_rows = list->fl_rows; if (row <= orig_rows) { return 0; } if (CONFIG_NFILE_DESCRIPTORS_PER_BLOCK * (orig_rows + 1) > OPEN_MAX) { fdlist_dump(list); return -EMFILE; } fds = fs_heap_malloc(sizeof(FAR struct fd *) * row); DEBUGASSERT(fds); if (fds == NULL) { return -ENFILE; } i = orig_rows; do { fds[i] = fs_heap_zalloc(sizeof(struct fd) * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK); if (fds[i] == NULL) { while (--i >= orig_rows) { fs_heap_free(fds[i]); } fs_heap_free(fds); return -ENFILE; } } while (++i < row); flags = spin_lock_irqsave_notrace(&list->fl_lock); /* To avoid race condition, if the file list is updated by other threads * and list rows is greater or equal than temp list, * release the obsolete buffers */ if (orig_rows != list->fl_rows && list->fl_rows >= row) { spin_unlock_irqrestore_notrace(&list->fl_lock, flags); for (j = orig_rows; j < i; j++) { fs_heap_free(fds[j]); } fs_heap_free(fds); return OK; } if (list->fl_fds != NULL) { memcpy(fds, list->fl_fds, list->fl_rows * sizeof(FAR struct fd *)); } tmp = list->fl_fds; list->fl_fds = fds; list->fl_rows = row; spin_unlock_irqrestore_notrace(&list->fl_lock, flags); if (tmp != NULL && tmp != &list->fl_prefd) { fs_heap_free(tmp); } return OK; } /**************************************************************************** * Name: fdlist_uninstall ****************************************************************************/ static void fdlist_uninstall(FAR struct fdlist *list, FAR struct fd *fdp) { FAR struct file *filep = NULL; irqstate_t flags; flags = spin_lock_irqsave_notrace(&list->fl_lock); if (fdp->f_file != NULL) { #ifdef CONFIG_FDSAN fdp->f_tag_fdsan = 0; #endif #ifdef CONFIG_FDCHECK fdp->f_tag_fdcheck = 0; #endif filep = fdp->f_file; fdp->f_file = NULL; } spin_unlock_irqrestore_notrace(&list->fl_lock, flags); file_put(filep); } static void fdlist_install(FAR struct fdlist *list, int fd, FAR struct file *filep, FAR struct fd *fdp, int oflags, bool copy) { FAR struct file *filep1; FAR struct fd *fdp1; irqstate_t flags; int l1; int l2; l1 = fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; l2 = fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; flags = spin_lock_irqsave_notrace(&list->fl_lock); fdp1 = &list->fl_fds[l1][l2]; filep1 = fdp1->f_file; fdp1->f_file = filep; file_ref(filep); fdp1->f_cloexec = !!(oflags & O_CLOEXEC); FS_ADD_BACKTRACE(fdp1); if (copy) { #ifdef CONFIG_FDSAN fdp1->f_tag_fdsan = fdp->f_tag_fdsan; #endif #ifdef CONFIG_FDCHECK fdp1->f_tag_fdcheck = fdp->f_tag_fdcheck; #endif } spin_unlock_irqrestore_notrace(&list->fl_lock, flags); file_put(filep1); } /**************************************************************************** * Name: task_fssync ****************************************************************************/ static void task_fssync(FAR struct tcb_s *tcb, FAR void *arg) { FAR struct tcb_s *ctcb; int pid = tcb->pid; uint8_t rows; int i; int j; if (tcb->group == NULL) { return; } rows = tcb->group->tg_fdlist.fl_rows; for (i = 0; i < rows; i++) { for (j = 0; j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j++) { FAR struct file *filep; ctcb = nxsched_get_tcb(pid); if (ctcb == NULL || ctcb->group == NULL || ctcb != tcb) { return; } fdlist_get_by_index(&ctcb->group->tg_fdlist, i, j, &filep, NULL); if (filep != NULL) { file_fsync(filep); file_put(filep); } } } } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: fdlist_dup3 * * Description: * fdlist_dup3() is similar to the standard 'dup3' interface * except that is not a cancellation point and it does not modify the * errno variable. * * fdlist_dup3() is an internal NuttX interface and should not be * called from applications. * * Clone a file descriptor to a specific descriptor number and * specific flags. * * Returned Value: * fd2 is returned on success; a negated errno value is return on * any failure. * ****************************************************************************/ int fdlist_dup3(FAR struct fdlist *list, int fd1, int fd2, int flags) { FAR struct file *filep1; FAR struct fd *fdp1; int ret; if (fd1 == fd2) { return -EINVAL; } #ifdef CONFIG_FDCHECK fd2 = fdcheck_restore(fd2); #endif /* Get the file descriptor list. It should not be NULL in this context. */ if (fd2 < 0) { return -EBADF; } if (fd2 >= fdlist_count(list)) { ret = fdlist_extend(list, fd2 / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + 1); if (ret < 0) { return ret; } } ret = fdlist_get2(list, fd1, &filep1, &fdp1); if (ret < 0) { return ret; } fdlist_install(list, fd2, filep1, fdp1, flags, false); file_put(filep1); #ifdef CONFIG_FDCHECK return fdcheck_protect(fd2); #else return fd2; #endif } /**************************************************************************** * Name: fdlist_init * * Description: Initializes the list of file descriptors for a new task. * ****************************************************************************/ void fdlist_init(FAR struct fdlist *list) { /* The first row will reuse pre-allocated files, which will avoid * unnecessary allocator accesses during file initialization. */ list->fl_rows = 1; list->fl_fds = &list->fl_prefd; list->fl_prefd = list->fl_prefds; spin_lock_init(&list->fl_lock); } /**************************************************************************** * Name: fdlist_dump * * Description: * Dump the list of file descriptors. * ****************************************************************************/ #ifdef CONFIG_SCHED_DUMP_ON_EXIT void fdlist_dump(FAR struct fdlist *list) { FAR char *path; int count = fdlist_count(list); int i; syslog(LOG_INFO, "%-4s%-4s%-8s%-5s%-10s%-14s" #if CONFIG_FS_BACKTRACE > 0 " BACKTRACE" #endif "\n", "PID", "FD", "FLAGS", "TYPE", "POS", "PATH" ); path = lib_get_pathbuffer(); if (path == NULL) { return; } for (i = 0; i < count; i++) { FAR struct fd *fdp; FAR struct file *filep; #if CONFIG_FS_BACKTRACE > 0 char buf[BACKTRACE_BUFFER_SIZE(CONFIG_FS_BACKTRACE)]; #endif /* Is there an inode associated with the file descriptor? */ if (fdlist_get2(list, i, &filep, &fdp) < 0) { continue; } if (file_ioctl(filep, FIOC_FILEPATH, path) < 0) { path[0] = '\0'; } #if CONFIG_FS_BACKTRACE > 0 backtrace_format(buf, sizeof(buf), fdp->f_backtrace, CONFIG_FS_BACKTRACE); #endif syslog(LOG_INFO, "%-4d%-4d%-8d%-5x%-10ld%-14s" #if CONFIG_FS_BACKTRACE > 0 " %s" #endif "\n", getpid(), i, filep->f_oflags, INODE_GET_TYPE(filep->f_inode), (long)filep->f_pos, path #if CONFIG_FS_BACKTRACE > 0 , buf #endif ); file_put(filep); } lib_put_pathbuffer(path); } #endif /**************************************************************************** * Name: fdlist_free * * Description: * Release the list of file descriptors. * * Assumptions: * Called during task deletion in a safe context. * ****************************************************************************/ void fdlist_free(FAR struct fdlist *list) { int i; int j; /* Close each file descriptor .. Normally, you would need take the list * mutex, but it is safe to ignore the mutex in this context * because there should not be any references in this context. */ for (i = list->fl_rows - 1; i >= 0; i--) { for (j = CONFIG_NFILE_DESCRIPTORS_PER_BLOCK - 1; j >= 0; j--) { fdlist_close(list, i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j); } if (i != 0) { fs_heap_free(list->fl_fds[i]); } } if (list->fl_fds != &list->fl_prefd) { fs_heap_free(list->fl_fds); } } /**************************************************************************** * Name: fdlist_count * * Description: * Get file descriptor count from file list. * * Input Parameters: * list - Pointer to the file descriptor list structure. * * Returned Value: * file count of file list. * ****************************************************************************/ int fdlist_count(FAR struct fdlist *list) { return list->fl_rows * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; } /**************************************************************************** * Name: fdlist_get2 * * Description: * Given a file descriptor, return the corresponding instance of struct * fd and filep. * * Input Parameters: * list - Pointer to the file descriptor list structure. * fd - The file descriptor * filep - The location to return the struct file instance * fdp - The location to return the struct fd instance * * Returned Value: * Return the pointer to file structure of list[fd] when list[fd].f_file * is valid, otherwise, a null pointer is returned. * ****************************************************************************/ int fdlist_get2(FAR struct fdlist *list, int fd, FAR struct file **filep, FAR struct fd **fdp) { if (list == NULL) { return -EINVAL; } #ifdef CONFIG_FDCHECK fd = fdcheck_restore(fd); #endif if (fd < 0 || fd >= fdlist_count(list)) { return -EBADF; } fdlist_get_by_index(list, fd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK, fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK, filep, fdp); if (*filep == NULL) { return -EBADF; } return 0; } /**************************************************************************** * Name: fdlist_dupfile * * Description: * Allocate a struct fd instance and bind it to the corresponding file * handle. * * Returned Value: * Returns the file descriptor == index into the files array on success; * a negated errno value is returned on any failure. * ****************************************************************************/ int fdlist_dupfile(FAR struct fdlist *list, int oflags, int minfd, FAR struct file *filep) { FAR struct fd *fdp; irqstate_t flags; int ret; int i; int j; DEBUGASSERT(filep); #ifdef CONFIG_FDCHECK minfd = fdcheck_restore(minfd); #endif i = minfd / CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j = minfd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; /* Find free file descriptor */ flags = spin_lock_irqsave_notrace(&list->fl_lock); for (; ; i++, j = 0) { if (i >= list->fl_rows) { spin_unlock_irqrestore_notrace(&list->fl_lock, flags); ret = fdlist_extend(list, i + 1); if (ret < 0) { return ret; } flags = spin_lock_irqsave_notrace(&list->fl_lock); } do { fdp = &list->fl_fds[i][j]; if (fdp->f_file == NULL) { atomic_fetch_add(&filep->f_refs, 1); fdp->f_file = filep; fdp->f_cloexec = !!(oflags & O_CLOEXEC); #ifdef CONFIG_FDSAN fdp->f_tag_fdsan = 0; #endif #ifdef CONFIG_FDCHECK fdp->f_tag_fdcheck = 0; #endif goto found; } } while (++j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK); } found: spin_unlock_irqrestore_notrace(&list->fl_lock, flags); FS_ADD_BACKTRACE(fdp); #ifdef CONFIG_FDCHECK return fdcheck_protect(i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j); #else return i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j; #endif } /**************************************************************************** * Name: file_allocate_from_inode * * Description: * Allocate a struct fd instance and associate it with an file instance. * And initialize them with inode, oflags, pos and priv. * * Returned Value: * Returns the file descriptor == index into the files array on success; * a negated errno value is returned on any failure. * ****************************************************************************/ int file_allocate_from_inode(FAR struct inode *inode, int oflags, off_t pos, FAR void *priv, int minfd) { FAR struct file *filep; int fd; filep = file_allocate(); if (filep == NULL) { return -ENOMEM; } inode_addref(inode); filep->f_inode = inode; filep->f_pos = pos; filep->f_oflags = oflags & ~O_CLOEXEC; filep->f_priv = priv; #if CONFIG_FS_LOCK_BUCKET_SIZE > 0 filep->f_locked = false; #endif fd = file_dup(filep, minfd, oflags); if (fd < 0) { inode_release(inode); file_deallocate(filep); } return fd; } /**************************************************************************** * Name: fdlist_copy * * Description: * Copy parent task's file descriptors to child task. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * any failure. * ****************************************************************************/ int fdlist_copy(FAR struct fdlist *plist, FAR struct fdlist *clist, FAR const posix_spawn_file_actions_t *actions, bool cloexec) { bool fcloexec; int ret; int fd; int i; int j; for (i = 0; i < plist->fl_rows; i++) { for (j = 0; j < CONFIG_NFILE_DESCRIPTORS_PER_BLOCK; j++) { FAR struct file *filep; FAR struct fd *fdp; fd = i * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + j; #ifdef CONFIG_FDCLONE_STDIO /* Determine how many file descriptors to clone. If * CONFIG_FDCLONE_DISABLE is set, no file descriptors will be * cloned. If CONFIG_FDCLONE_STDIO is set, only the first * three descriptors (stdin, stdout, and stderr) will be * cloned. Otherwise all file descriptors will be cloned. */ if (fd >= 3) { return OK; } #endif fdlist_get_by_index(plist, i, j, &filep, &fdp); if (filep == NULL) { continue; } fcloexec = cloexec && fdp->f_cloexec; /* Skip file dup if file action is unnecessary to duplicate */ if (actions != NULL) { #ifdef CONFIG_FDCHECK fd = fdcheck_protect(fd); #endif if (!spawn_file_is_duplicateable(actions, fd, fcloexec)) { file_put(filep); continue; } #ifdef CONFIG_FDCHECK fd = fdcheck_restore(fd); #endif } else if (fcloexec) { file_put(filep); continue; } ret = fdlist_extend(clist, i + 1); if (ret < 0) { file_put(filep); return ret; } /* Assign filep to the child's descriptor list. Omit the flags */ fdlist_install(clist, fd, filep, fdp, 0, true); file_put(filep); } } return OK; } /**************************************************************************** * Name: file_allocate * * Description: * Allocate a file instance and return * ****************************************************************************/ FAR struct file *file_allocate(void) { return fs_heap_zalloc(sizeof(struct file)); } /**************************************************************************** * Name: file_deallocate * * Description: * Free a file instance. * ****************************************************************************/ void file_deallocate(FAR struct file *filep) { fs_heap_free(filep); } /**************************************************************************** * Name: file_get2 * * Description: * Given a file descriptor, return the corresponding instance of struct * fd and filep * * Input Parameters: * fd - The file descriptor * filep - The location to return the struct file instance * fdp - The location to return the struct fd instance * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * any failure. * ****************************************************************************/ int file_get2(int fd, FAR struct file **filep, FAR struct fd **fdp) { return fdlist_get2(nxsched_get_fdlist(), fd, filep, fdp); } /**************************************************************************** * Name: file_ref * * Description: * To specify filep increase the reference count. * * Input Parameters: * None. * * Returned Value: * None. * ****************************************************************************/ void file_ref(FAR struct file *filep) { /* This interface is used to increase the reference count of filep */ DEBUGASSERT(filep); atomic_fetch_add(&filep->f_refs, 1); } /**************************************************************************** * Name: file_put * * Description: * Handles reference counts for files, less than or equal to 0 and close * the file * * Input Parameters: * filep - The caller provided location in which to return the 'struct * file' instance. ****************************************************************************/ int file_put(FAR struct file *filep) { int ret = 0; if (filep == NULL) { return ret; } /* If refs is zero, the close() had called, closing it now. */ if (atomic_fetch_sub(&filep->f_refs, 1) == 1) { ret = file_close(filep); if (ret < 0) { ferr("ERROR: fs putfilep file_close() failed: %d\n", ret); } fs_heap_free(filep); } return ret; } /**************************************************************************** * Name: fdlist_dup2 * * Description: * fdlist_dup2() is similar to the standard 'dup2' interface * except that is not a cancellation point and it does not modify the * errno variable. * * fdlist_dup2() is an internal NuttX interface and should not be * called from applications. * * Clone a file descriptor to a specific descriptor number. * * Returned Value: * fd2 is returned on success; a negated errno value is return on * any failure. * ****************************************************************************/ int fdlist_dup2(FAR struct fdlist *list, int fd1, int fd2) { /* If fd1 is a valid file descriptor, and fd1 == fd2, then dup2() does * nothing, and returns fd2. */ if (fd1 == fd2) { FAR struct file *filep; int ret; ret = file_get(fd1, &filep); if (ret < 0) { return ret; } /* Release the reference */ file_put(filep); return fd2; } return fdlist_dup3(list, fd1, fd2, 0); } /**************************************************************************** * Name: fdlist_close * * Description: * fdlist_close() is similar to the standard 'close' interface * except that is not a cancellation point and it does not modify the * errno variable. * * fdlist_close() is an internal NuttX interface and should not * be called from applications. * * Close an inode (if open) * * Returned Value: * Zero (OK) is returned on success; A negated errno value is returned on * on any failure. * * Assumptions: * Caller holds the list mutex because the file descriptor will be * freed. * ****************************************************************************/ int fdlist_close(FAR struct fdlist *list, int fd) { FAR struct file *filep; FAR struct fd *fdp; int ret; ret = fdlist_get2(list, fd, &filep, &fdp); if (ret < 0) { return ret; } /* Perform the protected close operation */ fdlist_uninstall(list, fdp); /* fdlist_get2 will increase the reference count, there call * file_put reduce reference count. */ return file_put(filep); } /**************************************************************************** * Name: sync * * Description: * sync() causes all pending modifications to filesystem metadata and * cached file data to be written to the underlying filesystems. * ****************************************************************************/ void sync(void) { nxsched_foreach(task_fssync, NULL); }