| /* |
| * 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. |
| */ |
| |
| #include <assert.h> |
| #include <string.h> |
| #include "nffs/nffs.h" |
| #include "nffs_priv.h" |
| |
| int |
| nffs_path_parse_next(struct nffs_path_parser *parser) |
| { |
| const char *slash_start; |
| int token_end; |
| int token_len; |
| |
| if (parser->npp_token_type == NFFS_PATH_TOKEN_LEAF) { |
| return FS_EINVAL; |
| } |
| |
| slash_start = strchr(parser->npp_path + parser->npp_off, '/'); |
| if (slash_start == NULL) { |
| if (parser->npp_token_type == NFFS_PATH_TOKEN_NONE) { |
| return FS_EINVAL; |
| } |
| parser->npp_token_type = NFFS_PATH_TOKEN_LEAF; |
| token_len = strlen(parser->npp_path + parser->npp_off); |
| } else { |
| parser->npp_token_type = NFFS_PATH_TOKEN_BRANCH; |
| token_end = slash_start - parser->npp_path; |
| token_len = token_end - parser->npp_off; |
| } |
| |
| if (token_len > NFFS_FILENAME_MAX_LEN) { |
| return FS_EINVAL; |
| } |
| |
| parser->npp_token = parser->npp_path + parser->npp_off; |
| parser->npp_token_len = token_len; |
| parser->npp_off += token_len + 1; |
| |
| return 0; |
| } |
| |
| void |
| nffs_path_parser_new(struct nffs_path_parser *parser, const char *path) |
| { |
| parser->npp_token_type = NFFS_PATH_TOKEN_NONE; |
| parser->npp_path = path; |
| parser->npp_off = 0; |
| } |
| |
| static int |
| nffs_path_find_child(struct nffs_inode_entry *parent, |
| const char *name, int name_len, |
| struct nffs_inode_entry **out_inode_entry) |
| { |
| struct nffs_inode_entry *cur; |
| struct nffs_inode inode; |
| int cmp; |
| int rc; |
| |
| SLIST_FOREACH(cur, &parent->nie_child_list, nie_sibling_next) { |
| rc = nffs_inode_from_entry(&inode, cur); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = nffs_inode_filename_cmp_ram(&inode, name, name_len, &cmp); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (cmp == 0) { |
| *out_inode_entry = cur; |
| return 0; |
| } |
| if (cmp > 0) { |
| break; |
| } |
| } |
| |
| return FS_ENOENT; |
| } |
| |
| /* |
| * Return the inode and optionally it's parent associated with the input path |
| * nffs_path_parser struct used to track location in hierarchy |
| */ |
| int |
| nffs_path_find(struct nffs_path_parser *parser, |
| struct nffs_inode_entry **out_inode_entry, |
| struct nffs_inode_entry **out_parent) |
| { |
| struct nffs_inode_entry *parent; |
| struct nffs_inode_entry *inode_entry; |
| int rc; |
| |
| *out_inode_entry = NULL; |
| if (out_parent != NULL) { |
| *out_parent = NULL; |
| } |
| |
| inode_entry = NULL; |
| while (1) { |
| parent = inode_entry; |
| rc = nffs_path_parse_next(parser); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| switch (parser->npp_token_type) { |
| case NFFS_PATH_TOKEN_BRANCH: |
| if (parent == NULL) { |
| /* First directory must be root. */ |
| if (parser->npp_token_len != 0) { |
| return FS_ENOENT; |
| } |
| |
| inode_entry = nffs_root_dir; |
| } else { |
| /* Ignore empty intermediate directory names. */ |
| if (parser->npp_token_len == 0) { |
| break; |
| } |
| |
| rc = nffs_path_find_child(parent, parser->npp_token, |
| parser->npp_token_len, &inode_entry); |
| if (rc != 0) { |
| goto done; |
| } |
| } |
| break; |
| case NFFS_PATH_TOKEN_LEAF: |
| if (parent == NULL) { |
| /* First token must be root directory. */ |
| return FS_ENOENT; |
| } |
| |
| if (parser->npp_token_len == 0) { |
| /* If the path ends with a slash, the leaf is the parent, not |
| * the trailing empty token. |
| */ |
| inode_entry = parent; |
| rc = 0; |
| } else { |
| rc = nffs_path_find_child(parent, parser->npp_token, |
| parser->npp_token_len, &inode_entry); |
| } |
| goto done; |
| } |
| } |
| |
| done: |
| *out_inode_entry = inode_entry; |
| if (out_parent != NULL) { |
| *out_parent = parent; |
| } |
| return rc; |
| } |
| |
| int |
| nffs_path_find_inode_entry(const char *filename, |
| struct nffs_inode_entry **out_inode_entry) |
| { |
| struct nffs_path_parser parser; |
| int rc; |
| |
| nffs_path_parser_new(&parser, filename); |
| rc = nffs_path_find(&parser, out_inode_entry, NULL); |
| |
| return rc; |
| } |
| |
| /** |
| * Unlinks the file or directory at the specified path. If the path refers to |
| * a directory, all the directory's descendants are recursively unlinked. Any |
| * open file handles refering to an unlinked file remain valid, and can be |
| * read from and written to. |
| * |
| * @path The path of the file or directory to unlink. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| nffs_path_unlink(const char *path) |
| { |
| struct nffs_inode_entry *inode_entry; |
| struct nffs_inode inode; |
| int rc; |
| |
| rc = nffs_path_find_inode_entry(path, &inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = nffs_inode_from_entry(&inode, inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = nffs_inode_unlink(&inode); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Performs a rename and / or move of the specified source path to the |
| * specified destination. The source path can refer to either a file or a |
| * directory. All intermediate directories in the destination path must |
| * already have been created. If the source path refers to a file, the |
| * destination path must contain a full filename path (i.e., if performing a |
| * move, the destination path should end with the same filename in the source |
| * path). If an object already exists at the specified destination path, this |
| * function causes it to be unlinked prior to the rename (i.e., the destination |
| * gets clobbered). |
| * |
| * @param from The source path. |
| * @param to The destination path. |
| * |
| * @return 0 on success; |
| * nonzero on failure. |
| */ |
| int |
| nffs_path_rename(const char *from, const char *to) |
| { |
| struct nffs_path_parser parser; |
| struct nffs_inode_entry *from_inode_entry; |
| struct nffs_inode_entry *to_inode_entry; |
| struct nffs_inode_entry *from_parent; |
| struct nffs_inode_entry *to_parent; |
| struct nffs_inode inode; |
| int rc; |
| |
| nffs_path_parser_new(&parser, from); |
| rc = nffs_path_find(&parser, &from_inode_entry, &from_parent); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| nffs_path_parser_new(&parser, to); |
| rc = nffs_path_find(&parser, &to_inode_entry, &to_parent); |
| switch (rc) { |
| case 0: |
| /* The user is clobbering something with the rename. */ |
| if (nffs_hash_id_is_dir(from_inode_entry->nie_hash_entry.nhe_id) ^ |
| nffs_hash_id_is_dir(to_inode_entry->nie_hash_entry.nhe_id)) { |
| |
| /* Cannot clobber one type of file with another. */ |
| return FS_EINVAL; |
| } |
| |
| rc = nffs_inode_from_entry(&inode, to_inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| /* |
| * Don't allow renames if the inode has been deleted |
| * Side-effect is that we've restored the inode as needed. |
| */ |
| if (nffs_inode_is_deleted(from_inode_entry)) { |
| assert(0); |
| return FS_ENOENT; |
| } |
| |
| rc = nffs_inode_unlink(&inode); |
| if (rc != 0) { |
| return rc; |
| } |
| break; |
| |
| case FS_ENOENT: |
| assert(to_parent != NULL); |
| if (parser.npp_token_type != NFFS_PATH_TOKEN_LEAF) { |
| /* Intermediate directory doesn't exist. */ |
| return FS_EINVAL; |
| } |
| break; |
| |
| default: |
| return rc; |
| } |
| |
| rc = nffs_inode_rename(from_inode_entry, to_parent, parser.npp_token); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Creates a new directory at the specified path. |
| * |
| * @param path The path of the directory to create. |
| * |
| * @return 0 on success; |
| * FS_EEXIST if there is another file or |
| * directory at the specified path. |
| * FS_ENONT if a required intermediate directory |
| * does not exist. |
| */ |
| int |
| nffs_path_new_dir(const char *path, struct nffs_inode_entry **out_inode_entry) |
| { |
| struct nffs_path_parser parser; |
| struct nffs_inode_entry *inode_entry; |
| struct nffs_inode_entry *parent; |
| int rc; |
| |
| nffs_path_parser_new(&parser, path); |
| rc = nffs_path_find(&parser, &inode_entry, &parent); |
| if (rc == 0) { |
| return FS_EEXIST; |
| } |
| if (rc != FS_ENOENT) { |
| return rc; |
| } |
| if (parser.npp_token_type != NFFS_PATH_TOKEN_LEAF || parent == NULL) { |
| return FS_ENOENT; |
| } |
| |
| rc = nffs_file_new(parent, parser.npp_token, parser.npp_token_len, 1, |
| &inode_entry); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| if (out_inode_entry != NULL) { |
| *out_inode_entry = inode_entry; |
| } |
| |
| return 0; |
| } |