1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2020, Georgy Yakovlev.  All rights reserved.
24 */
25
26#include <errno.h>
27#include <fcntl.h>
28#include <getopt.h>
29#include <inttypes.h>
30#include <limits.h>
31#include <stdint.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/stat.h>
36#include <time.h>
37#include <unistd.h>
38
39static void
40usage(void)
41{
42	(void) fprintf(stderr,
43	    "usage: zgenhostid [-fh] [-o path] [value]\n\n"
44	    "  -f\t\t force hostid file write\n"
45	    "  -h\t\t print this usage and exit\n"
46	    "  -o <filename>\t write hostid to this file\n\n"
47	    "If hostid file is not present, store a hostid in it.\n"
48	    "The optional value should be an 8-digit hex number between"
49	    " 1 and 2^32-1.\n"
50	    "If the value is 0 or no value is provided, a random one"
51	    " will be generated.\n"
52	    "The value must be unique among your systems.\n");
53	exit(EXIT_FAILURE);
54	/* NOTREACHED */
55}
56
57int
58main(int argc, char **argv)
59{
60	/* default file path, can be optionally set by user */
61	const char *path = "/etc/hostid";
62	/* holds converted user input or lrand48() generated value */
63	unsigned long input_i = 0;
64
65	int opt;
66	int force_fwrite = 0;
67	while ((opt = getopt_long(argc, argv, "fo:h?", 0, 0)) != -1) {
68		switch (opt) {
69		case 'f':
70			force_fwrite = 1;
71			break;
72		case 'o':
73			path = optarg;
74			break;
75		case 'h':
76		case '?':
77			usage();
78		}
79	}
80
81	char *in_s = argv[optind];
82	if (in_s != NULL) {
83		/* increment pointer by 2 if string is 0x prefixed */
84		if (strncasecmp("0x", in_s, 2) == 0) {
85			in_s += 2;
86		}
87
88		/* need to be exactly 8 characters */
89		const char *hex = "0123456789abcdefABCDEF";
90		if (strlen(in_s) != 8 || strspn(in_s, hex) != 8) {
91			fprintf(stderr, "%s\n", strerror(ERANGE));
92			usage();
93		}
94
95		input_i = strtoul(in_s, NULL, 16);
96		if (errno != 0) {
97			perror("strtoul");
98			exit(EXIT_FAILURE);
99		}
100
101		if (input_i > UINT32_MAX) {
102			fprintf(stderr, "%s\n", strerror(ERANGE));
103			usage();
104		}
105	}
106
107	struct stat fstat;
108	if (force_fwrite == 0 && stat(path, &fstat) == 0 &&
109	    S_ISREG(fstat.st_mode)) {
110		fprintf(stderr, "%s: %s\n", path, strerror(EEXIST));
111		exit(EXIT_FAILURE);
112	}
113
114	/*
115	 * generate if not provided by user
116	 * also handle unlikely zero return from lrand48()
117	 */
118	while (input_i == 0) {
119		srand48(getpid() ^ time(NULL));
120		input_i = lrand48();
121	}
122
123	FILE *fp = fopen(path, "wb");
124	if (!fp) {
125		perror("fopen");
126		exit(EXIT_FAILURE);
127	}
128
129	/*
130	 * we need just 4 bytes in native endianness
131	 * not using sethostid() because it may be missing or just a stub
132	 */
133	uint32_t hostid = input_i;
134	int written = fwrite(&hostid, 1, 4, fp);
135	if (written != 4) {
136		perror("fwrite");
137		exit(EXIT_FAILURE);
138	}
139
140	fclose(fp);
141	exit(EXIT_SUCCESS);
142}
143