xref: /freebsd/sys/contrib/openzfs/cmd/zgenhostid.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * This file and its contents are supplied under the terms of the
4  * Common Development and Distribution License ("CDDL"), version 1.0.
5  * You may only use this file in accordance with the terms of version
6  * 1.0 of the CDDL.
7  *
8  * A full copy of the text of the CDDL should have accompanied this
9  * source.  A copy of the CDDL is also available via the Internet at
10  * https://opensource.org/license/CDDL-1.0.
11  */
12 
13 /*
14  * Copyright (c) 2020, Georgy Yakovlev.  All rights reserved.
15  */
16 
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <getopt.h>
20 #include <inttypes.h>
21 #include <limits.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 
29 /*
30  * Fill buf with len bytes from the system CSPRNG.  Returns 0 on success
31  * and -1 on failure, with errno set.
32  */
33 static int
get_random_bytes(void * buf,size_t len)34 get_random_bytes(void *buf, size_t len)
35 {
36 	int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
37 	if (fd < 0)
38 		return (-1);
39 
40 	size_t got = 0;
41 	while (got < len) {
42 		ssize_t n = read(fd, (char *)buf + got, len - got);
43 		if (n <= 0) {
44 			if (n < 0 && errno == EINTR)
45 				continue;
46 			if (n == 0)
47 				errno = EIO;
48 			int saved = errno;
49 			(void) close(fd);
50 			errno = saved;
51 			return (-1);
52 		}
53 		got += (size_t)n;
54 	}
55 
56 	(void) close(fd);
57 	return (0);
58 }
59 
60 static __attribute__((noreturn)) void
usage(void)61 usage(void)
62 {
63 	(void) fprintf(stderr,
64 	    "usage: zgenhostid [-fh] [-o path] [value]\n\n"
65 	    "  -f\t\t force hostid file write\n"
66 	    "  -h\t\t print this usage and exit\n"
67 	    "  -o <filename>\t write hostid to this file\n\n"
68 	    "If hostid file is not present, store a hostid in it.\n"
69 	    "The optional value should be an 8-digit hex number between"
70 	    " 1 and 2^32-1.\n"
71 	    "If the value is 0 or no value is provided, a random one"
72 	    " will be generated.\n"
73 	    "The value must be unique among your systems.\n");
74 	exit(EXIT_FAILURE);
75 }
76 
77 int
main(int argc,char ** argv)78 main(int argc, char **argv)
79 {
80 	/* default file path, can be optionally set by user */
81 	const char *path = "/etc/hostid";
82 	/* holds converted user input or generated value */
83 	unsigned long input_i = 0;
84 
85 	int opt;
86 	int force_fwrite = 0;
87 	while ((opt = getopt_long(argc, argv, "fo:h?", 0, 0)) != -1) {
88 		switch (opt) {
89 		case 'f':
90 			force_fwrite = 1;
91 			break;
92 		case 'o':
93 			path = optarg;
94 			break;
95 		case 'h':
96 		case '?':
97 			usage();
98 		}
99 	}
100 
101 	char *in_s = argv[optind];
102 	if (in_s != NULL) {
103 		/* increment pointer by 2 if string is 0x prefixed */
104 		if (strncasecmp("0x", in_s, 2) == 0) {
105 			in_s += 2;
106 		}
107 
108 		/* need to be exactly 8 characters */
109 		const char *hex = "0123456789abcdefABCDEF";
110 		if (strlen(in_s) != 8 || strspn(in_s, hex) != 8) {
111 			fprintf(stderr, "%s\n", strerror(ERANGE));
112 			usage();
113 		}
114 
115 		input_i = strtoul(in_s, NULL, 16);
116 		if (errno != 0) {
117 			perror("strtoul");
118 			exit(EXIT_FAILURE);
119 		}
120 
121 		if (input_i > UINT32_MAX) {
122 			fprintf(stderr, "%s\n", strerror(ERANGE));
123 			usage();
124 		}
125 	}
126 
127 	struct stat fstat;
128 	if (force_fwrite == 0 && stat(path, &fstat) == 0 &&
129 	    S_ISREG(fstat.st_mode)) {
130 		fprintf(stderr, "%s: %s\n", path, strerror(EEXIST));
131 		exit(EXIT_FAILURE);
132 	}
133 
134 	/*
135 	 * Generate if not provided by user. The hostid identifies
136 	 * this machine to ZFS multihost protection, so it must be
137 	 * unique. Obtain a value from the system CSPRNG to ensure
138 	 * that even on systems that share a common image and boot
139 	 * deterministically different hostids will be generated.
140 	 * The loop handles the unlikely zero return case.
141 	 */
142 	while (input_i == 0) {
143 		uint32_t rnd;
144 
145 		if (get_random_bytes(&rnd, sizeof (rnd)) != 0) {
146 			(void) fprintf(stderr,
147 			    "zgenhostid: failed to read /dev/urandom: %s\n",
148 			    strerror(errno));
149 			exit(EXIT_FAILURE);
150 		}
151 
152 		input_i = rnd;
153 	}
154 
155 	FILE *fp = fopen(path, "wb");
156 	if (!fp) {
157 		perror("fopen");
158 		exit(EXIT_FAILURE);
159 	}
160 
161 	/*
162 	 * we need just 4 bytes in native endianness
163 	 * not using sethostid() because it may be missing or just a stub
164 	 */
165 	uint32_t hostid = input_i;
166 	int written = fwrite(&hostid, 1, 4, fp);
167 	if (written != 4) {
168 		perror("fwrite");
169 		exit(EXIT_FAILURE);
170 	}
171 
172 	fclose(fp);
173 	exit(EXIT_SUCCESS);
174 }
175