| /**************************************************************************** |
| * fs/vfs/fs_lock.c |
| * |
| * 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 <nuttx/config.h> |
| |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <search.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| |
| #include <nuttx/lib/lib.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/list.h> |
| |
| #include "lock.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_FS_LARGEFILE |
| # define OFFSET_MAX INT64_MAX |
| #else |
| # define OFFSET_MAX INT32_MAX |
| #endif |
| |
| #define l_end l_len |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct file_lock_s |
| { |
| struct flock fl_lock; /* File lock related information */ |
| FAR struct file *fl_file; /* Identifies the file descriptor information |
| * held by the caller |
| */ |
| struct list_node fl_node; /* Used to manage each filelock by means of a |
| * chained list. |
| */ |
| }; |
| |
| struct file_lock_bucket_s |
| { |
| struct list_node list; /* Manage a chained list for each |
| * filelock |
| */ |
| sem_t wait; /* Blocking lock, called when SETLKW is |
| * called and there is a conflict. |
| */ |
| size_t nwaiter; /* Indicates how many blocking locks are |
| * currently blocked. |
| */ |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct hsearch_data g_file_lock_table; |
| static mutex_t g_protect_lock = NXMUTEX_INITIALIZER; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: file_lock_get_path |
| ****************************************************************************/ |
| |
| static int file_lock_get_path(FAR struct file *filep, FAR char *path) |
| { |
| FAR struct tcb_s *tcb = nxsched_self(); |
| |
| /* We only apply file lock on mount points (f_inode won't be NULL). */ |
| |
| if (!INODE_IS_MOUNTPT(filep->f_inode) || |
| tcb->flags & TCB_FLAG_SIGNAL_ACTION) |
| { |
| return -EBADF; |
| } |
| |
| return file_fcntl(filep, F_GETPATH, path); |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_normalize |
| ****************************************************************************/ |
| |
| static int file_lock_normalize(FAR struct file *filep, |
| FAR struct flock *flock, |
| FAR struct flock *out) |
| { |
| off_t start; |
| off_t end; |
| |
| /* Check that the type brought in the flock is correct */ |
| |
| switch (flock->l_type) |
| { |
| case F_RDLCK: |
| case F_WRLCK: |
| case F_UNLCK: |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* Converts and saves flock information */ |
| |
| switch (flock->l_whence) |
| { |
| case SEEK_SET: |
| { |
| start = 0; |
| } |
| |
| break; |
| case SEEK_CUR: |
| { |
| start = filep->f_pos; |
| } |
| |
| break; |
| case SEEK_END: |
| { |
| struct stat st; |
| int ret; |
| |
| ret = file_fstat(filep, &st); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| start = st.st_size; |
| } |
| |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* Check for overflow in converted flock */ |
| |
| if (flock->l_start > OFFSET_MAX - start) |
| { |
| return -EOVERFLOW; |
| } |
| |
| start += flock->l_start; |
| if (start < 0) |
| { |
| return -EINVAL; |
| } |
| |
| if (flock->l_len > 0) |
| { |
| if (flock->l_len - 1 > OFFSET_MAX - start) |
| { |
| return -EOVERFLOW; |
| } |
| |
| end = start + flock->l_len - 1; |
| } |
| else if (flock->l_len < 0) |
| { |
| if (start + flock->l_len < 0) |
| { |
| return -EINVAL; |
| } |
| |
| end = start - 1; |
| start += flock->l_len; |
| } |
| else |
| { |
| end = OFFSET_MAX; |
| } |
| |
| out->l_whence = SEEK_SET; |
| out->l_type = flock->l_type; |
| out->l_start = start; |
| out->l_end = end; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_delete |
| ****************************************************************************/ |
| |
| static void file_lock_delete(FAR struct file_lock_s *file_lock) |
| { |
| list_delete(&file_lock->fl_node); |
| kmm_free(file_lock); |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_delete_bucket |
| ****************************************************************************/ |
| |
| static void file_lock_delete_bucket(FAR struct file_lock_bucket_s *bucket, |
| FAR const char *filepath) |
| { |
| ENTRY item; |
| |
| /* If there is still a lock on the chain table at this point, it means |
| * that there is still someone else holding it, so it doesn't need to be |
| * released |
| */ |
| |
| if (list_is_empty(&bucket->list)) |
| { |
| /* At this point, the file has no lock information context, so we can |
| * remove it from the hash table, and the return result is 0 or 1 means |
| * that the node does not exist, so we do not need to care about the |
| * final return results |
| */ |
| |
| item.key = (FAR char *)filepath; |
| hsearch_r(item, DELETE, NULL, &g_file_lock_table); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_is_conflict |
| ****************************************************************************/ |
| |
| static bool file_lock_is_conflict(FAR struct flock *request, |
| FAR struct flock *internal) |
| { |
| /* If the request is not exactly to the left or right of the internal, |
| * then there is an overlap. |
| */ |
| |
| if (request->l_start <= internal->l_end && request->l_end >= |
| internal->l_start) |
| { |
| if (request->l_type == F_WRLCK || internal->l_type == F_WRLCK) |
| { |
| return request->l_pid != internal->l_pid; |
| } |
| } |
| |
| return false; |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_find_bucket |
| ****************************************************************************/ |
| |
| static FAR struct file_lock_bucket_s * |
| file_lock_find_bucket(FAR const char *filepath) |
| { |
| FAR ENTRY *hretvalue; |
| ENTRY item; |
| |
| item.key = (FAR char *)filepath; |
| item.data = NULL; |
| |
| if (hsearch_r(item, FIND, &hretvalue, &g_file_lock_table) == 1) |
| { |
| return hretvalue->data; |
| } |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_create_bucket |
| ****************************************************************************/ |
| |
| static FAR struct file_lock_bucket_s * |
| file_lock_create_bucket(FAR const char *filepath) |
| { |
| FAR struct file_lock_bucket_s *bucket; |
| FAR ENTRY *hretvalue; |
| ENTRY item; |
| |
| bucket = kmm_zalloc(sizeof(*bucket)); |
| if (bucket == NULL) |
| { |
| return NULL; |
| } |
| |
| /* Creating an instance store */ |
| |
| item.key = strdup(filepath); |
| if (item.key == NULL) |
| { |
| kmm_free(bucket); |
| return NULL; |
| } |
| |
| item.data = bucket; |
| |
| if (hsearch_r(item, ENTER, &hretvalue, &g_file_lock_table) == 0) |
| { |
| lib_free(item.key); |
| kmm_free(bucket); |
| return NULL; |
| } |
| |
| list_initialize(&bucket->list); |
| nxsem_init(&bucket->wait, 0, 0); |
| |
| return bucket; |
| } |
| |
| /**************************************************************************** |
| * Name: file_lock_modify |
| ****************************************************************************/ |
| |
| static int file_lock_modify(FAR struct file *filep, |
| FAR struct file_lock_bucket_s *bucket, |
| FAR struct flock *request) |
| { |
| FAR struct file_lock_s *new_file_lock = NULL; |
| FAR struct file_lock_s *right = NULL; |
| FAR struct file_lock_s *left = NULL; |
| FAR struct file_lock_s *file_lock; |
| FAR struct file_lock_s *tmp; |
| bool added = false; |
| bool find = false; |
| |
| list_for_every_entry_safe(&bucket->list, file_lock, tmp, |
| struct file_lock_s, fl_node) |
| { |
| if (request->l_pid != file_lock->fl_lock.l_pid) |
| { |
| /* Only file locks with the same pid need to be processed, so the |
| * lookup is skipped. |
| */ |
| |
| if (find) |
| { |
| /* We've searched around and come back to the beginning. */ |
| |
| break; |
| } |
| } |
| else |
| { |
| find = true; |
| |
| /* Checking the type of overlapping locks */ |
| |
| if (request->l_type == file_lock->fl_lock.l_type) |
| { |
| /* Compare the starting point of the last lock with the |
| * starting point of the request, and use start - 1 instead of |
| * end + 1, because if end is "off_t" max, then end + 1 will |
| * be negative. |
| */ |
| |
| if (request->l_start - 1 > file_lock->fl_lock.l_end) |
| { |
| continue; |
| } |
| |
| if (request->l_end < file_lock->fl_lock.l_start - 1) |
| { |
| break; |
| } |
| |
| /* If the two locks are of the same type, then they are merged |
| * into one lock with a lower start position and a higher end |
| * position. |
| */ |
| |
| if (request->l_start < file_lock->fl_lock.l_start) |
| { |
| file_lock->fl_lock.l_start = request->l_start; |
| } |
| else |
| { |
| request->l_start = file_lock->fl_lock.l_start; |
| } |
| |
| if (request->l_end > file_lock->fl_lock.l_end) |
| { |
| file_lock->fl_lock.l_end = request->l_end; |
| } |
| else |
| { |
| request->l_end = file_lock->fl_lock.l_end; |
| } |
| |
| if (added) |
| { |
| file_lock_delete(file_lock); |
| continue; |
| } |
| |
| request = &file_lock->fl_lock; |
| added = true; |
| } |
| else |
| { |
| if (request->l_start > file_lock->fl_lock.l_end) |
| { |
| continue; |
| } |
| |
| if (request->l_end < file_lock->fl_lock.l_start) |
| { |
| break; |
| } |
| |
| /* Scenarios for handling different types of locks */ |
| |
| if (request->l_type == F_UNLCK) |
| { |
| added = true; |
| } |
| |
| /* The new lock and the old lock are adjacent or overlapping. |
| * The code will handle this depending on the situation. |
| * If the end address of the old lock is higher than the |
| * new lock, then go ahead and insert the new lock here. |
| */ |
| |
| if (request->l_start > file_lock->fl_lock.l_start) |
| { |
| left = file_lock; |
| } |
| |
| if (request->l_end < file_lock->fl_lock.l_end) |
| { |
| right = file_lock; |
| break; |
| } |
| |
| if (request->l_start <= file_lock->fl_lock.l_start) |
| { |
| /* In other cases, we are replacing old locks with new |
| * ones |
| */ |
| |
| if (added) |
| { |
| file_lock_delete(file_lock); |
| continue; |
| } |
| |
| memcpy(&file_lock->fl_lock, request, sizeof(struct flock)); |
| added = true; |
| } |
| } |
| } |
| } |
| |
| if (!added) |
| { |
| if (request->l_type == F_UNLCK) |
| { |
| return OK; |
| } |
| |
| /* insert a new lock */ |
| |
| new_file_lock = kmm_zalloc(sizeof(struct file_lock_s)); |
| if (new_file_lock == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| new_file_lock->fl_file = filep; |
| memcpy(&new_file_lock->fl_lock, request, sizeof(struct flock)); |
| list_add_before(&file_lock->fl_node, &new_file_lock->fl_node); |
| file_lock = new_file_lock; |
| } |
| |
| if (right) |
| { |
| if (left == right) |
| { |
| /* Splitting old locks */ |
| |
| new_file_lock = kmm_zalloc(sizeof(struct file_lock_s)); |
| if (new_file_lock == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| left = new_file_lock; |
| memcpy(left, right, sizeof(struct file_lock_s)); |
| list_add_before(&file_lock->fl_node, &left->fl_node); |
| } |
| |
| right->fl_lock.l_start = request->l_end + 1; |
| } |
| |
| if (left) |
| { |
| left->fl_lock.l_end = request->l_start - 1; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: file_getlk |
| * |
| * Description: |
| * Attempts to lock the region (not a real lock), and if there is a |
| * conflict then returns information about the conflicting locks |
| * |
| * Input Parameters: |
| * filep - File structure instance |
| * flock - Lock types to be converted |
| * |
| * Returned Value: |
| * The resulting 0 on success. A errno value is returned on any failure. |
| * |
| ****************************************************************************/ |
| |
| int file_getlk(FAR struct file *filep, FAR struct flock *flock) |
| { |
| FAR struct file_lock_bucket_s *bucket; |
| FAR struct file_lock_s *file_lock; |
| char path[PATH_MAX]; |
| int ret; |
| |
| /* We need to get the unique identifier (Path) via filep */ |
| |
| ret = file_lock_get_path(filep, path); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Convert a flock to a posix lock */ |
| |
| ret = file_lock_normalize(filep, flock, flock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| nxmutex_lock(&g_protect_lock); |
| |
| bucket = file_lock_find_bucket(path); |
| if (bucket != NULL) |
| { |
| list_for_every_entry(&bucket->list, file_lock, struct file_lock_s, |
| fl_node) |
| { |
| if (file_lock_is_conflict(flock, &file_lock->fl_lock)) |
| { |
| memcpy(flock, &file_lock->fl_lock, sizeof(*flock)); |
| goto out; |
| } |
| } |
| } |
| |
| flock->l_type = F_UNLCK; |
| |
| /* Convert back to flock |
| * The flock information saved in filelock is used as an offset |
| * to the relative position. And for upper level applications, |
| * l_len should be converted to cover the data quantity |
| */ |
| |
| out: |
| nxmutex_unlock(&g_protect_lock); |
| if (flock->l_end == OFFSET_MAX) |
| { |
| flock->l_len = 0; |
| } |
| else |
| { |
| flock->l_len = flock->l_end - flock->l_start + 1; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: file_setlk |
| * |
| * Description: |
| * Actual execution of locking and unlocking behaviors |
| * |
| * Input Parameters: |
| * filep - File structure instance |
| * flock - Lock types to be converted |
| * nonblock - Waiting for lock |
| * |
| * Returned Value: |
| * The resulting 0 on success. A errno value is returned on any failure. |
| * |
| ****************************************************************************/ |
| |
| int file_setlk(FAR struct file *filep, FAR struct flock *flock, |
| bool nonblock) |
| { |
| FAR struct file_lock_bucket_s *bucket; |
| FAR struct file_lock_s *file_lock; |
| struct flock request; |
| char path[PATH_MAX]; |
| int ret; |
| |
| /* We need to get the unique identifier (Path) via filep */ |
| |
| ret = file_lock_get_path(filep, path); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Convert a flock to a posix lock */ |
| |
| ret = file_lock_normalize(filep, flock, &request); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| request.l_pid = getpid(); |
| |
| nxmutex_lock(&g_protect_lock); |
| |
| bucket = file_lock_find_bucket(path); |
| if (bucket == NULL) |
| { |
| /* If we request to unlock and the bucket is not found, it means |
| * there is no lock here. |
| */ |
| |
| if (request.l_type == F_UNLCK) |
| { |
| nxmutex_unlock(&g_protect_lock); |
| return OK; |
| } |
| |
| /* It looks like we didn't find a bucket, let's go create one */ |
| |
| bucket = file_lock_create_bucket(path); |
| if (bucket == NULL) |
| { |
| nxmutex_unlock(&g_protect_lock); |
| return -ENOMEM; |
| } |
| } |
| else if (request.l_type != F_UNLCK) |
| { |
| retry: |
| list_for_every_entry(&bucket->list, file_lock, struct file_lock_s, |
| fl_node) |
| { |
| if (file_lock_is_conflict(&request, &file_lock->fl_lock)) |
| { |
| if (nonblock) |
| { |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| bucket->nwaiter++; |
| nxmutex_unlock(&g_protect_lock); |
| nxsem_wait(&bucket->wait); |
| nxmutex_lock(&g_protect_lock); |
| bucket->nwaiter--; |
| goto retry; |
| } |
| } |
| } |
| |
| ret = file_lock_modify(filep, bucket, &request); |
| if (ret < 0) |
| { |
| goto out; |
| } |
| |
| /* When there is a lock change, we need to wake up the blocking lock */ |
| |
| if (bucket->nwaiter > 0) |
| { |
| nxsem_post(&bucket->wait); |
| } |
| |
| out: |
| file_lock_delete_bucket(bucket, path); |
| nxmutex_unlock(&g_protect_lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: file_closelk |
| * |
| * Description: |
| * Remove all locks associated with the filep when call close is applied. |
| * |
| * Input Parameters: |
| * filep - The filep that corresponds to the shutdown. |
| * |
| ****************************************************************************/ |
| |
| void file_closelk(FAR struct file *filep) |
| { |
| FAR struct file_lock_bucket_s *bucket; |
| FAR struct file_lock_s *file_lock; |
| FAR struct file_lock_s *temp; |
| char path[PATH_MAX]; |
| bool deleted = false; |
| int ret; |
| |
| ret = file_lock_get_path(filep, path); |
| if (ret < 0) |
| { |
| /* It isn't an error if fs doesn't support F_GETPATH, so we just end |
| * it. |
| */ |
| |
| return; |
| } |
| |
| bucket = file_lock_find_bucket(path); |
| if (bucket == NULL) |
| { |
| /* There is no bucket here, so we don't need to free it. */ |
| |
| return; |
| } |
| |
| nxmutex_lock(&g_protect_lock); |
| list_for_every_entry_safe(&bucket->list, file_lock, temp, |
| struct file_lock_s, fl_node) |
| { |
| if (file_lock->fl_file == filep) |
| { |
| deleted = true; |
| file_lock_delete(file_lock); |
| } |
| } |
| |
| if (bucket->nwaiter > 0 && deleted) |
| { |
| nxsem_post(&bucket->wait); |
| } |
| else if (deleted) |
| { |
| file_lock_delete_bucket(bucket, path); |
| } |
| |
| nxmutex_unlock(&g_protect_lock); |
| } |
| |
| /**************************************************************************** |
| * Name: file_initlk |
| * |
| * Description: |
| * Initializing file locks |
| * |
| ****************************************************************************/ |
| |
| void file_initlk(void) |
| { |
| /* Initialize file lock context hash table */ |
| |
| hcreate_r(CONFIG_FS_LOCK_BUCKET_SIZE, &g_file_lock_table); |
| } |