Re: [PATCH v10 1/1] proto: add ability to populate a filesystem from a directory

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks for the review Christoph, Darrick
Sorry for the long delay, a v11 path will be sent in a bit, addressing
the issues raised in you reviews

Thanks
L.

On Thu, May 29, 2025 at 3:51 AM Darrick J. Wong <djwong@xxxxxxxxxx> wrote:
>
> On Thu, May 22, 2025 at 05:10:03PM -0400, 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.
>
> In addition to all of hch's comments...
>
> > Signed-off-by: Luca Di Maio <luca.dimaio1@xxxxxxxxx>
> > ---
> >  man/man8/mkfs.xfs.8.in |  41 ++-
> >  mkfs/proto.c           | 765 ++++++++++++++++++++++++++++++++++++++++-
> >  mkfs/proto.h           |  18 +-
> >  mkfs/xfs_mkfs.c        |  23 +-
> >  4 files changed, 821 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..0c456248 100644
> > --- a/mkfs/proto.c
> > +++ b/mkfs/proto.c
> > @@ -5,6 +5,10 @@
> >   */
> >
> >  #include "libxfs.h"
> > +#include "xfs_format.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 +25,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 +63,7 @@ getnum(
> >       return i;
> >  }
> >
> > -char *
> > +struct proto_source
> >  setup_proto(
> >       char    *fname)
> >  {
> > @@ -63,8 +72,37 @@ setup_proto(
> >       int             fd;
> >       long            size;
> >
> > -     if (!fname)
> > -             return dflt;
> > +     struct proto_source     result = {0};
> > +     struct stat     statbuf;
> > +
> > +     /*
> > +      * 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"), errno);
> > +     }
> > +
> > +     /*
> > +      * 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 +128,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;
> >
> >  out_fail:
> >       if (fd >= 0)
> > @@ -379,6 +420,13 @@ 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;
> > @@ -425,6 +473,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 +988,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);
> > +     }
> >  }
> >
> >  /* Create a sb-rooted metadata file. */
> > @@ -1171,3 +1242,683 @@ filesize(
> >               return -1;
> >       return stb.st_size;
> >  }
> > +
> > +/* Try to allow as many open directories as possible. */
> > +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)
> > +{
> > +     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 {
> > +     ino_t           src_ino;
> > +     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 };
> > +
> > +static void
> > +init_hardlink_tracker(void)
> > +{
> > +     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);
>
> Please zero out hardlink_tracker now that you've freed the array so that
> some future user won't accidentally add code that walks off the end of a
> dead pointer if they call get_hardlink_dst_inode after
> cleanup_hardlink_tracker.
>
> > +}
> > +
> > +static xfs_ino_t
> > +get_hardlink_dst_inode(
> > +     xfs_ino_t       i_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(
> > +     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;
> > +     xfs_ino_t               dst_ino;
> > +     struct xfs_inode        *ip;
> > +     struct xfs_trans        *tp;
> > +     struct xfs_parent_args *ppargs = NULL;
> > +
> > +     tp = getres(mp, 0);
> > +     ppargs = newpptr(mp);
> > +     dst_ino = get_hardlink_dst_inode(file_stat.st_ino);
> > +
> > +     /*
> > +      * 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)
> > +             fail(_("failed to get inode"), error);
> > +
> > +     /*
> > +      * 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)
> > +{
> > +
> > +     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
> > +      */
> > +     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[XFS_SYMLINK_MAXLEN];
> > +     int             error;
> > +     int             fd = -1;
> > +     int             flags;
> > +     int             majdev;
> > +     int             mindev;
> > +     int             mode;
>
> Nit: space ^ before tab; and all the variable names should be lined up
> to the same column...
>
> > +     struct stat     file_stat;
> > +     struct xfs_name xname;
> > +     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);
>
> ...and you can do things like:
>
>         size_t                  written =
>                 snprintf(...);
>
> if the columns go far enough to the right that you can't fit them all on
> one line.
>
> > +     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))
>
> Line these up, please:
>
>         if (S_ISSOCK(...) ||
>             S_ISLNK(...) ||
>             S_ISFIFO(...))
>
> > +             open_flags = O_NOFOLLOW | O_PATH;
> > +     if ((fd = open(path_buf, open_flags)) < 0) {
>
>
>         fd = open(...);
>         if (fd < 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);
>
> Yeah, each of these cases ought to be broken out into smaller function
> that each take care of a single child file type.  Not sure why some of
> these cases call create_file() and others don't?
>
> > +             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, XFS_SYMLINK_MAXLEN - 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';
>
> Link targets don't require null terminators, they have explicit lengths.
>
> > +
> > +             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:
> > +             flags |= XFS_ILOG_DEV;
> > +             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:
> > +             flags |= XFS_ILOG_DEV;
> > +             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:
> > +             /*
> > +              * 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:
> > +             /*
> > +              * 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);
>
> These two (FIFO/SOCK) need to set xname.type too.  Maybe that ought to
> be refactored out of parseproto since there's already a
> libxfs_mode_to_ftype function for that?
>
> > +             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;
>
> It's a little strange   ^ that some variable names are aligned to this
> column yet others are aligned   ^ to this column; they all should be the
> same.
>
> (I wish I could point you at an automatic reformatter but I don't know
> of any...)
>
> > +
> > +     /*
> > +      * 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)
> > +                     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;
> > +     int             fd = -1;
>
> Nit: space ^ here before a tab.
>
> > +     char            path_buf[PATH_MAX];
> > +     struct stat     file_stat;
> > +     struct xfs_inode        *ip;
> > +     struct xfs_trans        *tp;
> > +
> > +     /*
> > +      * 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';
> > +     if (snprintf(path_buf, PATH_MAX, "%s", cur_path) >= PATH_MAX)
> > +             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);
> > +     }
> > +     if ((fd = open(path_buf, O_NOFOLLOW | O_RDONLY | O_NOATIME)) < 0) {
> > +             fprintf(stderr, _("%s: cannot open %s: %s\n"), progname, path_buf,
> > +                     strerror(errno));
> > +             exit(1);
> > +     }
> > +
> > +     /*
> > +      * we first ensure we have the root inode
> > +      */
> > +     struct cred creds = {
> > +             .cr_uid = file_stat.st_uid,
> > +             .cr_gid = file_stat.st_gid,
> > +     };
> > +     mode = file_stat.st_mode;
> > +
> > +     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);
> > +
> > +     /*
> > +      * copy over attributes
> > +      */
> > +     writeattrs(ip, path_buf, fd);
> > +     writefsxattrs(ip, fsxp);
> > +
> > +     /*
> > +      * 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);
>
> I almost wonder if we should just dump all the proto* options into
> struct proto_source and pass that into parse_proto (instead of five
> separate arguments) but that's a cleanup that can happen later...
>
> --D
>
> >
> >       /*
> >        * Protect ourselves against possible stupidity
> > --
> > 2.49.0
> >





[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux