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(®exstr, "^%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