1/*	$NetBSD: create.c,v 1.78 2024/04/24 01:44:51 christos Exp $	*/
2
3/*-
4 * Copyright (c) 1989, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#if HAVE_NBTOOL_CONFIG_H
33#include "nbtool_config.h"
34#endif
35
36#include <sys/cdefs.h>
37#if defined(__RCSID) && !defined(lint)
38#if 0
39static char sccsid[] = "@(#)create.c	8.1 (Berkeley) 6/6/93";
40#else
41__RCSID("$NetBSD: create.c,v 1.78 2024/04/24 01:44:51 christos Exp $");
42#endif
43#endif /* not lint */
44
45#include <sys/param.h>
46#include <sys/stat.h>
47
48#if ! HAVE_NBTOOL_CONFIG_H
49#include <dirent.h>
50#endif
51
52#include <errno.h>
53#include <fcntl.h>
54#include <grp.h>
55#include <pwd.h>
56#include <stdio.h>
57#include <stdarg.h>
58#include <stdint.h>
59#include <stdlib.h>
60#include <string.h>
61#include <time.h>
62#include <unistd.h>
63
64#ifndef NO_MD5
65#include <md5.h>
66#endif
67#ifndef NO_RMD160
68#include <rmd160.h>
69#endif
70#ifndef NO_SHA1
71#include <sha1.h>
72#endif
73#ifndef NO_SHA2
74#include <sha2.h>
75#endif
76
77#include "extern.h"
78
79#define	INDENTNAMELEN	15
80#define	MAXLINELEN	80
81
82static gid_t gid;
83static uid_t uid;
84static mode_t mode;
85static u_long flags;
86
87#if defined(__FreeBSD__) && !defined(HAVE_NBTOOL_CONFIG_H)
88#define	FTS_CONST const
89#else
90#define	FTS_CONST
91#endif
92
93static int	dcmp(const FTSENT *FTS_CONST *, const FTSENT *FTS_CONST *);
94static void	output(FILE *, int, int *, const char *, ...)
95    __printflike(4, 5);
96static int	statd(FILE *, FTS *, FTSENT *, uid_t *, gid_t *, mode_t *,
97    u_long *);
98static void	statf(FILE *, int, FTSENT *);
99
100void
101cwalk(FILE *fp)
102{
103	FTS *t;
104	FTSENT *p;
105	time_t clocktime;
106	char host[MAXHOSTNAMELEN + 1];
107	const char *user;
108	char *argv[2];
109	char  dot[] = ".";
110	int indent = 0;
111
112	argv[0] = dot;
113	argv[1] = NULL;
114
115	time(&clocktime);
116	gethostname(host, sizeof(host));
117	host[sizeof(host) - 1] = '\0';
118	if ((user = getlogin()) == NULL) {
119		struct passwd *pw;
120		user = (pw = getpwuid(getuid())) != NULL ? pw->pw_name :
121		    "<unknown>";
122	}
123
124	if (!nflag)
125		fprintf(fp,
126	    	    "#\t   user: %s\n#\tmachine: %s\n#\t   tree: %s\n"
127		    "#\t   date: %s",
128		    user, host, fullpath, ctime(&clocktime));
129
130	if ((t = fts_open(argv, ftsoptions, dcmp)) == NULL)
131		mtree_err("fts_open: %s", strerror(errno));
132	while ((p = fts_read(t)) != NULL) {
133		if (jflag)
134			indent = p->fts_level * 4;
135		if (check_excludes(p->fts_name, p->fts_path)) {
136			fts_set(t, p, FTS_SKIP);
137			continue;
138		}
139		if (!find_only(p->fts_path)) {
140			fts_set(t, p, FTS_SKIP);
141			continue;
142		}
143		switch(p->fts_info) {
144		case FTS_D:
145			if (!bflag)
146				fprintf(fp, "\n");
147			if (!nflag)
148				fprintf(fp, "# %s\n", p->fts_path);
149			statd(fp, t, p, &uid, &gid, &mode, &flags);
150			statf(fp, indent, p);
151			break;
152		case FTS_DP:
153			if (p->fts_level > 0)
154				if (!nflag)
155					fprintf(fp, "%*s# %s\n", indent, "",
156					    p->fts_path);
157			if (p->fts_level > 0 || flavor == F_FREEBSD9) {
158				fprintf(fp, "%*s..\n", indent, "");
159				if (!bflag)
160					fprintf(fp, "\n");
161			}
162			break;
163		case FTS_DNR:
164		case FTS_ERR:
165		case FTS_NS:
166			mtree_err("%s: %s",
167			    p->fts_path, strerror(p->fts_errno));
168			break;
169		default:
170			if (!dflag)
171				statf(fp, indent, p);
172			break;
173
174		}
175	}
176	if (errno != 0)
177		mtree_err("fts_read: %s", strerror(errno));
178	fts_close(t);
179	if (sflag && keys & F_CKSUM)
180		mtree_err("%s checksum: %u", fullpath, crc_total);
181}
182
183static void
184dosum(FILE *fp, int indent, FTSENT *p, int *offset, int flag,
185    char * (*func)(const char *, char *), const char *key)
186{
187	char *digestbuf;
188
189	if ((keys & flag) == 0)
190		return;
191
192	digestbuf = (*func)(p->fts_accpath, NULL);
193	if (digestbuf != NULL) {
194		output(fp, indent, offset, "%s=%s", key, digestbuf);
195		free(digestbuf);
196		return;
197	}
198
199	if (qflag) {
200		warn("%s: %s failed", p->fts_path, key);
201		return;
202	}
203
204	mtree_err("%s: %s failed: %s", p->fts_path, key, strerror(errno));
205}
206
207static char *
208crcFile(const char *fname, char *dummy __unused)
209{
210	char *ptr;
211	uint32_t val, len;
212	int fd, e;
213
214	if ((fd = open(fname, O_RDONLY)) == -1)
215		goto out;
216
217	e = crc(fd, &val, &len);
218	close(fd);
219	if (e)
220		goto out;
221
222	if (asprintf(&ptr, "%u", val) < 0)
223		goto out;
224
225	return ptr;
226out:
227	mtree_err("%s: %s", fname, strerror(errno));
228	return NULL;
229}
230
231static void
232statf(FILE *fp, int indent, FTSENT *p)
233{
234	int offset;
235	const char *name = NULL;
236
237	offset = fprintf(fp, "%*s%s%s", indent, "",
238	    S_ISDIR(p->fts_statp->st_mode) ? "" : "    ", vispath(p->fts_name));
239
240	if (offset > (INDENTNAMELEN + indent))
241		offset = MAXLINELEN;
242	else
243		offset += fprintf(fp, "%*s",
244		    (INDENTNAMELEN + indent) - offset, "");
245
246	if (!S_ISREG(p->fts_statp->st_mode) && (flavor == F_NETBSD6 || !dflag))
247		output(fp, indent, &offset, "type=%s",
248		    inotype(p->fts_statp->st_mode));
249	if (keys & (F_UID | F_UNAME) && p->fts_statp->st_uid != uid) {
250		if (keys & F_UNAME &&
251		    (name = user_from_uid(p->fts_statp->st_uid, 1)) != NULL)
252			output(fp, indent, &offset, "uname=%s", name);
253		if (keys & F_UID || (keys & F_UNAME && name == NULL))
254			output(fp, indent, &offset, "uid=%u",
255			    p->fts_statp->st_uid);
256	}
257	if (keys & (F_GID | F_GNAME) && p->fts_statp->st_gid != gid) {
258		if (keys & F_GNAME &&
259		    (name = group_from_gid(p->fts_statp->st_gid, 1)) != NULL)
260			output(fp, indent, &offset, "gname=%s", name);
261		if (keys & F_GID || (keys & F_GNAME && name == NULL))
262			output(fp, indent, &offset, "gid=%u",
263			    p->fts_statp->st_gid);
264	}
265	if (keys & F_MODE && (p->fts_statp->st_mode & MBITS) != mode)
266		output(fp, indent, &offset, "mode=%#o",
267		    p->fts_statp->st_mode & MBITS);
268	if (keys & F_DEV &&
269	    (S_ISBLK(p->fts_statp->st_mode) || S_ISCHR(p->fts_statp->st_mode)))
270		output(fp, indent, &offset, "device=%#jx",
271		    (uintmax_t)p->fts_statp->st_rdev);
272	if (keys & F_NLINK && p->fts_statp->st_nlink != 1)
273		output(fp, indent, &offset, "nlink=%ju",
274		    (uintmax_t)p->fts_statp->st_nlink);
275	if (keys & F_SIZE &&
276	    (flavor == F_FREEBSD9 || S_ISREG(p->fts_statp->st_mode)))
277		output(fp, indent, &offset, "size=%ju",
278		    (uintmax_t)p->fts_statp->st_size);
279	if (keys & F_TIME)
280#if defined(BSD4_4) && !defined(HAVE_NBTOOL_CONFIG_H)
281		output(fp, indent, &offset, "time=%jd.%09ld",
282		    (intmax_t)p->fts_statp->st_mtimespec.tv_sec,
283		    p->fts_statp->st_mtimespec.tv_nsec);
284#else
285		output(fp, indent, &offset, "time=%jd.%09ld",
286		    (intmax_t)p->fts_statp->st_mtime, (long)0);
287#endif
288	if (S_ISREG(p->fts_statp->st_mode))  {
289		dosum(fp, indent, p, &offset, F_CKSUM, crcFile, "cksum");
290#ifndef NO_MD5
291		dosum(fp, indent, p, &offset, F_MD5, MD5File, MD5KEY);
292#endif	/* ! NO_MD5 */
293#ifndef NO_RMD160
294		dosum(fp, indent, p, &offset, F_RMD160, RMD160File, RMD160KEY);
295#endif	/* ! NO_RMD160 */
296#ifndef NO_SHA1
297		dosum(fp, indent, p, &offset, F_SHA1, SHA1File, SHA1KEY);
298#endif	/* ! NO_SHA1 */
299#ifndef NO_SHA2
300		dosum(fp, indent, p, &offset, F_SHA256, SHA256_File, SHA256KEY);
301#ifdef SHA384_BLOCK_LENGTH
302		dosum(fp, indent, p, &offset, F_SHA384, SHA384_File, SHA384KEY);
303#endif
304		dosum(fp, indent, p, &offset, F_SHA512, SHA512_File, SHA512KEY);
305#endif	/* ! NO_SHA2 */
306	}
307	if (keys & F_SLINK &&
308	    (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE))
309		output(fp, indent, &offset, "link=%s",
310		    vispath(rlink(p->fts_accpath)));
311#if HAVE_STRUCT_STAT_ST_FLAGS
312	if (keys & F_FLAGS && p->fts_statp->st_flags != flags) {
313		char *str = flags_to_string(p->fts_statp->st_flags, "none");
314		output(fp, indent, &offset, "flags=%s", str);
315		free(str);
316	}
317#endif
318	putchar('\n');
319}
320
321/* XXX
322 * FLAGS2INDEX will fail once the user and system settable bits need more
323 * than one byte, respectively.
324 */
325#define FLAGS2INDEX(x)  (((x >> 8) & 0x0000ff00) | (x & 0x000000ff))
326
327#define	MTREE_MAXGID	5000
328#define	MTREE_MAXUID	5000
329#define	MTREE_MAXMODE	(MBITS + 1)
330#if HAVE_STRUCT_STAT_ST_FLAGS
331#define	MTREE_MAXFLAGS  (FLAGS2INDEX(CH_MASK) + 1)   /* 1808 */
332#else
333#define MTREE_MAXFLAGS	1
334#endif
335#define	MTREE_MAXS 16
336
337static int
338statd(FILE *fp, FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode,
339    u_long *pflags)
340{
341	FTSENT *p;
342	gid_t sgid;
343	uid_t suid;
344	mode_t smode;
345	u_long sflags = 0;
346	const char *name = NULL;
347	gid_t savegid;
348	uid_t saveuid;
349	mode_t savemode;
350	u_long saveflags;
351	u_short maxgid, maxuid, maxmode, maxflags;
352	u_short g[MTREE_MAXGID], u[MTREE_MAXUID],
353		m[MTREE_MAXMODE], f[MTREE_MAXFLAGS];
354	static int first = 1;
355
356	savegid = *pgid;
357	saveuid = *puid;
358	savemode = *pmode;
359	saveflags = *pflags;
360	if ((p = fts_children(t, 0)) == NULL) {
361		if (errno)
362			mtree_err("%s: %s", RP(parent), strerror(errno));
363		return (1);
364	}
365
366	memset(g, 0, sizeof(g));
367	memset(u, 0, sizeof(u));
368	memset(m, 0, sizeof(m));
369	memset(f, 0, sizeof(f));
370
371	maxuid = maxgid = maxmode = maxflags = 0;
372	for (; p; p = p->fts_link) {
373		if (flavor == F_NETBSD6 || !dflag ||
374		    (dflag && S_ISDIR(p->fts_statp->st_mode))) {
375			smode = p->fts_statp->st_mode & MBITS;
376			if (smode < MTREE_MAXMODE && ++m[smode] > maxmode) {
377				savemode = smode;
378				maxmode = m[smode];
379			}
380			sgid = p->fts_statp->st_gid;
381			if (sgid < MTREE_MAXGID && ++g[sgid] > maxgid) {
382				savegid = sgid;
383				maxgid = g[sgid];
384			}
385			suid = p->fts_statp->st_uid;
386			if (suid < MTREE_MAXUID && ++u[suid] > maxuid) {
387				saveuid = suid;
388				maxuid = u[suid];
389			}
390
391#if HAVE_STRUCT_STAT_ST_FLAGS
392			sflags = FLAGS2INDEX(p->fts_statp->st_flags);
393			if (sflags < MTREE_MAXFLAGS && ++f[sflags] > maxflags) {
394				saveflags = p->fts_statp->st_flags;
395				maxflags = f[sflags];
396			}
397#endif
398		}
399	}
400	/*
401	 * If the /set record is the same as the last one we do not need to
402	 * output a new one.  So first we check to see if anything changed.
403	 * Note that we always output a /set record for the first directory.
404	 */
405	if (((keys & (F_UNAME | F_UID)) && (*puid != saveuid)) ||
406	    ((keys & (F_GNAME | F_GID)) && (*pgid != savegid)) ||
407	    ((keys & F_MODE) && (*pmode != savemode)) ||
408	    ((keys & F_FLAGS) && (*pflags != saveflags)) ||
409	    first) {
410		first = 0;
411		if (flavor != F_NETBSD6 && dflag)
412			fprintf(fp, "/set type=dir");
413		else
414			fprintf(fp, "/set type=file");
415		if (keys & (F_UID | F_UNAME)) {
416			if (keys & F_UNAME &&
417			    (name = user_from_uid(saveuid, 1)) != NULL)
418				fprintf(fp, " uname=%s", name);
419			if (keys & F_UID || (keys & F_UNAME && name == NULL))
420				fprintf(fp, " uid=%lu", (u_long)saveuid);
421		}
422		if (keys & (F_GID | F_GNAME)) {
423			if (keys & F_GNAME &&
424			    (name = group_from_gid(savegid, 1)) != NULL)
425				fprintf(fp, " gname=%s", name);
426			if (keys & F_GID || (keys & F_GNAME && name == NULL))
427				fprintf(fp, " gid=%lu", (u_long)savegid);
428		}
429		if (keys & F_MODE)
430			fprintf(fp, " mode=%#lo", (u_long)savemode);
431		if (keys & F_NLINK)
432			fprintf(fp, " nlink=1");
433		if (keys & F_FLAGS) {
434			char *str = flags_to_string(saveflags, "none");
435			fprintf(fp, " flags=%s", str);
436			free(str);
437		}
438		fprintf(fp, "\n");
439		*puid = saveuid;
440		*pgid = savegid;
441		*pmode = savemode;
442		*pflags = saveflags;
443	}
444	return (0);
445}
446
447/*
448 * dcmp --
449 *	used as a comparison function passed to fts_open() to control
450 *	the order in which fts_read() returns results.	We make
451 *	directories sort after non-directories, but otherwise sort in
452 *	strcmp() order.
453 *
454 * Keep this in sync with nodecmp() in spec.c.
455 */
456static int
457dcmp(const FTSENT *FTS_CONST *a, const FTSENT *FTS_CONST *b)
458{
459
460	if (S_ISDIR((*a)->fts_statp->st_mode)) {
461		if (!S_ISDIR((*b)->fts_statp->st_mode))
462			return (1);
463	} else if (S_ISDIR((*b)->fts_statp->st_mode))
464		return (-1);
465	return (strcmp((*a)->fts_name, (*b)->fts_name));
466}
467
468void
469output(FILE *fp, int indent, int *offset, const char *fmt, ...)
470{
471	va_list ap;
472	char buf[1024];
473
474	va_start(ap, fmt);
475	vsnprintf(buf, sizeof(buf), fmt, ap);
476	va_end(ap);
477
478	if (*offset + strlen(buf) > MAXLINELEN - 3) {
479		fprintf(fp, " \\\n%*s", INDENTNAMELEN + indent, "");
480		*offset = INDENTNAMELEN + indent;
481	}
482	*offset += fprintf(fp, " %s", buf) + 1;
483}
484