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