From 9cc177baa06e8efc0d092886e2c9b3de4b3ee50f Mon Sep 17 00:00:00 2001 From: Georgy Yakovlev <168902+gyakovlev@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:25:12 -0700 Subject: [PATCH] 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 Reviewed-by: Olaf Faaland Signed-off-by: Georgy Yakovlev Closes #10887 Closes #10925 --- cmd/zgenhostid/.gitignore | 1 + cmd/zgenhostid/Makefile.am | 6 +- cmd/zgenhostid/zgenhostid | 61 ---------- cmd/zgenhostid/zgenhostid.c | 152 ++++++++++++++++++++++++ contrib/dracut/90zfs/module-setup.sh.in | 9 +- contrib/dracut/90zfs/parse-zfs.sh.in | 6 +- man/man8/zgenhostid.8 | 71 +++++++---- 7 files changed, 213 insertions(+), 93 deletions(-) create mode 100644 cmd/zgenhostid/.gitignore delete mode 100755 cmd/zgenhostid/zgenhostid create mode 100644 cmd/zgenhostid/zgenhostid.c diff --git a/cmd/zgenhostid/.gitignore b/cmd/zgenhostid/.gitignore new file mode 100644 index 0000000000..072246c735 --- /dev/null +++ b/cmd/zgenhostid/.gitignore @@ -0,0 +1 @@ +/zgenhostid diff --git a/cmd/zgenhostid/Makefile.am b/cmd/zgenhostid/Makefile.am index 69c99ca9d8..0ba791f7cd 100644 --- a/cmd/zgenhostid/Makefile.am +++ b/cmd/zgenhostid/Makefile.am @@ -1 +1,5 @@ -dist_bin_SCRIPTS = zgenhostid +include $(top_srcdir)/config/Rules.am + +bin_PROGRAMS = zgenhostid + +zgenhostid_SOURCES = zgenhostid.c diff --git a/cmd/zgenhostid/zgenhostid b/cmd/zgenhostid/zgenhostid deleted file mode 100755 index 8b468740c7..0000000000 --- a/cmd/zgenhostid/zgenhostid +++ /dev/null @@ -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 -# -# 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 diff --git a/cmd/zgenhostid/zgenhostid.c b/cmd/zgenhostid/zgenhostid.c new file mode 100644 index 0000000000..562262928c --- /dev/null +++ b/cmd/zgenhostid/zgenhostid.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 \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); +} diff --git a/contrib/dracut/90zfs/module-setup.sh.in b/contrib/dracut/90zfs/module-setup.sh.in index 7e7a96d6e0..5b746049fb 100755 --- a/contrib/dracut/90zfs/module-setup.sh.in +++ b/contrib/dracut/90zfs/module-setup.sh.in @@ -5,7 +5,7 @@ check() { [ "${1}" = "-d" ] && return 0 # 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 done # Verify grep exists @@ -38,6 +38,7 @@ install() { inst_rules @udevruledir@/60-zvol.rules dracut_install hostid dracut_install grep + dracut_install @bindir@/zgenhostid dracut_install @sbindir@/zfs dracut_install @sbindir@/zpool # Workaround for zfsonlinux/zfs#4749 by ensuring libgcc_s.so(.1) is included @@ -83,11 +84,7 @@ install() { fi # Synchronize initramfs and system hostid - AA=`hostid | cut -b 1,2` - 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" + zgenhostid -o "${initdir}/etc/hostid" "$(hostid)" if dracut_module_included "systemd"; then mkdir -p "${initdir}/$systemdsystemunitdir/zfs-import.target.wants" diff --git a/contrib/dracut/90zfs/parse-zfs.sh.in b/contrib/dracut/90zfs/parse-zfs.sh.in index 6a805ae24a..2ff76d8fa0 100755 --- a/contrib/dracut/90zfs/parse-zfs.sh.in +++ b/contrib/dracut/90zfs/parse-zfs.sh.in @@ -6,11 +6,7 @@ spl_hostid=$(getarg spl_hostid=) if [ -n "${spl_hostid}" ] ; then info "ZFS: Using hostid from command line: ${spl_hostid}" - AA=$(echo "${spl_hostid}" | cut -b 1,2) - 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 + zgenhostid -f "${spl_hostid}" elif [ -f "/etc/hostid" ] ; then info "ZFS: Using hostid from /etc/hostid: $(hostid)" else diff --git a/man/man8/zgenhostid.8 b/man/man8/zgenhostid.8 index ff3d2d960b..ff198443dd 100644 --- a/man/man8/zgenhostid.8 +++ b/man/man8/zgenhostid.8 @@ -21,7 +21,7 @@ .\" .\" Copyright (c) 2017 by Lawrence Livermore National Security, LLC. .\" -.Dd September 16, 2017 +.Dd September 13, 2020 .Dt ZGENHOSTID 8 SMM .Os .Sh NAME @@ -30,42 +30,73 @@ .Em /etc/hostid .Sh SYNOPSIS .Nm +.Op Fl f +.Op Fl o Ar filename .Op Ar hostid .Sh DESCRIPTION -If -.Em /etc/hostid -does not exist, create it and store a hostid in it. If the user provides -.Op Ar hostid -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 +Creates +.Pa /etc/hostid +file and stores hostid in it. +If the user provides .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 -.Em /etc/hostid . -It must be a number with a value between 1 and 2^32-1. This value +.Pa /etc/hostid . +It must be a number with a value between 1 and 2^32-1. +This value .Sy must -be unique among your systems. It must be expressed in hexadecimal and be -exactly 8 digits long. +be unique among your systems. +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 -.Bl -tag -width Ds +.Bl -tag -width Bd .It Generate a random hostid and store it .Bd -literal # zgenhostid .Ed -.It Record the libc-generated hostid in Em /etc/hostid +.It Record the libc-generated hostid in Pa /etc/hostid .Bd -literal -# zgenhostid $(hostid) +# zgenhostid "$(hostid)" .Ed -.It Record a custom hostid (0xdeadbeef) in Em etc/hostid +.It Record a custom hostid (0xdeadbeef) in Pa /etc/hostid .Bd -literal # zgenhostid deadbeef .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 .Sh SEE ALSO .Xr genhostid 1 , .Xr hostid 1 , +.Xr sethostid 3 , .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.