text: /*
   * Copyright (c) 2018 Intel Corporation.
   * Copyright (c) 2020 Peter Bigot Consulting, LLC
   * Copyright (c) 2020 Nordic Semiconductor ASA
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <stdio.h>
  #include <string.h>
  #include <zephyr/types.h>
  #include <errno.h>
  #include <init.h>
  #include <fs/fs.h>
  #include <fs/fs_sys.h>
  #include <sys/stat.h>
  
  
  #define LOG_LEVEL CONFIG_FS_LOG_LEVEL
  #include <logging/log.h>
  LOG_MODULE_REGISTER(fs);
  
  /* list of mounted file systems */
  static sys_dlist_t fs_mnt_list;
  
  /* lock to protect mount list operations */
  static struct k_mutex mutex;
  
  /* Maps an identifier used in mount points to the file system
   * implementation.
   */
  struct registry_entry {
  	int type;
  	const struct fs_file_system_t *fstp;
  };
  static struct registry_entry registry[CONFIG_FILE_SYSTEM_MAX_TYPES];
  
  static inline void registry_clear_entry(struct registry_entry *ep)
  {
  	ep->fstp = NULL;
  }
  
  static int registry_add(int type,
  			const struct fs_file_system_t *fstp)
  {
  	int rv = -ENOSPC;
  
  	for (size_t i = 0; i < ARRAY_SIZE(registry); ++i) {
  		struct registry_entry *ep = &registry[i];
  
  		if (ep->fstp == NULL) {
  			ep->type = type;
  			ep->fstp = fstp;
  			rv = 0;
  			break;
  		}
  	}
  
  	return rv;
  }
  
  static struct registry_entry *registry_find(int type)
  {
  	for (size_t i = 0; i < ARRAY_SIZE(registry); ++i) {
  		struct registry_entry *ep = &registry[i];
  
  		if ((ep->fstp != NULL) && (ep->type == type)) {
  			return ep;
  		}
  	}
  	return NULL;
  }
  
  static const struct fs_file_system_t *fs_type_get(int type)
  {
  	struct registry_entry *ep = registry_find(type);
  
  	return (ep != NULL) ? ep->fstp : NULL;
  }
  
  static int fs_get_mnt_point(struct fs_mount_t **mnt_pntp,
  			    const char *name, size_t *match_len)
  {
  	struct fs_mount_t *mnt_p = NULL, *itr;
  	size_t longest_match = 0;
  	size_t len, name_len = strlen(name);
  	sys_dnode_t *node;
  
  	k_mutex_lock(&mutex, K_FOREVER);
  	SYS_DLIST_FOR_EACH_NODE(&fs_mnt_list, node) {
  		itr = CONTAINER_OF(node, struct fs_mount_t, node);
  		len = itr->mountp_len;
  
  		/*
  		 * Move to next node if mount point length is
  		 * shorter than longest_match match or if path
  		 * name is shorter than the mount point name.
  		 */
  		if ((len < longest_match) || (len > name_len)) {
  			continue;
  		}
  
  		/*
  		 * Move to next node if name does not have a directory
  		 * separator where mount point name ends.
  		 */
  		if ((len > 1) && (name[len] != '/') && (name[len] != '\0')) {
  			continue;
  		}
  
  		/* Check for mount point match */
  		if (strncmp(name, itr->mnt_point, len) == 0) {
  			mnt_p = itr;
  			longest_match = len;
  		}
  	}
  	k_mutex_unlock(&mutex);
  
  	if (mnt_p == NULL) {
  		return -ENOENT;
  	}
  
  	*mnt_pntp = mnt_p;
  	if (match_len)
  		*match_len = mnt_p->mountp_len;
  
  	return 0;
  }
  
  /* File operations */
  int fs_open(struct fs_file_t *zfp, const char *file_name, fs_mode_t flags)
  {
  	struct fs_mount_t *mp;
  	int rc = -EINVAL;
  
  	/* COpy flags to zfp for use with other fs_ API calls */
  	zfp->flags = flags;
  
  	if ((file_name == NULL) ||
  			(strlen(file_name) <= 1) || (file_name[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	rc = fs_get_mnt_point(&mp, file_name, NULL);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	zfp->mp = mp;
  
  	if (zfp->mp->fs->open != NULL) {
  		rc = zfp->mp->fs->open(zfp, file_name, flags);
  		if (rc < 0) {
  			LOG_ERR("file open error (%d)", rc);
  			return rc;
  		}
  	}
  
  	return rc;
  }
  
  int fs_close(struct fs_file_t *zfp)
  {
  	int rc = -EINVAL;
  
  	if (zfp->mp == NULL) {
  		return 0;
  	}
  
  	if (zfp->mp->fs->close != NULL) {
  		rc = zfp->mp->fs->close(zfp);
  		if (rc < 0) {
  			LOG_ERR("file close error (%d)", rc);
  			return rc;
  		}
  	}
  
  	zfp->mp = NULL;
  
  	return rc;
  }
  
  ssize_t fs_read(struct fs_file_t *zfp, void *ptr, size_t size)
  {
  	int rc = -EINVAL;
  
  	if (zfp->mp == NULL) {
  		return -EBADF;
  	}
  
  	if (zfp->mp->fs->read != NULL) {
  		rc = zfp->mp->fs->read(zfp, ptr, size);
  		if (rc < 0) {
  			LOG_ERR("file read error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  ssize_t fs_write(struct fs_file_t *zfp, const void *ptr, size_t size)
  {
  	int rc = -EINVAL;
  
  	if (zfp->mp == NULL) {
  		return -EBADF;
  	}
  
  	if (zfp->mp->fs->write != NULL) {
  		rc = zfp->mp->fs->write(zfp, ptr, size);
  		if (rc < 0) {
  			LOG_ERR("file write error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_seek(struct fs_file_t *zfp, off_t offset, int whence)
  {
  	int rc = -ENOTSUP;
  
  	if (zfp->mp == NULL) {
  		return -EBADF;
  	}
  
  	if (zfp->mp->fs->lseek != NULL) {
  		rc = zfp->mp->fs->lseek(zfp, offset, whence);
  		if (rc < 0) {
  			LOG_ERR("file seek error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  off_t fs_tell(struct fs_file_t *zfp)
  {
  	int rc = -ENOTSUP;
  
  	if (zfp->mp == NULL) {
  		return -EBADF;
  	}
  
  	if (zfp->mp->fs->tell != NULL) {
  		rc = zfp->mp->fs->tell(zfp);
  		if (rc < 0) {
  			LOG_ERR("file tell error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_truncate(struct fs_file_t *zfp, off_t length)
  {
  	int rc = -EINVAL;
  
  	if (zfp->mp == NULL) {
  		return -EBADF;
  	}
  
  	if (zfp->mp->fs->truncate != NULL) {
  		rc = zfp->mp->fs->truncate(zfp, length);
  		if (rc < 0) {
  			LOG_ERR("file truncate error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_sync(struct fs_file_t *zfp)
  {
  	int rc = -EINVAL;
  
  	if (zfp->mp == NULL) {
  		return -EBADF;
  	}
  
  	if (zfp->mp->fs->sync != NULL) {
  		rc = zfp->mp->fs->sync(zfp);
  		if (rc < 0) {
  			LOG_ERR("file sync error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  /* Directory operations */
  int fs_opendir(struct fs_dir_t *zdp, const char *abs_path)
  {
  	struct fs_mount_t *mp;
  	int rc = -EINVAL;
  
  	if ((abs_path == NULL) ||
  			(strlen(abs_path) < 1) || (abs_path[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	if (strcmp(abs_path, "/") == 0) {
  		/* Open VFS root dir, marked by zdp->mp == NULL */
  		k_mutex_lock(&mutex, K_FOREVER);
  
  		zdp->mp = NULL;
  		zdp->dirp = sys_dlist_peek_head(&fs_mnt_list);
  
  		k_mutex_unlock(&mutex);
  
  		return 0;
  	}
  
  	rc = fs_get_mnt_point(&mp, abs_path, NULL);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	zdp->mp = mp;
  
  	if (zdp->mp->fs->opendir != NULL) {
  		rc = zdp->mp->fs->opendir(zdp, abs_path);
  		if (rc < 0) {
  			LOG_ERR("directory open error (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_readdir(struct fs_dir_t *zdp, struct fs_dirent *entry)
  {
  	if (zdp->mp) {
  		/* Delegate to mounted filesystem */
  		int rc = -EINVAL;
  
  		if (zdp->mp->fs->readdir != NULL) {
  			/* Loop until error or not special directory */
  			while (true) {
  				rc = zdp->mp->fs->readdir(zdp, entry);
  				if (rc < 0) {
  					break;
  				}
  				if (entry->name[0] == 0) {
  					break;
  				}
  				if (entry->type != FS_DIR_ENTRY_DIR) {
  					break;
  				}
  				if ((strcmp(entry->name, ".") != 0)
  				    && (strcmp(entry->name, "..") != 0)) {
  					break;
  				}
  			}
  			if (rc < 0) {
  				LOG_ERR("directory read error (%d)", rc);
  			}
  		}
  
  		return rc;
  	}
  
  	/* VFS root dir */
  	if (zdp->dirp == NULL) {
  		/* No more entries */
  		entry->name[0] = 0;
  		return 0;
  	}
  
  	/* Find the current and next entries in the mount point dlist */
  	sys_dnode_t *node, *next = NULL;
  	bool found = false;
  
  	k_mutex_lock(&mutex, K_FOREVER);
  
  	SYS_DLIST_FOR_EACH_NODE(&fs_mnt_list, node) {
  		if (node == zdp->dirp) {
  			found = true;
  
  			/* Pull info from current entry */
  			struct fs_mount_t *mnt;
  
  			mnt = CONTAINER_OF(node, struct fs_mount_t, node);
  
  			entry->type = FS_DIR_ENTRY_DIR;
  			strncpy(entry->name, mnt->mnt_point + 1,
  				sizeof(entry->name) - 1);
  			entry->name[sizeof(entry->name) - 1] = 0;
  			entry->size = 0;
  
  			/* Save pointer to the next one, for later */
  			next = sys_dlist_peek_next(&fs_mnt_list, node);
  			break;
  		}
  	}
  
  	k_mutex_unlock(&mutex);
  
  	if (!found) {
  		/* Current entry must have been removed before this
  		 * call to readdir -- return an error
  		 */
  		return -ENOENT;
  	}
  
  	zdp->dirp = next;
  	return 0;
  }
  
  int fs_closedir(struct fs_dir_t *zdp)
  {
  	int rc = -EINVAL;
  
  	if (zdp->mp == NULL) {
  		/* VFS root dir */
  		zdp->dirp = NULL;
  		return 0;
  	}
  
  	if (zdp->mp->fs->closedir != NULL) {
  		rc = zdp->mp->fs->closedir(zdp);
  		if (rc < 0) {
  			LOG_ERR("directory close error (%d)", rc);
  			return rc;
  		}
  	}
  
  	zdp->mp = NULL;
  	return rc;
  }
  
  /* Filesystem operations */
  int fs_mkdir(const char *abs_path)
  {
  	struct fs_mount_t *mp;
  	int rc = -EINVAL;
  
  	if ((abs_path == NULL) ||
  			(strlen(abs_path) <= 1) || (abs_path[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	rc = fs_get_mnt_point(&mp, abs_path, NULL);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	if (mp->fs->mkdir != NULL) {
  		rc = mp->fs->mkdir(mp, abs_path);
  		if (rc < 0) {
  			LOG_ERR("failed to create directory (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_unlink(const char *abs_path)
  {
  	struct fs_mount_t *mp;
  	int rc = -EINVAL;
  
  	if ((abs_path == NULL) ||
  			(strlen(abs_path) <= 1) || (abs_path[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	rc = fs_get_mnt_point(&mp, abs_path, NULL);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	if (mp->fs->unlink != NULL) {
  		rc = mp->fs->unlink(mp, abs_path);
  		if (rc < 0) {
  			LOG_ERR("failed to unlink path (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_rename(const char *from, const char *to)
  {
  	struct fs_mount_t *mp;
  	size_t match_len;
  	int rc = -EINVAL;
  
  	if ((from == NULL) || (strlen(from) <= 1) || (from[0] != '/') ||
  			(to == NULL) || (strlen(to) <= 1) || (to[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	rc = fs_get_mnt_point(&mp, from, &match_len);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	/* Make sure both files are mounted on the same path */
  	if (strncmp(from, to, match_len) != 0) {
  		LOG_ERR("mount point not same!!");
  		return -EINVAL;
  	}
  
  	if (mp->fs->rename != NULL) {
  		rc = mp->fs->rename(mp, from, to);
  		if (rc < 0) {
  			LOG_ERR("failed to rename file or dir (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_stat(const char *abs_path, struct fs_dirent *entry)
  {
  	struct fs_mount_t *mp;
  	int rc = -EINVAL;
  
  	if ((abs_path == NULL) ||
  			(strlen(abs_path) <= 1) || (abs_path[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	rc = fs_get_mnt_point(&mp, abs_path, NULL);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	if (mp->fs->stat != NULL) {
  		rc = mp->fs->stat(mp, abs_path, entry);
  		if (rc < 0) {
  			LOG_ERR("failed get file or dir stat (%d)", rc);
  		}
  	}
  	return rc;
  }
  
  int fs_statvfs(const char *abs_path, struct fs_statvfs *stat)
  {
  	struct fs_mount_t *mp;
  	int rc;
  
  	if ((abs_path == NULL) ||
  			(strlen(abs_path) <= 1) || (abs_path[0] != '/')) {
  		LOG_ERR("invalid file name!!");
  		return -EINVAL;
  	}
  
  	rc = fs_get_mnt_point(&mp, abs_path, NULL);
  	if (rc < 0) {
  		LOG_ERR("%s:mount point not found!!", __func__);
  		return rc;
  	}
  
  	if (mp->fs->statvfs != NULL) {
  		rc = mp->fs->statvfs(mp, abs_path, stat);
  		if (rc < 0) {
  			LOG_ERR("failed get file or dir stat (%d)", rc);
  		}
  	}
  
  	return rc;
  }
  
  int fs_mount(struct fs_mount_t *mp)
  {
  	struct fs_mount_t *itr;
  	const struct fs_file_system_t *fs;
  	sys_dnode_t *node;
  	int rc = -EINVAL;
  
  	if ((mp == NULL) || (mp->mnt_point == NULL)) {
  		LOG_ERR("mount point not initialized!!");
  		return -EINVAL;
  	}
  
  	k_mutex_lock(&mutex, K_FOREVER);
  
  	/* Get file system information */
  	fs = fs_type_get(mp->type);
  	if (fs == NULL) {
  		LOG_ERR("requested file system type not registered!!");
  		rc = -ENOENT;
  		goto mount_err;
  	}
  
  	mp->mountp_len = strlen(mp->mnt_point);
  
  	if ((mp->mnt_point[0] != '/') ||
  			(strlen(mp->mnt_point) <= 1)) {
  		LOG_ERR("invalid mount point!!");
  		rc = -EINVAL;
  		goto mount_err;
  	}
  
  	if (fs->mount == NULL) {
  		LOG_ERR("fs ops functions not set!!");
  		rc = -EINVAL;
  		goto mount_err;
  	}
  
  	/* Check if mount point already exists */
  	SYS_DLIST_FOR_EACH_NODE(&fs_mnt_list, node) {
  		itr = CONTAINER_OF(node, struct fs_mount_t, node);
  		/* continue if length does not match */
  		if (mp->mountp_len != itr->mountp_len) {
  			continue;
  		}
  
  		if (strncmp(mp->mnt_point, itr->mnt_point,
  					mp->mountp_len) == 0) {
  			LOG_ERR("mount Point already exists!!");
  			rc = -EBUSY;
  			goto mount_err;
  		}
  	}
  
  
  	rc = fs->mount(mp);
  	if (rc < 0) {
  		LOG_ERR("fs mount error (%d)", rc);
  		goto mount_err;
  	}
  
  	/* set mount point fs interface */
  	mp->fs = fs;
  
  	/*  append to the mount list */
  	sys_dlist_append(&fs_mnt_list, &mp->node);
  	LOG_DBG("fs mounted at %s", log_strdup(mp->mnt_point));
  
  mount_err:
  	k_mutex_unlock(&mutex);
  	return rc;
  }
  
  
  int fs_unmount(struct fs_mount_t *mp)
  {
  	int rc = -EINVAL;
  
  	if ((mp == NULL) || (mp->mnt_point == NULL) ||
  				(strlen(mp->mnt_point) <= 1)) {
  		LOG_ERR("invalid mount point!!");
  		return -EINVAL;
  	}
  
  	k_mutex_lock(&mutex, K_FOREVER);
  	if ((mp->fs == NULL) || mp->fs->unmount == NULL) {
  		LOG_ERR("fs ops functions not set!!");
  		rc = -EINVAL;
  		goto unmount_err;
  	}
  
  	rc = mp->fs->unmount(mp);
  	if (rc < 0) {
  		LOG_ERR("fs unmount error (%d)", rc);
  		goto unmount_err;
  	}
  
  	/* clear file system interface */
  	mp->fs = NULL;
  
  	/* remove mount node from the list */
  	sys_dlist_remove(&mp->node);
  	LOG_DBG("fs unmounted from %s", log_strdup(mp->mnt_point));
  
  unmount_err:
  	k_mutex_unlock(&mutex);
  	return rc;
  }
  
  int fs_readmount(int *index, const char **name)
  {
  	sys_dnode_t *node;
  	int rc = -ENOENT;
  	int cnt = 0;
  	struct fs_mount_t *itr = NULL;
  
  	*name = NULL;
  
  	k_mutex_lock(&mutex, K_FOREVER);
  
  	SYS_DLIST_FOR_EACH_NODE(&fs_mnt_list, node) {
  		if (*index == cnt) {
  			itr = CONTAINER_OF(node, struct fs_mount_t, node);
  			break;
  		}
  
  		++cnt;
  	}
  
  	k_mutex_unlock(&mutex);
  
  	if (itr != NULL) {
  		rc = 0;
  		*name = itr->mnt_point;
  		++(*index);
  	}
  
  	return rc;
  
  }
  
  /* Register File system */
  int fs_register(int type, const struct fs_file_system_t *fs)
  {
  	int rc = 0;
  
  	k_mutex_lock(&mutex, K_FOREVER);
  
  	if (fs_type_get(type) != NULL) {
  		rc = -EALREADY;
  	} else {
  		rc = registry_add(type, fs);
  	}
  
  	k_mutex_unlock(&mutex);
  
  	LOG_DBG("fs register %d: %d", type, rc);
  
  	return rc;
  }
  
  /* Unregister File system */
  int fs_unregister(int type, const struct fs_file_system_t *fs)
  {
  	int rc = 0;
  	struct registry_entry *ep;
  
  	k_mutex_lock(&mutex, K_FOREVER);
  
  	ep = registry_find(type);
  	if ((ep == NULL) || (ep->fstp != fs)) {
  		rc = -EINVAL;
  	} else {
  		registry_clear_entry(ep);
  	}
  
  	k_mutex_unlock(&mutex);
  
  	LOG_DBG("fs unregister %d: %d", type, rc);
  	return rc;
  }
  
  static int fs_init(const struct device *dev)
  {
  	k_mutex_init(&mutex);
  	sys_dlist_init(&fs_mnt_list);
  	return 0;
  }
  
  SYS_INIT(fs_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
  