blob: 2f2995e13e3ff761f1657e9bdcd831060dac13b3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2000-2005 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include "libfrog/util.h"
#include "libxfs.h"
#include <ctype.h>
#include "xfs_multidisk.h"
#include "libxcmd.h"
#include "libfrog/fsgeom.h"
#include "libfrog/convert.h"
#include "libfrog/crc32cselftest.h"
#include "libfrog/dahashselftest.h"
#include "proto.h"
#include <ini.h>
#define TERABYTES(count, blog) ((uint64_t)(count) << (40 - (blog)))
#define GIGABYTES(count, blog) ((uint64_t)(count) << (30 - (blog)))
#define MEGABYTES(count, blog) ((uint64_t)(count) << (20 - (blog)))
/*
* Realistically, the log should never be smaller than 64MB. Studies by the
* kernel maintainer in early 2022 have shown a dramatic reduction in long tail
* latency of the xlog grant head waitqueue when running a heavy metadata
* update workload when the log size is at least 64MB.
*/
#define XFS_MIN_REALISTIC_LOG_BLOCKS(blog) (MEGABYTES(64, (blog)))
/*
* Use this macro before we have superblock and mount structure to
* convert from basic blocks to filesystem blocks.
*/
#define DTOBT(d, bl) ((xfs_rfsblock_t)((d) >> ((bl) - BBSHIFT)))
/*
* amount (in bytes) we zero at the beginning and end of the device to
* remove traces of other filesystems, raid superblocks, etc.
*/
#define WHACK_SIZE (128 * 1024)
/*
* XXX: The configured block and sector sizes are defined as global variables so
* that they don't need to be passed to getnum/cvtnum().
*/
static unsigned int blocksize;
static unsigned int sectorsize;
/*
* Enums for each CLI parameter type are declared first so we can calculate the
* maximum array size needed to hold them automatically.
*/
enum {
B_SIZE = 0,
B_MAX_OPTS,
};
enum {
C_OPTFILE = 0,
C_MAX_OPTS,
};
enum {
D_AGCOUNT = 0,
D_FILE,
D_NAME,
D_SIZE,
D_SUNIT,
D_SWIDTH,
D_AGSIZE,
D_SU,
D_SW,
D_SECTSIZE,
D_NOALIGN,
D_RTINHERIT,
D_PROJINHERIT,
D_EXTSZINHERIT,
D_COWEXTSIZE,
D_DAXINHERIT,
D_MAX_OPTS,
};
enum {
I_ALIGN = 0,
I_MAXPCT,
I_PERBLOCK,
I_SIZE,
I_ATTR,
I_PROJID32BIT,
I_SPINODES,
I_NREXT64,
I_MAX_OPTS,
};
enum {
L_AGNUM = 0,
L_INTERNAL,
L_SIZE,
L_VERSION,
L_SUNIT,
L_SU,
L_DEV,
L_SECTSIZE,
L_FILE,
L_NAME,
L_LAZYSBCNTR,
L_MAX_OPTS,
};
enum {
N_SIZE = 0,
N_VERSION,
N_FTYPE,
N_MAX_OPTS,
};
enum {
P_FILE = 0,
P_SLASHES,
P_MAX_OPTS,
};
enum {
R_EXTSIZE = 0,
R_SIZE,
R_DEV,
R_FILE,
R_NAME,
R_NOALIGN,
R_MAX_OPTS,
};
enum {
S_SIZE = 0,
S_SECTSIZE,
S_MAX_OPTS,
};
enum {
M_CRC = 0,
M_FINOBT,
M_UUID,
M_RMAPBT,
M_REFLINK,
M_INOBTCNT,
M_BIGTIME,
M_MAX_OPTS,
};
/*
* Just define the max options array size manually to the largest
* enum right now, leaving room for a NULL terminator at the end
*/
#define MAX_SUBOPTS (D_MAX_OPTS + 1)
#define SUBOPT_NEEDS_VAL (-1LL)
#define MAX_CONFLICTS 8
#define LAST_CONFLICT (-1)
/*
* Table for parsing mkfs parameters.
*
* Description of the structure members follows:
*
* name MANDATORY
* Name is a single char, e.g., for '-d file', name is 'd'.
*
* ini_section MANDATORY
* This field is required to connect each opt_params (that is to say, each
* option class) to a section in the config file. The only option class this
* is not required for is the config file specification class itself.
* The section name is a string, not longer than MAX_INI_NAME_LEN.
*
* subopts MANDATORY
* Subopts is a list of strings naming suboptions. In the example above,
* it would contain "file". The last entry of this list has to be NULL.
*
* subopt_params MANDATORY
* This is a list of structs tied with subopts. For each entry in subopts,
* a corresponding entry has to be defined:
*
* subopt_params struct:
* index MANDATORY
* This number, starting from zero, denotes which item in subopt_params
* it is. The index has to be the same as is the order in subopts list,
* so we can access the right item both in subopt_param and subopts.
*
* seen INTERNAL
* Do not set this flag when definning a subopt. It is used to remeber that
* this subopt was already seen, for example for conflicts detection.
*
* str_seen INTERNAL
* Do not set. It is used internally for respecification, when some options
* has to be parsed twice - at first as a string, then later as a number.
*
* convert OPTIONAL
* A flag signalling whether the user-given value can use suffixes.
* If you want to allow the use of user-friendly values like 13k, 42G,
* set it to true.
*
* is_power_2 OPTIONAL
* An optional flag for subopts where the given value has to be a power
* of two.
*
* conflicts MANDATORY
* If your subopt is in a conflict with some other option, specify it.
* Accepts the .index values of the conflicting subopts and the last
* member of this list has to be LAST_CONFLICT.
*
* minval, maxval OPTIONAL
* These options are used for automatic range check and they have to be
* always used together in pair. If you don't want to limit the max value,
* use something like UINT_MAX. If no value is given, then you must either
* supply your own validation, or refuse any value in the 'case
* X_SOMETHING' block. If you forget to define the min and max value, but
* call a standard function for validating user's value, it will cause an
* error message notifying you about this issue.
*
* (Said in another way, you can't have minval and maxval both equal
* to zero. But if one value is different: minval=0 and maxval=1,
* then it is OK.)
*
* defaultval MANDATORY
* The value used if user specifies the subopt, but no value.
* If the subopt accepts some values (-d file=[1|0]), then this
* sets what is used with simple specifying the subopt (-d file).
* A special SUBOPT_NEEDS_VAL can be used to require a user-given
* value in any case.
*/
struct opt_params {
const char name;
#define MAX_INI_NAME_LEN 32
const char ini_section[MAX_INI_NAME_LEN];
const char *subopts[MAX_SUBOPTS];
struct subopt_param {
int index;
bool seen;
bool str_seen;
bool convert;
bool is_power_2;
struct _conflict {
struct opt_params *opts;
int subopt;
} conflicts[MAX_CONFLICTS];
long long minval;
long long maxval;
long long defaultval;
} subopt_params[MAX_SUBOPTS];
};
/*
* The two dimensional conflict array requires some initialisations to know
* about tables that haven't yet been defined. Work around this ordering
* issue with extern definitions here.
*/
static struct opt_params sopts;
static struct opt_params bopts = {
.name = 'b',
.ini_section = "block",
.subopts = {
[B_SIZE] = "size",
[B_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = B_SIZE,
.convert = true,
.is_power_2 = true,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = XFS_MIN_BLOCKSIZE,
.maxval = XFS_MAX_BLOCKSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
},
};
/*
* Config file specification. Usage is:
*
* mkfs.xfs -c options=<name>
*
* A subopt is used for the filename so in future we can extend the behaviour
* of the config file (e.g. specified defaults rather than options) if we ever
* have a need to do that sort of thing.
*/
static struct opt_params copts = {
.name = 'c',
.subopts = {
[C_OPTFILE] = "options",
[C_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = C_OPTFILE,
.conflicts = { { NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
},
};
static struct opt_params dopts = {
.name = 'd',
.ini_section = "data",
.subopts = {
[D_AGCOUNT] = "agcount",
[D_FILE] = "file",
[D_NAME] = "name",
[D_SIZE] = "size",
[D_SUNIT] = "sunit",
[D_SWIDTH] = "swidth",
[D_AGSIZE] = "agsize",
[D_SU] = "su",
[D_SW] = "sw",
[D_SECTSIZE] = "sectsize",
[D_NOALIGN] = "noalign",
[D_RTINHERIT] = "rtinherit",
[D_PROJINHERIT] = "projinherit",
[D_EXTSZINHERIT] = "extszinherit",
[D_COWEXTSIZE] = "cowextsize",
[D_DAXINHERIT] = "daxinherit",
[D_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = D_AGCOUNT,
.conflicts = { { &dopts, D_AGSIZE },
{ NULL, LAST_CONFLICT } },
.minval = 1,
.maxval = XFS_MAX_AGNUMBER,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_FILE,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = D_NAME,
.conflicts = { { NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_SIZE,
.conflicts = { { NULL, LAST_CONFLICT } },
.convert = true,
.minval = XFS_AG_MIN_BYTES,
.maxval = LLONG_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_SUNIT,
.conflicts = { { &dopts, D_NOALIGN },
{ &dopts, D_SU },
{ &dopts, D_SW },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_SWIDTH,
.conflicts = { { &dopts, D_NOALIGN },
{ &dopts, D_SU },
{ &dopts, D_SW },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_AGSIZE,
.conflicts = { { &dopts, D_AGCOUNT },
{ NULL, LAST_CONFLICT } },
.convert = true,
.minval = XFS_AG_MIN_BYTES,
.maxval = XFS_AG_MAX_BYTES,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_SU,
.conflicts = { { &dopts, D_NOALIGN },
{ &dopts, D_SUNIT },
{ &dopts, D_SWIDTH },
{ NULL, LAST_CONFLICT } },
.convert = true,
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_SW,
.conflicts = { { &dopts, D_NOALIGN },
{ &dopts, D_SUNIT },
{ &dopts, D_SWIDTH },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_SECTSIZE,
.conflicts = { { &sopts, S_SIZE },
{ &sopts, S_SECTSIZE },
{ NULL, LAST_CONFLICT } },
.convert = true,
.is_power_2 = true,
.minval = XFS_MIN_SECTORSIZE,
.maxval = XFS_MAX_SECTORSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_NOALIGN,
.conflicts = { { &dopts, D_SU },
{ &dopts, D_SW },
{ &dopts, D_SUNIT },
{ &dopts, D_SWIDTH },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = D_RTINHERIT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = D_PROJINHERIT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_EXTSZINHERIT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_COWEXTSIZE,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = D_DAXINHERIT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
},
};
static struct opt_params iopts = {
.name = 'i',
.ini_section = "inode",
.subopts = {
[I_ALIGN] = "align",
[I_MAXPCT] = "maxpct",
[I_PERBLOCK] = "perblock",
[I_SIZE] = "size",
[I_ATTR] = "attr",
[I_PROJID32BIT] = "projid32bit",
[I_SPINODES] = "sparse",
[I_NREXT64] = "nrext64",
[I_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = I_ALIGN,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = I_MAXPCT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 100,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = I_PERBLOCK,
.conflicts = { { &iopts, I_SIZE },
{ NULL, LAST_CONFLICT } },
.is_power_2 = true,
.minval = XFS_MIN_INODE_PERBLOCK,
.maxval = XFS_MAX_BLOCKSIZE / XFS_DINODE_MIN_SIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = I_SIZE,
.conflicts = { { &iopts, I_PERBLOCK },
{ NULL, LAST_CONFLICT } },
.is_power_2 = true,
.minval = XFS_DINODE_MIN_SIZE,
.maxval = XFS_DINODE_MAX_SIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = I_ATTR,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 2,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = I_PROJID32BIT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = I_SPINODES,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = I_NREXT64,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
}
},
};
static struct opt_params lopts = {
.name = 'l',
.ini_section = "log",
.subopts = {
[L_AGNUM] = "agnum",
[L_INTERNAL] = "internal",
[L_SIZE] = "size",
[L_VERSION] = "version",
[L_SUNIT] = "sunit",
[L_SU] = "su",
[L_DEV] = "logdev",
[L_SECTSIZE] = "sectsize",
[L_FILE] = "file",
[L_NAME] = "name",
[L_LAZYSBCNTR] = "lazy-count",
[L_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = L_AGNUM,
.conflicts = { { &lopts, L_DEV },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = UINT_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_INTERNAL,
.conflicts = { { &lopts, L_FILE },
{ &lopts, L_DEV },
{ &lopts, L_SECTSIZE },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = L_SIZE,
.conflicts = { { NULL, LAST_CONFLICT } },
.convert = true,
.minval = 2 * 1024 * 1024LL, /* XXX: XFS_MIN_LOG_BYTES */
.maxval = XFS_MAX_LOG_BYTES,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_VERSION,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 1,
.maxval = 2,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_SUNIT,
.conflicts = { { &lopts, L_SU },
{ NULL, LAST_CONFLICT } },
.minval = 1,
.maxval = BTOBB(XLOG_MAX_RECORD_BSIZE),
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_SU,
.conflicts = { { &lopts, L_SUNIT },
{ NULL, LAST_CONFLICT } },
.convert = true,
.minval = BBTOB(1),
.maxval = XLOG_MAX_RECORD_BSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_DEV,
.conflicts = { { &lopts, L_AGNUM },
{ &lopts, L_NAME },
{ &lopts, L_INTERNAL },
{ NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_SECTSIZE,
.conflicts = { { &lopts, L_INTERNAL },
{ NULL, LAST_CONFLICT } },
.convert = true,
.is_power_2 = true,
.minval = XFS_MIN_SECTORSIZE,
.maxval = XFS_MAX_SECTORSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_FILE,
.conflicts = { { &lopts, L_INTERNAL },
{ NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = L_NAME,
.conflicts = { { &lopts, L_AGNUM },
{ &lopts, L_DEV },
{ &lopts, L_INTERNAL },
{ NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = L_LAZYSBCNTR,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
},
};
static struct opt_params nopts = {
.name = 'n',
.ini_section = "naming",
.subopts = {
[N_SIZE] = "size",
[N_VERSION] = "version",
[N_FTYPE] = "ftype",
[N_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = N_SIZE,
.conflicts = { { NULL, LAST_CONFLICT } },
.convert = true,
.is_power_2 = true,
.minval = 1 << XFS_MIN_REC_DIRSIZE,
.maxval = XFS_MAX_BLOCKSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = N_VERSION,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 2,
.maxval = 2,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = N_FTYPE,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
},
};
static struct opt_params popts = {
.name = 'p',
.ini_section = "proto",
.subopts = {
[P_FILE] = "file",
[P_SLASHES] = "slashes_are_spaces",
[P_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = P_FILE,
.conflicts = { { NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = P_SLASHES,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
},
};
static struct opt_params ropts = {
.name = 'r',
.ini_section = "realtime",
.subopts = {
[R_EXTSIZE] = "extsize",
[R_SIZE] = "size",
[R_DEV] = "rtdev",
[R_FILE] = "file",
[R_NAME] = "name",
[R_NOALIGN] = "noalign",
[R_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = R_EXTSIZE,
.conflicts = { { NULL, LAST_CONFLICT } },
.convert = true,
.minval = XFS_MIN_RTEXTSIZE,
.maxval = XFS_MAX_RTEXTSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = R_SIZE,
.conflicts = { { NULL, LAST_CONFLICT } },
.convert = true,
.minval = 0,
.maxval = LLONG_MAX,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = R_DEV,
.conflicts = { { &ropts, R_NAME },
{ NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = R_FILE,
.minval = 0,
.maxval = 1,
.defaultval = 1,
.conflicts = { { NULL, LAST_CONFLICT } },
},
{ .index = R_NAME,
.conflicts = { { &ropts, R_DEV },
{ NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = R_NOALIGN,
.minval = 0,
.maxval = 1,
.defaultval = 1,
.conflicts = { { NULL, LAST_CONFLICT } },
},
},
};
static struct opt_params sopts = {
.name = 's',
.ini_section = "sector",
.subopts = {
[S_SIZE] = "size",
[S_SECTSIZE] = "sectsize",
[S_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = S_SIZE,
.conflicts = { { &sopts, S_SECTSIZE },
{ &dopts, D_SECTSIZE },
{ NULL, LAST_CONFLICT } },
.convert = true,
.is_power_2 = true,
.minval = XFS_MIN_SECTORSIZE,
.maxval = XFS_MAX_SECTORSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = S_SECTSIZE,
.conflicts = { { &sopts, S_SIZE },
{ &dopts, D_SECTSIZE },
{ NULL, LAST_CONFLICT } },
.convert = true,
.is_power_2 = true,
.minval = XFS_MIN_SECTORSIZE,
.maxval = XFS_MAX_SECTORSIZE,
.defaultval = SUBOPT_NEEDS_VAL,
},
},
};
static struct opt_params mopts = {
.name = 'm',
.ini_section = "metadata",
.subopts = {
[M_CRC] = "crc",
[M_FINOBT] = "finobt",
[M_UUID] = "uuid",
[M_RMAPBT] = "rmapbt",
[M_REFLINK] = "reflink",
[M_INOBTCNT] = "inobtcount",
[M_BIGTIME] = "bigtime",
[M_MAX_OPTS] = NULL,
},
.subopt_params = {
{ .index = M_CRC,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = M_FINOBT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = M_UUID,
.conflicts = { { NULL, LAST_CONFLICT } },
.defaultval = SUBOPT_NEEDS_VAL,
},
{ .index = M_RMAPBT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = M_REFLINK,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = M_INOBTCNT,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
{ .index = M_BIGTIME,
.conflicts = { { NULL, LAST_CONFLICT } },
.minval = 0,
.maxval = 1,
.defaultval = 1,
},
},
};
/* quick way of checking if a parameter was set on the CLI */
static bool
cli_opt_set(
struct opt_params *opts,
int subopt)
{
return opts->subopt_params[subopt].seen ||
opts->subopt_params[subopt].str_seen;
}
/*
* Options configured on the command line.
*
* This stores all the specific config parameters the user sets on the command
* line. We do not use these values directly - they are inputs to the mkfs
* geometry validation and override any default configuration value we have.
*
* We don't keep flags to indicate what parameters are set - if we need to check
* if an option was set on the command line, we check the relevant entry in the
* option table which records whether it was specified in the .seen and
* .str_seen variables in the table.
*
* Some parameters are stored as strings for post-parsing after their dependent
* options have been resolved (e.g. block size and sector size have been parsed
* and validated).
*
* This allows us to check that values have been set without needing separate
* flags for each value, and hence avoids needing to record and check for each
* specific option that can set the value later on in the code. In the cases
* where we don't have a cli_params structure around, the above cli_opt_set()
* function can be used.
*/
struct sb_feat_args {
int log_version;
int attr_version;
int dir_version;
bool inode_align; /* XFS_SB_VERSION_ALIGNBIT */
bool nci; /* XFS_SB_VERSION_BORGBIT */
bool lazy_sb_counters; /* XFS_SB_VERSION2_LAZYSBCOUNTBIT */
bool parent_pointers; /* XFS_SB_VERSION2_PARENTBIT */
bool projid32bit; /* XFS_SB_VERSION2_PROJID32BIT */
bool crcs_enabled; /* XFS_SB_VERSION2_CRCBIT */
bool dirftype; /* XFS_SB_VERSION2_FTYPE */
bool finobt; /* XFS_SB_FEAT_RO_COMPAT_FINOBT */
bool spinodes; /* XFS_SB_FEAT_INCOMPAT_SPINODES */
bool rmapbt; /* XFS_SB_FEAT_RO_COMPAT_RMAPBT */
bool reflink; /* XFS_SB_FEAT_RO_COMPAT_REFLINK */
bool inobtcnt; /* XFS_SB_FEAT_RO_COMPAT_INOBTCNT */
bool bigtime; /* XFS_SB_FEAT_INCOMPAT_BIGTIME */
bool nodalign;
bool nortalign;
bool nrext64;
};
struct cli_params {
int sectorsize;
int blocksize;
char *cfgfile;
char *protofile;
/* parameters that depend on sector/block size being validated. */
char *dsize;
char *agsize;
char *dsu;
char *dirblocksize;
char *logsize;
char *lsu;
char *rtextsize;
char *rtsize;
/* parameters where 0 is a valid CLI value */
int dsunit;
int dswidth;
int dsw;
int64_t logagno;
int loginternal;
int lsunit;
int is_supported;
int proto_slashes_are_spaces;
/* parameters where 0 is not a valid value */
int64_t agcount;
int inodesize;
int inopblock;
int imaxpct;
int lsectorsize;
uuid_t uuid;
/* feature flags that are set */
struct sb_feat_args sb_feat;
/* root inode characteristics */
struct fsxattr fsx;
/* libxfs device setup */
struct libxfs_xinit *xi;
};
/*
* Calculated filesystem feature and geometry information.
*
* This structure contains the information we will use to create the on-disk
* filesystem from. The validation and calculation code uses it to store all the
* temporary and final config state for the filesystem.
*
* The information in this structure will contain a mix of validated CLI input
* variables, default feature state and calculated values that are needed to
* construct the superblock and other on disk features. These are all in one
* place so that we don't have to pass handfuls of seemingly arbitrary variables
* around to different functions to do the work we need to do.
*/
struct mkfs_params {
int blocksize;
int blocklog;
int sectorsize;
int sectorlog;
int lsectorsize;
int lsectorlog;
int dirblocksize;
int dirblocklog;
int inodesize;
int inodelog;
int inopblock;
uint64_t dblocks;
uint64_t logblocks;
uint64_t rtblocks;
uint64_t rtextblocks;
uint64_t rtextents;
uint64_t rtbmblocks; /* rt bitmap blocks */
int dsunit; /* in FSBs */
int dswidth; /* in FSBs */
int lsunit; /* in FSBs */
uint64_t agsize;
uint64_t agcount;
int imaxpct;
bool loginternal;
uint64_t logstart;
uint64_t logagno;
uuid_t uuid;
char *label;
struct sb_feat_args sb_feat;
};
/*
* Default filesystem features and configuration values
*
* This structure contains the default mkfs values that are to be used when
* a user does not specify the option on the command line. We do not use these
* values directly - they are inputs to the mkfs geometry validation and
* calculations.
*/
struct mkfs_default_params {
char *source; /* where the defaults came from */
int sectorsize;
int blocksize;
/* feature flags that are set */
struct sb_feat_args sb_feat;
/* root inode characteristics */
struct fsxattr fsx;
};
static void __attribute__((noreturn))
usage( void )
{
fprintf(stderr, _("Usage: %s\n\
/* blocksize */ [-b size=num]\n\
/* config file */ [-c options=xxx]\n\
/* metadata */ [-m crc=0|1,finobt=0|1,uuid=xxx,rmapbt=0|1,reflink=0|1,\n\
inobtcount=0|1,bigtime=0|1]\n\
/* data subvol */ [-d agcount=n,agsize=n,file,name=xxx,size=num,\n\
(sunit=value,swidth=value|su=num,sw=num|noalign),\n\
sectsize=num\n\
/* force overwrite */ [-f]\n\
/* inode size */ [-i perblock=n|size=num,maxpct=n,attr=0|1|2,\n\
projid32bit=0|1,sparse=0|1,nrext64=0|1]\n\
/* no discard */ [-K]\n\
/* log subvol */ [-l agnum=n,internal,size=num,logdev=xxx,version=n\n\
sunit=value|su=num,sectsize=num,lazy-count=0|1]\n\
/* label */ [-L label (maximum 12 characters)]\n\
/* naming */ [-n size=num,version=2|ci,ftype=0|1]\n\
/* no-op info only */ [-N]\n\
/* prototype file */ [-p fname]\n\
/* quiet */ [-q]\n\
/* realtime subvol */ [-r extsize=num,size=num,rtdev=xxx]\n\
/* sectorsize */ [-s size=num]\n\
/* version */ [-V]\n\
devicename\n\
<devicename> is required unless -d name=xxx is given.\n\
<num> is xxx (bytes), xxxs (sectors), xxxb (fs blocks), xxxk (xxx KiB),\n\
xxxm (xxx MiB), xxxg (xxx GiB), xxxt (xxx TiB) or xxxp (xxx PiB).\n\
<value> is xxx (512 byte blocks).\n"),
progname);
exit(1);
}
static void
conflict(
struct opt_params *opts,
int option,
struct opt_params *con_opts,
int conflict)
{
fprintf(stderr, _("Cannot specify both -%c %s and -%c %s\n"),
con_opts->name, con_opts->subopts[conflict],
opts->name, opts->subopts[option]);
usage();
}
static void
illegal(
const char *value,
const char *opt)
{
fprintf(stderr, _("Invalid value %s for -%s option\n"), value, opt);
usage();
}
static int
ispow2(
unsigned int i)
{
return (i & (i - 1)) == 0;
}
static void __attribute__((noreturn))
reqval(
char opt,
const char *tab[],
int idx)
{
fprintf(stderr, _("-%c %s option requires a value\n"), opt, tab[idx]);
usage();
}
static void
respec(
char opt,
const char *tab[],
int idx)
{
fprintf(stderr, "-%c ", opt);
if (tab)
fprintf(stderr, "%s ", tab[idx]);
fprintf(stderr, _("option respecified\n"));
usage();
}
static void
unknown(
const char opt,
const char *s)
{
fprintf(stderr, _("unknown option -%c %s\n"), opt, s);
usage();
}
static void
invalid_cfgfile_opt(
const char *filename,
const char *section,
const char *name,
const char *value)
{
fprintf(stderr, _("%s: invalid config file option: [%s]: %s=%s\n"),
filename, section, name, value);
}
static void
check_device_type(
const char *name,
int *isfile,
bool no_size,
bool no_name,
int *create,
const char *optname)
{
struct stat statbuf;
if (*isfile && (no_size || no_name)) {
fprintf(stderr,
_("if -%s file then -%s name and -%s size are required\n"),
optname, optname, optname);
usage();
}
if (!name) {
fprintf(stderr, _("No device name specified\n"));
usage();
}
if (stat(name, &statbuf)) {
if (errno == ENOENT && *isfile) {
if (create)
*create = 1;
return;
}
fprintf(stderr,
_("Error accessing specified device %s: %s\n"),
name, strerror(errno));
usage();
return;
}
/*
* We only want to completely truncate and recreate an existing file if
* we were specifically told it was a file. Set the create flag only in
* this case to trigger that behaviour.
*/
if (S_ISREG(statbuf.st_mode)) {
if (!*isfile)
*isfile = 1;
else if (create)
*create = 1;
return;
}
if (S_ISBLK(statbuf.st_mode)) {
if (*isfile) {
fprintf(stderr,
_("specified \"-%s file\" on a block device %s\n"),
optname, name);
usage();
}
return;
}
fprintf(stderr,
_("specified device %s not a file or block device\n"),
name);
usage();
}
static void
validate_overwrite(
const char *name,
bool force_overwrite)
{
if (!force_overwrite && check_overwrite(name)) {
fprintf(stderr,
_("%s: Use the -f option to force overwrite.\n"),
progname);
exit(1);
}
}
static void
validate_ag_geometry(
int blocklog,
uint64_t dblocks,
uint64_t agsize,
uint64_t agcount)
{
if (agsize < XFS_AG_MIN_BLOCKS(blocklog)) {
fprintf(stderr,
_("agsize (%lld blocks) too small, need at least %lld blocks\n"),
(long long)agsize,
(long long)XFS_AG_MIN_BLOCKS(blocklog));
usage();
}
if (agsize > XFS_AG_MAX_BLOCKS(blocklog)) {
fprintf(stderr,
_("agsize (%lld blocks) too big, maximum is %lld blocks\n"),
(long long)agsize,
(long long)XFS_AG_MAX_BLOCKS(blocklog));
usage();
}
if (agsize > dblocks) {
fprintf(stderr,
_("agsize (%lld blocks) too big, data area is %lld blocks\n"),
(long long)agsize, (long long)dblocks);
usage();
}
if (agsize < XFS_AG_MIN_BLOCKS(blocklog)) {
fprintf(stderr,
_("too many allocation groups for size = %lld\n"),
(long long)agsize);
fprintf(stderr, _("need at most %lld allocation groups\n"),
(long long)(dblocks / XFS_AG_MIN_BLOCKS(blocklog) +
(dblocks % XFS_AG_MIN_BLOCKS(blocklog) != 0)));
usage();
}
if (agsize > XFS_AG_MAX_BLOCKS(blocklog)) {
fprintf(stderr,
_("too few allocation groups for size = %lld\n"), (long long)agsize);
fprintf(stderr,
_("need at least %lld allocation groups\n"),
(long long)(dblocks / XFS_AG_MAX_BLOCKS(blocklog) +
(dblocks % XFS_AG_MAX_BLOCKS(blocklog) != 0)));
usage();
}
/*
* If the last AG is too small, reduce the filesystem size
* and drop the blocks.
*/
if ( dblocks % agsize != 0 &&
(dblocks % agsize < XFS_AG_MIN_BLOCKS(blocklog))) {
fprintf(stderr,
_("last AG size %lld blocks too small, minimum size is %lld blocks\n"),
(long long)(dblocks % agsize),
(long long)XFS_AG_MIN_BLOCKS(blocklog));
usage();
}
/*
* If agcount is too large, make it smaller.
*/
if (agcount > XFS_MAX_AGNUMBER + 1) {
fprintf(stderr,
_("%lld allocation groups is too many, maximum is %lld\n"),
(long long)agcount, (long long)XFS_MAX_AGNUMBER + 1);
usage();
}
}
static void
zero_old_xfs_structures(
libxfs_init_t *xi,
xfs_sb_t *new_sb)
{
void *buf;
xfs_sb_t sb;
uint32_t bsize;
int i;
xfs_off_t off;
/*
* We open regular files with O_TRUNC|O_CREAT. Nothing to do here...
*/
if (xi->disfile && xi->dcreat)
return;
/*
* read in existing filesystem superblock, use its geometry
* settings and zero the existing secondary superblocks.
*/
buf = memalign(libxfs_device_alignment(), new_sb->sb_sectsize);
if (!buf) {
fprintf(stderr,
_("error reading existing superblock -- failed to memalign buffer\n"));
return;
}
memset(buf, 0, new_sb->sb_sectsize);
/*
* If we are creating an image file, it might be of zero length at this
* point in time. Hence reading the existing superblock is going to
* return zero bytes. It's not a failure we need to warn about in this
* case.
*/
off = pread(xi->dfd, buf, new_sb->sb_sectsize, 0);
if (off != new_sb->sb_sectsize) {
if (!xi->disfile)
fprintf(stderr,
_("error reading existing superblock: %s\n"),
strerror(errno));
goto done;
}
libxfs_sb_from_disk(&sb, buf);
/*
* perform same basic superblock validation to make sure we
* actually zero secondary blocks
*/
if (sb.sb_magicnum != XFS_SB_MAGIC || sb.sb_blocksize == 0)
goto done;
for (bsize = 1, i = 0; bsize < sb.sb_blocksize &&
i < sizeof(sb.sb_blocksize) * NBBY; i++)
bsize <<= 1;
if (i < XFS_MIN_BLOCKSIZE_LOG || i > XFS_MAX_BLOCKSIZE_LOG ||
i != sb.sb_blocklog)
goto done;
if (sb.sb_dblocks > ((uint64_t)sb.sb_agcount * sb.sb_agblocks) ||
sb.sb_dblocks < ((uint64_t)(sb.sb_agcount - 1) *
sb.sb_agblocks + XFS_MIN_AG_BLOCKS))
goto done;
/*
* block size and basic geometry seems alright, zero the secondaries.
*/
memset(buf, 0, new_sb->sb_sectsize);
off = 0;
for (i = 1; i < sb.sb_agcount; i++) {
off += sb.sb_agblocks;
if (pwrite(xi->dfd, buf, new_sb->sb_sectsize,
off << sb.sb_blocklog) == -1)
break;
}
done:
free(buf);
}
static void
discard_blocks(dev_t dev, uint64_t nsectors, int quiet)
{
int fd;
uint64_t offset = 0;
/* Discard the device 2G at a time */
const uint64_t step = 2ULL << 30;
const uint64_t count = BBTOB(nsectors);
fd = libxfs_device_to_fd(dev);
if (fd <= 0)
return;
/* The block discarding happens in smaller batches so it can be
* interrupted prematurely
*/
while (offset < count) {
uint64_t tmp_step = min(step, count - offset);
/*
* We intentionally ignore errors from the discard ioctl. It is
* not necessary for the mkfs functionality but just an
* optimization. However we should stop on error.
*/
if (platform_discard_blocks(fd, offset, tmp_step) == 0) {
if (offset == 0 && !quiet) {
printf("Discarding blocks...");
fflush(stdout);
}
} else {
if (offset > 0 && !quiet)
printf("\n");
return;
}
offset += tmp_step;
}
if (offset > 0 && !quiet)
printf("Done.\n");
}
static __attribute__((noreturn)) void
illegal_option(
const char *value,
struct opt_params *opts,
int index,
const char *reason)
{
fprintf(stderr,
_("Invalid value %s for -%c %s option. %s\n"),
value, opts->name, opts->subopts[index],
reason);
usage();
}
/*
* Check for conflicts and option respecification.
*/
static void
check_opt(
struct opt_params *opts,
int index,
bool str_seen)
{
struct subopt_param *sp = &opts->subopt_params[index];
int i;
if (sp->index != index) {
fprintf(stderr,
_("Developer screwed up option parsing (%d/%d)! Please report!\n"),
sp->index, index);
reqval(opts->name, opts->subopts, index);
}
/*
* Check for respecification of the option. This is more complex than it
* seems because some options are parsed twice - once as a string during
* input parsing, then later the string is passed to getnum for
* conversion into a number and bounds checking. Hence the two variables
* used to track the different uses based on the @str parameter passed
* to us.
*/
if (!str_seen) {
if (sp->seen)
respec(opts->name, opts->subopts, index);
sp->seen = true;
} else {
if (sp->str_seen)
respec(opts->name, opts->subopts, index);
sp->str_seen = true;
}
/* check for conflicts with the option */
for (i = 0; i < MAX_CONFLICTS; i++) {
struct _conflict *con = &sp->conflicts[i];
if (con->subopt == LAST_CONFLICT)
break;
if (con->opts->subopt_params[con->subopt].seen ||
con->opts->subopt_params[con->subopt].str_seen)
conflict(opts, index, con->opts, con->subopt);
}
}
static long long
getnum(
const char *str,
struct opt_params *opts,
int index)
{
struct subopt_param *sp = &opts->subopt_params[index];
long long c;
check_opt(opts, index, false);
/* empty strings might just return a default value */
if (!str || *str == '\0') {
if (sp->defaultval == SUBOPT_NEEDS_VAL)
reqval(opts->name, opts->subopts, index);
return sp->defaultval;
}
if (sp->minval == 0 && sp->maxval == 0) {
fprintf(stderr,
_("Option -%c %s has undefined minval/maxval."
"Can't verify value range. This is a bug.\n"),
opts->name, opts->subopts[index]);
exit(1);
}
/*
* Some values are pure numbers, others can have suffixes that define
* the units of the number. Those get passed to cvtnum(), otherwise we
* convert it ourselves to guarantee there is no trailing garbage in the
* number.
*/
if (sp->convert) {
c = cvtnum(blocksize, sectorsize, str);
if (c == -1LL) {
illegal_option(str, opts, index,
_("Not a valid value or illegal suffix"));
}
} else {
char *str_end;
c = strtoll(str, &str_end, 0);
if (c == 0 && str_end == str)
illegal_option(str, opts, index,
_("Value not recognized as number."));
if (*str_end != '\0')
illegal_option(str, opts, index,
_("Unit suffixes are not allowed."));
}
/* Validity check the result. */
if (c < sp->minval)
illegal_option(str, opts, index, _("Value is too small."));
else if (c > sp->maxval)
illegal_option(str, opts, index, _("Value is too large."));
if (sp->is_power_2 && !ispow2(c))
illegal_option(str, opts, index, _("Value must be a power of 2."));
return c;
}
/*
* Option is a string - do all the option table work, and check there
* is actually an option string. Otherwise we don't do anything with the string
* here - validation will be done later when the string is converted to a value
* or used as a file/device path.
*/
static char *
getstr(
const char *str,
struct opt_params *opts,
int index)
{
char *ret;
check_opt(opts, index, true);
/* empty strings for string options are not valid */
if (!str || *str == '\0')
reqval(opts->name, opts->subopts, index);
ret = strdup(str);
if (!ret) {
fprintf(stderr, _("Out of memory while saving suboptions.\n"));
exit(1);
}
return ret;
}
static int
block_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case B_SIZE:
cli->blocksize = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
cfgfile_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case C_OPTFILE:
cli->cfgfile = getstr(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
data_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case D_AGCOUNT:
cli->agcount = getnum(value, opts, subopt);
break;
case D_AGSIZE:
cli->agsize = getstr(value, opts, subopt);
break;
case D_FILE:
cli->xi->disfile = getnum(value, opts, subopt);
break;
case D_NAME:
cli->xi->dname = getstr(value, opts, subopt);
break;
case D_SIZE:
cli->dsize = getstr(value, opts, subopt);
break;
case D_SUNIT:
cli->dsunit = getnum(value, opts, subopt);
break;
case D_SWIDTH:
cli->dswidth = getnum(value, opts, subopt);
break;
case D_SU:
cli->dsu = getstr(value, opts, subopt);
break;
case D_SW:
cli->dsw = getnum(value, opts, subopt);
break;
case D_NOALIGN:
cli->sb_feat.nodalign = getnum(value, opts, subopt);
break;
case D_SECTSIZE:
cli->sectorsize = getnum(value, opts, subopt);
break;
case D_RTINHERIT:
if (getnum(value, opts, subopt))
cli->fsx.fsx_xflags |= FS_XFLAG_RTINHERIT;
else
cli->fsx.fsx_xflags &= ~FS_XFLAG_RTINHERIT;
break;
case D_PROJINHERIT:
cli->fsx.fsx_projid = getnum(value, opts, subopt);
cli->fsx.fsx_xflags |= FS_XFLAG_PROJINHERIT;
break;
case D_EXTSZINHERIT:
cli->fsx.fsx_extsize = getnum(value, opts, subopt);
if (cli->fsx.fsx_extsize)
cli->fsx.fsx_xflags |= FS_XFLAG_EXTSZINHERIT;
else
cli->fsx.fsx_xflags &= ~FS_XFLAG_EXTSZINHERIT;
break;
case D_COWEXTSIZE:
cli->fsx.fsx_cowextsize = getnum(value, opts, subopt);
if (cli->fsx.fsx_cowextsize)
cli->fsx.fsx_xflags |= FS_XFLAG_COWEXTSIZE;
else
cli->fsx.fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
break;
case D_DAXINHERIT:
if (getnum(value, opts, subopt))
cli->fsx.fsx_xflags |= FS_XFLAG_DAX;
else
cli->fsx.fsx_xflags &= ~FS_XFLAG_DAX;
break;
default:
return -EINVAL;
}
return 0;
}
static int
inode_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case I_ALIGN:
cli->sb_feat.inode_align = getnum(value, opts, subopt);
break;
case I_MAXPCT:
cli->imaxpct = getnum(value, opts, subopt);
break;
case I_PERBLOCK:
cli->inopblock = getnum(value, opts, subopt);
break;
case I_SIZE:
cli->inodesize = getnum(value, opts, subopt);
break;
case I_ATTR:
cli->sb_feat.attr_version = getnum(value, opts, subopt);
break;
case I_PROJID32BIT:
cli->sb_feat.projid32bit = getnum(value, opts, subopt);
break;
case I_SPINODES:
cli->sb_feat.spinodes = getnum(value, opts, subopt);
break;
case I_NREXT64:
cli->sb_feat.nrext64 = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
log_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case L_AGNUM:
cli->logagno = getnum(value, opts, subopt);
break;
case L_FILE:
cli->xi->lisfile = getnum(value, opts, subopt);
break;
case L_INTERNAL:
cli->loginternal = getnum(value, opts, subopt);
break;
case L_SU:
cli->lsu = getstr(value, opts, subopt);
break;
case L_SUNIT:
cli->lsunit = getnum(value, opts, subopt);
break;
case L_NAME:
case L_DEV:
cli->xi->logname = getstr(value, opts, subopt);
cli->loginternal = 0;
break;
case L_VERSION:
cli->sb_feat.log_version = getnum(value, opts, subopt);
break;
case L_SIZE:
cli->logsize = getstr(value, opts, subopt);
break;
case L_SECTSIZE:
cli->lsectorsize = getnum(value, opts, subopt);
break;
case L_LAZYSBCNTR:
cli->sb_feat.lazy_sb_counters = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
meta_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case M_CRC:
cli->sb_feat.crcs_enabled = getnum(value, opts, subopt);
if (cli->sb_feat.crcs_enabled)
cli->sb_feat.dirftype = true;
break;
case M_FINOBT:
cli->sb_feat.finobt = getnum(value, opts, subopt);
break;
case M_UUID:
if (!value || *value == '\0')
reqval('m', opts->subopts, subopt);
if (platform_uuid_parse(value, &cli->uuid))
illegal(value, "m uuid");
break;
case M_RMAPBT:
cli->sb_feat.rmapbt = getnum(value, opts, subopt);
break;
case M_REFLINK:
cli->sb_feat.reflink = getnum(value, opts, subopt);
break;
case M_INOBTCNT:
cli->sb_feat.inobtcnt = getnum(value, opts, subopt);
break;
case M_BIGTIME:
cli->sb_feat.bigtime = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
naming_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case N_SIZE:
cli->dirblocksize = getstr(value, opts, subopt);
break;
case N_VERSION:
value = getstr(value, &nopts, subopt);
if (!strcasecmp(value, "ci")) {
/* ASCII CI mode */
cli->sb_feat.nci = true;
} else {
cli->sb_feat.dir_version = getnum(value, opts, subopt);
}
free((char *)value);
break;
case N_FTYPE:
cli->sb_feat.dirftype = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
proto_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case P_SLASHES:
cli->proto_slashes_are_spaces = getnum(value, opts, subopt);
break;
case P_FILE:
fallthrough;
default:
if (cli->protofile) {
if (subopt < 0)
subopt = P_FILE;
respec(opts->name, opts->subopts, subopt);
}
cli->protofile = strdup(value);
if (!cli->protofile) {
fprintf(stderr,
_("Out of memory while saving protofile option.\n"));
exit(1);
}
break;
}
return 0;
}
static int
rtdev_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case R_EXTSIZE:
cli->rtextsize = getstr(value, opts, subopt);
break;
case R_FILE:
cli->xi->risfile = getnum(value, opts, subopt);
break;
case R_NAME:
case R_DEV:
cli->xi->rtname = getstr(value, opts, subopt);
break;
case R_SIZE:
cli->rtsize = getstr(value, opts, subopt);
break;
case R_NOALIGN:
cli->sb_feat.nortalign = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
sector_opts_parser(
struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli)
{
switch (subopt) {
case S_SIZE:
case S_SECTSIZE:
cli->sectorsize = getnum(value, opts, subopt);
cli->lsectorsize = cli->sectorsize;
break;
default:
return -EINVAL;
}
return 0;
}
static struct subopts {
struct opt_params *opts;
int (*parser)(struct opt_params *opts,
int subopt,
const char *value,
struct cli_params *cli);
} subopt_tab[] = {
{ &bopts, block_opts_parser },
{ &copts, cfgfile_opts_parser },
{ &dopts, data_opts_parser },
{ &iopts, inode_opts_parser },
{ &lopts, log_opts_parser },
{ &mopts, meta_opts_parser },
{ &nopts, naming_opts_parser },
{ &popts, proto_opts_parser },
{ &ropts, rtdev_opts_parser },
{ &sopts, sector_opts_parser },
{ NULL, NULL },
};
static void
parse_subopts(
char opt,
char *arg,
struct cli_params *cli)
{
struct subopts *sop = &subopt_tab[0];
char *p;
int ret = 0;
while (sop->opts) {
if (sop->opts->name == opt)
break;
sop++;
}
/* should never happen */
if (!sop->opts)
return;
p = arg;
while (*p != '\0') {
char **subopts = (char **)sop->opts->subopts;
char *value;
int subopt;
subopt = getsubopt(&p, subopts, &value);
ret = (sop->parser)(sop->opts, subopt, value, cli);
if (ret)
unknown(opt, value);
}
}
static bool
parse_cfgopt(
const char *section,
const char *name,
const char *value,
struct cli_params *cli)
{
struct subopts *sop = &subopt_tab[0];
char **subopts;
int ret = 0;
int i;
while (sop->opts) {
if (sop->opts->ini_section[0] != '\0' &&
strcasecmp(section, sop->opts->ini_section) == 0)
break;
sop++;
}
/* Config files with unknown sections get caught here. */
if (!sop->opts)
goto invalid_opt;
subopts = (char **)sop->opts->subopts;
for (i = 0; i < MAX_SUBOPTS; i++) {
if (!subopts[i])
break;
if (strcasecmp(name, subopts[i]) == 0) {
ret = (sop->parser)(sop->opts, i, value, cli);
if (ret)
goto invalid_opt;
return true;
}
}
invalid_opt:
invalid_cfgfile_opt(cli->cfgfile, section, name, value);
return false;
}
static void
validate_sectorsize(
struct mkfs_params *cfg,
struct cli_params *cli,
struct mkfs_default_params *dft,
struct fs_topology *ft,
char *dfile,
int dry_run,
int force_overwrite)
{
/*
* Before anything else, verify that we are correctly operating on
* files or block devices and set the control parameters correctly.
*/
check_device_type(dfile, &cli->xi->disfile, !cli->dsize, !dfile,
dry_run ? NULL : &cli->xi->dcreat, "d");
if (!cli->loginternal)
check_device_type(cli->xi->logname, &cli->xi->lisfile,
!cli->logsize, !cli->xi->logname,
dry_run ? NULL : &cli->xi->lcreat, "l");
if (cli->xi->rtname)
check_device_type(cli->xi->rtname, &cli->xi->risfile,
!cli->rtsize, !cli->xi->rtname,
dry_run ? NULL : &cli->xi->rcreat, "r");
/*
* Explicitly disable direct IO for image files so we don't error out on
* sector size mismatches between the new filesystem and the underlying
* host filesystem.
*/
if (cli->xi->disfile || cli->xi->lisfile || cli->xi->risfile)
cli->xi->isdirect = 0;
memset(ft, 0, sizeof(*ft));
get_topology(cli->xi, ft, force_overwrite);
/* set configured sector sizes in preparation for checks */
if (!cli->sectorsize) {
/*
* Unless specified manually on the command line use the
* advertised sector size of the device. We use the physical
* sector size unless the requested block size is smaller
* than that, then we can use logical, but warn about the
* inefficiency.
*
* Set the topology sectors if they were not probed to the
* minimum supported sector size.
*/
if (!ft->lsectorsize)
ft->lsectorsize = dft->sectorsize;
/*
* Older kernels may not have physical/logical distinction.
*
* Some architectures have a page size > XFS_MAX_SECTORSIZE.
* In that case, a ramdisk or persistent memory device may
* advertise a physical sector size that is too big to use.
*/
if (!ft->psectorsize || ft->psectorsize > XFS_MAX_SECTORSIZE)
ft->psectorsize = ft->lsectorsize;
cfg->sectorsize = ft->psectorsize;
if (cfg->blocksize < cfg->sectorsize &&
cfg->blocksize >= ft->lsectorsize) {
fprintf(stderr,
_("specified blocksize %d is less than device physical sector size %d\n"
"switching to logical sector size %d\n"),
cfg->blocksize, ft->psectorsize,
ft->lsectorsize);
cfg->sectorsize = ft->lsectorsize;
}
} else
cfg->sectorsize = cli->sectorsize;
cfg->sectorlog = libxfs_highbit32(cfg->sectorsize);
/* validate specified/probed sector size */
if (cfg->sectorsize < XFS_MIN_SECTORSIZE ||
cfg->sectorsize > XFS_MAX_SECTORSIZE) {
fprintf(stderr, _("illegal sector size %d\n"), cfg->sectorsize);
usage();
}
if (cfg->blocksize < cfg->sectorsize) {
fprintf(stderr,
_("block size %d cannot be smaller than sector size %d\n"),
cfg->blocksize, cfg->sectorsize);
usage();
}
if (cfg->sectorsize < ft->lsectorsize) {
fprintf(stderr, _("illegal sector size %d; hw sector is %d\n"),
cfg->sectorsize, ft->lsectorsize);
usage();
}
}
static void
validate_blocksize(
struct mkfs_params *cfg,
struct cli_params *cli,
struct mkfs_default_params *dft)
{
/*
* Blocksize and sectorsize first, other things depend on them
* For RAID4/5/6 we want to align sector size and block size,
* so we need to start with the device geometry extraction too.
*/
if (!cli->blocksize)
cfg->blocksize = dft->blocksize;
else
cfg->blocksize = cli->blocksize;
cfg->blocklog = libxfs_highbit32(cfg->blocksize);
/* validate block sizes are in range */
if (cfg->blocksize < XFS_MIN_BLOCKSIZE ||
cfg->blocksize > XFS_MAX_BLOCKSIZE) {
fprintf(stderr, _("illegal block size %d\n"), cfg->blocksize);
usage();
}
if (cli->sb_feat.crcs_enabled &&
cfg->blocksize < XFS_MIN_CRC_BLOCKSIZE) {
fprintf(stderr,
_("Minimum block size for CRC enabled filesystems is %d bytes.\n"),
XFS_MIN_CRC_BLOCKSIZE);
usage();
}
}
/*
* Grab log sector size and validate.
*
* XXX: should we probe sector size on external log device rather than using
* the data device sector size?
*/
static void
validate_log_sectorsize(
struct mkfs_params *cfg,
struct cli_params *cli,
struct mkfs_default_params *dft)
{
if (cli->loginternal && cli->lsectorsize &&
cli->lsectorsize != cfg->sectorsize) {
fprintf(stderr,
_("Can't change sector size on internal log!\n"));
usage();
}
if (cli->lsectorsize)
cfg->lsectorsize = cli->lsectorsize;
else if (cli->loginternal)
cfg->lsectorsize = cfg->sectorsize;
else
cfg->lsectorsize = dft->sectorsize;
cfg->lsectorlog = libxfs_highbit32(cfg->lsectorsize);
if (cfg->lsectorsize < XFS_MIN_SECTORSIZE ||
cfg->lsectorsize > XFS_MAX_SECTORSIZE ||
cfg->lsectorsize > cfg->blocksize) {
fprintf(stderr, _("illegal log sector size %d\n"),
cfg->lsectorsize);
usage();
}
if (cfg->lsectorsize > XFS_MIN_SECTORSIZE) {
if (cli->sb_feat.log_version < 2) {
/* user specified non-default log version */
fprintf(stderr,
_("Version 1 logs do not support sector size %d\n"),
cfg->lsectorsize);
usage();
}
}
/* if lsu or lsunit was specified, automatically use v2 logs */
if ((cli_opt_set(&lopts, L_SU) || cli_opt_set(&lopts, L_SUNIT)) &&
cli->sb_feat.log_version == 1) {
fprintf(stderr,
_("log stripe unit specified, using v2 logs\n"));
cli->sb_feat.log_version = 2;
}
}
/*
* Check that the incoming features make sense. The CLI structure was
* initialised with the default values before parsing, so we can just
* check it and copy it straight across to the cfg structure if it
* checks out.
*/
static void
validate_sb_features(
struct mkfs_params *cfg,
struct cli_params *cli)
{
/*
* Now we have blocks and sector sizes set up, check parameters that are
* no longer optional for CRC enabled filesystems. Catch them up front
* here before doing anything else.
*/
if (cli->sb_feat.crcs_enabled) {
/* minimum inode size is 512 bytes, rest checked later */
if (cli->inodesize &&
cli->inodesize < (1 << XFS_DINODE_DFL_CRC_LOG)) {
fprintf(stderr,
_("Minimum inode size for CRCs is %d bytes\n"),
1 << XFS_DINODE_DFL_CRC_LOG);
usage();
}
/* inodes always aligned */
if (!cli->sb_feat.inode_align) {
fprintf(stderr,
_("Inodes always aligned for CRC enabled filesystems\n"));
usage();
}
/* lazy sb counters always on */
if (!cli->sb_feat.lazy_sb_counters) {
fprintf(stderr,
_("Lazy superblock counters always enabled for CRC enabled filesystems\n"));
usage();
}
/* version 2 logs always on */
if (cli->sb_feat.log_version != 2) {
fprintf(stderr,
_("V2 logs always enabled for CRC enabled filesystems\n"));
usage();
}
/* attr2 always on */
if (cli->sb_feat.attr_version != 2) {
fprintf(stderr,
_("V2 attribute format always enabled on CRC enabled filesystems\n"));
usage();
}
/* 32 bit project quota always on */
/* attr2 always on */
if (!cli->sb_feat.projid32bit) {
fprintf(stderr,
_("32 bit Project IDs always enabled on CRC enabled filesystems\n"));
usage();
}
/* ftype always on */
if (!cli->sb_feat.dirftype) {
fprintf(stderr,
_("Directory ftype field always enabled on CRC enabled filesystems\n"));
usage();
}
} else { /* !crcs_enabled */
/*
* The V4 filesystem format is deprecated in the upstream Linux
* kernel. In September 2025 it will be turned off by default
* in the kernel and in September 2030 support will be removed
* entirely.
*/
fprintf(stdout,
_("V4 filesystems are deprecated and will not be supported by future versions.\n"));
/*
* The kernel doesn't support crc=0,finobt=1 filesystems.
* If crcs are not enabled and the user has not explicitly
* turned finobt on, then silently turn it off to avoid an
* unnecessary warning.
* If the user explicitly tried to use crc=0,finobt=1,
* then issue an error.
* The same is also true for sparse inodes and reflink.
*/
if (cli->sb_feat.finobt && cli_opt_set(&mopts, M_FINOBT)) {
fprintf(stderr,
_("finobt not supported without CRC support\n"));
usage();
}
cli->sb_feat.finobt = false;
if (cli->sb_feat.spinodes && cli_opt_set(&iopts, I_SPINODES)) {
fprintf(stderr,
_("sparse inodes not supported without CRC support\n"));
usage();
}
cli->sb_feat.spinodes = false;
if (cli->sb_feat.rmapbt && cli_opt_set(&mopts, M_RMAPBT)) {
fprintf(stderr,
_("rmapbt not supported without CRC support\n"));
usage();
}
cli->sb_feat.rmapbt = false;
if (cli->sb_feat.reflink && cli_opt_set(&mopts, M_REFLINK)) {
fprintf(stderr,
_("reflink not supported without CRC support\n"));
usage();
}
cli->sb_feat.reflink = false;
if (cli->sb_feat.inobtcnt && cli_opt_set(&mopts, M_INOBTCNT)) {
fprintf(stderr,
_("inode btree counters not supported without CRC support\n"));
usage();
}
cli->sb_feat.inobtcnt = false;
if (cli->sb_feat.bigtime && cli_opt_set(&mopts, M_BIGTIME)) {
fprintf(stderr,
_("timestamps later than 2038 not supported without CRC support\n"));
usage();
}
cli->sb_feat.bigtime = false;
if (cli->sb_feat.nrext64 &&
cli_opt_set(&iopts, I_NREXT64)) {
fprintf(stderr,
_("64 bit extent count not supported without CRC support\n"));
usage();
}
cli->sb_feat.nrext64 = false;
}
if (!cli->sb_feat.finobt) {
if (cli->sb_feat.inobtcnt && cli_opt_set(&mopts, M_INOBTCNT)) {
fprintf(stderr,
_("inode btree counters not supported without finobt support\n"));
usage();
}
cli->sb_feat.inobtcnt = false;
}
if (cli->xi->rtname) {
if (cli->sb_feat.reflink && cli_opt_set(&mopts, M_REFLINK)) {
fprintf(stderr,
_("reflink not supported with realtime devices\n"));
usage();
}
cli->sb_feat.reflink = false;
if (cli->sb_feat.rmapbt && cli_opt_set(&mopts, M_RMAPBT)) {
fprintf(stderr,
_("rmapbt not supported with realtime devices\n"));
usage();
}
cli->sb_feat.rmapbt = false;
}
if ((cli->fsx.fsx_xflags & FS_XFLAG_COWEXTSIZE) &&
!cli->sb_feat.reflink) {
fprintf(stderr,
_("cowextsize not supported without reflink support\n"));
usage();
}
/*
* Copy features across to config structure now.
*/
cfg->sb_feat = cli->sb_feat;
if (!platform_uuid_is_null(&cli->uuid))
platform_uuid_copy(&cfg->uuid, &cli->uuid);
}
static void
validate_dirblocksize(
struct mkfs_params *cfg,
struct cli_params *cli)
{
if (cli->dirblocksize)
cfg->dirblocksize = getnum(cli->dirblocksize, &nopts, N_SIZE);
if (cfg->dirblocksize) {
if (cfg->dirblocksize < cfg->blocksize ||
cfg->dirblocksize > XFS_MAX_BLOCKSIZE) {
fprintf(stderr, _("illegal directory block size %d\n"),
cfg->dirblocksize);
usage();
}
cfg->dirblocklog = libxfs_highbit32(cfg->dirblocksize);
return;
}
/* use default size based on current block size */
if (cfg->blocksize < (1 << XFS_MIN_REC_DIRSIZE))
cfg->dirblocklog = XFS_MIN_REC_DIRSIZE;
else
cfg->dirblocklog = cfg->blocklog;
cfg->dirblocksize = 1 << cfg->dirblocklog;
}
static void
validate_inodesize(
struct mkfs_params *cfg,
struct cli_params *cli)
{
if (cli->inopblock)
cfg->inodelog = cfg->blocklog - libxfs_highbit32(cli->inopblock);
else if (cli->inodesize)
cfg->inodelog = libxfs_highbit32(cli->inodesize);
else if (cfg->sb_feat.crcs_enabled)
cfg->inodelog = XFS_DINODE_DFL_CRC_LOG;
else
cfg->inodelog = XFS_DINODE_DFL_LOG;
cfg->inodesize = 1 << cfg->inodelog;
cfg->inopblock = cfg->blocksize / cfg->inodesize;
/* input parsing has already validated non-crc inode size range */
if (cfg->sb_feat.crcs_enabled &&
cfg->inodelog < XFS_DINODE_DFL_CRC_LOG) {
fprintf(stderr,
_("Minimum inode size for CRCs is %d bytes\n"),
1 << XFS_DINODE_DFL_CRC_LOG);
usage();
}
if (cfg->inodesize > cfg->blocksize / XFS_MIN_INODE_PERBLOCK ||
cfg->inopblock < XFS_MIN_INODE_PERBLOCK ||
cfg->inodesize < XFS_DINODE_MIN_SIZE ||
cfg->inodesize > XFS_DINODE_MAX_SIZE) {
int maxsz;
fprintf(stderr, _("illegal inode size %d\n"), cfg->inodesize);
maxsz = min(cfg->blocksize / XFS_MIN_INODE_PERBLOCK,
XFS_DINODE_MAX_SIZE);
if (XFS_DINODE_MIN_SIZE == maxsz)
fprintf(stderr,
_("allowable inode size with %d byte blocks is %d\n"),
cfg->blocksize, XFS_DINODE_MIN_SIZE);
else
fprintf(stderr,
_("allowable inode size with %d byte blocks is between %d and %d\n"),
cfg->blocksize, XFS_DINODE_MIN_SIZE, maxsz);
exit(1);
}
}
static xfs_rfsblock_t
calc_dev_size(
char *size,
struct mkfs_params *cfg,
struct opt_params *opts,
int sizeopt,
char *type)
{
uint64_t dbytes;
xfs_rfsblock_t dblocks;
if (!size)
return 0;
dbytes = getnum(size, opts, sizeopt);
if (dbytes % XFS_MIN_BLOCKSIZE) {
fprintf(stderr,
_("illegal %s length %lld, not a multiple of %d\n"),
type, (long long)dbytes, XFS_MIN_BLOCKSIZE);
usage();
}
dblocks = (xfs_rfsblock_t)(dbytes >> cfg->blocklog);
if (dbytes % cfg->blocksize) {
fprintf(stderr,
_("warning: %s length %lld not a multiple of %d, truncated to %lld\n"),
type, (long long)dbytes, cfg->blocksize,
(long long)(dblocks << cfg->blocklog));
}
return dblocks;
}
static void
validate_rtextsize(
struct mkfs_params *cfg,
struct cli_params *cli,
struct fs_topology *ft)
{
uint64_t rtextbytes;
/*
* If specified, check rt extent size against its constraints.
*/
if (cli->rtextsize) {
rtextbytes = getnum(cli->rtextsize, &ropts, R_EXTSIZE);
if (rtextbytes % cfg->blocksize) {
fprintf(stderr,
_("illegal rt extent size %lld, not a multiple of %d\n"),
(long long)rtextbytes, cfg->blocksize);
usage();
}
cfg->rtextblocks = (xfs_extlen_t)(rtextbytes >> cfg->blocklog);
} else {
/*
* If realtime extsize has not been specified by the user,
* and the underlying volume is striped, then set rtextblocks
* to the stripe width.
*/
uint64_t rswidth;
if (!cfg->sb_feat.nortalign && !cli->xi->risfile &&
!(!cli->rtsize && cli->xi->disfile))
rswidth = ft->rtswidth;
else
rswidth = 0;
/* check that rswidth is a multiple of fs blocksize */
if (!cfg->sb_feat.nortalign && rswidth &&
!(BBTOB(rswidth) % cfg->blocksize)) {
rswidth = DTOBT(rswidth, cfg->blocklog);
rtextbytes = rswidth << cfg->blocklog;
if (rtextbytes > XFS_MIN_RTEXTSIZE &&
rtextbytes <= XFS_MAX_RTEXTSIZE) {
cfg->rtextblocks = rswidth;
}
}
if (!cfg->rtextblocks) {
cfg->rtextblocks = (cfg->blocksize < XFS_MIN_RTEXTSIZE)
? XFS_MIN_RTEXTSIZE >> cfg->blocklog
: 1;
}
}
ASSERT(cfg->rtextblocks);
}
/* Validate the incoming extsize hint. */
static void
validate_extsize_hint(
struct xfs_mount *mp,
struct cli_params *cli)
{
xfs_failaddr_t fa;
uint16_t flags = 0;
/*
* First we validate the extent size inherit hint on a directory so
* that we know that we'll be propagating a correct hint and flag to
* new files on the data device.
*/
if (cli->fsx.fsx_xflags & FS_XFLAG_EXTSZINHERIT)
flags |= XFS_DIFLAG_EXTSZINHERIT;
fa = libxfs_inode_validate_extsize(mp, cli->fsx.fsx_extsize, S_IFDIR,
flags);
if (fa) {
fprintf(stderr,
_("illegal extent size hint %lld, must be less than %u.\n"),
(long long)cli->fsx.fsx_extsize,
min(XFS_MAX_BMBT_EXTLEN, mp->m_sb.sb_agblocks / 2));
usage();
}
/*
* If the value is to be passed on to realtime files, revalidate with
* a realtime file so that we know the hint and flag that get passed on
* to realtime files will be correct.
*/
if (!(cli->fsx.fsx_xflags & FS_XFLAG_RTINHERIT))
return;
flags = XFS_DIFLAG_REALTIME;
if (cli->fsx.fsx_xflags & FS_XFLAG_EXTSZINHERIT)
flags |= XFS_DIFLAG_EXTSIZE;
fa = libxfs_inode_validate_extsize(mp, cli->fsx.fsx_extsize, S_IFREG,
flags);
if (fa) {
fprintf(stderr,
_("illegal extent size hint %lld, must be less than %u and a multiple of %u.\n"),
(long long)cli->fsx.fsx_extsize,
min(XFS_MAX_BMBT_EXTLEN, mp->m_sb.sb_agblocks / 2),
mp->m_sb.sb_rextsize);
usage();
}
}
/* Validate the incoming CoW extsize hint. */
static void
validate_cowextsize_hint(
struct xfs_mount *mp,
struct cli_params *cli)
{
xfs_failaddr_t fa;
uint64_t flags2 = 0;
/*
* Validate the copy on write extent size inherit hint on a directory
* so that we know that we'll be propagating a correct hint and flag to
* new files on the data device.
*/
if (cli->fsx.fsx_xflags & FS_XFLAG_COWEXTSIZE)
flags2 |= XFS_DIFLAG2_COWEXTSIZE;
fa = libxfs_inode_validate_cowextsize(mp, cli->fsx.fsx_cowextsize,
S_IFDIR, 0, flags2);
if (fa) {
fprintf(stderr,
_("illegal CoW extent size hint %lld, must be less than %u.\n"),
(long long)cli->fsx.fsx_cowextsize,
min(XFS_MAX_BMBT_EXTLEN, mp->m_sb.sb_agblocks / 2));
usage();
}
}
/* Complain if this filesystem is not a supported configuration. */
static void
validate_supported(
struct xfs_mount *mp,
struct cli_params *cli)
{
/* Undocumented option to enable unsupported tiny filesystems. */
if (!cli->is_supported) {
printf(
_("Filesystems formatted with --unsupported are not supported!!\n"));
return;
}
/*
* fstests has a large number of tests that create tiny filesystems to
* perform specific regression and resource depletion tests in a
* controlled environment. Avoid breaking fstests by allowing
* unsupported configurations if TEST_DIR, TEST_DEV, and QA_CHECK_FS
* are all set.
*/
if (getenv("TEST_DIR") && getenv("TEST_DEV") && getenv("QA_CHECK_FS"))
return;
/*
* We don't support filesystems smaller than 300MB anymore. Tiny
* filesystems have never been XFS' design target. This limit has been
* carefully calculated to prevent formatting with a log smaller than
* the "realistic" size.
*
* If the realistic log size is 64MB, there are four AGs, and the log
* AG should be at least 1/8 free after formatting, this gives us:
*
* 64MB * (8 / 7) * 4 = 293MB
*/
if (mp->m_sb.sb_dblocks < MEGABYTES(300, mp->m_sb.sb_blocklog)) {
fprintf(stderr,
_("Filesystem must be larger than 300MB.\n"));
usage();
}
/*
* For best performance, we don't allow unrealistically small logs.
* See the comment for XFS_MIN_REALISTIC_LOG_BLOCKS.
*/
if (mp->m_sb.sb_logblocks <
XFS_MIN_REALISTIC_LOG_BLOCKS(mp->m_sb.sb_blocklog)) {
fprintf(stderr,
_("Log size must be at least 64MB.\n"));
usage();
}
/*
* Filesystems should not have fewer than two AGs, because we need to
* have redundant superblocks.
*/
if (mp->m_sb.sb_agcount < 2) {
fprintf(stderr,
_("Filesystem must have at least 2 superblocks for redundancy!\n"));
usage();
}
}
/*
* Validate the configured stripe geometry, or is none is specified, pull
* the configuration from the underlying device.
*
* CLI parameters come in as different units, go out as filesystem blocks.
*/
static void
calc_stripe_factors(
struct mkfs_params *cfg,
struct cli_params *cli,
struct fs_topology *ft)
{
long long int big_dswidth;
int dsunit = 0;
int dswidth = 0;
int lsunit = 0;
int dsu = 0;
int dsw = 0;
int lsu = 0;
bool use_dev = false;
if (cli_opt_set(&dopts, D_SUNIT))
dsunit = cli->dsunit;
if (cli_opt_set(&dopts, D_SWIDTH))
dswidth = cli->dswidth;
if (cli_opt_set(&dopts, D_SU))
dsu = getnum(cli->dsu, &dopts, D_SU);
if (cli_opt_set(&dopts, D_SW))
dsw = cli->dsw;
/* data sunit/swidth options */
if (cli_opt_set(&dopts, D_SUNIT) != cli_opt_set(&dopts, D_SWIDTH)) {
fprintf(stderr,
_("both data sunit and data swidth options must be specified\n"));
usage();
}
/* convert dsu/dsw to dsunit/dswidth and use them from now on */
if (dsu || dsw) {
if (cli_opt_set(&dopts, D_SU) != cli_opt_set(&dopts, D_SW)) {
fprintf(stderr,
_("both data su and data sw options must be specified\n"));
usage();
}
big_dswidth = (long long int)dsu * dsw;
if (BTOBBT(big_dswidth) > INT_MAX) {
fprintf(stderr,
_("data stripe width (%lld) is too large of a multiple of the data stripe unit (%d)\n"),
big_dswidth, dsu);
usage();
}
if (!libxfs_validate_stripe_geometry(NULL, dsu, big_dswidth,
cfg->sectorsize, false))
usage();
dsunit = BTOBBT(dsu);
dswidth = BTOBBT(big_dswidth);
} else if (!libxfs_validate_stripe_geometry(NULL, BBTOB(dsunit),
BBTOB(dswidth), cfg->sectorsize, false)) {
usage();
}
/* If sunit & swidth were manually specified as 0, same as noalign */
if ((cli_opt_set(&dopts, D_SUNIT) || cli_opt_set(&dopts, D_SU)) &&
!dsunit && !dswidth)
cfg->sb_feat.nodalign = true;
/* if we are not using alignment, don't apply device defaults */
if (cfg->sb_feat.nodalign) {
cfg->dsunit = 0;
cfg->dswidth = 0;
goto check_lsunit;
}
/* if no stripe config set, use the device default */
if (!dsunit) {
/* Ignore nonsense from device report. */
if (!libxfs_validate_stripe_geometry(NULL, BBTOB(ft->dsunit),
BBTOB(ft->dswidth), 0, true)) {
fprintf(stderr,
_("%s: Volume reports invalid stripe unit (%d) and stripe width (%d), ignoring.\n"),
progname, BBTOB(ft->dsunit), BBTOB(ft->dswidth));
ft->dsunit = 0;
ft->dswidth = 0;
} else if (cfg->dblocks < GIGABYTES(1, cfg->blocklog)) {
/*
* Don't use automatic stripe detection if the device
* size is less than 1GB because the performance gains
* on such a small system are not worth the risk that
* we'll end up with an undersized log.
*/
if (ft->dsunit || ft->dswidth)
fprintf(stderr,
_("%s: small data volume, ignoring data volume stripe unit %d and stripe width %d\n"),
progname, ft->dsunit,
ft->dswidth);
ft->dsunit = 0;
ft->dswidth = 0;
} else {
dsunit = ft->dsunit;
dswidth = ft->dswidth;
use_dev = true;
}
} else {
/* check and warn if user-specified alignment is sub-optimal */
if (ft->dsunit && ft->dsunit != dsunit) {
fprintf(stderr,
_("%s: Specified data stripe unit %d is not the same as the volume stripe unit %d\n"),
progname, dsunit, ft->dsunit);
}
if (ft->dswidth && ft->dswidth != dswidth) {
fprintf(stderr,
_("%s: Specified data stripe width %d is not the same as the volume stripe width %d\n"),
progname, dswidth, ft->dswidth);
}
}
/*
* now we have our stripe config, check it's a multiple of block
* size.
*/
if ((BBTOB(dsunit) % cfg->blocksize) ||
(BBTOB(dswidth) % cfg->blocksize)) {
/*
* If we are using device defaults, just clear them and we're
* good to go. Otherwise bail out with an error.
*/
if (!use_dev) {
fprintf(stderr,
_("%s: Stripe unit(%d) or stripe width(%d) is not a multiple of the block size(%d)\n"),
progname, BBTOB(dsunit), BBTOB(dswidth),
cfg->blocksize);
exit(1);
}
dsunit = 0;
dswidth = 0;
cfg->sb_feat.nodalign = true;
}
/* convert from 512 byte blocks to fs blocksize */
cfg->dsunit = DTOBT(dsunit, cfg->blocklog);
cfg->dswidth = DTOBT(dswidth, cfg->blocklog);
check_lsunit:
/* log sunit options */
if (cli_opt_set(&lopts, L_SUNIT))
lsunit = cli->lsunit;
else if (cli_opt_set(&lopts, L_SU))
lsu = getnum(cli->lsu, &lopts, L_SU);
else if (cfg->lsectorsize > XLOG_HEADER_SIZE)
lsu = cfg->blocksize; /* lsunit matches filesystem block size */
if (lsu) {
/* verify if lsu is a multiple block size */
if (lsu % cfg->blocksize != 0) {
fprintf(stderr,
_("log stripe unit (%d) must be a multiple of the block size (%d)\n"),
lsu, cfg->blocksize);
usage();
}
lsunit = (int)BTOBBT(lsu);
}
if (BBTOB(lsunit) % cfg->blocksize != 0) {
fprintf(stderr,
_("log stripe unit (%d) must be a multiple of the block size (%d)\n"),
BBTOB(lsunit), cfg->blocksize);
usage();
}
/*
* check that log sunit is modulo fsblksize or default it to dsunit.
*/
if (lsunit) {
/* convert from 512 byte blocks to fs blocks */
cfg->lsunit = DTOBT(lsunit, cfg->blocklog);
} else if (cfg->sb_feat.log_version == 2 &&
cfg->loginternal && cfg->dsunit) {
/* lsunit and dsunit now in fs blocks */
cfg->lsunit = cfg->dsunit;
}
if (cfg->sb_feat.log_version == 2 &&
cfg->lsunit * cfg->blocksize > 256 * 1024) {
/* Warn only if specified on commandline */
if (cli->lsu || cli->lsunit != -1) {
fprintf(stderr,
_("log stripe unit (%d bytes) is too large (maximum is 256KiB)\n"
"log stripe unit adjusted to 32KiB\n"),
(cfg->lsunit * cfg->blocksize));
}
/* XXX: 64k block size? */
cfg->lsunit = (32 * 1024) / cfg->blocksize;
}
}
static void
open_devices(
struct mkfs_params *cfg,
struct libxfs_xinit *xi)
{
uint64_t sector_mask;
/*
* Initialize. This will open the log and rt devices as well.
*/
xi->setblksize = cfg->sectorsize;
if (!libxfs_init(xi))
usage();
if (!xi->ddev) {
fprintf(stderr, _("no device name given in argument list\n"));
usage();
}
/*
* Ok, Linux only has a 1024-byte resolution on device _size_,
* and the sizes below are in basic 512-byte blocks,
* so if we have (size % 2), on any partition, we can't get
* to the last 512 bytes. The same issue exists for larger
* sector sizes - we cannot write past the last sector.
*
* So, we reduce the size (in basic blocks) to a perfect
* multiple of the sector size, or 1024, whichever is larger.
*/
sector_mask = (uint64_t)-1 << (max(cfg->sectorlog, 10) - BBSHIFT);
xi->dsize &= sector_mask;
xi->rtsize &= sector_mask;
xi->logBBsize &= (uint64_t)-1 << (max(cfg->lsectorlog, 10) - BBSHIFT);
}
static void
discard_devices(
struct libxfs_xinit *xi,
int quiet)
{
/*
* This function has to be called after libxfs has been initialized.
*/
if (!xi->disfile)
discard_blocks(xi->ddev, xi->dsize, quiet);
if (xi->rtdev && !xi->risfile)
discard_blocks(xi->rtdev, xi->rtsize, quiet);
if (xi->logdev && xi->logdev != xi->ddev && !xi->lisfile)
discard_blocks(xi->logdev, xi->logBBsize, quiet);
}
static void
validate_datadev(
struct mkfs_params *cfg,
struct cli_params *cli)
{
struct libxfs_xinit *xi = cli->xi;
if (!xi->dsize) {
/*
* if the device is a file, we can't validate the size here.
* Instead, the file will be truncated to the correct length
* later on. if it's not a file, we've got a dud device.
*/
if (!xi->disfile) {
fprintf(stderr, _("can't get size of data subvolume\n"));
usage();
}
ASSERT(cfg->dblocks);
} else if (cfg->dblocks) {
/* check the size fits into the underlying device */
if (cfg->dblocks > DTOBT(xi->dsize, cfg->blocklog)) {
fprintf(stderr,
_("size %s specified for data subvolume is too large, maximum is %lld blocks\n"),
cli->dsize,
(long long)DTOBT(xi->dsize, cfg->blocklog));
usage();
}
} else {
/* no user size, so use the full block device */
cfg->dblocks = DTOBT(xi->dsize, cfg->blocklog);
}
if (cfg->dblocks < XFS_MIN_DATA_BLOCKS(cfg)) {
fprintf(stderr,
_("size %lld of data subvolume is too small, minimum %lld blocks\n"),
(long long)cfg->dblocks, XFS_MIN_DATA_BLOCKS(cfg));
usage();
}
if (xi->dbsize > cfg->sectorsize) {
fprintf(stderr, _(
"Warning: the data subvolume sector size %u is less than the sector size \n\
reported by the device (%u).\n"),
cfg->sectorsize, xi->dbsize);
}
}
/*
* This is more complex than it needs to be because we still support volume
* based external logs. They are only discovered *after* the devices have been
* opened, hence the crazy "is this really an internal log" checks here.
*/
static void
validate_logdev(
struct mkfs_params *cfg,
struct cli_params *cli,
char **devname)
{
struct libxfs_xinit *xi = cli->xi;
*devname = NULL;
/* check for volume log first */
if (cli->loginternal && xi->volname && xi->logdev) {
*devname = _("volume log");
cfg->loginternal = false;
} else
cfg->loginternal = cli->loginternal;
/* now run device checks */
if (cfg->loginternal) {
if (xi->logdev) {
fprintf(stderr,
_("can't have both external and internal logs\n"));
usage();
}
/*
* if no sector size has been specified on the command line,
* use what has been configured and validated for the data
* device.
*/
if (!cli->lsectorsize) {
cfg->lsectorsize = cfg->sectorsize;
cfg->lsectorlog = cfg->sectorlog;
}
if (cfg->sectorsize != cfg->lsectorsize) {
fprintf(stderr,
_("data and log sector sizes must be equal for internal logs\n"));
usage();
}
if (cli->logsize && cfg->logblocks >= cfg->dblocks) {
fprintf(stderr,
_("log size %lld too large for internal log\n"),
(long long)cfg->logblocks);
usage();
}
*devname = _("internal log");
return;
}
/* External/log subvolume checks */
if (xi->logname)
*devname = xi->logname;
if (!*devname || !xi->logdev) {
fprintf(stderr, _("no log subvolume or external log.\n"));
usage();
}
if (!cfg->logblocks) {
if (xi->logBBsize == 0) {
fprintf(stderr,
_("unable to get size of the log subvolume.\n"));
usage();
}
cfg->logblocks = DTOBT(xi->logBBsize, cfg->blocklog);
} else if (cfg->logblocks > DTOBT(xi->logBBsize, cfg->blocklog)) {
fprintf(stderr,
_("size %s specified for log subvolume is too large, maximum is %lld blocks\n"),
cli->logsize,
(long long)DTOBT(xi->logBBsize, cfg->blocklog));
usage();
}
if (xi->lbsize > cfg->lsectorsize) {
fprintf(stderr, _(
"Warning: the log subvolume sector size %u is less than the sector size\n\
reported by the device (%u).\n"),
cfg->lsectorsize, xi->lbsize);
}
}
static void
validate_rtdev(
struct mkfs_params *cfg,
struct cli_params *cli,
char **devname)
{
struct libxfs_xinit *xi = cli->xi;
*devname = NULL;
if (!xi->rtdev) {
if (cli->rtsize) {
fprintf(stderr,
_("size specified for non-existent rt subvolume\n"));
usage();
}
*devname = _("none");
cfg->rtblocks = 0;
cfg->rtextents = 0;
cfg->rtbmblocks = 0;
return;
}
if (!xi->rtsize) {
fprintf(stderr, _("Invalid zero length rt subvolume found\n"));
usage();
}
/* volume rtdev */
if (xi->volname)
*devname = _("volume rt");
else
*devname = xi->rtname;
if (cli->rtsize) {
if (cfg->rtblocks > DTOBT(xi->rtsize, cfg->blocklog)) {
fprintf(stderr,
_("size %s specified for rt subvolume is too large, maxi->um is %lld blocks\n"),
cli->rtsize,
(long long)DTOBT(xi->rtsize, cfg->blocklog));
usage();
}
if (xi->rtbsize > cfg->sectorsize) {
fprintf(stderr, _(
"Warning: the realtime subvolume sector size %u is less than the sector size\n\
reported by the device (%u).\n"),
cfg->sectorsize, xi->rtbsize);
}
} else {
/* grab volume size */
cfg->rtblocks = DTOBT(xi->rtsize, cfg->blocklog);
}
cfg->rtextents = cfg->rtblocks / cfg->rtextblocks;
cfg->rtbmblocks = (xfs_extlen_t)howmany(cfg->rtextents,
NBBY * cfg->blocksize);
}
static void
calculate_initial_ag_geometry(
struct mkfs_params *