blob: 039b1dcc5afa5ff895f587ebea8960528031a2a3 [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/topology.h"
#include "libfrog/convert.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)))
/*
* 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 {
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_MAX_OPTS,
};
enum {
I_ALIGN = 0,
I_MAXPCT,
I_PERBLOCK,
I_SIZE,
I_ATTR,
I_PROJID32BIT,
I_SPINODES,
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 {
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_MAX_OPTS,
};
/* Just define the max options array size manually right now */
#define MAX_SUBOPTS D_MAX_OPTS
#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'.
*
* 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;
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',
.subopts = {
[B_SIZE] = "size",
},
.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,
},
},
};
static struct opt_params dopts = {
.name = 'd',
.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",
},
.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 = 1,
.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,
},
},
};
static struct opt_params iopts = {
.name = 'i',
.subopts = {
[I_ALIGN] = "align",
[I_MAXPCT] = "maxpct",
[I_PERBLOCK] = "perblock",
[I_SIZE] = "size",
[I_ATTR] = "attr",
[I_PROJID32BIT] = "projid32bit",
[I_SPINODES] = "sparse",
},
.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,
},
},
};
static struct opt_params lopts = {
.name = 'l',
.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",
},
.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',
.subopts = {
[N_SIZE] = "size",
[N_VERSION] = "version",
[N_FTYPE] = "ftype",
},
.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 ropts = {
.name = 'r',
.subopts = {
[R_EXTSIZE] = "extsize",
[R_SIZE] = "size",
[R_DEV] = "rtdev",
[R_FILE] = "file",
[R_NAME] = "name",
[R_NOALIGN] = "noalign",
},
.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',
.subopts = {
[S_SIZE] = "size",
[S_SECTSIZE] = "sectsize",
},
.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',
.subopts = {
[M_CRC] = "crc",
[M_FINOBT] = "finobt",
[M_UUID] = "uuid",
[M_RMAPBT] = "rmapbt",
[M_REFLINK] = "reflink",
},
.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,
},
},
};
/* 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 nodalign;
bool nortalign;
};
struct cli_params {
int sectorsize;
int blocksize;
/* 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;
/* 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\
/* metadata */ [-m crc=0|1,finobt=0|1,uuid=xxx,rmapbt=0|1,reflink=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]\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(
char opt,
char *s)
{
fprintf(stderr, _("unknown option -%c %s\n"), opt, s);
usage();
}
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(
char *str,
struct opt_params *opts,
int index)
{
check_opt(opts, index, true);
/* empty strings for string options are not valid */
if (!str || *str == '\0')
reqval(opts->name, opts->subopts, index);
return str;
}
static int
block_opts_parser(
struct opt_params *opts,
int subopt,
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
data_opts_parser(
struct opt_params *opts,
int subopt,
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;
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);
cli->fsx.fsx_xflags |= FS_XFLAG_EXTSZINHERIT;
break;
case D_COWEXTSIZE:
cli->fsx.fsx_cowextsize = getnum(value, opts, subopt);
cli->fsx.fsx_xflags |= FS_XFLAG_COWEXTSIZE;
break;
default:
return -EINVAL;
}
return 0;
}
static int
inode_opts_parser(
struct opt_params *opts,
int subopt,
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;
default:
return -EINVAL;
}
return 0;
}
static int
log_opts_parser(
struct opt_params *opts,
int subopt,
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,
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;
default:
return -EINVAL;
}
return 0;
}
static int
naming_opts_parser(
struct opt_params *opts,
int subopt,
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);
}
break;
case N_FTYPE:
cli->sb_feat.dirftype = getnum(value, opts, subopt);
break;
default:
return -EINVAL;
}
return 0;
}
static int
rtdev_opts_parser(
struct opt_params *opts,
int subopt,
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,
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 {
char opt;
struct opt_params *opts;
int (*parser)(struct opt_params *opts,
int subopt,
char *value,
struct cli_params *cli);
} subopt_tab[] = {
{ 'b', &bopts, block_opts_parser },
{ 'd', &dopts, data_opts_parser },
{ 'i', &iopts, inode_opts_parser },
{ 'l', &lopts, log_opts_parser },
{ 'm', &mopts, meta_opts_parser },
{ 'n', &nopts, naming_opts_parser },
{ 'r', &ropts, rtdev_opts_parser },
{ 's', &sopts, sector_opts_parser },
{ '\0', 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->opt == 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 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)
{
/* set configured sector sizes in preparation for checks */
if (!cli->sectorsize) {
cfg->sectorsize = dft->sectorsize;
} else {
cfg->sectorsize = cli->sectorsize;
}
cfg->sectorlog = libxfs_highbit32(cfg->sectorsize);
/*
* 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);
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 = XFS_MIN_SECTORSIZE;
/* Older kernels may not have physical/logical distinction */
if (!ft->psectorsize)
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;
}
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 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) {
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->fsx.fsx_xflags & FS_XFLAG_COWEXTSIZE) &&
!cli->sb_feat.reflink) {
fprintf(stderr,
_("cowextsize not supported without reflink support\n"));
usage();
}
if (cli->sb_feat.reflink && cli->xi->rtname) {
fprintf(stderr,
_("reflink not supported with realtime devices\n"));
usage();
cli->sb_feat.reflink = false;
}
if (cli->sb_feat.rmapbt && cli->xi->rtname) {
fprintf(stderr,
_("rmapbt not supported with realtime devices\n"));
usage();
cli->sb_feat.rmapbt = false;
}
/*
* 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(MAXEXTLEN, mp->m_sb.sb_agblocks / 2));
usage();
}
/*
* Now we do it again with a realtime file so that we know the hint and
* flag that get passed on to realtime files will be correct.
*/
if (mp->m_sb.sb_rextsize == 0)
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(MAXEXTLEN, 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(MAXEXTLEN, mp->m_sb.sb_agblocks / 2));
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();
}
if (dsu % cfg->sectorsize) {
fprintf(stderr,
_("data su must be a multiple of the sector size (%d)\n"), cfg->sectorsize);
usage();
}
dsunit = (int)BTOBBT(dsu);
big_dswidth = (long long int)dsunit * dsw;
if (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, dsunit);
usage();
}
dswidth = big_dswidth;
}
if ((dsunit && !dswidth) || (!dsunit && dswidth) ||
(dsunit && (dswidth % dsunit != 0))) {
fprintf(stderr,
_("data stripe width (%d) must be a multiple of the data stripe unit (%d)\n"),
dswidth, dsunit);
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. XXX add more validation */
if (ft->dsunit && ft->dswidth == 0) {
fprintf(stderr,
_("%s: Volume reports stripe unit of %d bytes and stripe width of 0, ignoring.\n"),
progname, BBTOB(ft->dsunit));
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) {
fprintf(stderr,
_("size %lld of data subvolume is too small, minimum %d blocks\n"),
(long long)cfg->dblocks, XFS_MIN_DATA_BLOCKS);
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 *cfg,
struct cli_params *cli)
{
if (cli->agsize) { /* User-specified AG size */
cfg->agsize = getnum(cli->agsize, &dopts, D_AGSIZE);
/*
* Check specified agsize is a multiple of blocksize.
*/
if (cfg->agsize % cfg->blocksize) {
fprintf(stderr,
_("agsize (%s) not a multiple of fs blk size (%d)\n"),
cli->agsize, cfg->blocksize);
usage();
}
cfg->agsize /= cfg->blocksize;
cfg->agcount = cfg->dblocks / cfg->agsize +
(cfg->dblocks % cfg->agsize != 0);
} else if (cli->agcount) { /* User-specified AG count */
cfg->agcount = cli->agcount;
cfg->agsize = cfg->dblocks / cfg->agcount +
(cfg->dblocks % cfg->agcount != 0);
} else {
calc_default_ag_geometry(cfg->blocklog, cfg->dblocks,
cfg->dsunit, &cfg->agsize,
&cfg->agcount);
}
}
/*
* Align the AG size to stripe geometry. If this fails and we are using
* discovered stripe geometry, tell the caller to clear the stripe geometry.
* Otherwise, set the aligned geometry (valid or invalid!) so that the
* validation call will fail and exit.
*/
static void
align_ag_geometry(
struct mkfs_params *cfg)
{
uint64_t tmp_agsize;
int dsunit = cfg->dsunit;
if (!dsunit)
goto validate;
/*
* agsize is not a multiple of dsunit
*/
if ((cfg->agsize % dsunit) != 0) {
/*
* Round up to stripe unit boundary. Also make sure
* that agsize is still larger than
* XFS_AG_MIN_BLOCKS(blocklog)
*/
tmp_agsize = ((cfg->agsize + dsunit - 1) / dsunit) * dsunit;
/*
* Round down to stripe unit boundary if rounding up
* created an AG size that is larger than the AG max.
*/
if (tmp_agsize > XFS_AG_MAX_BLOCKS(cfg->blocklog))
tmp_agsize = (cfg->agsize / dsunit) * dsunit;
if (tmp_agsize < XFS_AG_MIN_BLOCKS(cfg->blocklog) &&
tmp_agsize > XFS_AG_MAX_BLOCKS(cfg->blocklog)) {