cmd/zgenhostid: replace with simple c implementation

It was discovered that dracut scripts and zgenhostid
always generate little-endian /etc/hostid.

This commit provides simple endianess-aware binary
and updates the scripts to use it.

New features include:
 -f flag to force overwrite.
 -o flag to write to different file (for dracut)
 accepting both 0x01234567 and 01234567 values as input

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Olaf Faaland <faaland1@llnl.gov>
Signed-off-by: Georgy Yakovlev <gyakovlev@gentoo.org>
Closes #10887 
Closes #10925
This commit is contained in:
Georgy Yakovlev 2020-09-16 12:25:12 -07:00 committed by GitHub
parent 9569c31161
commit 9cc177baa0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 213 additions and 93 deletions

1
cmd/zgenhostid/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/zgenhostid

View File

@ -1 +1,5 @@
dist_bin_SCRIPTS = zgenhostid include $(top_srcdir)/config/Rules.am
bin_PROGRAMS = zgenhostid
zgenhostid_SOURCES = zgenhostid.c

View File

@ -1,61 +0,0 @@
#!/usr/bin/env bash
# Emulate genhostid(1) available on RHEL/CENTOS, for use on distros
# which do not provide that utility.
#
# Usage:
# zgenhostid
# zgenhostid <value>
#
# If /etc/hostid already exists and is size > 0, the script exits immediately
# and changes nothing. Unlike genhostid, this generates an error message.
#
# The first form generates a random hostid and stores it in /etc/hostid.
# The second form checks that the provided value is between 0x1 and 0xFFFFFFFF
# and if so, stores it in /etc/hostid. This form is not supported by
# genhostid(1).
hostid_file=/etc/hostid
function usage {
echo "$0 [value]"
echo "If $hostid_file is not present, store a hostid in it." >&2
echo "The optional value must be an 8-digit hex number between" >&2
echo "1 and 2^32-1. If no value is provided, a random one will" >&2
echo "be generated. The value must be unique among your systems." >&2
}
# hostid(1) ignores contents of /etc/hostid if size < 4 bytes. It would
# be better if this checked size >= 4 bytes but it the method must be
# widely portable.
if [ -s $hostid_file ]; then
echo "$hostid_file already exists. No change made." >&2
exit 1
fi
if [ -n "$1" ]; then
host_id=$1
else
# $RANDOM goes from 0..32k-1
number=$((((RANDOM % 4) * 32768 + RANDOM) * 32768 + RANDOM))
host_id=$(printf "%08x" $number)
fi
if egrep -o '^0{8}$' <<< $host_id >/dev/null 2>&1; then
usage
exit 2
fi
if ! egrep -o '^[a-fA-F0-9]{8}$' <<< $host_id >/dev/null 2>&1; then
usage
exit 3
fi
a=${host_id:6:2}
b=${host_id:4:2}
c=${host_id:2:2}
d=${host_id:0:2}
echo -ne \\x$a\\x$b\\x$c\\x$d > $hostid_file
exit 0

152
cmd/zgenhostid/zgenhostid.c Normal file
View File

@ -0,0 +1,152 @@
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2020, Georgy Yakovlev. All rights reserved.
*/
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
static void usage(void);
static void
usage(void)
{
(void) fprintf(stderr,
"usage: zgenhostid [-fh] [-o path] [value]\n\n"
" -f\t\t force hostid file write\n"
" -h\t\t print this usage and exit\n"
" -o <filename>\t write hostid to this file\n\n"
"If hostid file is not present, store a hostid in it.\n"
"The optional value must be an 8-digit hex number between"
"1 and 2^32-1.\n"
"If no value is provided, a random one will"
"be generated.\n"
"The value must be unique among your systems.\n");
exit(EXIT_FAILURE);
/* NOTREACHED */
}
int
main(int argc, char **argv)
{
/* default file path, can be optionally set by user */
char path[PATH_MAX] = "/etc/hostid";
/* holds converted user input or lrand48() generated value */
unsigned long input_i = 0;
int opt;
int pathlen;
int force_fwrite = 0;
while ((opt = getopt_long(argc, argv, "fo:h?", 0, 0)) != -1) {
switch (opt) {
case 'f':
force_fwrite = 1;
break;
case 'o':
pathlen = snprintf(path, sizeof (path), "%s", optarg);
if (pathlen >= sizeof (path)) {
fprintf(stderr, "%s\n", strerror(EOVERFLOW));
exit(EXIT_FAILURE);
} else if (pathlen < 1) {
fprintf(stderr, "%s\n", strerror(EINVAL));
exit(EXIT_FAILURE);
}
break;
case 'h':
case '?':
usage();
}
}
char *in_s = argv[optind];
if (in_s != NULL) {
/* increment pointer by 2 if string is 0x prefixed */
if (strncasecmp("0x", in_s, 2) == 0) {
in_s += 2;
}
/* need to be exactly 8 characters */
const char *hex = "0123456789abcdefABCDEF";
if (strlen(in_s) != 8 || strspn(in_s, hex) != 8) {
fprintf(stderr, "%s\n", strerror(ERANGE));
usage();
}
input_i = strtoul(in_s, NULL, 16);
if (errno != 0) {
perror("strtoul");
exit(EXIT_FAILURE);
}
if (input_i < 0x1 || input_i > UINT32_MAX) {
fprintf(stderr, "%s\n", strerror(ERANGE));
usage();
}
}
struct stat fstat;
if (force_fwrite == 0 && stat(path, &fstat) == 0 &&
S_ISREG(fstat.st_mode)) {
fprintf(stderr, "%s: %s\n", path, strerror(EEXIST));
exit(EXIT_FAILURE);
}
/*
* generate if not provided by user
* also handle unlikely zero return from lrand48()
*/
while (input_i == 0) {
srand48(getpid() ^ time(NULL));
input_i = lrand48();
}
FILE *fp = fopen(path, "wb");
if (!fp) {
perror("fopen");
exit(EXIT_FAILURE);
}
/*
* we need just 4 bytes in native endianess
* not using sethostid() because it may be missing or just a stub
*/
uint32_t hostid = input_i;
int written = fwrite(&hostid, 1, 4, fp);
if (written != 4) {
perror("fwrite");
exit(EXIT_FAILURE);
}
fclose(fp);
exit(EXIT_SUCCESS);
}

View File

@ -5,7 +5,7 @@ check() {
[ "${1}" = "-d" ] && return 0 [ "${1}" = "-d" ] && return 0
# Verify the zfs tool chain # Verify the zfs tool chain
for tool in "@sbindir@/zpool" "@sbindir@/zfs" "@mounthelperdir@/mount.zfs" ; do for tool in "@bindir@/zgenhostid" "@sbindir@/zpool" "@sbindir@/zfs" "@mounthelperdir@/mount.zfs" ; do
test -x "$tool" || return 1 test -x "$tool" || return 1
done done
# Verify grep exists # Verify grep exists
@ -38,6 +38,7 @@ install() {
inst_rules @udevruledir@/60-zvol.rules inst_rules @udevruledir@/60-zvol.rules
dracut_install hostid dracut_install hostid
dracut_install grep dracut_install grep
dracut_install @bindir@/zgenhostid
dracut_install @sbindir@/zfs dracut_install @sbindir@/zfs
dracut_install @sbindir@/zpool dracut_install @sbindir@/zpool
# Workaround for zfsonlinux/zfs#4749 by ensuring libgcc_s.so(.1) is included # Workaround for zfsonlinux/zfs#4749 by ensuring libgcc_s.so(.1) is included
@ -83,11 +84,7 @@ install() {
fi fi
# Synchronize initramfs and system hostid # Synchronize initramfs and system hostid
AA=`hostid | cut -b 1,2` zgenhostid -o "${initdir}/etc/hostid" "$(hostid)"
BB=`hostid | cut -b 3,4`
CC=`hostid | cut -b 5,6`
DD=`hostid | cut -b 7,8`
echo -ne "\\x${DD}\\x${CC}\\x${BB}\\x${AA}" > "${initdir}/etc/hostid"
if dracut_module_included "systemd"; then if dracut_module_included "systemd"; then
mkdir -p "${initdir}/$systemdsystemunitdir/zfs-import.target.wants" mkdir -p "${initdir}/$systemdsystemunitdir/zfs-import.target.wants"

View File

@ -6,11 +6,7 @@
spl_hostid=$(getarg spl_hostid=) spl_hostid=$(getarg spl_hostid=)
if [ -n "${spl_hostid}" ] ; then if [ -n "${spl_hostid}" ] ; then
info "ZFS: Using hostid from command line: ${spl_hostid}" info "ZFS: Using hostid from command line: ${spl_hostid}"
AA=$(echo "${spl_hostid}" | cut -b 1,2) zgenhostid -f "${spl_hostid}"
BB=$(echo "${spl_hostid}" | cut -b 3,4)
CC=$(echo "${spl_hostid}" | cut -b 5,6)
DD=$(echo "${spl_hostid}" | cut -b 7,8)
echo -ne "\\x${DD}\\x${CC}\\x${BB}\\x${AA}" >/etc/hostid
elif [ -f "/etc/hostid" ] ; then elif [ -f "/etc/hostid" ] ; then
info "ZFS: Using hostid from /etc/hostid: $(hostid)" info "ZFS: Using hostid from /etc/hostid: $(hostid)"
else else

View File

@ -21,7 +21,7 @@
.\" .\"
.\" Copyright (c) 2017 by Lawrence Livermore National Security, LLC. .\" Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
.\" .\"
.Dd September 16, 2017 .Dd September 13, 2020
.Dt ZGENHOSTID 8 SMM .Dt ZGENHOSTID 8 SMM
.Os .Os
.Sh NAME .Sh NAME
@ -30,42 +30,73 @@
.Em /etc/hostid .Em /etc/hostid
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl f
.Op Fl o Ar filename
.Op Ar hostid .Op Ar hostid
.Sh DESCRIPTION .Sh DESCRIPTION
If Creates
.Em /etc/hostid .Pa /etc/hostid
does not exist, create it and store a hostid in it. If the user provides file and stores hostid in it.
.Op Ar hostid If the user provides
on the command line, store that value. Otherwise, randomly generate a
value to store.
.Pp
This emulates the
.Xr genhostid 1
utility and is provided for use on systems which do not include the utility.
.Sh OPTIONS
.Op Ar hostid .Op Ar hostid
on the command line, validates and stores that value.
Otherwise, randomly generates a value to store.
.Bl -tag -width "hostid"
.It Fl h
Display a summary of the command-line options.
.It Fl f
Force file overwrite.
.It Fl o Ar filename
Write to
.Pa filename
instead of default
.Pa /etc/hostd
.It Ar hostid
Specifies the value to be placed in Specifies the value to be placed in
.Em /etc/hostid . .Pa /etc/hostid .
It must be a number with a value between 1 and 2^32-1. This value It must be a number with a value between 1 and 2^32-1.
This value
.Sy must .Sy must
be unique among your systems. It must be expressed in hexadecimal and be be unique among your systems.
exactly 8 digits long. It
.Sy must
be expressed in hexadecimal and be exactly
.Em 8
digits long, optionally prefixed by
.Em 0x .
.El
.Sh FILES
.Pa /etc/hostid
.Sh EXAMPLES .Sh EXAMPLES
.Bl -tag -width Ds .Bl -tag -width Bd
.It Generate a random hostid and store it .It Generate a random hostid and store it
.Bd -literal .Bd -literal
# zgenhostid # zgenhostid
.Ed .Ed
.It Record the libc-generated hostid in Em /etc/hostid .It Record the libc-generated hostid in Pa /etc/hostid
.Bd -literal .Bd -literal
# zgenhostid $(hostid) # zgenhostid "$(hostid)"
.Ed .Ed
.It Record a custom hostid (0xdeadbeef) in Em etc/hostid .It Record a custom hostid (0xdeadbeef) in Pa /etc/hostid
.Bd -literal .Bd -literal
# zgenhostid deadbeef # zgenhostid deadbeef
.Ed .Ed
.It Record a custom hostid (0x01234567) in Pa /tmp/hostid
and ovewrite the file if it exists
.Bd -literal
# zgenhostid -f -o /tmp/hostid 0x01234567
.Ed
.El .El
.Sh SEE ALSO .Sh SEE ALSO
.Xr genhostid 1 , .Xr genhostid 1 ,
.Xr hostid 1 , .Xr hostid 1 ,
.Xr sethostid 3 ,
.Xr spl-module-parameters 5 .Xr spl-module-parameters 5
.Sh HISTORY
.Nm
emulates the
.Xr genhostid 1
utility and is provided for use on systems which
do not include the utility or do not provide
.Xr sethostid 3
call.