// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2003-2005 Silicon Graphics, Inc.
 * All Rights Reserved.
 */

#include "command.h"
#include "input.h"
#include "init.h"
#include "io.h"
#include "libfrog/file_attr.h"

static cmdinfo_t chattr_cmd;
static cmdinfo_t lsattr_cmd;
static unsigned int orflags;
static unsigned int andflags;
unsigned int recurse_all;
unsigned int recurse_dir;

static struct xflags {
	uint	flag;
	char	*shortname;
	char	*longname;
} xflags[] = {
	{ FS_XFLAG_REALTIME,		"r", "realtime"		},
	{ FS_XFLAG_PREALLOC,		"p", "prealloc"		},
	{ FS_XFLAG_IMMUTABLE,		"i", "immutable"	},
	{ FS_XFLAG_APPEND,		"a", "append-only"	},
	{ FS_XFLAG_SYNC,		"s", "sync"		},
	{ FS_XFLAG_NOATIME,		"A", "no-atime"		},
	{ FS_XFLAG_NODUMP,		"d", "no-dump"		},
	{ FS_XFLAG_RTINHERIT,		"t", "rt-inherit"	},
	{ FS_XFLAG_PROJINHERIT,		"P", "proj-inherit"	},
	{ FS_XFLAG_NOSYMLINKS,		"n", "nosymlinks"	},
	{ FS_XFLAG_EXTSIZE,		"e", "extsize"		},
	{ FS_XFLAG_EXTSZINHERIT,	"E", "extsz-inherit"	},
	{ FS_XFLAG_NODEFRAG,		"f", "no-defrag"	},
	{ FS_XFLAG_FILESTREAM,		"S", "filestream"	},
	{ FS_XFLAG_DAX,			"x", "dax"		},
	{ FS_XFLAG_COWEXTSIZE,		"C", "cowextsize"	},
	{ FS_XFLAG_HASATTR,		"X", "has-xattr"	},
	{ 0, NULL, NULL }
};
#define CHATTR_XFLAG_LIST	"r"/*p*/"iasAdtPneEfSxC"/*X*/

static void
lsattr_help(void)
{
	printf(_(
"\n"
" displays the set of extended inode flags associated with the current file\n"
"\n"
" Each individual flag is displayed as a single character, in this order:\n"
" r -- file data is stored in the realtime section\n"
" p -- file has preallocated extents (cannot be changed using chattr)\n"
" i -- immutable, file cannot be modified\n"
" a -- append-only, file can only be appended to\n"
" s -- all updates are synchronous\n"
" A -- the access time is not updated for this inode\n"
" d -- do not include this file in a dump of the filesystem\n"
" t -- child created in this directory has realtime bit set by default\n"
" P -- child created in this directory has parents project ID by default\n"
" n -- symbolic links cannot be created in this directory\n"
" e -- for non-realtime files, observe the inode extent size value\n"
" E -- children created in this directory inherit the extent size value\n"
" f -- do not include this file when defragmenting the filesystem\n"
" S -- enable filestreams allocator for this directory\n"
" x -- Use direct access (DAX) for data in this file\n"
" C -- for files with shared blocks, observe the inode CoW extent size value\n"
" X -- file has extended attributes (cannot be changed using chattr)\n"
"\n"
" Options:\n"
" -R -- recursively descend (useful when current file is a directory)\n"
" -D -- recursively descend, but only list attributes on directories\n"
" -a -- show all flags which can be set alongside those which are set\n"
" -v -- verbose mode; show long names of flags, not single characters\n"
"\n"));
}

static void
chattr_help(void)
{
	printf(_(
"\n"
" modifies the set of extended inode flags associated with the current file\n"
"\n"
" Examples:\n"
" 'chattr +a' - sets the append-only flag\n"
" 'chattr -a' - clears the append-only flag\n"
"\n"
" -R -- recursively descend (useful when current file is a directory)\n"
" -D -- recursively descend, only modifying attributes on directories\n"
" +/-r -- set/clear the realtime flag\n"
" +/-i -- set/clear the immutable flag\n"
" +/-a -- set/clear the append-only flag\n"
" +/-s -- set/clear the sync flag\n"
" +/-A -- set/clear the no-atime flag\n"
" +/-d -- set/clear the no-dump flag\n"
" +/-t -- set/clear the realtime inheritance flag\n"
" +/-P -- set/clear the project ID inheritance flag\n"
" +/-n -- set/clear the no-symbolic-links flag\n"
" +/-e -- set/clear the extent-size flag\n"
" +/-E -- set/clear the extent-size inheritance flag\n"
" +/-f -- set/clear the no-defrag flag\n"
" +/-S -- set/clear the filestreams allocator flag\n"
" +/-x -- set/clear the direct access (DAX) flag\n"
" +/-C -- set/clear the CoW extent-size flag\n"
" Note1: user must have certain capabilities to modify immutable/append-only.\n"
" Note2: immutable/append-only files cannot be deleted; removing these files\n"
"        requires the immutable/append-only flag to be cleared first.\n"
" Note3: the realtime flag can only be set if the filesystem has a realtime\n"
"        section, and the (regular) file must be empty when the flag is set.\n"
"\n"));
}

void
print_xflags(
	uint		flags,
	int		verbose,
	int		dofname,
	const char	*fname,
	int		dobraces,
	int		doeol)
{
	struct xflags	*p;
	int		first = 1;

	if (dobraces)
		fputs("[", stdout);
	for (p = xflags; p->flag; p++) {
		if (flags & p->flag) {
			if (verbose) {
				if (first)
					first = 0;
				else
					fputs(", ", stdout);
				fputs(p->longname, stdout);
			} else {
				fputs(p->shortname, stdout);
			}
		} else if (!verbose) {
			fputs("-", stdout);
		}
	}
	if (dobraces)
		fputs("]", stdout);
	if (dofname)
		printf(" %s ", fname);
	if (doeol)
		fputs("\n", stdout);
}

static int
lsattr_callback(
	const char		*path,
	const struct stat	*stat,
	int			status,
	struct FTW		*data)
{
	struct file_attr	fa;
	int			error;

	if (recurse_dir && !S_ISDIR(stat->st_mode))
		return 0;

	error = xfrog_file_getattr(AT_FDCWD, path, stat, &fa,
				   AT_SYMLINK_NOFOLLOW);
	if (error) {
		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
			progname, path, strerror(errno));
		exitcode = 1;
		return 0;
	}

	print_xflags(fa.fa_xflags, 0, 1, path, 0, 1);

	return 0;
}

static int
lsattr_f(
	int			argc,
	char			**argv)
{
	struct file_attr	fa;
	char			*name = file->name;
	int			c, aflag = 0, vflag = 0;
	struct stat		st;
	int			error;

	recurse_all = recurse_dir = 0;
	while ((c = getopt(argc, argv, "DRav")) != EOF) {
		switch (c) {
		case 'D':
			recurse_dir = 1;
			break;
		case 'R':
			recurse_all = 1;
			break;
		case 'a':
			aflag = 1;
			break;
		case 'v':
			vflag = 1;
			break;
		default:
			exitcode = 1;
			return command_usage(&lsattr_cmd);
		}
	}

	if (recurse_all || recurse_dir) {
		nftw(name, lsattr_callback,
			100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH);
		return 0;
	}

	error = stat(name, &st);
	if (error)
		return error;

	error = xfrog_file_getattr(AT_FDCWD, name, &st, &fa,
				   AT_SYMLINK_NOFOLLOW);
	if (error) {
		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
			progname, name, strerror(errno));
		exitcode = 1;
		return 0;
	}

	print_xflags(fa.fa_xflags, vflag, !aflag, name, vflag, !aflag);
	if (aflag) {
		fputs("/", stdout);
		print_xflags(-1, 0, 1, name, 0, 1);
	}

	return 0;
}

static int
chattr_callback(
	const char		*path,
	const struct stat	*stat,
	int			status,
	struct FTW		*data)
{
	struct file_attr	attr;
	int			error;

	if (recurse_dir && !S_ISDIR(stat->st_mode))
		return 0;

	error = xfrog_file_getattr(AT_FDCWD, path, stat, &attr,
				   AT_SYMLINK_NOFOLLOW);
	if (error) {
		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
			progname, path, strerror(errno));
		exitcode = 1;
		return 0;
	}

	attr.fa_xflags |= orflags;
	attr.fa_xflags &= ~andflags;
	error = xfrog_file_setattr(AT_FDCWD, path, stat->st_mode, &attr,
				   AT_SYMLINK_NOFOLLOW);
	if (error) {
		fprintf(stderr, _("%s: cannot set flags on %s: %s\n"),
			progname, path, strerror(errno));
		exitcode = 1;
	}

	return 0;
}

static int
chattr_f(
	int			argc,
	char			**argv)
{
	struct file_attr	attr;
	struct xflags		*p;
	unsigned int		i = 0;
	char			*c, *name = file->name;
	struct stat		st;
	int			error;

	orflags = andflags = 0;
	recurse_all = recurse_dir = 0;
	while (++i < argc) {
		if (argv[i][0] == '-' && argv[i][1] == 'R') {
			recurse_all = 1;
		} else if (argv[i][0] == '-' && argv[i][1] == 'D') {
			recurse_dir = 1;
		} else if (argv[i][0] == '+') {
			for (c = &argv[i][1]; *c; c++) {
				for (p = xflags; p->flag; p++) {
					if (strncmp(p->shortname, c, 1) == 0) {
						orflags |= p->flag;
						break;
					}
				}
				if (!p->flag) {
					fprintf(stderr, _("%s: unknown flag\n"),
						progname);
					exitcode = 1;
					return 0;
				}
			}
		} else if (argv[i][0] == '-') {
			for (c = &argv[i][1]; *c; c++) {
				for (p = xflags; p->flag; p++) {
					if (strncmp(p->shortname, c, 1) == 0) {
						andflags |= p->flag;
						break;
					}
				}
				if (!p->flag) {
					fprintf(stderr, _("%s: unknown flag\n"),
						progname);
					exitcode = 1;
					return 0;
				}
			}
		} else {
			fprintf(stderr, _("%s: bad chattr command, not +/-X\n"),
				progname);
			exitcode = 1;
			return 0;
		}
	}

	if (recurse_all && recurse_dir) {
		fprintf(stderr, _("%s: -R and -D options are mutually exclusive\n"),
			progname);
		exitcode = 1;
		return 0;
	}

	if (recurse_all || recurse_dir) {
		nftw(name, chattr_callback,
			100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH);
		return 0;
	}

	error = stat(name, &st);
	if (error)
		return error;

	error = xfrog_file_getattr(AT_FDCWD, name, &st, &attr,
				   AT_SYMLINK_NOFOLLOW);
	if (error) {
		fprintf(stderr, _("%s: cannot get flags on %s: %s\n"),
			progname, name, strerror(errno));
		exitcode = 1;
		return 0;
	}

	attr.fa_xflags |= orflags;
	attr.fa_xflags &= ~andflags;
	error = xfrog_file_setattr(AT_FDCWD, name, st.st_mode, &attr,
				   AT_SYMLINK_NOFOLLOW);
	if (error) {
		fprintf(stderr, _("%s: cannot set flags on %s: %s\n"),
			progname, name, strerror(errno));
		exitcode = 1;
	}

	return 0;
}

void
attr_init(void)
{
	chattr_cmd.name = "chattr";
	chattr_cmd.cfunc = chattr_f;
	chattr_cmd.args = _("[-R|-D] [+/-"CHATTR_XFLAG_LIST"]");
	chattr_cmd.argmin = 1;
	chattr_cmd.argmax = -1;
	chattr_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK;
	chattr_cmd.oneline =
		_("change extended inode flags on the currently open file");
	chattr_cmd.help = chattr_help;

	lsattr_cmd.name = "lsattr";
	lsattr_cmd.cfunc = lsattr_f;
	lsattr_cmd.args = _("[-R|-D|-a|-v]");
	lsattr_cmd.argmin = 0;
	lsattr_cmd.argmax = 1;
	lsattr_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK;
	lsattr_cmd.oneline =
		_("list extended inode flags set on the currently open file");
	lsattr_cmd.help = lsattr_help;

	add_command(&chattr_cmd);
	add_command(&lsattr_cmd);
}
