From: "Darrick J. Wong" <djwong@kernel.org>
To: Luca Di Maio <luca.dimaio1@gmail.com>
Cc: linux-xfs@vger.kernel.org
Subject: Re: [PATCH v9 1/1] proto: add ability to populate a filesystem from a directory
Date: Mon, 12 May 2025 09:34:54 -0700 [thread overview]
Message-ID: <20250512163454.GG2701446@frogsfrogsfrogs> (raw)
In-Reply-To: <20250507164852.379200-2-luca.dimaio1@gmail.com>
On Wed, May 07, 2025 at 06:48:52PM +0200, Luca Di Maio wrote:
> This patch implements the functionality to populate a newly created XFS
> filesystem directly from an existing directory structure.
>
> It resuses existing protofile logic, it branches if input is a
> directory.
>
> The population process steps are as follows:
> - create the root inode before populating content
> - recursively process nested directories
> - handle regular files, directories, symlinks, char devices, block
> devices, sockets, fifos
> - preserve attributes (ownership, permissions)
> - preserve mtime timestamps from source files to maintain file history
> - use current time for atime/ctime/crtime
> - possible to specify atime=1 to preserve atime timestamps from
> source files
> - preserve extended attributes and fsxattrs for all file types
> - preserve hardlinks
>
> At the moment, the implementation for the hardlink tracking is very
> simple, as it involves a linear search.
> from my local testing using larger source directories
> (1.3mln inodes, ~400k hardlinks) the difference was actually
> just a few seconds (given that most of the time is doing i/o).
> We might want to revisit that in the future if this becomes a
> bottleneck.
>
> This functionality makes it easier to create populated filesystems
> without having to mount them, it's particularly useful for
> reproducible builds.
>
> Signed-off-by: Luca Di Maio <luca.dimaio1@gmail.com>
> ---
> man/man8/mkfs.xfs.8.in | 41 ++-
> mkfs/proto.c | 757 ++++++++++++++++++++++++++++++++++++++++-
> mkfs/proto.h | 18 +-
> mkfs/xfs_mkfs.c | 23 +-
> 4 files changed, 813 insertions(+), 26 deletions(-)
>
> diff --git a/man/man8/mkfs.xfs.8.in b/man/man8/mkfs.xfs.8.in
> index 37e3a88e..bb38c148 100644
> --- a/man/man8/mkfs.xfs.8.in
> +++ b/man/man8/mkfs.xfs.8.in
> @@ -28,7 +28,7 @@ mkfs.xfs \- construct an XFS filesystem
> .I naming_options
> ] [
> .B \-p
> -.I protofile_options
> +.I prototype_options
> ] [
> .B \-q
> ] [
> @@ -977,30 +977,39 @@ option set.
> .PP
> .PD 0
> .TP
> -.BI \-p " protofile_options"
> +.BI \-p " prototype_options"
> .TP
> .BI "Section Name: " [proto]
> .PD
> -These options specify the protofile parameters for populating the filesystem.
> +These options specify the prototype parameters for populating the filesystem.
> The valid
> -.I protofile_options
> +.I prototype_options
> are:
> .RS 1.2i
> .TP
> -.BI [file=] protofile
> +.BI [file=]
> The
> .B file=
> prefix is not required for this CLI argument for legacy reasons.
> If specified as a config file directive, the prefix is required.
> -
> +.TP
> +.BI [file=] directory
> If the optional
> .PD
> -.I protofile
> -argument is given,
> +.I prototype
> +argument is given, and it's a directory,
> .B mkfs.xfs
> -uses
> -.I protofile
> -as a prototype file and takes its directions from that file.
> +will populate the root file system with the contents of the given directory.
> +Content, timestamps (atime, mtime), attributes and extended attributes are preserved
> +for all file types.
> +.TP
> +.BI [file=] protofile
> +If the optional
> +.PD
> +.I prototype
> +argument is given, and points to a regular file,
> +.B mkfs.xfs
> +uses it as a prototype file and takes its directions from that file.
> The blocks and inodes specifiers in the
> .I protofile
> are provided for backwards compatibility, but are otherwise unused.
> @@ -1136,8 +1145,16 @@ always terminated with the dollar (
> .B $
> ) token.
> .TP
> +.BI atime= value
> +If set to 1, when we're populating the root filesystem from a directory (
> +.B file=directory
> +option)
> +access times are going to be preserved and are copied from the source files.
> +Set to 0 to set access times to the current time instead.
> +By default, this is set to 0.
> +.TP
> .BI slashes_are_spaces= value
> -If set to 1, slashes ("/") in the first token of each line of the protofile
> +If set to 1, slashes ("/") in the first token of each line of the prototype file
> are converted to spaces.
> This enables the creation of a filesystem containing filenames with spaces.
> By default, this is set to 0.
> diff --git a/mkfs/proto.c b/mkfs/proto.c
> index 7f56a3d8..ffd580a2 100644
> --- a/mkfs/proto.c
> +++ b/mkfs/proto.c
> @@ -5,6 +5,9 @@
> */
>
> #include "libxfs.h"
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <sys/resource.h>
> #include <sys/stat.h>
> #include <sys/xattr.h>
> #include <linux/xattr.h>
> @@ -21,6 +24,11 @@ static void rsvfile(xfs_mount_t *mp, xfs_inode_t *ip, long long len);
> static int newregfile(char **pp, char **fname);
> static void rtinit(xfs_mount_t *mp);
> static off_t filesize(int fd);
> +static void populate_from_dir(struct xfs_mount *mp,
> + struct fsxattr *fsxp, char *source_dir);
> +static void walk_dir(struct xfs_mount *mp, struct xfs_inode *pip,
> + struct fsxattr *fsxp, char *path_buf);
> +static int preserve_atime;
> static int slashes_are_spaces;
>
> /*
> @@ -54,7 +62,7 @@ getnum(
> return i;
> }
>
> -char *
> +struct proto_source
> setup_proto(
> char *fname)
> {
> @@ -63,8 +71,36 @@ setup_proto(
> int fd;
> long size;
>
> - if (!fname)
> - return dflt;
> + struct proto_source result = {0};
> + struct stat statbuf;
> +
> + /*
> + * for no input, use default root inode
> + * this is actually a protofile, which
> + * is valid.
I think this could be better worded as "If no prototype path is
supplied, use the default protofile which creates only a root
directory."
> + */
> + if (!fname) {
> + result.type = PROTO_SRC_PROTOFILE;
> + result.data = dflt;
> + return result;
> + }
> +
> + if (stat(fname, &statbuf) < 0) {
> + fail(_("invalid or unreadable source path"), ENOENT);
Um, why not feed errno to fail()? It doesn't make sense to squash any
number of errors to "File not found".
> + }
> +
> + /*
> + * handle directory inputs
> + */
> + if (S_ISDIR(statbuf.st_mode)) {
> + result.type = PROTO_SRC_DIR;
> + result.data = fname;
> + return result;
> + }
> +
> + /*
> + * else this is a protofile, let's handle traditionally
> + */
> if ((fd = open(fname, O_RDONLY)) < 0 || (size = filesize(fd)) < 0) {
> fprintf(stderr, _("%s: failed to open %s: %s\n"),
> progname, fname, strerror(errno));
> @@ -90,7 +126,10 @@ setup_proto(
> (void)getnum(getstr(&buf), 0, 0, false); /* block count */
> (void)getnum(getstr(&buf), 0, 0, false); /* inode count */
> close(fd);
> - return buf;
> +
> + result.type = PROTO_SRC_PROTOFILE;
> + result.data = buf;
> + return result;
/me sighs for
enum Prototype {
File(String),
Dir(std::path::PathBuf)
}
-EWRONGLANGUAGE :(
> out_fail:
> if (fd >= 0)
> @@ -379,11 +418,19 @@ writeattr(
> int error;
>
> ret = fgetxattr(fd, attrname, valuebuf, valuelen);
> + /*
> + * in case of filedescriptors with O_PATH, fgetxattr() will
> + * fail with EBADF. let's try to fallback to lgetxattr() using input
> + * path.
> + */
> + if (ret < 0 && errno == EBADF)
> + ret = lgetxattr(fname, attrname, valuebuf, valuelen);
> if (ret < 0) {
> if (errno == EOPNOTSUPP)
> return;
> fail(_("error collecting xattr value"), errno);
> }
> +
> if (ret == 0)
> return;
>
> @@ -425,6 +472,13 @@ writeattrs(
> fail(_("error allocating xattr name buffer"), errno);
>
> ret = flistxattr(fd, namebuf, XATTR_LIST_MAX);
> + /*
> + * in case of filedescriptors with O_PATH, flistxattr() will
> + * fail with EBADF. let's try to fallback to llistxattr() using input
> + * path.
> + */
> + if (ret < 0 && errno == EBADF)
> + ret = llistxattr(fname, namebuf, XATTR_LIST_MAX);
> if (ret < 0) {
> if (errno == EOPNOTSUPP)
> goto out_namebuf;
> @@ -933,11 +987,27 @@ void
> parse_proto(
> xfs_mount_t *mp,
> struct fsxattr *fsx,
> - char **pp,
> - int proto_slashes_are_spaces)
> + struct proto_source *protosource,
> + int proto_slashes_are_spaces,
> + int proto_preserve_atime)
> {
> slashes_are_spaces = proto_slashes_are_spaces;
> - parseproto(mp, NULL, fsx, pp, NULL);
> + preserve_atime = proto_preserve_atime;
> +
> + /*
> + * in case of a file input, we will use the prototype file logic
> + * else we will fallback to populate from dir.
> + */
> + switch(protosource->type) {
> + case PROTO_SRC_PROTOFILE:
> + parseproto(mp, NULL, fsx, &protosource->data, NULL);
> + break;
> + case PROTO_SRC_DIR:
> + populate_from_dir(mp, fsx, protosource->data);
> + break;
> + case PROTO_SRC_NONE:
> + fail(_("invalid or unreadable source path"), ENOENT);
Where is PROTO_SRC_NONE set?
> + }
> }
>
> /* Create a sb-rooted metadata file. */
> @@ -1171,3 +1241,676 @@ filesize(
> return -1;
> return stb.st_size;
> }
> +
> +/* Try to allow as many memfds as possible. */
memfds? Do you mean open prototype directories?
> +static void
> +bump_max_fds(void)
> +{
> + struct rlimit rlim = {};
> + int ret;
> +
> + ret = getrlimit(RLIMIT_NOFILE, &rlim);
> + if (ret)
> + return;
> +
> + rlim.rlim_cur = rlim.rlim_max;
> + ret = setrlimit(RLIMIT_NOFILE, &rlim);
> + if (ret < 0)
> + fprintf(stderr, _("%s: could not bump fd limit: [ %d - %s]\n"),
> + progname, errno, strerror(errno));
> +}
> +
> +static void
> +writefsxattrs(
> + struct xfs_inode *ip,
> + struct fsxattr *fsxp)
> +{
> + ip->i_projid = fsxp->fsx_projid;
> + ip->i_extsize = fsxp->fsx_extsize;
> + ip->i_diflags = xfs_flags2diflags(ip, fsxp->fsx_xflags);
> + if (xfs_has_v3inodes(ip->i_mount)) {
> + ip->i_diflags2 = xfs_flags2diflags2(ip, fsxp->fsx_xflags);
> + ip->i_cowextsize = fsxp->fsx_cowextsize;
> + }
> +}
> +
> +static void
> +writetimestamps(
> + struct xfs_inode *ip,
> + struct stat statbuf)
These ^ columns still need to lined up properly:
struct xfs_inode *ip,
struct stat statbuf)
{
struct timespec64 ts;
I'm also not sure why you're passing the statbuf by value instead of
reference.
> +{
> + struct timespec64 ts;
> +
> + /*
> + * Copy timestamps from source file to destination inode.
> + * Usually reproducible archives will delete or not register
> + * atime and ctime, for example:
> + * https://www.gnu.org/software/tar/manual/html_section/Reproducibility.html
> + * hence we will only copy mtime, and let ctime/crtime be set to
> + * current time.
> + * atime will be copied over if atime is true.
> + */
> + ts.tv_sec = statbuf.st_mtim.tv_sec;
> + ts.tv_nsec = statbuf.st_mtim.tv_nsec;
> + inode_set_mtime_to_ts(VFS_I(ip), ts);
> +
> + /*
> + * in case of atime option, we will copy the atime
> + * timestamp from source.
> + */
> + if (preserve_atime) {
> + ts.tv_sec = statbuf.st_atim.tv_sec;
> + ts.tv_nsec = statbuf.st_atim.tv_nsec;
> + inode_set_atime_to_ts(VFS_I(ip), ts);
> + }
> +}
> +
> +struct hardlink {
> + xfs_ino_t src_ino;
ino_t for src_ino. Don't cast when you don't have to.
> + xfs_ino_t dst_ino;
> +};
> +
> +struct hardlinks {
> + size_t count;
> + size_t size;
> + struct hardlink *entries;
> +};
> +
> +/* Growth strategy for hardlink tracking array */
> +#define HARDLINK_DEFAULT_GROWTH_FACTOR 2 /* Double size for small arrays */
> +#define HARDLINK_LARGE_GROWTH_FACTOR 0.25 /* Grow by 25% for large arrays */
> +#define HARDLINK_THRESHOLD 1024 /* Threshold to switch growth strategies */
> +#define HARDLINK_TRACKER_INITIAL_SIZE 4096 /* Initial allocation size */
> +
> +/*
> + * keep track of source inodes that are from hardlinks
> + * so we can retrieve them when needed to setup in
> + * destination.
> + */
> +static struct hardlinks *hardlink_tracker = { 0 };
Why is this global variable a pointer? You could define the entire
struct and then 1) hardlink_tracker would be initialized to zeroes at
program start and 2) only hardlink_tracker.entries would have to be
allocated dynamically.
> +
> +static void
> +init_hardlink_tracker(void)
> +{
> + hardlink_tracker = calloc(1, sizeof(struct hardlinks));
> + if (!hardlink_tracker)
> + fail(_("error allocating hardlinks tracking array"), errno);
> +
> + hardlink_tracker->size = HARDLINK_TRACKER_INITIAL_SIZE;
> + hardlink_tracker->entries = calloc(
> + hardlink_tracker->size,
> + sizeof(struct hardlink));
> + if (!hardlink_tracker->entries)
> + fail(_("error allocating hardlinks tracking array"), errno);
> +}
> +
> +static void
> +cleanup_hardlink_tracker(void)
> +{
> + free(hardlink_tracker->entries);
> + free(hardlink_tracker);
> + hardlink_tracker = NULL;
> +}
> +
> +static xfs_ino_t
> +get_hardlink_dst_inode(
> + xfs_ino_t i_ino)
same indenting problems here and below
static xfs_ino_t
get_hardlink_dst_inode(
ino_t src_ino)
{
> +{
> + for (size_t i = 0; i < hardlink_tracker->count; i++) {
> + if (hardlink_tracker->entries[i].src_ino == i_ino) {
> + return hardlink_tracker->entries[i].dst_ino;
> + }
> + }
> + return 0;
> +}
> +
> +static void
> +track_hardlink_inode(
> + xfs_ino_t src_ino,
> + xfs_ino_t dst_ino)
> +{
> + if (hardlink_tracker->count >= hardlink_tracker->size) {
> + /*
> + * double for smaller capacity.
> + * instead grow by 25% steps for larger capacities.
> + */
> + const size_t old_size = hardlink_tracker->size;
> + size_t new_size = old_size * HARDLINK_DEFAULT_GROWTH_FACTOR;
> + if (old_size > HARDLINK_THRESHOLD)
> + new_size = old_size + (old_size * HARDLINK_LARGE_GROWTH_FACTOR);
> +
> + struct hardlink *resized_array = reallocarray(
> + hardlink_tracker->entries,
> + new_size,
> + sizeof(struct hardlink));
> + if (!resized_array)
> + fail(_("error enlarging hardlinks tracking array"), errno);
> +
> + memset(&resized_array[old_size], 0,
> + (new_size - old_size) * sizeof(struct hardlink));
> +
> + hardlink_tracker->entries = resized_array;
> + hardlink_tracker->size = new_size;
> + }
> +
> + hardlink_tracker->entries[hardlink_tracker->count].src_ino = src_ino;
> + hardlink_tracker->entries[hardlink_tracker->count].dst_ino = dst_ino;
> + hardlink_tracker->count++;
> +}
> +
> +/*
> + * this function will first check in our tracker if
> + * the input hardlink has already been stored, if not
> + * report false so create_file() can continue handling
> + * the inode as a regular file type, and later save
> + * the source inode in our buffer for future consumption.
> + */
> +static bool
> +handle_hardlink(
> + struct xfs_mount *mp,
> + struct xfs_inode *pip,
> + struct xfs_name xname,
> + struct stat file_stat)
> +{
> + int error;
> + struct xfs_inode *ip;
> + struct xfs_trans *tp;
> + struct xfs_parent_args *ppargs = NULL;
> + tp = getres(mp, 0);
> + ppargs = newpptr(mp);
> +
> + xfs_ino_t dst_ino = get_hardlink_dst_inode(file_stat.st_ino);
Style nit: blank space after the variable declarations; mixing code and
declarations (yes, they're allowed now, but we try not to do that in the
xfs codebase); and the same column indneting problems.
> + /*
> + * we didn't find the hardlink inode, this means
> + * it's the first time we see it, report error
> + * so create_file() can continue handling the inode
> + * as a regular file type, and later save
> + * the source inode in our buffer for future consumption.
> + */
> + if (dst_ino == 0)
> + return false;
> +
> + error = libxfs_iget(mp, NULL, dst_ino, 0, &ip);
> + if (error) {
> + fprintf(stderr, _("failed to iget inode %lu\n"), dst_ino);
Print the error, perhaps?
> + exit(1);
> + }
> +
> + /*
> + * In case the inode was already in our tracker
> + * we need to setup the hardlink and skip file
> + * copy.
> + */
> + libxfs_trans_ijoin(tp, pip, 0);
> + libxfs_trans_ijoin(tp, ip, 0);
> + newdirent(mp, tp, pip, &xname, ip, ppargs);
> +
> + /*
> + * Increment the link count
> + */
> + libxfs_bumplink(tp, ip);
> +
> + libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
> +
> + error = -libxfs_trans_commit(tp);
> + if (error)
> + fail(_("Error encountered creating file from prototype file"), error);
> +
> + libxfs_parent_finish(mp, ppargs);
> + libxfs_irele(ip);
> +
> + return true;
> +}
> +
> +static void
> +create_file(
> + struct xfs_mount *mp,
> + struct xfs_inode *pip,
> + struct fsxattr *fsxp,
> + int mode,
> + struct cred creds,
> + struct xfs_name xname,
> + int flags,
> + struct stat file_stat,
> + xfs_dev_t rdev,
> + int fd,
> + char *fname)
Urrggh, more column alignment problems here..
> +{
> +
> + int error;
> + struct xfs_inode *ip;
> + struct xfs_trans *tp;
> + struct xfs_parent_args *ppargs = NULL;
> +
> + /*
> + * if handle_hardlink() returns true it means the hardlink has
> + * been correctly found and set, so we don't need to
> + * do anything else.
> + */
> + if (file_stat.st_nlink > 1 && handle_hardlink(mp, pip, xname, file_stat)) {
> + close(fd);
> + return;
> + }
> + /*
> + * if instead we have an error it means the hardlink
> + * was not registered, so we proceed to treat it like
> + * a regular file, and save it to our tracker later.
> + */
> + tp = getres(mp, 0);
> + ppargs = newpptr(mp);
> +
> + error = creatproto(&tp, pip, mode, rdev, &creds, fsxp, &ip);
> + if (error)
> + fail(_("Inode allocation failed"), error);
> +
> + libxfs_trans_ijoin(tp, pip, 0);
> + newdirent(mp, tp, pip, &xname, ip, ppargs);
> +
> + /*
> + * copy over timestamps
> + */
> + writetimestamps(ip, file_stat);
> +
> + libxfs_trans_log_inode(tp, ip, flags);
> +
> + error = -libxfs_trans_commit(tp);
> + if (error)
> + fail(_("Error encountered creating file from prototype file"), error);
> +
> + libxfs_parent_finish(mp, ppargs);
> +
> + /*
> + * copy over file content, attributes,
> + * extended attributes and timestamps
> + *
> + * hardlinks will be skipped as fd will
> + * be closed before this.
Is this comment still accurate? I thought this function returned
early if a hardlink was succesfully made.
> + */
> + if (fd >= 0) {
> + writefile(ip, fname, fd);
> + writeattrs(ip, fname, fd);
> + close(fd);
> + }
> + /*
> + * we do fsxattr also for file types where we don't have
> + * an fd, for example FIFOs
> + */
> + writefsxattrs(ip, fsxp);
> +
> + /*
> + * if we're here it means this is the first time we're
> + * encountering an hardlink, so we need to store it
> + */
> + if (file_stat.st_nlink > 1)
> + track_hardlink_inode(file_stat.st_ino, ip->i_ino);
> +
> + libxfs_irele(ip);
> +}
> +
> +static void
> +handle_direntry(
> + struct xfs_mount *mp,
> + struct xfs_inode *pip,
> + struct fsxattr *fsxp,
> + char *path_buf,
> + struct dirent *entry)
> +{
> + char link_target[PATH_MAX];
If this buffer holds the symlink target, then shouldn't its length be
the maximum symlink target length on an xfs filesystem?
> + int error;
> + int fd = -1;
> + int flags;
> + int majdev;
> + int mindev;
> + int mode;
> + struct stat file_stat;
> + struct xfs_name xname;
Same indentation problems I've been noting all over this patch.
> + struct xfs_inode *ip;
> + struct xfs_trans *tp;
> + struct xfs_parent_args *ppargs = NULL;
> +
> + /*
> + * save original path length so we can
> + * restore the original value at the end
> + * of the function
> + */
> + size_t path_save_len = strlen(path_buf);
> + size_t path_len = path_save_len;
> + size_t entry_len = strlen(entry->d_name);
> +
> + /*
> + * ensure the constructed path is within PATH_MAX limits
> + */
> + size_t remaining = PATH_MAX - path_len;
> + size_t written = snprintf(path_buf + path_len, remaining, "/%s",
> + entry->d_name);
> + if (written >= remaining) {
> + fail(_("path name too long"), ENAMETOOLONG);
> + }
> +
> + if (lstat(path_buf, &file_stat) < 0) {
> + fprintf(stderr, _("%s: cannot stat '%s': %s (errno=%d)\n"),
> + progname, path_buf, strerror(errno), errno);
> + exit(1);
> + }
> +
> + /*
> + * avoid opening FIFOs as they're blocking
> + */
> + int open_flags = O_NOFOLLOW | O_RDONLY | O_NOATIME;
> + /*
> + * symlinks and sockets will need to be opened with O_PATH to work,
> + * so we handle this special case.
> + */
> + if (S_ISSOCK(file_stat.st_mode)
> + || S_ISLNK(file_stat.st_mode)
> + || S_ISFIFO(file_stat.st_mode))
Not sure why you sometimes put the boolean logic operator on the
preceeding line (e.g. the handle_hardlink conditional below) in some
places but on the continuation line as is done here.
> + open_flags = O_NOFOLLOW | O_PATH;
> + if ((fd = open(path_buf, open_flags)) < 0) {
> + fprintf(stderr, _("%s: cannot open %s: %s\n"), progname, path_buf,
> + strerror(errno));
> + exit(1);
> + }
> +
> + struct cred creds = {
> + .cr_uid = file_stat.st_uid,
> + .cr_gid = file_stat.st_gid,
> + };
> +
> + xname.name = (unsigned char *)entry->d_name;
> + xname.len = entry_len;
> + xname.type = 0;
> + mode = file_stat.st_mode;
> + flags = XFS_ILOG_CORE;
> +
> + switch (file_stat.st_mode & S_IFMT) {
> + case S_IFDIR:
> + tp = getres(mp, 0);
> + ppargs = newpptr(mp);
> +
> + error = creatproto(&tp, pip, mode, 0, &creds, fsxp, &ip);
> + if (error)
> + fail(_("Inode allocation failed"), error);
> +
> + libxfs_trans_ijoin(tp, pip, 0);
> +
> + xname.type = XFS_DIR3_FT_DIR;
> + newdirent(mp, tp, pip, &xname, ip, ppargs);
> +
> + libxfs_bumplink(tp, pip);
> + libxfs_trans_log_inode(tp, pip, XFS_ILOG_CORE);
> + newdirectory(mp, tp, ip, pip);
> +
> + /*
> + * copy over timestamps
> + */
> + writetimestamps(ip, file_stat);
> +
> + libxfs_trans_log_inode(tp, ip, flags);
> +
> + error = -libxfs_trans_commit(tp);
> + if (error)
> + fail(_("Directory inode allocation failed."), error);
> +
> + libxfs_parent_finish(mp, ppargs);
> + tp = NULL;
> +
> + /*
> + * copy over attributes
> + */
> + writeattrs(ip, entry->d_name, fd);
> + writefsxattrs(ip, fsxp);
> + close(fd);
> +
> + walk_dir(mp, ip, fsxp, path_buf);
> +
> + libxfs_irele(ip);
> + break;
> + case S_IFLNK:
> + /*
> + * if handle_hardlink() returns true it means the hardlink has
> + * been correctly found and set, so we don't need to
> + * do anything else.
> + */
> + if (file_stat.st_nlink > 1 &&
> + handle_hardlink(mp, pip, xname, file_stat)) {
> + close(fd);
> + break;
> + }
> + /*
> + * if instead we have false it means the hardlink
> + * was not registered, so we proceed to treat it like
> + * a regular symlink, and save it to our tracker later.
> + */
> + ssize_t len = readlink(path_buf, link_target, PATH_MAX - 1);
> + if (len < 0)
> + fail(_("could not resolve symlink"), errno);
> + if (len >= PATH_MAX -1)
> + fail(_("symlink target too long"), ENAMETOOLONG);
> + link_target[len] = '\0';
> +
> + tp = getres(mp, XFS_B_TO_FSB(mp, len));
> + ppargs = newpptr(mp);
> +
> + error = creatproto(&tp, pip, mode, 0, &creds, fsxp, &ip);
> + if (error)
> + fail(_("Inode allocation failed"), error);
> +
> + writesymlink(tp, ip, link_target, len);
> + libxfs_trans_ijoin(tp, pip, 0);
> +
> + xname.type = XFS_DIR3_FT_SYMLINK;
> + newdirent(mp, tp, pip, &xname, ip, ppargs);
> +
> + /*
> + * copy over timestamps
> + */
> + writetimestamps(ip, file_stat);
> +
> + libxfs_trans_log_inode(tp, ip, flags);
> +
> + error = -libxfs_trans_commit(tp);
> + if (error)
> + fail(_("Error encountered creating file from prototype file"),
> + error);
> +
> + libxfs_parent_finish(mp, ppargs);
> +
> + /*
> + * copy over attributes
> + *
> + * being a symlink we opened the filedescriptor with O_PATH
> + * this will make flistxattr() and fgetxattr() fail wil EBADF,
> + * so we will need to fallback to llistxattr() and lgetxattr(),
> + * this will need the full path to the original file, not just the
> + * entry name.
> + */
> + writeattrs(ip, path_buf, fd);
> + writefsxattrs(ip, fsxp);
> + close(fd);
> +
> + /*
> + * if we're here it means this is the first time we're
> + * encountering an hardlink, so we need to store it
> + */
> + if (file_stat.st_nlink > 1)
> + track_hardlink_inode(file_stat.st_ino, ip->i_ino);
> +
> + libxfs_irele(ip);
> + break;
> + case S_IFREG:
> + xname.type = XFS_DIR3_FT_REG_FILE;
> + create_file(mp, pip, fsxp, mode, creds, xname, flags, file_stat,
> + 0, fd, entry->d_name);
> + break;
> + case S_IFCHR:
> + xname.type = XFS_DIR3_FT_CHRDEV;
> + majdev = major(file_stat.st_rdev);
> + mindev = minor(file_stat.st_rdev);
> + create_file(mp, pip, fsxp, mode, creds, xname, flags, file_stat,
> + IRIX_MKDEV(majdev, mindev), fd, entry->d_name);
> + break;
> + case S_IFBLK:
> + xname.type = XFS_DIR3_FT_BLKDEV;
> + majdev = major(file_stat.st_rdev);
> + mindev = minor(file_stat.st_rdev);
> + create_file(mp, pip, fsxp, mode, creds, xname, flags, file_stat,
> + IRIX_MKDEV(majdev, mindev), fd, entry->d_name);
> + break;
> + case S_IFIFO:
> + flags |= XFS_ILOG_DEV;
> + /*
> + * being a fifo we opened the filedescriptor with O_PATH
> + * this will make flistxattr() and fgetxattr() fail wil EBADF,
> + * so we will need to fallback to llistxattr() and lgetxattr(),
> + * this will need the full path to the original file, not just the
> + * entry name.
> + */
> + create_file(mp, pip, fsxp, mode, creds, xname, flags, file_stat,
> + 0, fd, path_buf);
> + break;
> + case S_IFSOCK:
> + flags |= XFS_ILOG_DEV;
Why do you log the device number for sockets and fifos? They don't have
device numbers, only block and char devices do. Why is this missing for
those two cases?
> + /*
> + * being a socket we opened the filedescriptor with O_PATH
> + * this will make flistxattr() and fgetxattr() fail wil EBADF,
> + * so we will need to fallback to llistxattr() and lgetxattr(),
> + * this will need the full path to the original file, not just the
> + * entry name.
> + */
> + create_file(mp, pip, fsxp, mode, creds, xname, flags, file_stat,
> + 0, fd, path_buf);
> + break;
> + default:
> + break;
> + }
> +
> + /*
> + * restore path buffer to original length before returning
> + * */
> + path_buf[path_save_len] = '\0';
> +}
> +
> +/*
> + * walk_dir will recursively list files and directories
> + * and populate the mountpoint *mp with them using handle_direntry().
> + */
> +static void
> +walk_dir(
> + struct xfs_mount *mp,
> + struct xfs_inode *pip,
> + struct fsxattr *fsxp,
> + char *path_buf)
> +{
> + DIR *dir;
> + struct dirent *entry;
More column alignment problems...
> +
> + /*
> + * open input directory and iterate over all entries in it.
> + * when another directory is found, we will recursively call
> + * walk_dir.
> + */
> + if ((dir = opendir(path_buf)) == NULL) {
> + fprintf(stderr, _("%s: cannot open input dir: %s [%d - %s]\n"),
> + progname, path_buf, errno, strerror(errno));
> + exit(1);
> + }
> + while ((entry = readdir(dir)) != NULL) {
> + if (strcmp(entry->d_name, ".") == 0 ||
> + strcmp(entry->d_name, "..") == 0) {
Continuations of conditionals should be aligned so that the strings
line up:
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0)
continue;
> + continue;
> + }
> +
> + handle_direntry(mp, pip, fsxp, path_buf, entry);
> + }
> + closedir(dir);
> +}
> +
> +static void
> +populate_from_dir(
> + struct xfs_mount *mp,
> + struct fsxattr *fsxp,
> + char *cur_path)
> +{
> + int error;
> + int mode;
> + struct xfs_inode *ip;
> + struct xfs_trans *tp;
More column alignment problems...
> +
> + char path_buf[PATH_MAX];
> +
> + /*
> + * initialize path_buf cur_path, strip trailing slashes
> + * they're automatically added when walking the dir
> + */
> + if (strlen(cur_path) > 1 && cur_path[strlen(cur_path)-1] == '/')
> + cur_path[strlen(cur_path)-1] = '\0';
When is cur_path not null terminated?
> + if (snprintf(path_buf, PATH_MAX, "%s", cur_path) >= PATH_MAX)
> + fail(_("path name too long"), ENAMETOOLONG);
> +
> + /*
> + * we first ensure we have the root inode
> + */
> + struct cred creds = {
> + .cr_uid = 0,
> + .cr_gid = 0,
> +
> + };
> + mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
Why doesn't the root directory get its owner/mode/fsxattrs from the
protodir like everything else?
--D
> +
> + tp = getres(mp, 0);
> +
> + error = creatproto(&tp, NULL, mode | S_IFDIR, 0, &creds, fsxp, &ip);
> + if (error)
> + fail(_("Inode allocation failed"), error);
> +
> + mp->m_sb.sb_rootino = ip->i_ino;
> + libxfs_log_sb(tp);
> + newdirectory(mp, tp, ip, ip);
> + libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
> +
> + error = -libxfs_trans_commit(tp);
> + if (error)
> + fail(_("Inode allocation failed"), error);
> +
> + libxfs_parent_finish(mp, NULL);
> +
> + /*
> + * RT initialization. Do this here to ensure that
> + * the RT inodes get placed after the root inode.
> + */
> + error = create_metadir(mp);
> + if (error)
> + fail(_("Creation of the metadata directory inode failed"), error);
> +
> + rtinit(mp);
> +
> + /*
> + * by nature of walk_dir() we could be opening
> + * a great number of fds for deeply nested directory
> + * trees.
> + * try to bump max fds limit.
> + */
> + bump_max_fds();
> +
> + /*
> + * initialize the hardlinks tracker
> + */
> + init_hardlink_tracker();
> + /*
> + * now that we have a root inode, let's
> + * walk the input dir and populate the partition
> + */
> + walk_dir(mp, ip, fsxp, path_buf);
> +
> + /*
> + * cleanup hardlinks tracker
> + */
> + cleanup_hardlink_tracker();
> +
> + /*
> + * we free up our root inode
> + * only when we finished populating the
> + * root filesystem
> + */
> + libxfs_irele(ip);
> +}
> diff --git a/mkfs/proto.h b/mkfs/proto.h
> index be1ceb45..476f7851 100644
> --- a/mkfs/proto.h
> +++ b/mkfs/proto.h
> @@ -6,9 +6,21 @@
> #ifndef MKFS_PROTO_H_
> #define MKFS_PROTO_H_
>
> -char *setup_proto(char *fname);
> -void parse_proto(struct xfs_mount *mp, struct fsxattr *fsx, char **pp,
> - int proto_slashes_are_spaces);
> +enum proto_source_type {
> + PROTO_SRC_NONE = 0,
> + PROTO_SRC_PROTOFILE,
> + PROTO_SRC_DIR
> +};
> +struct proto_source {
> + enum proto_source_type type;
> + char *data;
> +};
> +
> +struct proto_source setup_proto(char *fname);
> +void parse_proto(struct xfs_mount *mp, struct fsxattr *fsx,
> + struct proto_source *protosource,
> + int proto_slashes_are_spaces,
> + int proto_preserve_atime);
> void res_failed(int err);
>
> #endif /* MKFS_PROTO_H_ */
> diff --git a/mkfs/xfs_mkfs.c b/mkfs/xfs_mkfs.c
> index 3f4455d4..a32c077e 100644
> --- a/mkfs/xfs_mkfs.c
> +++ b/mkfs/xfs_mkfs.c
> @@ -121,6 +121,7 @@ enum {
>
> enum {
> P_FILE = 0,
> + P_ATIME,
> P_SLASHES,
> P_MAX_OPTS,
> };
> @@ -709,6 +710,7 @@ static struct opt_params popts = {
> .ini_section = "proto",
> .subopts = {
> [P_FILE] = "file",
> + [P_ATIME] = "atime",
> [P_SLASHES] = "slashes_are_spaces",
> [P_MAX_OPTS] = NULL,
> },
> @@ -717,6 +719,12 @@ static struct opt_params popts = {
> .conflicts = { { NULL, LAST_CONFLICT } },
> .defaultval = SUBOPT_NEEDS_VAL,
> },
> + { .index = P_ATIME,
> + .conflicts = { { NULL, LAST_CONFLICT } },
> + .minval = 0,
> + .maxval = 1,
> + .defaultval = 1,
> + },
> { .index = P_SLASHES,
> .conflicts = { { NULL, LAST_CONFLICT } },
> .minval = 0,
> @@ -1045,6 +1053,7 @@ struct cli_params {
> int lsunit;
> int is_supported;
> int proto_slashes_are_spaces;
> + int proto_atime;
> int data_concurrency;
> int log_concurrency;
> int rtvol_concurrency;
> @@ -1170,6 +1179,7 @@ usage( void )
> /* naming */ [-n size=num,version=2|ci,ftype=0|1,parent=0|1]]\n\
> /* no-op info only */ [-N]\n\
> /* prototype file */ [-p fname]\n\
> +/* populate from directory */ [-p dirname,atime=0|1]\n\
> /* quiet */ [-q]\n\
> /* realtime subvol */ [-r extsize=num,size=num,rtdev=xxx,rgcount=n,rgsize=n,\n\
> concurrency=num]\n\
> @@ -2067,6 +2077,9 @@ proto_opts_parser(
> case P_SLASHES:
> cli->proto_slashes_are_spaces = getnum(value, opts, subopt);
> break;
> + case P_ATIME:
> + cli->proto_atime = getnum(value, opts, subopt);
> + break;
> case P_FILE:
> fallthrough;
> default:
> @@ -5162,7 +5175,7 @@ main(
> int discard = 1;
> int force_overwrite = 0;
> int quiet = 0;
> - char *protostring = NULL;
> + struct proto_source protosource;
> int worst_freelist = 0;
>
> struct libxfs_init xi = {
> @@ -5311,8 +5324,6 @@ main(
> */
> cfgfile_parse(&cli);
>
> - protostring = setup_proto(cli.protofile);
> -
> /*
> * Extract as much of the valid config as we can from the CLI input
> * before opening the libxfs devices.
> @@ -5480,7 +5491,11 @@ main(
> /*
> * Allocate the root inode and anything else in the proto file.
> */
> - parse_proto(mp, &cli.fsx, &protostring, cli.proto_slashes_are_spaces);
> + protosource = setup_proto(cli.protofile);
> + parse_proto(mp, &cli.fsx,
> + &protosource,
> + cli.proto_slashes_are_spaces,
> + cli.proto_atime);
>
> /*
> * Protect ourselves against possible stupidity
> --
> 2.49.0
>
>
next prev parent reply other threads:[~2025-05-12 16:34 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-07 16:48 [PATCH v9 0/1] mkfs: add ability to populate filesystem from directory Luca Di Maio
2025-05-07 16:48 ` [PATCH v9 1/1] proto: add ability to populate a filesystem from a directory Luca Di Maio
2025-05-12 16:34 ` Darrick J. Wong [this message]
2025-05-22 13:54 ` Luca Di Maio
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250512163454.GG2701446@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=linux-xfs@vger.kernel.org \
--cc=luca.dimaio1@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox