// SPDX-License-Identifier: GPL-2.0-only
/* * This file is part of UBIFS.
 *
 * Copyright (C) 2006-2008 Nokia Corporation.
 * Copyright (C) 2006, 2007 University of Szeged, Hungary
 *
 * Authors: Artem Bityutskiy (Битюцкий Артём)
 *          Adrian Hunter
 *          Zoltan Sogor
 */

/*
 * This file implements directory operations.
 *
 * All FS operations in this file allocate budget before writing anything to the
 * media. If they fail to allocate it, the error is returned. The only
 * exceptions are 'ubifs_unlink()' and 'ubifs_rmdir()' which keep working even
 * if they unable to allocate the budget, because deletion %-ENOSPC failure is
 * not what users are usually ready to get. UBIFS budgeting subsystem has some
 * space reserved for these purposes.
 *
 * All operations in this file write all inodes which they change straight
 * away, instead of marking them dirty. For example, 'ubifs_link()' changes
 * @i_size of the parent inode and writes the parent inode together with the
 * target inode. This was done to simplify file-system recovery which would
 * otherwise be very difficult to do. The only exception is rename which marks
 * the re-named inode dirty (because its @i_ctime is updated) but does not
 * write it, but just marks it as dirty.
 */

#include <sys/stat.h>

#include "linux_err.h"
#include "bitops.h"
#include "kmem.h"
#include "ubifs.h"
#include "defs.h"
#include "debug.h"
#include "key.h"
#include "misc.h"

/**
 * inherit_flags - inherit flags of the parent inode.
 * @c: UBIFS file-system description object
 * @dir: parent inode
 * @mode: new inode mode flags
 *
 * This is a helper function for 'ubifs_new_inode()' which inherits flag of the
 * parent directory inode @dir. UBIFS inodes inherit the following flags:
 * o %UBIFS_COMPR_FL, which is useful to switch compression on/of on
 *   sub-directory basis;
 * o %UBIFS_SYNC_FL - useful for the same reasons;
 * o %UBIFS_DIRSYNC_FL - similar, but relevant only to directories.
 *
 * This function returns the inherited flags.
 */
static int inherit_flags(struct ubifs_info *c, const struct inode *dir,
			 unsigned int mode)
{
	int flags;
	const struct ubifs_inode *ui = ubifs_inode(dir);

	ubifs_assert(c, S_ISDIR(dir->mode));

	flags = ui->flags & (UBIFS_COMPR_FL | UBIFS_SYNC_FL | UBIFS_DIRSYNC_FL);
	if (!S_ISDIR(mode))
		/* The "DIRSYNC" flag only applies to directories */
		flags &= ~UBIFS_DIRSYNC_FL;
	return flags;
}

/**
 * ubifs_new_inode - allocate new UBIFS inode object.
 * @c: UBIFS file-system description object
 * @dir: parent inode
 * @mode: inode mode flags
 *
 * This function finds an unused inode number, allocates new ubifs inode and
 * initializes it. Returns new ubifs inode in case of success and an error code
 * in case of failure.
 */
static struct ubifs_inode *ubifs_new_inode(struct ubifs_info *c,
					   const struct inode *dir,
					   unsigned int mode)
{
	int err;
	time_t now = time(NULL);
	struct ubifs_inode *ui;
	struct inode *inode;

	ui = kzalloc(sizeof(struct ubifs_inode), GFP_KERNEL);
	if (!ui)
		return ERR_PTR(-ENOMEM);

	inode = &ui->vfs_inode;
	inode->atime_sec = inode->ctime_sec = inode->mtime_sec = now;
	inode->nlink = 1;
	inode->mode = mode;
	if (dir) {
		/* Create non root dir. */
		inode->uid = dir->uid;
		inode->gid = dir->gid;
		if ((dir->mode & S_ISGID) && S_ISDIR(mode))
			inode->mode |= S_ISGID;
		ui->flags = inherit_flags(c, dir, mode);
	}
	if (S_ISDIR(mode))
		ui->ui_size = UBIFS_INO_NODE_SZ;
	if (S_ISREG(mode))
		ui->compr_type = c->default_compr;
	else
		ui->compr_type = UBIFS_COMPR_NONE;

	if (dir) {
		spin_lock(&c->cnt_lock);
		/* Inode number overflow is currently not supported */
		if (c->highest_inum >= INUM_WARN_WATERMARK) {
			if (c->highest_inum >= INUM_WATERMARK) {
				spin_unlock(&c->cnt_lock);
				ubifs_err(c, "out of inode numbers");
				err = -EINVAL;
				goto out;
			}
			ubifs_warn(c, "running out of inode numbers (current %lu, max %u)",
				   (unsigned long)c->highest_inum, INUM_WATERMARK);
		}
		inode->inum = ++c->highest_inum;
	} else {
		/* Create root dir. */
		inode->inum = UBIFS_ROOT_INO;
	}
	/*
	 * The creation sequence number remains with this inode for its
	 * lifetime. All nodes for this inode have a greater sequence number,
	 * and so it is possible to distinguish obsolete nodes belonging to a
	 * previous incarnation of the same inode number - for example, for the
	 * purpose of rebuilding the index.
	 */
	ui->creat_sqnum = ++c->max_sqnum;
	spin_unlock(&c->cnt_lock);

	return ui;

out:
	kfree(ui);
	return ERR_PTR(err);
}

/**
 * ubifs_lookup_by_inum - look up the UBIFS inode according to inode number.
 * @c: UBIFS file-system description object
 * @inum: inode number
 *
 * This function looks up the UBIFS inode according to a given inode number.
 * Returns zero in case of success and an error code in case of failure.
 */
struct ubifs_inode *ubifs_lookup_by_inum(struct ubifs_info *c, ino_t inum)
{
	int err;
	union ubifs_key key;
	struct inode *inode;
	struct ubifs_inode *ui;
	struct ubifs_ino_node *ino = NULL;

	ino = kmalloc(UBIFS_MAX_INO_NODE_SZ, GFP_NOFS);
	if (!ino)
		return ERR_PTR(-ENOMEM);

	ui = kzalloc(sizeof(struct ubifs_inode), GFP_KERNEL);
	if (!ui) {
		err = -ENOMEM;
		goto out;
	}

	inode = &ui->vfs_inode;
	ino_key_init(c, &key, inum);
	err = ubifs_tnc_lookup(c, &key, ino);
	if (err) {
		kfree(ui);
		ubifs_assert(c, !get_failure_reason_callback(c));
		goto out;
	}

	inode = &ui->vfs_inode;
	inode->inum = inum;
	inode->uid = le32_to_cpu(ino->uid);
	inode->gid = le32_to_cpu(ino->gid);
	inode->mode = le32_to_cpu(ino->mode);
	inode->nlink = le32_to_cpu(ino->nlink);
	inode->atime_sec = le64_to_cpu(ino->atime_sec);
	inode->ctime_sec = le64_to_cpu(ino->ctime_sec);
	inode->mtime_sec = le64_to_cpu(ino->mtime_sec);
	inode->atime_nsec = le32_to_cpu(ino->atime_nsec);
	inode->ctime_nsec = le32_to_cpu(ino->ctime_nsec);
	inode->mtime_nsec = le32_to_cpu(ino->mtime_nsec);
	ui->creat_sqnum = le64_to_cpu(ino->creat_sqnum);
	ui->xattr_size = le32_to_cpu(ino->xattr_size);
	ui->xattr_cnt = le32_to_cpu(ino->xattr_cnt);
	ui->xattr_names = le32_to_cpu(ino->xattr_names);
	ui->compr_type = le16_to_cpu(ino->compr_type);
	ui->ui_size = le64_to_cpu(ino->size);
	ui->flags = le32_to_cpu(ino->flags);
	ui->data_len = le32_to_cpu(ino->data_len);

out:
	kfree(ino);
	return err ? ERR_PTR(err) : ui;
}

struct ubifs_inode *ubifs_lookup(struct ubifs_info *c,
				 struct ubifs_inode *dir_ui,
				 const struct fscrypt_name *nm)
{
	int err;
	ino_t inum;
	union ubifs_key key;
	struct ubifs_dent_node *dent;

	if (fname_len(nm) > UBIFS_MAX_NLEN)
		return ERR_PTR(-ENAMETOOLONG);

	dent = kmalloc(UBIFS_MAX_DENT_NODE_SZ, GFP_NOFS);
	if (!dent)
		return ERR_PTR(-ENOMEM);

	dent_key_init(c, &key, dir_ui->vfs_inode.inum, nm);
	err = ubifs_tnc_lookup_nm(c, &key, dent, nm);
	if (err) {
		kfree(dent);
		ubifs_assert(c, !get_failure_reason_callback(c));
		return ERR_PTR(err);
	}
	inum = le64_to_cpu(dent->inum);
	kfree(dent);

	return ubifs_lookup_by_inum(c, inum);
}

int ubifs_mkdir(struct ubifs_info *c, struct ubifs_inode *dir_ui,
		const struct fscrypt_name *nm, unsigned int mode)
{
	struct ubifs_inode *ui;
	struct inode *inode, *dir = &dir_ui->vfs_inode;
	int err, sz_change;
	struct ubifs_budget_req req = { .new_ino = 1, .new_dent = 1,
					.dirtied_ino = 1};
	/*
	 * Budget request settings: new inode, new direntry and changing parent
	 * directory inode.
	 */
	dbg_gen("dent '%s', mode %#hx in dir ino %lu",
		fname_name(nm), mode, dir->inum);

	/* New dir is not allowed to be created under an encrypted directory. */
	ubifs_assert(c, !(dir_ui->flags & UBIFS_CRYPT_FL));

	err = ubifs_budget_space(c, &req);
	if (err)
		return err;

	sz_change = CALC_DENT_SIZE(fname_len(nm));

	ui = ubifs_new_inode(c, dir, S_IFDIR | mode);
	if (IS_ERR(ui)) {
		err = PTR_ERR(ui);
		goto out_budg;
	}

	inode = &ui->vfs_inode;
	inode->nlink++;
	dir->nlink++;
	dir_ui->ui_size += sz_change;
	dir->ctime_sec = dir->mtime_sec = inode->ctime_sec;
	err = ubifs_jnl_update_file(c, dir_ui, nm, ui);
	if (err) {
		ubifs_err(c, "cannot create directory, error %d", err);
		goto out_cancel;
	}

	kfree(ui);
	ubifs_release_budget(c, &req);
	return 0;

out_cancel:
	dir_ui->ui_size -= sz_change;
	dir->nlink--;
	kfree(ui);
out_budg:
	ubifs_release_budget(c, &req);
	return err;
}

/**
 * ubifs_link_recovery - link a disconnected file into the target directory.
 * @c: UBIFS file-system description object
 * @dir_ui: target directory
 * @ui: the UBIFS inode of disconnected file
 * @nm: directory entry name
 *
 * This function links the inode of disconnected file to a directory entry name
 * under the target directory. Returns zero in case of success and an error code
 * in case of failure.
 */
int ubifs_link_recovery(struct ubifs_info *c, struct ubifs_inode *dir_ui,
			struct ubifs_inode *ui, const struct fscrypt_name *nm)
{
	struct inode *inode = &ui->vfs_inode, *dir = &dir_ui->vfs_inode;
	int err, sz_change;
	struct ubifs_budget_req req = { .new_dent = 1, .dirtied_ino = 2,
				.dirtied_ino_d = ALIGN(ui->data_len, 8) };
	time_t now = time(NULL);

	/*
	 * Budget request settings: new direntry, changing the target inode,
	 * changing the parent inode.
	 */
	dbg_gen("dent '%s' to ino %lu (nlink %d) in dir ino %lu",
		fname_name(nm), inode->inum, inode->nlink, dir->inum);

	/* New dir is not allowed to be created under an encrypted directory. */
	ubifs_assert(c, !(dir_ui->flags & UBIFS_CRYPT_FL));

	sz_change = CALC_DENT_SIZE(fname_len(nm));

	err = ubifs_budget_space(c, &req);
	if (err)
		return err;

	inode->ctime_sec = now;
	dir_ui->ui_size += sz_change;
	dir->ctime_sec = dir->mtime_sec = now;
	err = ubifs_jnl_update_file(c, dir_ui, nm, ui);
	if (err)
		goto out_cancel;

	ubifs_release_budget(c, &req);
	return 0;

out_cancel:
	dir_ui->ui_size -= sz_change;
	ubifs_release_budget(c, &req);
	return err;
}

/**
 * ubifs_create_root - create the root inode.
 * @c: UBIFS file-system description object
 *
 * This function creates a new inode for the root directory. Returns zero in
 * case of success and an error code in case of failure.
 */
int ubifs_create_root(struct ubifs_info *c)
{
	int err;
	struct inode *inode;
	struct ubifs_budget_req req = { .new_ino = 1 };
	struct ubifs_inode *ui;

	/* Budget request settings: new inode. */
	dbg_gen("create root dir");

	err = ubifs_budget_space(c, &req);
	if (err)
		return err;

	ui = ubifs_new_inode(c, NULL, S_IFDIR | S_IRUGO | S_IWUSR | S_IXUGO);
	if (IS_ERR(ui)) {
		err = PTR_ERR(ui);
		goto out_budg;
	}

	inode = &ui->vfs_inode;
	inode->nlink = 2;
	ui->ui_size = UBIFS_INO_NODE_SZ;
	ui->flags = UBIFS_COMPR_FL;
	err = ubifs_jnl_update_file(c, NULL, NULL, ui);
	if (err)
		goto out_ui;

	kfree(ui);
	ubifs_release_budget(c, &req);
	return 0;

out_ui:
	kfree(ui);
out_budg:
	ubifs_release_budget(c, &req);
	ubifs_err(c, "cannot create root dir, error %d", err);
	return err;
}