1105756Srwatson/*-
2125959Srwatson * Copyright (c) 2002, 2004 Networks Associates Technology, Inc.
3105756Srwatson * All rights reserved.
4105756Srwatson *
5105756Srwatson * This software was developed for the FreeBSD Project by NAI Labs, the
6105756Srwatson * Security Research Division of Network Associates, Inc. under
7105756Srwatson * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the DARPA
8105756Srwatson * CHATS research program.
9105756Srwatson *
10105756Srwatson * Redistribution and use in source and binary forms, with or without
11105756Srwatson * modification, are permitted provided that the following conditions
12105756Srwatson * are met:
13105756Srwatson * 1. Redistributions of source code must retain the above copyright
14105756Srwatson *    notice, this list of conditions and the following disclaimer.
15105756Srwatson * 2. Redistributions in binary form must reproduce the above copyright
16105756Srwatson *    notice, this list of conditions and the following disclaimer in the
17105756Srwatson *    documentation and/or other materials provided with the distribution.
18105756Srwatson *
19105756Srwatson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20105756Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21105756Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22105756Srwatson * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23105756Srwatson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24105756Srwatson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25105756Srwatson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26105756Srwatson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27105756Srwatson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28105756Srwatson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29105756Srwatson * SUCH DAMAGE.
30105756Srwatson *
31105756Srwatson * $FreeBSD$
32105756Srwatson */
33107489Srwatson
34105756Srwatson#include <sys/types.h>
35105756Srwatson#include <sys/mac.h>
36107489Srwatson#include <sys/queue.h>
37107489Srwatson#include <sys/stat.h>
38105756Srwatson
39107489Srwatson#include <ctype.h>
40105756Srwatson#include <err.h>
41107489Srwatson#include <errno.h>
42107489Srwatson#include <fts.h>
43107489Srwatson#include <libgen.h>
44107489Srwatson#include <regex.h>
45105756Srwatson#include <stdio.h>
46105756Srwatson#include <stdlib.h>
47105756Srwatson#include <string.h>
48105756Srwatson#include <unistd.h>
49105756Srwatson
50107489Srwatsonstruct label_spec {
51107489Srwatson	struct label_spec_entry {
52107489Srwatson		regex_t regex;	/* compiled regular expression to match */
53107489Srwatson		char *regexstr;	/* uncompiled regular expression */
54107489Srwatson		mode_t mode;	/* mode to possibly match */
55140907Sdelphij		const char *modestr;	/* print-worthy ",-?" mode string */
56107489Srwatson		char *mactext;	/* MAC label to apply */
57107489Srwatson		int flags;	/* miscellaneous flags */
58107489Srwatson#define		F_DONTLABEL	0x01
59107489Srwatson#define		F_ALWAYSMATCH	0x02
60107489Srwatson	} *entries,		/* entries[0..nentries] */
61107489Srwatson	  *match;		/* cached decision for MAC label to apply */
62107489Srwatson	size_t nentries;	/* size of entries list */
63107489Srwatson	STAILQ_ENTRY(label_spec) link;
64107489Srwatson};
65105756Srwatson
66107489Srwatsonstruct label_specs {
67107489Srwatson	STAILQ_HEAD(label_specs_head, label_spec) head;
68107489Srwatson};
69105756Srwatson
70107489Srwatsonvoid usage(int) __dead2;
71107489Srwatsonstruct label_specs *new_specs(void);
72107489Srwatsonvoid add_specs(struct label_specs *, const char *, int);
73107489Srwatsonvoid add_setfmac_specs(struct label_specs *, char *);
74107489Srwatsonvoid add_spec_line(const char *, int, struct label_spec_entry *, char *);
75107489Srwatsonint apply_specs(struct label_specs *, FTSENT *, int, int);
76107489Srwatsonint specs_empty(struct label_specs *);
77105756Srwatson
78125959Srwatsonstatic int qflag;
79125959Srwatson
80105756Srwatsonint
81107489Srwatsonmain(int argc, char **argv)
82105756Srwatson{
83107489Srwatson	FTSENT *ftsent;
84107489Srwatson	FTS *fts;
85107489Srwatson	struct label_specs *specs;
86107489Srwatson	int eflag = 0, xflag = 0, vflag = 0, Rflag = 0, hflag;
87107489Srwatson	int ch, is_setfmac;
88107489Srwatson	char *bn;
89105756Srwatson
90107489Srwatson	bn = basename(argv[0]);
91107489Srwatson	if (bn == NULL)
92107489Srwatson		err(1, "basename");
93107489Srwatson	is_setfmac = strcmp(bn, "setfmac") == 0;
94107489Srwatson	hflag = is_setfmac ? FTS_LOGICAL : FTS_PHYSICAL;
95107489Srwatson	specs = new_specs();
96125959Srwatson	while ((ch = getopt(argc, argv, is_setfmac ? "Rhq" : "ef:qs:vx")) !=
97125959Srwatson	    -1) {
98105756Srwatson		switch (ch) {
99107489Srwatson		case 'R':
100107489Srwatson			Rflag = 1;
101107489Srwatson			break;
102107489Srwatson		case 'e':
103107489Srwatson			eflag = 1;
104107489Srwatson			break;
105107489Srwatson		case 'f':
106107489Srwatson			add_specs(specs, optarg, 0);
107107489Srwatson			break;
108105756Srwatson		case 'h':
109107489Srwatson			hflag = FTS_PHYSICAL;
110105756Srwatson			break;
111125959Srwatson		case 'q':
112125959Srwatson			qflag = 1;
113125959Srwatson			break;
114107489Srwatson		case 's':
115107489Srwatson			add_specs(specs, optarg, 1);
116107489Srwatson			break;
117107489Srwatson		case 'v':
118107489Srwatson			vflag++;
119107489Srwatson			break;
120107489Srwatson		case 'x':
121107489Srwatson			xflag = FTS_XDEV;
122107489Srwatson			break;
123105756Srwatson		default:
124107489Srwatson			usage(is_setfmac);
125105756Srwatson		}
126105756Srwatson	}
127107489Srwatson	argc -= optind;
128105756Srwatson	argv += optind;
129105756Srwatson
130107489Srwatson	if (is_setfmac) {
131107489Srwatson		if (argc <= 1)
132107489Srwatson			usage(is_setfmac);
133107489Srwatson		add_setfmac_specs(specs, *argv);
134107489Srwatson		argc--;
135107489Srwatson		argv++;
136107489Srwatson	} else {
137107489Srwatson		if (argc == 0 || specs_empty(specs))
138107489Srwatson			usage(is_setfmac);
139107489Srwatson	}
140107489Srwatson	fts = fts_open(argv, hflag | xflag, NULL);
141107489Srwatson	if (fts == NULL)
142107489Srwatson		err(1, "cannot traverse filesystem%s", argc ? "s" : "");
143107489Srwatson	while ((ftsent = fts_read(fts)) != NULL) {
144107489Srwatson		switch (ftsent->fts_info) {
145107489Srwatson		case FTS_DP:		/* skip post-order */
146107489Srwatson			break;
147107489Srwatson		case FTS_D:		/* do pre-order */
148107489Srwatson		case FTS_DC:		/* do cyclic? */
149107489Srwatson			/* don't ever recurse directories as setfmac(8) */
150107489Srwatson			if (is_setfmac && !Rflag)
151107489Srwatson				fts_set(fts, ftsent, FTS_SKIP);
152107489Srwatson		case FTS_DEFAULT:	/* do default */
153107489Srwatson		case FTS_F:		/* do regular */
154107489Srwatson		case FTS_SL:		/* do symlink */
155107794Sgreen		case FTS_SLNONE:	/* do symlink */
156107489Srwatson		case FTS_W:		/* do whiteout */
157107489Srwatson			if (apply_specs(specs, ftsent, hflag, vflag)) {
158107489Srwatson				if (eflag) {
159175796Syar					errx(1, "labeling not supported in %s",
160107489Srwatson					    ftsent->fts_path);
161107489Srwatson				}
162125959Srwatson				if (!qflag)
163175796Syar					warnx("labeling not supported in %s",
164125959Srwatson					    ftsent->fts_path);
165107489Srwatson				fts_set(fts, ftsent, FTS_SKIP);
166107489Srwatson			}
167107489Srwatson			break;
168107489Srwatson		case FTS_DNR:		/* die on all errors */
169107489Srwatson		case FTS_ERR:
170107489Srwatson		case FTS_NS:
171175796Syar			err(1, "traversing %s", ftsent->fts_path);
172107489Srwatson		default:
173175796Syar			errx(1, "CANNOT HAPPEN (%d) traversing %s",
174175796Syar			    ftsent->fts_info, ftsent->fts_path);
175107489Srwatson		}
176107489Srwatson	}
177107489Srwatson	fts_close(fts);
178107489Srwatson	exit(0);
179107489Srwatson}
180105756Srwatson
181107489Srwatsonvoid
182107489Srwatsonusage(int is_setfmac)
183107489Srwatson{
184107489Srwatson
185107489Srwatson	if (is_setfmac)
186125959Srwatson		fprintf(stderr, "usage: setfmac [-Rhq] label file ...\n");
187107489Srwatson	else
188125959Srwatson		fprintf(stderr, "usage: setfsmac [-ehqvx] [-f specfile [...]] [-s specfile [...]] file ...\n");
189107489Srwatson	exit(1);
190107489Srwatson}
191107489Srwatson
192140907Sdelphijstatic int
193107489Srwatsonchomp_line(char **line, size_t *linesize)
194107489Srwatson{
195107489Srwatson	char *s;
196107489Srwatson	int freeme = 0;
197107489Srwatson
198140907Sdelphij	for (s = *line; (unsigned)(s - *line) < *linesize; s++) {
199107489Srwatson		if (!isspace(*s))
200107489Srwatson			break;
201105756Srwatson	}
202107489Srwatson	if (*s == '#') {
203107489Srwatson		**line = '\0';
204107489Srwatson		*linesize = 0;
205107489Srwatson		return (freeme);
206107489Srwatson	}
207107489Srwatson	memmove(*line, s, *linesize - (s - *line));
208107489Srwatson	*linesize -= s - *line;
209107489Srwatson	for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
210107489Srwatson		if (!isspace(*s))
211107489Srwatson			break;
212107489Srwatson	}
213107489Srwatson	if (s != &(*line)[*linesize - 1]) {
214107489Srwatson		*linesize = s - *line + 1;
215107489Srwatson	} else {
216107489Srwatson		s = malloc(*linesize + 1);
217107489Srwatson		if (s == NULL)
218107489Srwatson			err(1, "malloc");
219107489Srwatson		strncpy(s, *line, *linesize);
220107489Srwatson		*line = s;
221107489Srwatson		freeme = 1;
222107489Srwatson	}
223107489Srwatson	(*line)[*linesize] = '\0';
224107489Srwatson	return (freeme);
225107489Srwatson}
226105756Srwatson
227107489Srwatsonvoid
228107489Srwatsonadd_specs(struct label_specs *specs, const char *file, int is_sebsd)
229107489Srwatson{
230107489Srwatson	struct label_spec *spec;
231107489Srwatson	FILE *fp;
232107489Srwatson	char *line;
233107489Srwatson	size_t nlines = 0, linesize;
234107489Srwatson	int freeline;
235107489Srwatson
236107489Srwatson	spec = malloc(sizeof(*spec));
237107489Srwatson	if (spec == NULL)
238107489Srwatson		err(1, "malloc");
239107489Srwatson	fp = fopen(file, "r");
240107489Srwatson	if (fp == NULL)
241107489Srwatson		err(1, "opening %s", file);
242107489Srwatson	while ((line = fgetln(fp, &linesize)) != NULL) {
243107489Srwatson		freeline = chomp_line(&line, &linesize);
244107489Srwatson		if (linesize > 0) /* only allocate space for non-comments */
245107489Srwatson			nlines++;
246107489Srwatson		if (freeline)
247107489Srwatson			free(line);
248107489Srwatson	}
249107489Srwatson	if (ferror(fp))
250107489Srwatson		err(1, "fgetln on %s", file);
251107489Srwatson	rewind(fp);
252107489Srwatson	spec->entries = calloc(nlines, sizeof(*spec->entries));
253107489Srwatson	if (spec->entries == NULL)
254107489Srwatson		err(1, "malloc");
255107489Srwatson	spec->nentries = nlines;
256107489Srwatson	while (nlines > 0) {
257107489Srwatson		line = fgetln(fp, &linesize);
258107489Srwatson		if (line == NULL) {
259107489Srwatson			if (feof(fp))
260107489Srwatson				errx(1, "%s ended prematurely", file);
261107489Srwatson			else
262107489Srwatson				err(1, "failure reading %s", file);
263105756Srwatson		}
264107489Srwatson		freeline = chomp_line(&line, &linesize);
265107489Srwatson		if (linesize == 0) {
266107489Srwatson			if (freeline)
267107489Srwatson				free(line);
268107489Srwatson			continue;
269107489Srwatson		}
270107489Srwatson		add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
271107489Srwatson		if (freeline)
272107489Srwatson			free(line);
273107489Srwatson	}
274107489Srwatson	fclose(fp);
275125959Srwatson	if (!qflag)
276125959Srwatson		warnx("%s: read %lu specifications", file,
277125959Srwatson		    (long)spec->nentries);
278107489Srwatson	STAILQ_INSERT_TAIL(&specs->head, spec, link);
279107489Srwatson}
280105756Srwatson
281107489Srwatsonvoid
282107489Srwatsonadd_setfmac_specs(struct label_specs *specs, char *label)
283107489Srwatson{
284107489Srwatson	struct label_spec *spec;
285107489Srwatson
286107489Srwatson	spec = malloc(sizeof(*spec));
287107489Srwatson	if (spec == NULL)
288107489Srwatson		err(1, "malloc");
289107489Srwatson	spec->nentries = 1;
290107489Srwatson	spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
291107489Srwatson	if (spec->entries == NULL)
292107489Srwatson		err(1, "malloc");
293107489Srwatson	/* The _only_ thing specified here is the mactext! */
294107489Srwatson	spec->entries->mactext = label;
295107489Srwatson	spec->entries->flags |= F_ALWAYSMATCH;
296107489Srwatson	STAILQ_INSERT_TAIL(&specs->head, spec, link);
297107489Srwatson}
298107489Srwatson
299107489Srwatsonvoid
300107489Srwatsonadd_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
301107489Srwatson    char *line)
302107489Srwatson{
303107489Srwatson	char *regexstr, *modestr, *macstr, *regerrorstr;
304107489Srwatson	size_t size;
305107489Srwatson	int error;
306107489Srwatson
307107489Srwatson	regexstr = strtok(line, " \t");
308107489Srwatson	if (regexstr == NULL)
309107489Srwatson		errx(1, "%s: need regular expression", file);
310107489Srwatson	modestr = strtok(NULL, " \t");
311107489Srwatson	if (modestr == NULL)
312107489Srwatson		errx(1, "%s: need a label", file);
313107489Srwatson	macstr = strtok(NULL, " \t");
314107489Srwatson	if (macstr == NULL) {	/* the mode is just optional */
315107489Srwatson		macstr = modestr;
316107489Srwatson		modestr = NULL;
317105756Srwatson	}
318107489Srwatson	if (strtok(NULL, " \t") != NULL)
319107489Srwatson		errx(1, "%s: extraneous fields at end of line", file);
320107489Srwatson	/* assume we need to anchor this regex */
321107489Srwatson	if (asprintf(&regexstr, "^%s$", regexstr) == -1)
322107489Srwatson		err(1, "%s: processing regular expression", file);
323107489Srwatson	entry->regexstr = regexstr;
324107489Srwatson	error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
325107489Srwatson	if (error) {
326107489Srwatson		size = regerror(error, &entry->regex, NULL, 0);
327107489Srwatson		regerrorstr = malloc(size);
328107489Srwatson		if (regerrorstr == NULL)
329107489Srwatson			err(1, "malloc");
330107489Srwatson		(void)regerror(error, &entry->regex, regerrorstr, size);
331107489Srwatson		errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
332107489Srwatson	}
333107489Srwatson	if (!is_sebsd) {
334107489Srwatson		entry->mactext = strdup(macstr);
335107489Srwatson		if (entry->mactext == NULL)
336107489Srwatson			err(1, "strdup");
337107489Srwatson	} else {
338107489Srwatson		if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
339107489Srwatson			err(1, "asprintf");
340107489Srwatson		if (strcmp(macstr, "<<none>>") == 0)
341107489Srwatson			entry->flags |= F_DONTLABEL;
342107489Srwatson	}
343107489Srwatson	if (modestr != NULL) {
344107489Srwatson		if (strlen(modestr) != 2 || modestr[0] != '-')
345107489Srwatson			errx(1, "%s: invalid mode string: %s", file, modestr);
346107489Srwatson		switch (modestr[1]) {
347107489Srwatson		case 'b':
348107489Srwatson			entry->mode = S_IFBLK;
349107489Srwatson			entry->modestr = ",-b";
350107489Srwatson			break;
351107489Srwatson		case 'c':
352107489Srwatson			entry->mode = S_IFCHR;
353107489Srwatson			entry->modestr = ",-c";
354107489Srwatson			break;
355107489Srwatson		case 'd':
356107489Srwatson			entry->mode = S_IFDIR;
357107489Srwatson			entry->modestr = ",-d";
358107489Srwatson			break;
359107489Srwatson		case 'p':
360107489Srwatson			entry->mode = S_IFIFO;
361107489Srwatson			entry->modestr = ",-p";
362107489Srwatson			break;
363107489Srwatson		case 'l':
364107489Srwatson			entry->mode = S_IFLNK;
365107489Srwatson			entry->modestr = ",-l";
366107489Srwatson			break;
367107489Srwatson		case 's':
368107489Srwatson			entry->mode = S_IFSOCK;
369107489Srwatson			entry->modestr = ",-s";
370107489Srwatson			break;
371107489Srwatson		case '-':
372107489Srwatson			entry->mode = S_IFREG;
373107489Srwatson			entry->modestr = ",--";
374107489Srwatson			break;
375107489Srwatson		default:
376107489Srwatson			errx(1, "%s: invalid mode string: %s", file, modestr);
377107489Srwatson		}
378107489Srwatson	} else {
379107489Srwatson		entry->modestr = "";
380107489Srwatson	}
381107489Srwatson}
382105756Srwatson
383107489Srwatsonint
384107489Srwatsonspecs_empty(struct label_specs *specs)
385107489Srwatson{
386105756Srwatson
387107489Srwatson	return (STAILQ_EMPTY(&specs->head));
388105756Srwatson}
389107489Srwatson
390107489Srwatsonint
391107489Srwatsonapply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
392107489Srwatson{
393107489Srwatson	regmatch_t pmatch;
394107489Srwatson	struct label_spec *ls;
395107489Srwatson	struct label_spec_entry *ent;
396107489Srwatson	char *regerrorstr, *macstr;
397107489Srwatson	size_t size;
398107489Srwatson	mac_t mac;
399107489Srwatson	int error, matchedby;
400107489Srwatson
401107489Srwatson	/*
402107489Srwatson	 * Work through file context sources in order of specification
403107489Srwatson	 * on the command line, and through their entries in reverse
404107489Srwatson	 * order to find the "last" (hopefully "best") match.
405107489Srwatson	 */
406107489Srwatson	matchedby = 0;
407107489Srwatson	STAILQ_FOREACH(ls, &specs->head, link) {
408107489Srwatson		for (ls->match = NULL, ent = ls->entries;
409107489Srwatson		    ent < &ls->entries[ls->nentries]; ent++) {
410107489Srwatson			if (ent->flags & F_ALWAYSMATCH)
411107489Srwatson				goto matched;
412107489Srwatson			if (ent->mode != 0 &&
413107489Srwatson			    (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
414107489Srwatson				continue;
415107489Srwatson			pmatch.rm_so = 0;
416107489Srwatson			pmatch.rm_eo = ftsent->fts_pathlen;
417107489Srwatson			error = regexec(&ent->regex, ftsent->fts_path, 1,
418107489Srwatson			    &pmatch, REG_STARTEND);
419107489Srwatson			switch (error) {
420107489Srwatson			case REG_NOMATCH:
421107489Srwatson				continue;
422107489Srwatson			case 0:
423107489Srwatson				break;
424107489Srwatson			default:
425107489Srwatson				size = regerror(error, &ent->regex, NULL, 0);
426107489Srwatson				regerrorstr = malloc(size);
427107489Srwatson				if (regerrorstr == NULL)
428107489Srwatson					err(1, "malloc");
429107489Srwatson				(void)regerror(error, &ent->regex, regerrorstr,
430107489Srwatson				    size);
431107489Srwatson				errx(1, "%s: %s", ent->regexstr, regerrorstr);
432107489Srwatson			}
433107489Srwatson		matched:
434107489Srwatson			ls->match = ent;
435107489Srwatson			if (vflag) {
436107489Srwatson				if (matchedby == 0) {
437175796Syar					printf("%s matched by ",
438107489Srwatson					    ftsent->fts_path);
439107489Srwatson					matchedby = 1;
440107489Srwatson				}
441107489Srwatson				printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
442107489Srwatson				    ent->regexstr, ent->modestr, ent->mactext);
443107489Srwatson				if (matchedby == 1)
444107489Srwatson					matchedby = 2;
445107489Srwatson			}
446107489Srwatson			break;
447107489Srwatson		}
448107489Srwatson	}
449107489Srwatson	if (vflag && matchedby)
450107489Srwatson		printf("\n");
451107489Srwatson	size = 0;
452107489Srwatson	STAILQ_FOREACH(ls, &specs->head, link) {
453107489Srwatson		/* cached match decision */
454107489Srwatson		if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
455107489Srwatson			 /* add length of "x\0"/"y," */
456107489Srwatson			size += strlen(ls->match->mactext) + 1;
457107489Srwatson	}
458107489Srwatson	if (size == 0)
459107489Srwatson		return (0);
460107489Srwatson	macstr = malloc(size);
461107489Srwatson	if (macstr == NULL)
462107489Srwatson		err(1, "malloc");
463107489Srwatson	*macstr = '\0';
464107489Srwatson	STAILQ_FOREACH(ls, &specs->head, link) {
465107489Srwatson		/* cached match decision */
466107489Srwatson		if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
467107489Srwatson			if (*macstr != '\0')
468107489Srwatson				strcat(macstr, ",");
469107489Srwatson			strcat(macstr, ls->match->mactext);
470107489Srwatson		}
471107489Srwatson	}
472107489Srwatson	if (mac_from_text(&mac, macstr))
473107489Srwatson		err(1, "mac_from_text(%s)", macstr);
474107489Srwatson	if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
475107489Srwatson	    mac_set_file(ftsent->fts_accpath, mac)) != 0) {
476107489Srwatson		if (errno == EOPNOTSUPP) {
477107489Srwatson			mac_free(mac);
478107489Srwatson			free(macstr);
479107489Srwatson			return (1);
480107489Srwatson		}
481175796Syar		err(1, "mac_set_link(%s, %s)", ftsent->fts_path, macstr);
482107489Srwatson	}
483107489Srwatson	mac_free(mac);
484107489Srwatson	free(macstr);
485107489Srwatson	return (0);
486107489Srwatson}
487107489Srwatson
488107489Srwatsonstruct label_specs *
489107489Srwatsonnew_specs(void)
490107489Srwatson{
491107489Srwatson	struct label_specs *specs;
492107489Srwatson
493107489Srwatson	specs = malloc(sizeof(*specs));
494107489Srwatson	if (specs == NULL)
495107489Srwatson		err(1, "malloc");
496107489Srwatson	STAILQ_INIT(&specs->head);
497107489Srwatson	return (specs);
498107489Srwatson}
499