blob: 03a8a993adda7251b97525760e967b77febf2c0f [file] [log] [blame]
/*
* 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;
}