mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-25 23:21:33 +03:00
Merge pull request #24746 from DaanDeMeyer/repart-split
repart: Add --split option to generate split artifacts
This commit is contained in:
commit
e3a1cd9e98
@ -669,6 +669,15 @@
|
||||
all partition types that support it, except if the partition is marked read-only (and thus
|
||||
effectively, defaults to off for Verity partitions).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SplitName=</varname></term>
|
||||
|
||||
<listitem><para>Configures the suffix to append to split artifacts when the <option>--split</option>
|
||||
option of <command>systemd-repart</command> is used. Simple specifier expansion is supported, see
|
||||
below. Defaults to <literal>%t</literal>. To disable split artifact generation for a partition, set
|
||||
<varname>SplitName=</varname> to <literal>-</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
@ -676,8 +685,8 @@
|
||||
<title>Specifiers</title>
|
||||
|
||||
<para>Specifiers may be used in the <varname>Label=</varname>, <varname>CopyBlocks=</varname>,
|
||||
<varname>CopyFiles=</varname>, <varname>MakeDirectories=</varname> settings. The following expansions are
|
||||
understood:</para>
|
||||
<varname>CopyFiles=</varname>, <varname>MakeDirectories=</varname>, <varname>SplitName=</varname>
|
||||
settings. The following expansions are understood:</para>
|
||||
<table class='specifiers'>
|
||||
<title>Specifiers available</title>
|
||||
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
|
||||
@ -710,6 +719,46 @@
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>Additionally, for the <varname>SplitName=</varname> setting, the following specifiers are also
|
||||
understood:</para>
|
||||
<table class='specifiers'>
|
||||
<title>Specifiers available</title>
|
||||
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
|
||||
<colspec colname="spec" />
|
||||
<colspec colname="mean" />
|
||||
<colspec colname="detail" />
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Specifier</entry>
|
||||
<entry>Meaning</entry>
|
||||
<entry>Details</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row id='T'>
|
||||
<entry><literal>%T</literal></entry>
|
||||
<entry>Partition Type UUID</entry>
|
||||
<entry>The partition type UUID, as configured with <varname>Type=</varname></entry>
|
||||
</row>
|
||||
<row id='t'>
|
||||
<entry><literal>%t</literal></entry>
|
||||
<entry>Partition Type Identifier</entry>
|
||||
<entry>The partition type identifier corresponding to the partition type UUID</entry>
|
||||
</row>
|
||||
<row id='U'>
|
||||
<entry><literal>%U</literal></entry>
|
||||
<entry>Partition UUID</entry>
|
||||
<entry>The partition UUID, as configured with <varname>UUID=</varname></entry>
|
||||
</row>
|
||||
<row id='n'>
|
||||
<entry><literal>%n</literal></entry>
|
||||
<entry>Partition Number</entry>
|
||||
<entry>The partition number assigned to the partition</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
@ -332,6 +332,21 @@
|
||||
for details on these two options.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--split=</option><arg>BOOL</arg></term>
|
||||
|
||||
<listitem><para>Enables generation of split artifacts from partitions configured with
|
||||
<varname>SplitName=</varname>. If enabled, for each partition with <varname>SplitName=</varname> set,
|
||||
a separate output file containing just the contents of that partition is generated. The output
|
||||
filename consists of the loopback filename suffixed with the name configured with
|
||||
<varname>SplitName=</varname>. If the loopback filename ends with <literal>.raw</literal>, the suffix
|
||||
is inserted before the <literal>.raw</literal> extension instead.</para>
|
||||
|
||||
<para>Note that <option>--split</option> is independent from <option>--dry-run</option>. Even if
|
||||
<option>--dry-run</option> is enabled, split artifacts will still be generated from an existing image
|
||||
if <option>--split</option> is enabled.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
|
@ -117,6 +117,7 @@ static char *arg_tpm2_device = NULL;
|
||||
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
|
||||
static char *arg_tpm2_public_key = NULL;
|
||||
static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
|
||||
static bool arg_split = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
@ -195,6 +196,9 @@ struct Partition {
|
||||
uint8_t *roothash;
|
||||
size_t roothash_size;
|
||||
|
||||
char *split_name_format;
|
||||
char *split_name_resolved;
|
||||
|
||||
Partition *siblings[_VERITY_MODE_MAX];
|
||||
|
||||
LIST_FIELDS(Partition, partitions);
|
||||
@ -315,6 +319,9 @@ static Partition* partition_free(Partition *p) {
|
||||
|
||||
free(p->roothash);
|
||||
|
||||
free(p->split_name_format);
|
||||
free(p->split_name_resolved);
|
||||
|
||||
return mfree(p);
|
||||
}
|
||||
|
||||
@ -1449,28 +1456,29 @@ static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, V
|
||||
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
|
||||
|
||||
ConfigTableItem table[] = {
|
||||
{ "Partition", "Type", config_parse_type, 0, &p->type_uuid },
|
||||
{ "Partition", "Label", config_parse_label, 0, &p->new_label },
|
||||
{ "Partition", "UUID", config_parse_uuid, 0, p },
|
||||
{ "Partition", "Priority", config_parse_int32, 0, &p->priority },
|
||||
{ "Partition", "Weight", config_parse_weight, 0, &p->weight },
|
||||
{ "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight },
|
||||
{ "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min },
|
||||
{ "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max },
|
||||
{ "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min },
|
||||
{ "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max },
|
||||
{ "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset },
|
||||
{ "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p },
|
||||
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
|
||||
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
|
||||
{ "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
|
||||
{ "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
|
||||
{ "Partition", "Verity", config_parse_verity, 0, &p->verity },
|
||||
{ "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key },
|
||||
{ "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags },
|
||||
{ "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only },
|
||||
{ "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto },
|
||||
{ "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
|
||||
{ "Partition", "Type", config_parse_type, 0, &p->type_uuid },
|
||||
{ "Partition", "Label", config_parse_label, 0, &p->new_label },
|
||||
{ "Partition", "UUID", config_parse_uuid, 0, p },
|
||||
{ "Partition", "Priority", config_parse_int32, 0, &p->priority },
|
||||
{ "Partition", "Weight", config_parse_weight, 0, &p->weight },
|
||||
{ "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight },
|
||||
{ "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min },
|
||||
{ "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max },
|
||||
{ "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min },
|
||||
{ "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max },
|
||||
{ "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset },
|
||||
{ "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p },
|
||||
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
|
||||
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
|
||||
{ "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
|
||||
{ "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
|
||||
{ "Partition", "Verity", config_parse_verity, 0, &p->verity },
|
||||
{ "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key },
|
||||
{ "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags },
|
||||
{ "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only },
|
||||
{ "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto },
|
||||
{ "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
|
||||
{ "Partition", "SplitName", config_parse_string, 0, &p->split_name_format },
|
||||
{}
|
||||
};
|
||||
int r;
|
||||
@ -1560,6 +1568,15 @@ static int partition_read_definition(Partition *p, const char *path, const char
|
||||
p->read_only <= 0)
|
||||
p->growfs = true;
|
||||
|
||||
if (!p->split_name_format) {
|
||||
char *s = strdup("%t");
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
p->split_name_format = s;
|
||||
} else if (streq(p->split_name_format, "-"))
|
||||
p->split_name_format = mfree(p->split_name_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -3984,6 +4001,150 @@ static int context_mangle_partitions(Context *context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int split_name_printf(Partition *p) {
|
||||
assert(p);
|
||||
|
||||
const Specifier table[] = {
|
||||
{ 't', specifier_string, GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(p->type_uuid) },
|
||||
{ 'T', specifier_id128, &p->type_uuid },
|
||||
{ 'U', specifier_id128, &p->new_uuid },
|
||||
{ 'n', specifier_uint64, &p->partno },
|
||||
|
||||
COMMON_SYSTEM_SPECIFIERS,
|
||||
{}
|
||||
};
|
||||
|
||||
return specifier_printf(p->split_name_format, NAME_MAX, table, arg_root, p, &p->split_name_resolved);
|
||||
}
|
||||
|
||||
static int split_name_resolve(Context *context) {
|
||||
int r;
|
||||
|
||||
LIST_FOREACH(partitions, p, context->partitions) {
|
||||
if (p->dropped)
|
||||
continue;
|
||||
|
||||
if (!p->split_name_format)
|
||||
continue;
|
||||
|
||||
r = split_name_printf(p);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve specifiers in %s: %m", p->split_name_format);
|
||||
}
|
||||
|
||||
LIST_FOREACH(partitions, p, context->partitions) {
|
||||
if (!p->split_name_resolved)
|
||||
continue;
|
||||
|
||||
LIST_FOREACH(partitions, q, context->partitions) {
|
||||
if (p == q)
|
||||
continue;
|
||||
|
||||
if (!q->split_name_resolved)
|
||||
continue;
|
||||
|
||||
if (!streq(p->split_name_resolved, q->split_name_resolved))
|
||||
continue;
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
|
||||
"%s and %s have the same resolved split name \"%s\", refusing",
|
||||
p->definition_path, q->definition_path, p->split_name_resolved);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int split_node(const char *node, char **ret_base, char **ret_ext) {
|
||||
_cleanup_free_ char *base = NULL, *ext = NULL;
|
||||
char *e;
|
||||
int r;
|
||||
|
||||
assert(node);
|
||||
assert(ret_base);
|
||||
assert(ret_ext);
|
||||
|
||||
r = path_extract_filename(node, &base);
|
||||
if (r == O_DIRECTORY || r == -EADDRNOTAVAIL)
|
||||
return log_error_errno(r, "Device node %s cannot be a directory", arg_node);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from %s: %m", arg_node);
|
||||
|
||||
e = endswith(base, ".raw");
|
||||
if (e) {
|
||||
ext = strdup(e);
|
||||
if (!ext)
|
||||
return log_oom();
|
||||
|
||||
*e = 0;
|
||||
}
|
||||
|
||||
*ret_base = TAKE_PTR(base);
|
||||
*ret_ext = TAKE_PTR(ext);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int context_split(Context *context) {
|
||||
_cleanup_free_ char *base = NULL, *ext = NULL;
|
||||
_cleanup_close_ int dir_fd = -1;
|
||||
int fd = -1, r;
|
||||
|
||||
if (!arg_split)
|
||||
return 0;
|
||||
|
||||
assert(context);
|
||||
assert(arg_node);
|
||||
|
||||
/* We can't do resolution earlier because the partition UUIDs for verity partitions are only filled
|
||||
* in after they've been generated. */
|
||||
|
||||
r = split_name_resolve(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = split_node(arg_node, &base, &ext);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
dir_fd = r = open_parent(arg_node, O_PATH|O_CLOEXEC, 0);
|
||||
if (r == -EDESTADDRREQ)
|
||||
dir_fd = AT_FDCWD;
|
||||
else if (r < 0)
|
||||
return log_error_errno(r, "Failed to open parent directory of %s: %m", arg_node);
|
||||
|
||||
LIST_FOREACH(partitions, p, context->partitions) {
|
||||
_cleanup_free_ char *fname = NULL;
|
||||
_cleanup_close_ int fdt = -1;
|
||||
|
||||
if (p->dropped)
|
||||
continue;
|
||||
|
||||
if (!p->split_name_resolved)
|
||||
continue;
|
||||
|
||||
fname = strjoin(base, ".", p->split_name_resolved, ext);
|
||||
if (!fname)
|
||||
return log_oom();
|
||||
|
||||
fdt = openat(dir_fd, fname, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW|O_CREAT|O_EXCL, 0666);
|
||||
if (fdt < 0)
|
||||
return log_error_errno(errno, "Failed to open %s: %m", fname);
|
||||
|
||||
if (fd < 0)
|
||||
assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
|
||||
|
||||
if (lseek(fd, p->offset, SEEK_SET) < 0)
|
||||
return log_error_errno(errno, "Failed to seek to partition offset: %m");
|
||||
|
||||
r = copy_bytes_full(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES, NULL, NULL, NULL, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy to split partition %s: %m", fname);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int context_write_partition_table(
|
||||
Context *context,
|
||||
const char *node,
|
||||
@ -4597,6 +4758,7 @@ static int help(void) {
|
||||
" --size=BYTES Grow loopback file to specified size\n"
|
||||
" --json=pretty|short|off\n"
|
||||
" Generate JSON output\n"
|
||||
" --split=BOOL Whether to generate split artifacts\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
@ -4629,6 +4791,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_TPM2_PCRS,
|
||||
ARG_TPM2_PUBLIC_KEY,
|
||||
ARG_TPM2_PUBLIC_KEY_PCRS,
|
||||
ARG_SPLIT,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@ -4653,6 +4816,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
|
||||
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
|
||||
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
|
||||
{ "split", required_argument, NULL, ARG_SPLIT },
|
||||
{}
|
||||
};
|
||||
|
||||
@ -4859,6 +5023,14 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
break;
|
||||
|
||||
case ARG_SPLIT:
|
||||
r = parse_boolean_argument("--split=", optarg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_split = r;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@ -4910,6 +5082,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
|
||||
|
||||
if (arg_split && !arg_node)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"A path to a loopback file must be specified when --split is used.");
|
||||
|
||||
if (arg_tpm2_pcr_mask == UINT32_MAX)
|
||||
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
|
||||
if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
|
||||
@ -5524,6 +5700,10 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = context_split(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) context_dump(context, node, /*late=*/ true);
|
||||
|
||||
return 0;
|
||||
|
@ -16,6 +16,9 @@ const char *gpt_partition_type_uuid_to_string_harder(
|
||||
char buffer[static SD_ID128_UUID_STRING_MAX]);
|
||||
int gpt_partition_type_uuid_from_string(const char *s, sd_id128_t *ret);
|
||||
|
||||
#define GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(id) \
|
||||
gpt_partition_type_uuid_to_string_harder((id), (char[SD_ID128_UUID_STRING_MAX]) {})
|
||||
|
||||
Architecture gpt_partition_type_uuid_to_arch(sd_id128_t id);
|
||||
|
||||
typedef struct GptPartitionType {
|
||||
|
@ -151,9 +151,38 @@ int specifier_real_directory(char specifier, const void *data, const char *root,
|
||||
return path_extract_directory(path, ret);
|
||||
}
|
||||
|
||||
int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
|
||||
const sd_id128_t *id = ASSERT_PTR(data);
|
||||
char *n;
|
||||
|
||||
n = new(char, SD_ID128_STRING_MAX);
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = sd_id128_to_string(*id, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
|
||||
const sd_id128_t *id = ASSERT_PTR(data);
|
||||
char *n;
|
||||
|
||||
n = new(char, SD_ID128_UUID_STRING_MAX);
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = sd_id128_to_uuid_string(*id, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
|
||||
const uint64_t *n = ASSERT_PTR(data);
|
||||
|
||||
return asprintf(ret, "%" PRIu64, *n) < 0 ? -ENOMEM : 0;
|
||||
}
|
||||
|
||||
int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
|
||||
sd_id128_t id;
|
||||
char *n;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
@ -172,17 +201,11 @@ int specifier_machine_id(char specifier, const void *data, const char *root, con
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
n = new(char, SD_ID128_STRING_MAX);
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = sd_id128_to_string(id, n);
|
||||
return 0;
|
||||
return specifier_id128(specifier, &id, root, userdata, ret);
|
||||
}
|
||||
|
||||
int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
|
||||
sd_id128_t id;
|
||||
char *n;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
@ -191,12 +214,7 @@ int specifier_boot_id(char specifier, const void *data, const char *root, const
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
n = new(char, SD_ID128_STRING_MAX);
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = sd_id128_to_string(id, n);
|
||||
return 0;
|
||||
return specifier_id128(specifier, &id, root, userdata, ret);
|
||||
}
|
||||
|
||||
int specifier_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
|
||||
|
@ -16,6 +16,9 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[
|
||||
int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
|
||||
int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret);
|
||||
|
Loading…
Reference in New Issue
Block a user