| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "libxfs.h" |
| #include <dirent.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/xattr.h> |
| #include <linux/xattr.h> |
| #include "libfrog/convert.h" |
| #include "proto.h" |
| |
| /* |
| * Prototypes for internal functions. |
| */ |
| static char *getstr(char **pp); |
| static void fail(char *msg, int i); |
| static struct xfs_trans * getres(struct xfs_mount *mp, uint blocks); |
| 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, int path_len); |
| static int preserve_atime; |
| static int slashes_are_spaces; |
| |
| /* |
| * Use this for block reservations needed for mkfs's conditions |
| * (basically no fragmentation). |
| */ |
| #define MKFS_BLOCKRES_INODE \ |
| ((uint)(M_IGEO(mp)->ialloc_blks + (M_IGEO(mp)->inobt_maxlevels - 1))) |
| #define MKFS_BLOCKRES(rb) \ |
| ((uint)(MKFS_BLOCKRES_INODE + XFS_DA_NODE_MAXDEPTH + \ |
| (XFS_BM_MAXLEVELS(mp, XFS_DATA_FORK) - 1) + (rb))) |
| |
| static long long |
| getnum( |
| const char *str, |
| unsigned int blksize, |
| unsigned int sectsize, |
| bool convert) |
| { |
| long long i; |
| char *sp; |
| |
| if (convert) |
| return cvtnum(blksize, sectsize, str); |
| |
| i = strtoll(str, &sp, 0); |
| if (i == 0 && sp == str) |
| return -1LL; |
| if (*sp != '\0') |
| return -1LL; /* trailing garbage */ |
| return i; |
| } |
| |
| struct proto_source |
| setup_proto( |
| char *fname) |
| { |
| struct proto_source result = {}; |
| struct stat statbuf; |
| char *buf = NULL; |
| static char dflt[] = "d--755 0 0 $"; |
| int fd; |
| long size; |
| |
| /* |
| * 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 ((fd = open(fname, O_RDONLY)) < 0 || (size = filesize(fd)) < 0) { |
| fprintf(stderr, _("%s: failed to open %s: %s\n"), |
| progname, fname, strerror(errno)); |
| goto out_fail; |
| } |
| |
| if (fstat(fd, &statbuf) < 0) |
| fail(_("invalid or unreadable source path"), errno); |
| |
| /* |
| * Handle directory inputs. |
| */ |
| if (S_ISDIR(statbuf.st_mode)) { |
| close(fd); |
| result.type = PROTO_SRC_DIR; |
| result.data = fname; |
| return result; |
| } |
| |
| /* |
| * Else this is a protofile, let's handle traditionally. |
| */ |
| buf = malloc(size + 1); |
| if (read(fd, buf, size) < size) { |
| fprintf(stderr, _("%s: read failed on %s: %s\n"), |
| progname, fname, strerror(errno)); |
| goto out_fail; |
| } |
| if (buf[size - 1] != '\n') { |
| fprintf(stderr, _("%s: proto file %s premature EOF\n"), |
| progname, fname); |
| goto out_fail; |
| } |
| buf[size] = '\0'; |
| /* |
| * Skip past the stuff there for compatibility, a string and 2 numbers. |
| */ |
| (void)getstr(&buf); /* boot image name */ |
| (void)getnum(getstr(&buf), 0, 0, false); /* block count */ |
| (void)getnum(getstr(&buf), 0, 0, false); /* inode count */ |
| close(fd); |
| |
| result.type = PROTO_SRC_PROTOFILE; |
| result.data = buf; |
| return result; |
| |
| out_fail: |
| if (fd >= 0) |
| close(fd); |
| free(buf); |
| exit(1); |
| } |
| |
| static void |
| fail( |
| char *msg, |
| int i) |
| { |
| fprintf(stderr, "%s: %s [%d - %s]\n", progname, msg, i, strerror(i)); |
| exit(1); |
| } |
| |
| void |
| res_failed( |
| int i) |
| { |
| fail(_("cannot reserve space"), i); |
| } |
| |
| static struct xfs_trans * |
| getres( |
| struct xfs_mount *mp, |
| uint blocks) |
| { |
| struct xfs_trans *tp; |
| int i; |
| uint r; |
| |
| for (i = 0, r = MKFS_BLOCKRES(blocks); r >= blocks; r--) { |
| i = -libxfs_trans_alloc_rollable(mp, r, &tp); |
| if (i == 0) |
| return tp; |
| } |
| res_failed(i); |
| /* NOTREACHED */ |
| return NULL; |
| } |
| |
| static char * |
| getstr( |
| char **pp) |
| { |
| char c; |
| char *p; |
| char *rval; |
| |
| p = *pp; |
| while ((c = *p)) { |
| switch (c) { |
| case ' ': |
| case '\t': |
| case '\n': |
| p++; |
| continue; |
| case ':': |
| p++; |
| while (*p++ != '\n') |
| ; |
| continue; |
| default: |
| rval = p; |
| while (c != ' ' && c != '\t' && c != '\n' && c != '\0') |
| c = *++p; |
| *p++ = '\0'; |
| *pp = p; |
| return rval; |
| } |
| } |
| if (c != '\0') { |
| fprintf(stderr, _("%s: premature EOF in prototype file\n"), |
| progname); |
| exit(1); |
| } |
| return NULL; |
| } |
| |
| /* Extract directory entry name from a protofile. */ |
| static char * |
| getdirentname( |
| char **pp) |
| { |
| char *p = getstr(pp); |
| char *c = p; |
| |
| if (!p) |
| return NULL; |
| |
| if (!slashes_are_spaces) |
| return p; |
| |
| /* Replace slash with space because slashes aren't allowed. */ |
| while (*c) { |
| if (*c == '/') |
| *c = ' '; |
| c++; |
| } |
| |
| return p; |
| } |
| |
| static void |
| rsvfile( |
| xfs_mount_t *mp, |
| xfs_inode_t *ip, |
| long long llen) |
| { |
| int error; |
| xfs_trans_t *tp; |
| |
| error = -libxfs_alloc_file_space(ip, 0, llen, XFS_BMAPI_PREALLOC); |
| |
| if (error) { |
| fail(_("error reserving space for a file"), error); |
| exit(1); |
| } |
| |
| /* |
| * update the inode timestamp, mode, and prealloc flag bits |
| */ |
| error = -libxfs_trans_alloc_rollable(mp, 0, &tp); |
| if (error) |
| fail(_("allocating transaction for a file"), error); |
| libxfs_trans_ijoin(tp, ip, 0); |
| |
| VFS_I(ip)->i_mode &= ~S_ISUID; |
| |
| /* |
| * Note that we don't have to worry about mandatory |
| * file locking being disabled here because we only |
| * clear the S_ISGID bit if the Group execute bit is |
| * on, but if it was on then mandatory locking wouldn't |
| * have been enabled. |
| */ |
| if (VFS_I(ip)->i_mode & S_IXGRP) |
| VFS_I(ip)->i_mode &= ~S_ISGID; |
| |
| libxfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG); |
| |
| ip->i_diflags |= XFS_DIFLAG_PREALLOC; |
| |
| libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); |
| error = -libxfs_trans_commit(tp); |
| if (error) |
| fail(_("committing space for a file failed"), error); |
| } |
| |
| static void |
| writesymlink( |
| struct xfs_trans *tp, |
| struct xfs_inode *ip, |
| char *buf, |
| int len) |
| { |
| struct xfs_mount *mp = tp->t_mountp; |
| xfs_extlen_t nb = XFS_B_TO_FSB(mp, len); |
| int error; |
| |
| error = -libxfs_symlink_write_target(tp, ip, ip->i_ino, buf, len, nb, |
| nb); |
| if (error) { |
| fprintf(stderr, |
| _("%s: error %d creating symlink to '%s'.\n"), progname, error, buf); |
| exit(1); |
| } |
| } |
| |
| static void |
| writefile_range( |
| struct xfs_inode *ip, |
| const char *fname, |
| int fd, |
| off_t pos, |
| uint64_t len) |
| { |
| static char buf[131072]; |
| int error; |
| |
| if (XFS_IS_REALTIME_INODE(ip)) { |
| fprintf(stderr, |
| _("%s: creating realtime files from proto file not supported.\n"), |
| progname); |
| exit(1); |
| } |
| |
| while (len > 0) { |
| ssize_t read_len; |
| |
| read_len = pread(fd, buf, min(len, sizeof(buf)), pos); |
| if (read_len < 0) { |
| fprintf(stderr, _("%s: read failed on %s: %s\n"), |
| progname, fname, strerror(errno)); |
| exit(1); |
| } |
| |
| error = -libxfs_alloc_file_space(ip, pos, read_len, 0); |
| if (error) |
| fail(_("error allocating space for a file"), error); |
| |
| error = -libxfs_file_write(ip, buf, pos, read_len); |
| if (error) |
| fail(_("error writing file"), error); |
| |
| pos += read_len; |
| len -= read_len; |
| } |
| } |
| |
| static void |
| writefile( |
| struct xfs_inode *ip, |
| const char *fname, |
| int fd) |
| { |
| struct xfs_trans *tp; |
| struct xfs_mount *mp = ip->i_mount; |
| struct stat statbuf; |
| off_t data_pos; |
| int error; |
| |
| /* do not try to read from non-regular files */ |
| error = fstat(fd, &statbuf); |
| if (error < 0) |
| fail(_("unable to stat file to copyin"), errno); |
| |
| if (!S_ISREG(statbuf.st_mode)) |
| return; |
| |
| data_pos = lseek(fd, 0, SEEK_DATA); |
| while (data_pos >= 0) { |
| off_t hole_pos; |
| |
| hole_pos = lseek(fd, data_pos, SEEK_HOLE); |
| if (hole_pos < 0) { |
| /* save error, break */ |
| data_pos = hole_pos; |
| break; |
| } |
| if (hole_pos <= data_pos) { |
| /* shouldn't happen??? */ |
| break; |
| } |
| |
| writefile_range(ip, fname, fd, data_pos, hole_pos - data_pos); |
| data_pos = lseek(fd, hole_pos, SEEK_DATA); |
| } |
| if (data_pos < 0 && errno != ENXIO) |
| fail(_("error finding file data to import"), errno); |
| |
| /* extend EOF only after writing all the file data */ |
| error = -libxfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange, 0, 0, |
| false, &tp); |
| if (error) |
| fail(_("error creating isize transaction"), error); |
| |
| libxfs_trans_ijoin(tp, ip, 0); |
| ip->i_disk_size = statbuf.st_size; |
| libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); |
| error = -libxfs_trans_commit(tp); |
| if (error) |
| fail(_("error committing isize transaction"), error); |
| } |
| |
| static void |
| writeattr( |
| struct xfs_inode *ip, |
| const char *fname, |
| int fd, |
| const char *attrname, |
| char *valuebuf, |
| size_t valuelen) |
| { |
| struct xfs_da_args args = { |
| .dp = ip, |
| .geo = ip->i_mount->m_attr_geo, |
| .owner = ip->i_ino, |
| .whichfork = XFS_ATTR_FORK, |
| .op_flags = XFS_DA_OP_OKNOENT, |
| .value = valuebuf, |
| }; |
| ssize_t ret; |
| 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; |
| |
| if (!strncmp(attrname, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) { |
| args.name = (unsigned char *)attrname + XATTR_TRUSTED_PREFIX_LEN; |
| args.attr_filter = LIBXFS_ATTR_ROOT; |
| } else if (!strncmp(attrname, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN)) { |
| args.name = (unsigned char *)attrname + XATTR_SECURITY_PREFIX_LEN; |
| args.attr_filter = LIBXFS_ATTR_SECURE; |
| } else if (!strncmp(attrname, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) { |
| args.name = (unsigned char *)attrname + XATTR_USER_PREFIX_LEN; |
| args.attr_filter = 0; |
| } else { |
| args.name = (unsigned char *)attrname; |
| args.attr_filter = 0; |
| } |
| args.namelen = strlen((char *)args.name); |
| |
| args.valuelen = ret; |
| libxfs_attr_sethash(&args); |
| |
| error = -libxfs_attr_set(&args, XFS_ATTRUPDATE_UPSERT, false); |
| if (error) |
| fail(_("setting xattr value"), error); |
| } |
| |
| static void |
| writeattrs( |
| struct xfs_inode *ip, |
| const char *fname, |
| int fd) |
| { |
| char *namebuf, *p, *end; |
| char *valuebuf = NULL; |
| ssize_t ret; |
| |
| namebuf = malloc(XATTR_LIST_MAX); |
| if (!namebuf) |
| 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; |
| fail(_("error collecting xattr names"), errno); |
| } |
| |
| p = namebuf; |
| end = namebuf + ret; |
| for (p = namebuf; p < end; p += strlen(p) + 1) { |
| if (!valuebuf) { |
| valuebuf = malloc(ATTR_MAX_VALUELEN); |
| if (!valuebuf) |
| fail(_("error allocating xattr value buffer"), |
| errno); |
| } |
| |
| writeattr(ip, fname, fd, p, valuebuf, ATTR_MAX_VALUELEN); |
| } |
| |
| free(valuebuf); |
| out_namebuf: |
| free(namebuf); |
| } |
| |
| static int |
| newregfile( |
| char **pp, |
| char **fname) |
| { |
| int fd; |
| off_t size; |
| |
| *fname = getstr(pp); |
| if ((fd = open(*fname, O_RDONLY)) < 0 || (size = filesize(fd)) < 0) { |
| fprintf(stderr, _("%s: cannot open %s: %s\n"), |
| progname, *fname, strerror(errno)); |
| exit(1); |
| } |
| |
| return fd; |
| } |
| |
| static void |
| newdirent( |
| struct xfs_mount *mp, |
| struct xfs_trans *tp, |
| struct xfs_inode *pip, |
| struct xfs_name *name, |
| struct xfs_inode *ip, |
| struct xfs_parent_args *ppargs) |
| { |
| int error; |
| int rsv; |
| |
| if (!libxfs_dir2_namecheck(name->name, name->len)) { |
| fprintf(stderr, _("%.*s: invalid directory entry name\n"), |
| name->len, name->name); |
| exit(1); |
| } |
| |
| rsv = XFS_DIRENTER_SPACE_RES(mp, name->len); |
| |
| error = -libxfs_dir_createname(tp, pip, name, ip->i_ino, rsv); |
| if (error) |
| fail(_("directory createname error"), error); |
| |
| if (ppargs) { |
| error = -libxfs_parent_addname(tp, ppargs, pip, name, ip); |
| if (error) |
| fail(_("parent addname error"), error); |
| } |
| } |
| |
| static void |
| newdirectory( |
| xfs_mount_t *mp, |
| xfs_trans_t *tp, |
| xfs_inode_t *dp, |
| xfs_inode_t *pdp) |
| { |
| int error; |
| |
| error = -libxfs_dir_init(tp, dp, pdp); |
| if (error) |
| fail(_("directory create error"), error); |
| } |
| |
| static struct xfs_parent_args * |
| newpptr( |
| struct xfs_mount *mp) |
| { |
| struct xfs_parent_args *ret; |
| int error; |
| |
| error = -libxfs_parent_start(mp, &ret); |
| if (error) |
| fail(_("initializing parent pointer"), error); |
| |
| return ret; |
| } |
| |
| struct cred { |
| uid_t cr_uid; |
| gid_t cr_gid; |
| }; |
| |
| static int |
| creatproto( |
| struct xfs_trans **tpp, |
| struct xfs_inode *dp, |
| mode_t mode, |
| xfs_dev_t rdev, |
| struct cred *cr, |
| struct fsxattr *fsx, |
| struct xfs_inode **ipp) |
| { |
| struct xfs_icreate_args args = { |
| .idmap = libxfs_nop_idmap, |
| .pip = dp, |
| .rdev = rdev, |
| .mode = mode, |
| }; |
| struct xfs_inode *ip; |
| struct inode *inode; |
| xfs_ino_t ino; |
| int error; |
| |
| /* Root directories cannot be linked to a parent. */ |
| if (!dp) |
| args.flags |= XFS_ICREATE_UNLINKABLE; |
| |
| /* |
| * Call the space management code to pick the on-disk inode to be |
| * allocated. |
| */ |
| error = -libxfs_dialloc(tpp, &args, &ino); |
| if (error) |
| return error; |
| |
| error = -libxfs_icreate(*tpp, ino, &args, &ip); |
| if (error) |
| return error; |
| |
| inode = VFS_I(ip); |
| i_uid_write(inode, cr->cr_uid); |
| i_gid_write(inode, cr->cr_gid); |
| |
| /* If there is no parent dir, initialize the file from fsxattr data. */ |
| if (dp == NULL) { |
| ip->i_projid = fsx->fsx_projid; |
| ip->i_extsize = fsx->fsx_extsize; |
| ip->i_diflags = xfs_flags2diflags(ip, fsx->fsx_xflags); |
| |
| if (xfs_has_v3inodes(ip->i_mount)) { |
| ip->i_diflags2 = xfs_flags2diflags2(ip, |
| fsx->fsx_xflags); |
| ip->i_cowextsize = fsx->fsx_cowextsize; |
| } |
| |
| /* xfsdump breaks if the root dir has a nonzero generation */ |
| inode->i_generation = 0; |
| } |
| |
| libxfs_trans_log_inode(*tpp, ip, XFS_ILOG_CORE); |
| *ipp = ip; |
| return 0; |
| } |
| |
| /* Create a new metadata root directory. */ |
| static int |
| create_metadir( |
| struct xfs_mount *mp) |
| { |
| struct xfs_inode *ip = NULL; |
| struct xfs_trans *tp; |
| int error; |
| struct xfs_icreate_args args = { |
| .mode = S_IFDIR, |
| .flags = XFS_ICREATE_UNLINKABLE, |
| }; |
| xfs_ino_t ino; |
| |
| if (!xfs_has_metadir(mp)) |
| return 0; |
| |
| error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_create, |
| libxfs_create_space_res(mp, MAXNAMELEN), 0, 0, &tp); |
| if (error) |
| return error; |
| |
| /* |
| * Create a new inode and set the sb pointer. The primary super is |
| * still marked inprogress, so we do not need to log the metadirino |
| * change ourselves. |
| */ |
| error = -libxfs_dialloc(&tp, &args, &ino); |
| if (error) |
| goto out_cancel; |
| error = -libxfs_icreate(tp, ino, &args, &ip); |
| if (error) |
| goto out_cancel; |
| mp->m_sb.sb_metadirino = ino; |
| |
| /* |
| * Initialize the root directory. There are no ILOCKs in userspace |
| * so we do not need to drop it here. |
| */ |
| libxfs_metafile_set_iflag(tp, ip, XFS_METAFILE_DIR); |
| error = -libxfs_dir_init(tp, ip, ip); |
| if (error) |
| goto out_cancel; |
| |
| error = -libxfs_trans_commit(tp); |
| if (error) |
| goto out_rele; |
| |
| mp->m_metadirip = ip; |
| return 0; |
| |
| out_cancel: |
| libxfs_trans_cancel(tp); |
| out_rele: |
| if (ip) |
| libxfs_irele(ip); |
| return error; |
| } |
| |
| static void |
| parseproto( |
| xfs_mount_t *mp, |
| xfs_inode_t *pip, |
| struct fsxattr *fsxp, |
| char **pp, |
| char *name) |
| { |
| #define IF_REGULAR 0 |
| #define IF_RESERVED 1 |
| #define IF_BLOCK 2 |
| #define IF_CHAR 3 |
| #define IF_DIRECTORY 4 |
| #define IF_SYMLINK 5 |
| #define IF_FIFO 6 |
| |
| char *buf; |
| int error; |
| int flags; |
| int fmt; |
| int i; |
| xfs_inode_t *ip; |
| int fd = -1; |
| off_t len; |
| long long llen; |
| int majdev; |
| int mindev; |
| int mode; |
| char *mstr; |
| xfs_trans_t *tp; |
| int val; |
| int isroot = 0; |
| struct cred creds; |
| char *value; |
| char *fname = NULL; |
| struct xfs_name xname; |
| struct xfs_parent_args *ppargs = NULL; |
| |
| memset(&creds, 0, sizeof(creds)); |
| mstr = getstr(pp); |
| switch (mstr[0]) { |
| case '-': |
| fmt = IF_REGULAR; |
| break; |
| case 'r': |
| fmt = IF_RESERVED; |
| break; |
| case 'b': |
| fmt = IF_BLOCK; |
| break; |
| case 'c': |
| fmt = IF_CHAR; |
| break; |
| case 'd': |
| fmt = IF_DIRECTORY; |
| break; |
| case 'l': |
| fmt = IF_SYMLINK; |
| break; |
| case 'p': |
| fmt = IF_FIFO; |
| break; |
| default: |
| fprintf(stderr, _("%s: bad format string %s\n"), |
| progname, mstr); |
| exit(1); |
| } |
| mode = 0; |
| switch (mstr[1]) { |
| case '-': |
| break; |
| case 'u': |
| mode |= S_ISUID; |
| break; |
| default: |
| fprintf(stderr, _("%s: bad format string %s\n"), |
| progname, mstr); |
| exit(1); |
| } |
| switch (mstr[2]) { |
| case '-': |
| break; |
| case 'g': |
| mode |= S_ISGID; |
| break; |
| default: |
| fprintf(stderr, _("%s: bad format string %s\n"), |
| progname, mstr); |
| exit(1); |
| } |
| val = 0; |
| for (i = 3; i < 6; i++) { |
| if (mstr[i] < '0' || mstr[i] > '7') { |
| fprintf(stderr, _("%s: bad format string %s\n"), |
| progname, mstr); |
| exit(1); |
| } |
| val = val * 8 + mstr[i] - '0'; |
| } |
| mode |= val; |
| creds.cr_uid = (int)getnum(getstr(pp), 0, 0, false); |
| creds.cr_gid = (int)getnum(getstr(pp), 0, 0, false); |
| xname.name = (unsigned char *)name; |
| xname.len = name ? strlen(name) : 0; |
| xname.type = 0; |
| flags = XFS_ILOG_CORE; |
| switch (fmt) { |
| case IF_REGULAR: |
| fd = newregfile(pp, &fname); |
| tp = getres(mp, 0); |
| ppargs = newpptr(mp); |
| error = creatproto(&tp, pip, mode | S_IFREG, 0, &creds, fsxp, |
| &ip); |
| if (error) |
| fail(_("Inode allocation failed"), error); |
| libxfs_trans_ijoin(tp, pip, 0); |
| xname.type = XFS_DIR3_FT_REG_FILE; |
| newdirent(mp, tp, pip, &xname, ip, ppargs); |
| break; |
| |
| case IF_RESERVED: /* pre-allocated space only */ |
| value = getstr(pp); |
| llen = getnum(value, mp->m_sb.sb_blocksize, |
| mp->m_sb.sb_sectsize, true); |
| if (llen < 0) { |
| fprintf(stderr, |
| _("%s: Bad value %s for proto file %s\n"), |
| progname, value, name); |
| exit(1); |
| } |
| tp = getres(mp, XFS_B_TO_FSB(mp, llen)); |
| ppargs = newpptr(mp); |
| error = creatproto(&tp, pip, mode | S_IFREG, 0, &creds, fsxp, |
| &ip); |
| if (error) |
| fail(_("Inode pre-allocation failed"), error); |
| |
| libxfs_trans_ijoin(tp, pip, 0); |
| |
| xname.type = XFS_DIR3_FT_REG_FILE; |
| newdirent(mp, tp, pip, &xname, ip, ppargs); |
| libxfs_trans_log_inode(tp, ip, flags); |
| error = -libxfs_trans_commit(tp); |
| if (error) |
| fail(_("Space preallocation failed."), error); |
| libxfs_parent_finish(mp, ppargs); |
| rsvfile(mp, ip, llen); |
| libxfs_irele(ip); |
| return; |
| |
| case IF_BLOCK: |
| tp = getres(mp, 0); |
| ppargs = newpptr(mp); |
| majdev = getnum(getstr(pp), 0, 0, false); |
| mindev = getnum(getstr(pp), 0, 0, false); |
| error = creatproto(&tp, pip, mode | S_IFBLK, |
| IRIX_MKDEV(majdev, mindev), &creds, fsxp, &ip); |
| if (error) { |
| fail(_("Inode allocation failed"), error); |
| } |
| libxfs_trans_ijoin(tp, pip, 0); |
| xname.type = XFS_DIR3_FT_BLKDEV; |
| newdirent(mp, tp, pip, &xname, ip, ppargs); |
| flags |= XFS_ILOG_DEV; |
| break; |
| |
| case IF_CHAR: |
| tp = getres(mp, 0); |
| ppargs = newpptr(mp); |
| majdev = getnum(getstr(pp), 0, 0, false); |
| mindev = getnum(getstr(pp), 0, 0, false); |
| error = creatproto(&tp, pip, mode | S_IFCHR, |
| IRIX_MKDEV(majdev, mindev), &creds, fsxp, &ip); |
| if (error) |
| fail(_("Inode allocation failed"), error); |
| libxfs_trans_ijoin(tp, pip, 0); |
| xname.type = XFS_DIR3_FT_CHRDEV; |
| newdirent(mp, tp, pip, &xname, ip, ppargs); |
| flags |= XFS_ILOG_DEV; |
| break; |
| |
| case IF_FIFO: |
| tp = getres(mp, 0); |
| ppargs = newpptr(mp); |
| error = creatproto(&tp, pip, mode | S_IFIFO, 0, &creds, fsxp, |
| &ip); |
| if (error) |
| fail(_("Inode allocation failed"), error); |
| libxfs_trans_ijoin(tp, pip, 0); |
| xname.type = XFS_DIR3_FT_FIFO; |
| newdirent(mp, tp, pip, &xname, ip, ppargs); |
| break; |
| case IF_SYMLINK: |
| buf = getstr(pp); |
| len = (int)strlen(buf); |
| tp = getres(mp, XFS_B_TO_FSB(mp, len)); |
| ppargs = newpptr(mp); |
| error = creatproto(&tp, pip, mode | S_IFLNK, 0, &creds, fsxp, |
| &ip); |
| if (error) |
| fail(_("Inode allocation failed"), error); |
| writesymlink(tp, ip, buf, len); |
| libxfs_trans_ijoin(tp, pip, 0); |
| xname.type = XFS_DIR3_FT_SYMLINK; |
| newdirent(mp, tp, pip, &xname, ip, ppargs); |
| break; |
| case IF_DIRECTORY: |
| tp = getres(mp, 0); |
| error = creatproto(&tp, pip, mode | S_IFDIR, 0, &creds, fsxp, |
| &ip); |
| if (error) |
| fail(_("Inode allocation failed"), error); |
| if (!pip) { |
| pip = ip; |
| mp->m_sb.sb_rootino = ip->i_ino; |
| libxfs_log_sb(tp); |
| isroot = 1; |
| } else { |
| ppargs = newpptr(mp); |
| 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); |
| 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); |
| |
| /* |
| * RT initialization. Do this here to ensure that |
| * the RT inodes get placed after the root inode. |
| */ |
| if (isroot) { |
| error = create_metadir(mp); |
| if (error) |
| fail( |
| _("Creation of the metadata directory inode failed"), |
| error); |
| |
| rtinit(mp); |
| } |
| tp = NULL; |
| for (;;) { |
| name = getdirentname(pp); |
| if (!name) |
| break; |
| if (strcmp(name, "$") == 0) |
| break; |
| parseproto(mp, ip, fsxp, pp, name); |
| } |
| libxfs_irele(ip); |
| return; |
| default: |
| ASSERT(0); |
| fail(_("Unknown format"), EINVAL); |
| } |
| 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); |
| if (fmt == IF_REGULAR) { |
| writefile(ip, fname, fd); |
| writeattrs(ip, fname, fd); |
| close(fd); |
| } |
| libxfs_irele(ip); |
| } |
| |
| void |
| parse_proto( |
| xfs_mount_t *mp, |
| struct fsxattr *fsx, |
| struct proto_source *protosource, |
| int proto_slashes_are_spaces, |
| int proto_preserve_atime) |
| { |
| preserve_atime = proto_preserve_atime; |
| slashes_are_spaces = proto_slashes_are_spaces; |
| |
| /* |
| * 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. */ |
| static void |
| create_sb_metadata_file( |
| struct xfs_rtgroup *rtg, |
| enum xfs_rtg_inodes type, |
| int (*create)(struct xfs_rtgroup *rtg, |
| struct xfs_inode *ip, |
| struct xfs_trans *tp, |
| bool init)) |
| { |
| struct xfs_mount *mp = rtg_mount(rtg); |
| struct xfs_icreate_args args = { |
| .mode = S_IFREG, |
| .flags = XFS_ICREATE_UNLINKABLE, |
| }; |
| struct xfs_trans *tp; |
| struct xfs_inode *ip = NULL; |
| xfs_ino_t ino; |
| int error; |
| |
| error = -libxfs_trans_alloc_rollable(mp, MKFS_BLOCKRES_INODE, &tp); |
| if (error) |
| res_failed(error); |
| |
| error = -libxfs_dialloc(&tp, &args, &ino); |
| if (error) |
| goto fail; |
| |
| error = -libxfs_icreate(tp, ino, &args, &ip); |
| if (error) |
| goto fail; |
| |
| error = create(rtg, ip, tp, true); |
| if (error < 0) |
| error = -error; |
| if (error) |
| goto fail; |
| |
| switch (type) { |
| case XFS_RTGI_BITMAP: |
| mp->m_sb.sb_rbmino = ip->i_ino; |
| break; |
| case XFS_RTGI_SUMMARY: |
| mp->m_sb.sb_rsumino = ip->i_ino; |
| break; |
| default: |
| error = EFSCORRUPTED; |
| goto fail; |
| } |
| libxfs_log_sb(tp); |
| |
| error = -libxfs_trans_commit(tp); |
| if (error) |
| goto fail; |
| rtg->rtg_inodes[type] = ip; |
| return; |
| |
| fail: |
| if (ip) |
| libxfs_irele(ip); |
| if (error) |
| fail(_("Realtime inode allocation failed"), error); |
| } |
| |
| /* |
| * Free the whole realtime area using transactions. Each transaction may clear |
| * up to 32 rtbitmap blocks. |
| */ |
| static void |
| rtfreesp_init( |
| struct xfs_rtgroup *rtg) |
| { |
| struct xfs_mount *mp = rtg_mount(rtg); |
| struct xfs_trans *tp; |
| const xfs_rtxnum_t max_rtx = mp->m_rtx_per_rbmblock * 32; |
| xfs_rtxnum_t start_rtx = 0; |
| int error; |
| |
| /* |
| * First zero the realtime bitmap and summary files. |
| */ |
| error = -libxfs_rtfile_initialize_blocks(rtg, XFS_RTGI_BITMAP, 0, |
| mp->m_sb.sb_rbmblocks, NULL); |
| if (error) |
| fail(_("Initialization of rtbitmap inode failed"), error); |
| |
| error = -libxfs_rtfile_initialize_blocks(rtg, XFS_RTGI_SUMMARY, 0, |
| mp->m_rsumblocks, NULL); |
| if (error) |
| fail(_("Initialization of rtsummary inode failed"), error); |
| |
| if (!mp->m_sb.sb_rbmblocks) |
| return; |
| |
| /* |
| * Then free the blocks into the allocator, one bitmap block at a time. |
| */ |
| while (start_rtx < rtg->rtg_extents) { |
| xfs_rtxlen_t nr = min(rtg->rtg_extents - start_rtx, max_rtx); |
| |
| /* |
| * The rt superblock, if present, must not be marked free. |
| * This may be the only rtx in the entire volume. |
| */ |
| if (xfs_has_rtsb(mp) && rtg_rgno(rtg) == 0 && start_rtx == 0) { |
| start_rtx++; |
| nr--; |
| |
| if (start_rtx == rtg->rtg_extents) |
| break; |
| } |
| |
| error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, |
| 0, 0, 0, &tp); |
| if (error) |
| res_failed(error); |
| |
| libxfs_trans_ijoin(tp, rtg_bitmap(rtg), 0); |
| error = -libxfs_rtfree_extent(tp, rtg, start_rtx, nr); |
| if (error) { |
| fprintf(stderr, |
| _("Error initializing the realtime free space near rgno %u rtx %lld-%lld (max %lld): %s\n"), |
| rtg_rgno(rtg), |
| (unsigned long long)start_rtx, |
| (unsigned long long)start_rtx + nr - 1, |
| (unsigned long long)rtg->rtg_extents, |
| strerror(error)); |
| exit(1); |
| } |
| |
| error = -libxfs_trans_commit(tp); |
| if (error) |
| fail(_("Initialization of the realtime space failed"), |
| error); |
| |
| start_rtx += nr; |
| } |
| } |
| |
| static void |
| rtinit_nogroups( |
| struct xfs_mount *mp) |
| { |
| struct xfs_rtgroup *rtg = NULL; |
| |
| while ((rtg = xfs_rtgroup_next(mp, rtg))) { |
| create_sb_metadata_file(rtg, XFS_RTGI_BITMAP, |
| libxfs_rtbitmap_create); |
| create_sb_metadata_file(rtg, XFS_RTGI_SUMMARY, |
| libxfs_rtsummary_create); |
| |
| rtfreesp_init(rtg); |
| } |
| } |
| |
| static int |
| init_rtrmap_for_rtsb( |
| struct xfs_rtgroup *rtg) |
| { |
| struct xfs_mount *mp = rtg_mount(rtg); |
| struct xfs_trans *tp; |
| int error; |
| |
| error = -libxfs_trans_alloc_inode(rtg_rmap(rtg), |
| &M_RES(mp)->tr_itruncate, 0, 0, false, &tp); |
| if (error) |
| return error; |
| |
| error = -libxfs_rtrmapbt_init_rtsb(mp, rtg, tp); |
| if (error) { |
| libxfs_trans_cancel(tp); |
| return error; |
| } |
| |
| return -libxfs_trans_commit(tp); |
| } |
| |
| static void |
| rtinit_groups( |
| struct xfs_mount *mp) |
| { |
| struct xfs_rtgroup *rtg = NULL; |
| unsigned int i; |
| int error; |
| |
| error = -libxfs_rtginode_mkdir_parent(mp); |
| if (error) |
| fail(_("rtgroup directory allocation failed"), error); |
| |
| while ((rtg = xfs_rtgroup_next(mp, rtg))) { |
| for (i = 0; i < XFS_RTGI_MAX; i++) { |
| error = -libxfs_rtginode_create(rtg, i, true); |
| if (error) |
| fail(_("rt group inode creation failed"), |
| error); |
| } |
| |
| if (xfs_has_rtsb(mp) && xfs_has_rtrmapbt(mp) && |
| rtg_rgno(rtg) == 0) { |
| error = init_rtrmap_for_rtsb(rtg); |
| if (error) |
| fail(_("rtrmap rtsb init failed"), error); |
| } |
| |
| if (!xfs_has_zoned(mp)) |
| rtfreesp_init(rtg); |
| } |
| } |
| |
| /* |
| * Allocate the realtime bitmap and summary inodes, and fill in data if any. |
| */ |
| static void |
| rtinit( |
| struct xfs_mount *mp) |
| { |
| if (xfs_has_rtgroups(mp)) |
| rtinit_groups(mp); |
| else |
| rtinit_nogroups(mp); |
| } |
| |
| static off_t |
| filesize( |
| int fd) |
| { |
| struct stat stb; |
| |
| if (fstat(fd, &stb) < 0) |
| 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 */ |
| /* Double size for small arrays */ |
| #define HARDLINK_DEFAULT_GROWTH_FACTOR 2 |
| /* Grow by 25% for large arrays */ |
| #define HARDLINK_LARGE_GROWTH_FACTOR 0.25 |
| /* Threshold to switch growth strategies */ |
| #define HARDLINK_THRESHOLD 1024 |
| /* Initial allocation size */ |
| #define HARDLINK_TRACKER_INITIAL_SIZE 4096 |
| |
| /* |
| * 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); |
| hardlink_tracker.entries = NULL; |
| hardlink_tracker.count = 0; |
| hardlink_tracker.size = 0; |
| } |
| |
| 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_nondir_inode() can continue |
| * handling the inode as regularly, 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_nondir_inode() 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_directory_inode( |
| 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, |
| int fd, |
| char *entryname, |
| char *path_buf, |
| int path_len) |
| { |
| |
| int error; |
| struct xfs_inode *ip; |
| struct xfs_trans *tp; |
| struct xfs_parent_args *ppargs = NULL; |
| |
| 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); |
| |
| 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, entryname, fd); |
| writefsxattrs(ip, fsxp); |
| close(fd); |
| |
| walk_dir(mp, ip, fsxp, path_buf, path_len); |
| |
| libxfs_irele(ip); |
| } |
| |
| static void |
| create_nondir_inode( |
| 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 *src_fname) |
| { |
| |
| char link_target[XFS_SYMLINK_MAXLEN]; |
| int error; |
| ssize_t link_len = 0; |
| 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); |
| /* |
| * In case of symlinks, we need to handle things a little differently. |
| * We need to read out our link target and act accordingly. |
| */ |
| if (xname.type == XFS_DIR3_FT_SYMLINK) { |
| link_len = readlink(src_fname, link_target, XFS_SYMLINK_MAXLEN); |
| if (link_len < 0) |
| fail(_("could not resolve symlink"), errno); |
| if (link_len >= PATH_MAX) |
| fail(_("symlink target too long"), ENAMETOOLONG); |
| tp = getres(mp, XFS_B_TO_FSB(mp, link_len)); |
| } |
| ppargs = newpptr(mp); |
| |
| error = creatproto(&tp, pip, mode, rdev, &creds, fsxp, &ip); |
| if (error) |
| fail(_("Inode allocation failed"), error); |
| |
| /* |
| * In case of symlinks, we now write it down, for other file types |
| * this is handled later before cleanup. |
| */ |
| if (xname.type == XFS_DIR3_FT_SYMLINK) |
| writesymlink(tp, ip, link_target, link_len); |
| |
| 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 non dir inode"), error); |
| |
| libxfs_parent_finish(mp, ppargs); |
| |
| /* |
| * Copy over file content, attributes, extended attributes and |
| * timestamps. |
| */ |
| if (fd >= 0) { |
| /* We need to writefile only when not dealing with a symlink. */ |
| if (xname.type != XFS_DIR3_FT_SYMLINK) |
| writefile(ip, src_fname, fd); |
| writeattrs(ip, src_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, |
| int path_len, |
| struct dirent *entry) |
| { |
| char *fname = ""; |
| int flags; |
| int majdev; |
| int mindev; |
| int mode; |
| int pathfd,fd = -1; |
| int rdev = 0; |
| struct stat file_stat; |
| struct xfs_name xname; |
| |
| pathfd = open(path_buf, O_NOFOLLOW | O_PATH); |
| if (pathfd < 0){ |
| fprintf(stderr, _("%s: cannot open %s: %s\n"), progname, |
| path_buf, strerror(errno)); |
| exit(1); |
| } |
| |
| /* |
| * Symlinks and sockets will need to be opened with O_PATH to work, so |
| * we handle this special case. |
| */ |
| fd = openat(pathfd, entry->d_name, O_NOFOLLOW | O_PATH); |
| if(fd < 0) { |
| fprintf(stderr, _("%s: cannot open %s: %s\n"), progname, |
| path_buf, strerror(errno)); |
| exit(1); |
| } |
| |
| if (fstat(fd, &file_stat) < 0) { |
| fprintf(stderr, _("%s: cannot stat '%s': %s (errno=%d)\n"), |
| progname, path_buf, strerror(errno), errno); |
| exit(1); |
| } |
| |
| /* Ensure we're within the limits of PATH_MAX. */ |
| size_t avail = PATH_MAX - path_len; |
| size_t wrote = snprintf(path_buf + path_len, avail, "/%s", entry->d_name); |
| if (wrote > avail) |
| fail(path_buf, ENAMETOOLONG); |
| |
| /* |
| * Regular files instead need to be reopened with broader flags so we |
| * check if that's the case and reopen those. |
| */ |
| if (!S_ISSOCK(file_stat.st_mode) && |
| !S_ISLNK(file_stat.st_mode) && |
| !S_ISFIFO(file_stat.st_mode)) { |
| close(fd); |
| /* |
| * Try to open the source file noatime to avoid a flood of |
| * writes to the source fs, but we can fall back to plain |
| * readonly mode if we don't have enough permission. |
| */ |
| fd = openat(pathfd, entry->d_name, |
| O_NOFOLLOW | O_RDONLY | O_NOATIME); |
| if (fd < 0) |
| fd = openat(pathfd, entry->d_name, |
| O_NOFOLLOW | O_RDONLY); |
| 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 = strlen(entry->d_name); |
| xname.type = 0; |
| mode = file_stat.st_mode; |
| flags = XFS_ILOG_CORE; |
| |
| switch (file_stat.st_mode & S_IFMT) { |
| case S_IFDIR: |
| xname.type = XFS_DIR3_FT_DIR; |
| create_directory_inode(mp, pip, fsxp, mode, creds, xname, flags, |
| file_stat, fd, entry->d_name, path_buf, |
| path_len + strlen(entry->d_name) + 1); |
| goto out; |
| case S_IFREG: |
| xname.type = XFS_DIR3_FT_REG_FILE; |
| fname = 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); |
| rdev = IRIX_MKDEV(majdev, mindev); |
| fname = 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); |
| rdev = IRIX_MKDEV(majdev, mindev); |
| fname = entry->d_name; |
| break; |
| case S_IFLNK: |
| /* |
| * Being a symlink we opened the filedescriptor with O_PATH |
| * this will make flistxattr() and fgetxattr() fail with 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. |
| */ |
| xname.type = XFS_DIR3_FT_SYMLINK; |
| fname = path_buf; |
| break; |
| case S_IFIFO: |
| /* |
| * Being a fifo we opened the filedescriptor with O_PATH |
| * this will make flistxattr() and fgetxattr() fail with 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. |
| */ |
| xname.type = XFS_DIR3_FT_FIFO; |
| fname = path_buf; |
| break; |
| case S_IFSOCK: |
| /* |
| * Being a socket we opened the filedescriptor with O_PATH |
| * this will make flistxattr() and fgetxattr() fail with 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. |
| */ |
| xname.type = XFS_DIR3_FT_SOCK; |
| fname = path_buf; |
| break; |
| default: |
| break; |
| } |
| |
| create_nondir_inode(mp, pip, fsxp, mode, creds, xname, flags, file_stat, |
| rdev, fd, fname); |
| out: |
| /* Reset path_buf to original */ |
| path_buf[path_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, |
| int path_len) |
| { |
| DIR *dir; |
| struct dirent *entry; |
| |
| /* |
| * 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, path_len, 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; |
| 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); |
| } |
| fd = open(path_buf, O_NOFOLLOW | O_RDONLY | O_NOATIME); |
| if (fd < 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); |
| close(fd); |
| |
| /* |
| * 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, strlen(cur_path)); |
| |
| /* |
| * Cleanup hardlinks tracker. |
| */ |
| cleanup_hardlink_tracker(); |
| |
| /* |
| * We free up our root inode only when we finished populating the root |
| * filesystem. |
| */ |
| libxfs_irele(ip); |
| } |