u64 check_prog_version(char *prog) { char stderrbuf[2048]; char command[2048]; char options[1024]; char temp1[1024]; char delims[]="\n\r"; char *saveptr; char *result; int foundversion; int x, y, z; // init memset(options, 0, sizeof(options)); memset(stderrbuf, 0, sizeof(stderrbuf)); if (exec_command(command, sizeof(command), NULL, NULL, 0, stderrbuf, sizeof(stderrbuf), "%s -V", prog)!=0) { errprintf("program %s was not found or has bad permissions.\n", prog); return -1; } foundversion=false; result=strtok_r(stderrbuf, delims, &saveptr); while (result != NULL && foundversion==false) { if ((memcmp(result, prog, strlen(prog))==0)) foundversion=true; else result = strtok_r(NULL, delims, &saveptr); } if (foundversion==false) { errprintf("can't parse %s version number: no match\n", prog); return 0; } x=y=z=0; sscanf(result, "%s %d.%d.%d", temp1, &x, &y, &z); if (x==0 && y==0) { errprintf("can't parse %s version number: x=y=0\n", prog); return 0; } return PROGVER(x,y,z); }
#include <stdio.h> #include <ext2fs.h> #include <blkid.h> #include <e2p.h> #include <uuid.h> #include "fsarchiver.h" #include "dico.h" #include "common.h" #include "strlist.h" #include "filesys.h" #include "fs_ext2.h" #include "error.h" // e2fsprogs version required to work on ext2, ext3, ext4 u64 e2fsprogs_minver[]={PROGVER(1,39,0), PROGVER(1,39,0), PROGVER(1,41,0)}; struct mntopt { unsigned int mask; const char *string; }; struct s_features { char *name; // name of that feature int mask; // identifier for that feature int compat; // compat type for that feature (see e2p.h) int firstfs; // type of the first filesystem to support it u64 firste2p; // first e2fsprogs version that supports that feature };
int extfs_mkfs(cdico *d, char *partition, int extfstype, char *fsoptions) { cstrlist strfeatures; u64 features_tab[3]; u64 fsextrevision; int origextfstype; char buffer[2048]; char command[2048]; char options[2048]; char temp[1024]; char progname[64]; u64 e2fstoolsver; int compat_type; u64 temp64; int exitst; int ret=0; int res; int i; // init memset(options, 0, sizeof(options)); snprintf(progname, sizeof(progname), "mke2fs"); strlist_init(&strfeatures); // ---- check that mkfs is installed and get its version if (exec_command(command, sizeof(command), NULL, NULL, 0, NULL, 0, "%s -V", progname)!=0) { errprintf("%s not found. please install a recent e2fsprogs on your system or check the PATH.\n", progname); ret=-1; goto extfs_mkfs_cleanup; } e2fstoolsver=check_prog_version(progname); // ---- filesystem revision (good-old-rev or dynamic) if (dico_get_u64(d, 0, FSYSHEADKEY_FSEXTREVISION, &fsextrevision)!=0) fsextrevision=EXT2_DYNAMIC_REV; // don't fail (case of fs conversion to extfs) // "mke2fs -q" prevents problems in exec_command when too many output details printed strlcatf(options, sizeof(options), " -q "); // filesystem revision: good-old-rev or dynamic strlcatf(options, sizeof(options), " -r %d ", (int)fsextrevision); strlcatf(options, sizeof(options), " %s ", fsoptions); // ---- set the advanced filesystem settings from the dico if (dico_get_string(d, 0, FSYSHEADKEY_FSLABEL, buffer, sizeof(buffer))==0 && strlen(buffer)>0) strlcatf(options, sizeof(options), " -L '%.16s' ", buffer); if (dico_get_u64(d, 0, FSYSHEADKEY_FSEXTBLOCKSIZE, &temp64)==0) strlcatf(options, sizeof(options), " -b %ld ", (long)temp64); if (dico_get_u64(d, 0, FSYSHEADKEY_FSINODESIZE, &temp64)==0) strlcatf(options, sizeof(options), " -I %ld ", (long)temp64); // ---- get original filesystem features (if the original filesystem was an ext{2,3,4}) if (dico_get_u64(d, 0, FSYSHEADKEY_FSEXTFEATURECOMPAT, &features_tab[E2P_FEATURE_COMPAT])!=0 || dico_get_u64(d, 0, FSYSHEADKEY_FSEXTFEATUREINCOMPAT, &features_tab[E2P_FEATURE_INCOMPAT])!=0 || dico_get_u64(d, 0, FSYSHEADKEY_FSEXTFEATUREROCOMPAT, &features_tab[E2P_FEATURE_RO_INCOMPAT])!=0) { // dont fail the original filesystem may not be ext{2,3,4}. in that case set defaults features features_tab[E2P_FEATURE_COMPAT]=EXT2_FEATURE_COMPAT_RESIZE_INODE|EXT2_FEATURE_COMPAT_DIR_INDEX; features_tab[E2P_FEATURE_INCOMPAT]=EXT2_FEATURE_INCOMPAT_FILETYPE; features_tab[E2P_FEATURE_RO_INCOMPAT]=EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; } // ---- check that fsarchiver is aware of all the filesystem features used on that filesystem if (extfs_check_compatibility(features_tab[E2P_FEATURE_COMPAT], features_tab[E2P_FEATURE_INCOMPAT], features_tab[E2P_FEATURE_RO_INCOMPAT])!=0) { errprintf("this filesystem has ext{2,3,4} features which are not supported by this fsarchiver version.\n"); return -1; } // ---- get original filesystem type origextfstype=extfs_get_fstype_from_compat_flags(features_tab[E2P_FEATURE_COMPAT], features_tab[E2P_FEATURE_INCOMPAT], features_tab[E2P_FEATURE_RO_INCOMPAT]); msgprintf(MSG_VERB2, "the filesystem type determined by the original filesystem features is [%s]\n", format_fstype(origextfstype)); // remove all the features not supported by the filesystem to create (conversion = downgrade fs) for (i=0; mkfeatures[i].name; i++) { compat_type=mkfeatures[i].compat; if (mkfeatures[i].firstfs > extfstype) features_tab[compat_type] &= ~mkfeatures[i].mask; } // add new features if the filesystem to create is newer than the filesystem type that was backed up // eg: user did a "savefs" of an ext3 and does a "restfs mkfs=ext4" --> add features to force ext4 // it's a bit more difficult because we only want to add such a feature if no feature of the new // filesystem is currently enabled. msgprintf(MSG_VERB2, "the filesystem type to create considering the command options is [%s]\n", format_fstype(extfstype)); if (origextfstype==EXTFSTYPE_EXT2 && extfstype>EXTFSTYPE_EXT2) // upgrade ext2 to ext{3,4} { fsextrevision=EXT2_DYNAMIC_REV; features_tab[E2P_FEATURE_COMPAT]|=EXT3_FEATURE_COMPAT_HAS_JOURNAL; } if (origextfstype<EXTFSTYPE_EXT4 && extfstype>=EXTFSTYPE_EXT4) // upgrade ext{2,3} to ext4 { fsextrevision=EXT2_DYNAMIC_REV; features_tab[E2P_FEATURE_INCOMPAT]|=EXT3_FEATURE_INCOMPAT_EXTENTS; } // convert int features to string to be passed to mkfs for (i=0; mkfeatures[i].name; i++) { if (mkfeatures[i].firste2p<=e2fstoolsver) // don't pass an option to a program that does not support it { compat_type=mkfeatures[i].compat; if (features_tab[compat_type] & mkfeatures[i].mask) { msgprintf(MSG_VERB2, "--> feature [%s]=YES\n", mkfeatures[i].name); strlist_add(&strfeatures, mkfeatures[i].name); } else { msgprintf(MSG_VERB2, "--> feature [%s]=NO\n", mkfeatures[i].name); snprintf(temp, sizeof(temp), "^%s", mkfeatures[i].name); // exclude feature strlist_add(&strfeatures, temp); } } } // if extfs revision is dynamic and there are features in the list if (fsextrevision!=EXT2_GOOD_OLD_REV && strlist_count(&strfeatures)>0) { strlist_merge(&strfeatures, temp, sizeof(temp), ','); strlcatf(options, sizeof(options), " -O %s ", temp); msgprintf(MSG_VERB2, "features: mkfs_options+=[-O %s]\n", temp); } // ---- check mke2fs version requirement msgprintf(MSG_VERB2, "mke2fs version detected: %s\n", format_prog_version(e2fstoolsver, temp, sizeof(temp))); msgprintf(MSG_VERB2, "mke2fs version required: %s\n", format_prog_version(e2fsprogs_minver[extfstype], temp, sizeof(temp))); if (e2fstoolsver < e2fsprogs_minver[extfstype]) { errprintf("mke2fs was found but is too old, please upgrade to a version %s or more recent.\n", format_prog_version(e2fsprogs_minver[extfstype], temp, sizeof(temp))); ret=-1; goto extfs_mkfs_cleanup; } // ---- extended options if (dico_get_u64(d, 0, FSYSHEADKEY_FSEXTEOPTRAIDSTRIDE, &temp64)==0) strlcatf(options, sizeof(options), " -E stride=%ld ", (long)temp64); if ((dico_get_u64(d, 0, FSYSHEADKEY_FSEXTEOPTRAIDSTRIPEWIDTH, &temp64)==0) && e2fstoolsver>=PROGVER(1,40,7)) strlcatf(options, sizeof(options), " -E stripe-width=%ld ", (long)temp64); // ---- execute mke2fs msgprintf(MSG_VERB2, "exec: %s\n", command); if (exec_command(command, sizeof(command), &exitst, NULL, 0, NULL, 0, "%s %s %s", progname, partition, options)!=0 || exitst!=0) { errprintf("command [%s] failed with return status=%d\n", command, exitst); ret=-1; goto extfs_mkfs_cleanup; } // ---- use tune2fs to set the other advanced options memset(options, 0, sizeof(options)); if (dico_get_string(d, 0, FSYSHEADKEY_FSUUID, buffer, sizeof(buffer))==0 && strlen(buffer)==36) strlcatf(options, sizeof(options), " -U %s ", buffer); if (dico_get_string(d, 0, FSYSHEADKEY_FSEXTDEFMNTOPT, buffer, sizeof(buffer))==0 && strlen(buffer)>0) strlcatf(options, sizeof(options), " -o %s ", buffer); if (dico_get_u64(d, 0, FSYSHEADKEY_FSEXTFSCKMAXMNTCOUNT, &temp64)==0) strlcatf(options, sizeof(options), " -c %ld ", (long)temp64); if (dico_get_u64(d, 0, FSYSHEADKEY_FSEXTFSCKCHECKINTERVAL, &temp64)==0) strlcatf(options, sizeof(options), " -i %ldd ", (long)(temp64/86400L)); if (options[0]) { if (exec_command(command, sizeof(command), &exitst, NULL, 0, NULL, 0, "tune2fs %s %s", partition, options)!=0 || exitst!=0) { errprintf("command [%s] failed with return status=%d\n", command, exitst); ret=-1; goto extfs_mkfs_cleanup; } // run e2fsck to workaround an tune2fs bug in e2fsprogs < 1.41.4 on ext4 // http://article.gmane.org/gmane.comp.file-systems.ext4/11181 if (extfstype==EXTFSTYPE_EXT4 && e2fstoolsver<PROGVER(1,41,4)) { if ( ((res=exec_command(command, sizeof(command), &exitst, NULL, 0, NULL, 0, "e2fsck -fy %s", partition))!=0) || ((exitst!=0) && (exitst!=1)) ) { errprintf("command [%s] failed with return status=%d\n", command, exitst); ret=-1; goto extfs_mkfs_cleanup; } } } extfs_mkfs_cleanup: strlist_destroy(&strfeatures); return ret; }
int xfs_mkfs(cdico *d, char *partition, char *fsoptions, char *mkfslabel, char *mkfsuuid) { char stdoutbuf[2048]; char command[2048]; char buffer[2048]; char mkfsopts[2048]; char xadmopts[2048]; char uuid[64]; u64 xfstoolsver; int exitst; u64 temp64; u64 xfsver; int x, y, z; int optval; u64 sb_features_compat=0; u64 sb_features_ro_compat=0; u64 sb_features_incompat=0; u64 sb_features_log_incompat=0; // ---- check that mkfs is installed and get its version if (exec_command(command, sizeof(command), NULL, stdoutbuf, sizeof(stdoutbuf), NULL, 0, "mkfs.xfs -V")!=0) { errprintf("mkfs.xfs not found. please install xfsprogs on your system or check the PATH.\n"); return -1; } x=y=z=0; sscanf(stdoutbuf, "mkfs.xfs version %d.%d.%d", &x, &y, &z); if (x==0 && y==0) { errprintf("Can't parse mkfs.xfs version number: x=y=0\n"); return -1; } xfstoolsver=PROGVER(x,y,z); msgprintf(MSG_VERB2, "Detected mkfs.xfs version %d.%d.%d\n", x, y, z); memset(mkfsopts, 0, sizeof(mkfsopts)); memset(xadmopts, 0, sizeof(xadmopts)); memset(uuid, 0, sizeof(uuid)); strlcatf(mkfsopts, sizeof(mkfsopts), " %s ", fsoptions); if (strlen(mkfslabel) > 0) strlcatf(mkfsopts, sizeof(mkfsopts), " -L '%.12s' ", mkfslabel); else if (dico_get_string(d, 0, FSYSHEADKEY_FSLABEL, buffer, sizeof(buffer))==0 && strlen(buffer)>0) strlcatf(mkfsopts, sizeof(mkfsopts), " -L '%.12s' ", buffer); if ((dico_get_u64(d, 0, FSYSHEADKEY_FSXFSBLOCKSIZE, &temp64)==0) && (temp64%512==0) && (temp64>=512) && (temp64<=65536)) strlcatf(mkfsopts, sizeof(mkfsopts), " -b size=%ld ", (long)temp64); // ---- get xfs features attributes from the archive dico_get_u64(d, 0, FSYSHEADKEY_FSXFSFEATURECOMPAT, &sb_features_compat); dico_get_u64(d, 0, FSYSHEADKEY_FSXFSFEATUREROCOMPAT, &sb_features_ro_compat); dico_get_u64(d, 0, FSYSHEADKEY_FSXFSFEATUREINCOMPAT, &sb_features_incompat); dico_get_u64(d, 0, FSYSHEADKEY_FSXFSFEATURELOGINCOMPAT, &sb_features_log_incompat); // ---- check fsarchiver is aware of all the filesystem features used on that filesystem if (xfs_check_compatibility(sb_features_compat, sb_features_ro_compat, sb_features_incompat, sb_features_log_incompat)!=0) return -1; // Preserve version 4 of XFS if the original filesystem was an XFSv4 or if // it was saved with fsarchiver <= 0.6.19 which does not store the XFS // version in the metadata: make an XFSv4 if unsure for better compatibility, // as upgrading later is easier than downgrading if ((dico_get_u64(d, 0, FSYSHEADKEY_FSXFSVERSION, &temp64)!=0) || (temp64==XFS_SB_VERSION_4)) xfsver = XFS_SB_VERSION_4; else xfsver = XFS_SB_VERSION_5; // Unfortunately it is impossible to preserve the UUID (stamped on every // metadata block) of an XFSv5 filesystem with mkfs.xfs < 4.3.0. // Restoring with a new random UUID would work but could prevent the system // from booting if this is a boot/root filesystem because grub/fstab often // use the UUID to identify it. Hence it is much safer to restore it as an // XFSv4 and it also provides a better compatibility with older kernels. // Hence this is the safest option. // More details: https://github.com/fdupoux/fsarchiver/issues/4 if ((xfsver==XFS_SB_VERSION_5) && (xfstoolsver < PROGVER(4,3,0))) { xfsver = XFS_SB_VERSION_4; // Do not preserve the XFS version msgprintf(MSG_FORCE, "It is impossible to restore this filesystem as an XFSv5 and preserve its UUID\n" "with mkfs.xfs < 4.3.0. This filesystem will be restored as an XFSv4 instead\n" "as this is a much safer option (preserving the UUID may be required on\n" "boot/root filesystems for the operating system to be able to start). If you\n" "really want to have an XFSv5 filesystem, please upgrade xfsprogs to version\n" "4.3.0 or more recent and rerun this operation to get an XFSv5 with the same\n" "UUID as original filesystem\n"); } // Determine if the "crc" mkfs option should be enabled (checksum) // - checksum must be disabled if we want to recreate an XFSv4 filesystem // - checksum must be enabled if we want to recreate an XFSv5 filesystem if (xfstoolsver >= PROGVER(3,2,0)) // only use "crc" option when it is supported by mkfs { optval = (xfsver==XFS_SB_VERSION_5); strlcatf(mkfsopts, sizeof(mkfsopts), " -m crc=%d ", (int)optval); } // Determine if the "finobt" mkfs option should be enabled (free inode btree) // - starting with linux-3.16 XFS has added a btree that tracks free inodes // - this feature relies on the new v5 on-disk format but it is optional // - this feature is enabled by default when using xfsprogs 3.2.3 or later // - this feature will be enabled if the original filesystem was XFSv5 and had it if (xfstoolsver >= PROGVER(3,2,1)) // only use "finobt" option when it is supported by mkfs { optval = ((xfsver==XFS_SB_VERSION_5) && (sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_FINOBT)); strlcatf(mkfsopts, sizeof(mkfsopts), " -m finobt=%d ", (int)optval); } // Determine if the "rmapbt" mkfs option should be enabled (reverse mapping btree) // - starting with linux-4.8 XFS has added a btree that maps filesystem blocks // to their owner // - this feature relies on the new v5 on-disk format but it is optional // - this feature will be enabled if the original filesystem was XFSv5 and had it if (xfstoolsver >= PROGVER(4,8,0)) // only use "rmapbt" option when it is supported by mkfs { optval = ((xfsver==XFS_SB_VERSION_5) && (sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_RMAPBT)); strlcatf(mkfsopts, sizeof(mkfsopts), " -m rmapbt=%d ", (int)optval); } // Determine if the "reflink" mkfs option should be enabled // - starting with linux-4.9 XFS has added support for reflinked files // - this feature relies on the new v5 on-disk format but it is optional // - this feature will be enabled if the original filesystem was XFSv5 and had it if (xfstoolsver >= PROGVER(4,9,0)) // only use "reflink" option when it is supported by mkfs { optval = ((xfsver==XFS_SB_VERSION_5) && (sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_REFLINK)); strlcatf(mkfsopts, sizeof(mkfsopts), " -m reflink=%d ", (int)optval); } // Attempt to preserve UUID of the filesystem // - the "-m uuid=<UUID>" option in mkfs.xfs was added in mkfs.xfs 4.3.0 and is the best way to set UUIDs // - the UUID of XFSv4 can be successfully set using either xfs_admin or mkfs.xfs >= 4.3.0 // - it is impossible to set both types of UUIDs of an XFSv5 filesystem using xfsprogs < 4.3.0 // for this reason the XFS version is forced to v4 if xfsprogs version < 4.3.0 if (strlen(mkfsuuid) > 0) snprintf(uuid, sizeof(uuid), "%s", mkfsuuid); else if (dico_get_string(d, 0, FSYSHEADKEY_FSUUID, buffer, sizeof(buffer))==0) snprintf(uuid, sizeof(uuid), "%s", buffer); if (strlen(uuid)==36) { if (xfstoolsver >= PROGVER(4,3,0)) strlcatf(mkfsopts, sizeof(mkfsopts), " -m uuid=%s ", uuid); else strlcatf(xadmopts, sizeof(xadmopts), " -U %s ", uuid); } // Determine if the "ftype" mkfs option should be enabled (filetype in dirent) // - this feature allows the inode type to be stored in the directory structure // - mkfs.xfs 4.2.0 enabled ftype by default (supported since mkfs.xfs 3.2.0) for XFSv4 volumes // - when CRCs are enabled via -m crc=1, the ftype functionality is always enabled // - ftype is madatory in XFSv5 volumes but it is optional for XFSv4 volumes // - the "ftype" option must be specified after the "crc" option in mkfs.xfs < 4.2.0: // https://git.kernel.org/cgit/fs/xfs/xfsprogs-dev.git/commit/?id=b990de8ba4e2df2bc76a140799d3ddb4a0eac4ce // - do not set ftype=1 with crc=1 as mkfs.xfs may fail when both options are enabled (at least with xfsprogs-3.2.2) // - XFSv4 with ftype=1 is supported since linux-3.13. We purposely always // disable ftype for V4 volumes to keep them compatible with older kernels if (xfstoolsver >= PROGVER(3,2,0)) // only use "ftype" option when it is supported by mkfs { // ftype is already set to 1 when it is XFSv5 if (xfsver==XFS_SB_VERSION_4) strlcatf(mkfsopts, sizeof(mkfsopts), " -n ftype=0 "); } // Determine if the "sparse" mkfs option should be enabled (sparse inode allocation) // - starting with linux-4.2 XFS can allocate discontinuous inode chunks // - this feature relies on the new v5 on-disk format but it is optional // - this feature will be enabled if the original filesystem was XFSv5 and had it // - this feature is supported since mkfs.xfs 4.2.0, but unfortunately // mkfs.xfs 4.5.0 in RHEL 7.3 carries a custom patch removing the option, // hence require mkfs.xfs 4.7.0, next version after 4.5.0 if (xfstoolsver >= PROGVER(4,7,0)) // only use "sparse" option when it is supported by mkfs { optval = ((xfsver==XFS_SB_VERSION_5) && (sb_features_incompat & XFS_SB_FEAT_INCOMPAT_SPINODES)); strlcatf(mkfsopts, sizeof(mkfsopts), " -i sparse=%d ", (int)optval); } // ---- create the new filesystem using mkfs.xfs if (exec_command(command, sizeof(command), &exitst, NULL, 0, NULL, 0, "mkfs.xfs -f %s %s", partition, mkfsopts)!=0 || exitst!=0) { errprintf("command [%s] failed\n", command); return -1; } // ---- use xfs_admin to set the UUID if not already done with mkfs.xfs if (xadmopts[0]) { if (exec_command(command, sizeof(command), &exitst, NULL, 0, NULL, 0, "xfs_admin %s %s", xadmopts, partition)!=0 || exitst!=-0) { errprintf("command [%s] failed\n", command); return -1; } } return 0; }