Home | History | Annotate | Download | only in src
      1 /*
      2  * File contexts backend for labeling system
      3  *
      4  * Author : Eamon Walsh <ewalsh (at) tycho.nsa.gov>
      5  */
      6 
      7 #include <fcntl.h>
      8 #include <stdarg.h>
      9 #include <string.h>
     10 #include <stdio.h>
     11 #include <ctype.h>
     12 #include <errno.h>
     13 #include <limits.h>
     14 #include <regex.h>
     15 #include <sys/types.h>
     16 #include <sys/stat.h>
     17 #include <unistd.h>
     18 #include "callbacks.h"
     19 #include "label_internal.h"
     20 
     21 /*
     22  * Internals, mostly moved over from matchpathcon.c
     23  */
     24 
     25 /* A file security context specification. */
     26 typedef struct spec {
     27 	struct selabel_lookup_rec lr;	/* holds contexts for lookup result */
     28 	char *regex_str;	/* regular expession string for diagnostics */
     29 	char *type_str;		/* type string for diagnostic messages */
     30 	regex_t regex;		/* compiled regular expression */
     31 	char regcomp;           /* regex_str has been compiled to regex */
     32 	mode_t mode;		/* mode format value */
     33 	int matches;		/* number of matching pathnames */
     34 	int hasMetaChars;	/* regular expression has meta-chars */
     35 	int stem_id;		/* indicates which stem-compression item */
     36 } spec_t;
     37 
     38 /* A regular expression stem */
     39 typedef struct stem {
     40 	char *buf;
     41 	int len;
     42 } stem_t;
     43 
     44 /* Our stored configuration */
     45 struct saved_data {
     46 	/*
     47 	 * The array of specifications, initially in the same order as in
     48 	 * the specification file. Sorting occurs based on hasMetaChars.
     49 	 */
     50 	spec_t *spec_arr;
     51 	unsigned int nspec;
     52 	unsigned int ncomp;
     53 
     54 	/*
     55 	 * The array of regular expression stems.
     56 	 */
     57 	stem_t *stem_arr;
     58 	int num_stems;
     59 	int alloc_stems;
     60 };
     61 
     62 /* Return the length of the text that can be considered the stem, returns 0
     63  * if there is no identifiable stem */
     64 static int get_stem_from_spec(const char *const buf)
     65 {
     66 	const char *tmp = strchr(buf + 1, '/');
     67 	const char *ind;
     68 
     69 	if (!tmp)
     70 		return 0;
     71 
     72 	for (ind = buf; ind < tmp; ind++) {
     73 		if (strchr(".^$?*+|[({", (int)*ind))
     74 			return 0;
     75 	}
     76 	return tmp - buf;
     77 }
     78 
     79 /* return the length of the text that is the stem of a file name */
     80 static int get_stem_from_file_name(const char *const buf)
     81 {
     82 	const char *tmp = strchr(buf + 1, '/');
     83 
     84 	if (!tmp)
     85 		return 0;
     86 	return tmp - buf;
     87 }
     88 
     89 /* find the stem of a file spec, returns the index into stem_arr for a new
     90  * or existing stem, (or -1 if there is no possible stem - IE for a file in
     91  * the root directory or a regex that is too complex for us). */
     92 static int find_stem_from_spec(struct saved_data *data, const char *buf)
     93 {
     94 	int i, num = data->num_stems;
     95 	int stem_len = get_stem_from_spec(buf);
     96 
     97 	if (!stem_len)
     98 		return -1;
     99 	for (i = 0; i < num; i++) {
    100 		if (stem_len == data->stem_arr[i].len
    101 		    && !strncmp(buf, data->stem_arr[i].buf, stem_len))
    102 			return i;
    103 	}
    104 	if (data->alloc_stems == num) {
    105 		stem_t *tmp_arr;
    106 		data->alloc_stems = data->alloc_stems * 2 + 16;
    107 		tmp_arr = realloc(data->stem_arr,
    108 				  sizeof(stem_t) * data->alloc_stems);
    109 		if (!tmp_arr)
    110 			return -1;
    111 		data->stem_arr = tmp_arr;
    112 	}
    113 	data->stem_arr[num].len = stem_len;
    114 	data->stem_arr[num].buf = malloc(stem_len + 1);
    115 	if (!data->stem_arr[num].buf)
    116 		return -1;
    117 	memcpy(data->stem_arr[num].buf, buf, stem_len);
    118 	data->stem_arr[num].buf[stem_len] = '\0';
    119 	data->num_stems++;
    120 	buf += stem_len;
    121 	return num;
    122 }
    123 
    124 /* find the stem of a file name, returns the index into stem_arr (or -1 if
    125  * there is no match - IE for a file in the root directory or a regex that is
    126  * too complex for us).  Makes buf point to the text AFTER the stem. */
    127 static int find_stem_from_file(struct saved_data *data, const char **buf)
    128 {
    129 	int i;
    130 	int stem_len = get_stem_from_file_name(*buf);
    131 
    132 	if (!stem_len)
    133 		return -1;
    134 	for (i = 0; i < data->num_stems; i++) {
    135 		if (stem_len == data->stem_arr[i].len
    136 		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
    137 			*buf += stem_len;
    138 			return i;
    139 		}
    140 	}
    141 	return -1;
    142 }
    143 
    144 /*
    145  * Warn about duplicate specifications.
    146  */
    147 static int nodups_specs(struct saved_data *data, const char *path)
    148 {
    149 	int rc = 0;
    150 	unsigned int ii, jj;
    151 	struct spec *curr_spec, *spec_arr = data->spec_arr;
    152 
    153 	for (ii = 0; ii < data->nspec; ii++) {
    154 		curr_spec = &spec_arr[ii];
    155 		for (jj = ii + 1; jj < data->nspec; jj++) {
    156 			if ((!strcmp
    157 			     (spec_arr[jj].regex_str, curr_spec->regex_str))
    158 			    && (!spec_arr[jj].mode || !curr_spec->mode
    159 				|| spec_arr[jj].mode == curr_spec->mode)) {
    160 				rc = -1;
    161 				errno = EINVAL;
    162 				if (strcmp
    163 				    (spec_arr[jj].lr.ctx_raw,
    164 				     curr_spec->lr.ctx_raw)) {
    165 					selinux_log
    166 						(SELINUX_ERROR,
    167 						 "%s: Multiple different specifications for %s  (%s and %s).\n",
    168 						 path, curr_spec->regex_str,
    169 						 spec_arr[jj].lr.ctx_raw,
    170 						 curr_spec->lr.ctx_raw);
    171 				} else {
    172 					selinux_log
    173 						(SELINUX_ERROR,
    174 						 "%s: Multiple same specifications for %s.\n",
    175 						 path, curr_spec->regex_str);
    176 				}
    177 			}
    178 		}
    179 	}
    180 	return rc;
    181 }
    182 
    183 /* Determine if the regular expression specification has any meta characters. */
    184 static void spec_hasMetaChars(struct spec *spec)
    185 {
    186 	char *c;
    187 	int len;
    188 	char *end;
    189 
    190 	c = spec->regex_str;
    191 	len = strlen(spec->regex_str);
    192 	end = c + len;
    193 
    194 	spec->hasMetaChars = 0;
    195 
    196 	/* Look at each character in the RE specification string for a
    197 	 * meta character. Return when any meta character reached. */
    198 	while (c != end) {
    199 		switch (*c) {
    200 		case '.':
    201 		case '^':
    202 		case '$':
    203 		case '?':
    204 		case '*':
    205 		case '+':
    206 		case '|':
    207 		case '[':
    208 		case '(':
    209 		case '{':
    210 			spec->hasMetaChars = 1;
    211 			return;
    212 		case '\\':	/* skip the next character */
    213 			c++;
    214 			break;
    215 		default:
    216 			break;
    217 
    218 		}
    219 		c++;
    220 	}
    221 	return;
    222 }
    223 
    224 static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf)
    225 {
    226 	char *reg_buf, *anchored_regex, *cp;
    227 	stem_t *stem_arr = data->stem_arr;
    228 	size_t len;
    229 	int regerr;
    230 
    231 	if (spec->regcomp)
    232 		return 0; /* already done */
    233 
    234 	data->ncomp++; /* how many compiled regexes required */
    235 
    236 	/* Skip the fixed stem. */
    237 	reg_buf = spec->regex_str;
    238 	if (spec->stem_id >= 0)
    239 		reg_buf += stem_arr[spec->stem_id].len;
    240 
    241 	/* Anchor the regular expression. */
    242 	len = strlen(reg_buf);
    243 	cp = anchored_regex = malloc(len + 3);
    244 	if (!anchored_regex)
    245 		return -1;
    246 	/* Create ^...$ regexp.  */
    247 	*cp++ = '^';
    248 	memcpy(cp, reg_buf, len);
    249 	cp += len;
    250 	*cp++ = '$';
    251 	*cp = '\0';
    252 
    253 	/* Compile the regular expression. */
    254 	regerr = regcomp(&spec->regex, anchored_regex,
    255 			 REG_EXTENDED | REG_NOSUB);
    256 	if (regerr != 0) {
    257 		size_t errsz = 0;
    258 		errsz = regerror(regerr, &spec->regex, NULL, 0);
    259 		if (errsz && errbuf)
    260 			*errbuf = malloc(errsz);
    261 		if (errbuf && *errbuf)
    262 			(void)regerror(regerr, &spec->regex,
    263 				       *errbuf, errsz);
    264 
    265 		free(anchored_regex);
    266 		return -1;
    267 	}
    268 	free(anchored_regex);
    269 
    270 	/* Done. */
    271 	spec->regcomp = 1;
    272 
    273 	return 0;
    274 }
    275 
    276 
    277 static int process_line(struct selabel_handle *rec,
    278 			const char *path, const char *prefix,
    279 			char *line_buf, int pass, unsigned lineno)
    280 {
    281 	int items, len;
    282 	char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ];
    283 	char *buf_p, *regex = buf1, *type = buf2, *context = buf3;
    284 	struct saved_data *data = (struct saved_data *)rec->data;
    285 	spec_t *spec_arr = data->spec_arr;
    286 	unsigned int nspec = data->nspec;
    287 
    288 	len = strlen(line_buf);
    289 	if (line_buf[len - 1] == '\n')
    290 		line_buf[len - 1] = 0;
    291 	buf_p = line_buf;
    292 	while (isspace(*buf_p))
    293 		buf_p++;
    294 	/* Skip comment lines and empty lines. */
    295 	if (*buf_p == '#' || *buf_p == 0)
    296 		return 0;
    297 	items = sscanf(line_buf, "%255s %255s %255s", regex, type, context);
    298 	if (items < 2) {
    299 		selinux_log(SELINUX_WARNING,
    300 			    "%s:  line %d is missing fields, skipping\n", path,
    301 			    lineno);
    302 		return 0;
    303 	} else if (items == 2) {
    304 		/* The type field is optional. */
    305 		context = type;
    306 		type = NULL;
    307 	}
    308 
    309 	len = get_stem_from_spec(regex);
    310 	if (len && prefix && strncmp(prefix, regex, len)) {
    311 		/* Stem of regex does not match requested prefix, discard. */
    312 		return 0;
    313 	}
    314 
    315 	if (pass == 1) {
    316 		/* On the second pass, process and store the specification in spec. */
    317 		char *errbuf = NULL;
    318 		spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
    319 		spec_arr[nspec].regex_str = strdup(regex);
    320 		if (!spec_arr[nspec].regex_str) {
    321 			selinux_log(SELINUX_WARNING,
    322 				   "%s:  out of memory at line %d on regex %s\n",
    323 				   path, lineno, regex);
    324 			return -1;
    325 
    326 		}
    327 		if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
    328 			selinux_log(SELINUX_WARNING,
    329 				   "%s:  line %d has invalid regex %s:  %s\n",
    330 				   path, lineno, regex,
    331 				   (errbuf ? errbuf : "out of memory"));
    332 		}
    333 
    334 		/* Convert the type string to a mode format */
    335 		spec_arr[nspec].mode = 0;
    336 		if (!type)
    337 			goto skip_type;
    338 		spec_arr[nspec].type_str = strdup(type);
    339 		len = strlen(type);
    340 		if (type[0] != '-' || len != 2) {
    341 			selinux_log(SELINUX_WARNING,
    342 				    "%s:  line %d has invalid file type %s\n",
    343 				    path, lineno, type);
    344 			return 0;
    345 		}
    346 		switch (type[1]) {
    347 		case 'b':
    348 			spec_arr[nspec].mode = S_IFBLK;
    349 			break;
    350 		case 'c':
    351 			spec_arr[nspec].mode = S_IFCHR;
    352 			break;
    353 		case 'd':
    354 			spec_arr[nspec].mode = S_IFDIR;
    355 			break;
    356 		case 'p':
    357 			spec_arr[nspec].mode = S_IFIFO;
    358 			break;
    359 		case 'l':
    360 			spec_arr[nspec].mode = S_IFLNK;
    361 			break;
    362 		case 's':
    363 			spec_arr[nspec].mode = S_IFSOCK;
    364 			break;
    365 		case '-':
    366 			spec_arr[nspec].mode = S_IFREG;
    367 			break;
    368 		default:
    369 			selinux_log(SELINUX_WARNING,
    370 				    "%s:  line %d has invalid file type %s\n",
    371 				    path, lineno, type);
    372 			return 0;
    373 		}
    374 
    375 	skip_type:
    376 		spec_arr[nspec].lr.ctx_raw = strdup(context);
    377 
    378 		if (strcmp(context, "<<none>>") && rec->validating) {
    379 			if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
    380 				selinux_log(SELINUX_WARNING,
    381 					    "%s:  line %d has invalid context %s\n",
    382 					    path, lineno, spec_arr[nspec].lr.ctx_raw);
    383 			}
    384 		}
    385 
    386 		/* Determine if specification has
    387 		 * any meta characters in the RE */
    388 		spec_hasMetaChars(&spec_arr[nspec]);
    389 	}
    390 
    391 	data->nspec = ++nspec;
    392 	return 0;
    393 }
    394 
    395 static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
    396 		unsigned n)
    397 {
    398 	struct saved_data *data = (struct saved_data *)rec->data;
    399 	const char *path = NULL;
    400 	const char *prefix = NULL;
    401 	FILE *fp;
    402 	FILE *localfp = NULL;
    403 	FILE *homedirfp = NULL;
    404 	char local_path[PATH_MAX + 1];
    405 	char homedir_path[PATH_MAX + 1];
    406 	char line_buf[BUFSIZ];
    407 	unsigned int lineno, pass, i, j, maxnspec;
    408 	spec_t *spec_copy = NULL;
    409 	int status = -1, baseonly = 0;
    410 	struct stat sb;
    411 
    412 	/* Process arguments */
    413 	while (n--)
    414 		switch(opts[n].type) {
    415 		case SELABEL_OPT_PATH:
    416 			path = opts[n].value;
    417 			break;
    418 		case SELABEL_OPT_SUBSET:
    419 			prefix = opts[n].value;
    420 			break;
    421 		case SELABEL_OPT_BASEONLY:
    422 			baseonly = !!opts[n].value;
    423 			break;
    424 		}
    425 
    426 	/* Open the specification file. */
    427 	if ((fp = fopen(path, "r")) == NULL)
    428 		return -1;
    429 
    430 	if (fstat(fileno(fp), &sb) < 0)
    431 		return -1;
    432 	if (!S_ISREG(sb.st_mode)) {
    433 		errno = EINVAL;
    434 		return -1;
    435 	}
    436 
    437 	if (!baseonly) {
    438 		snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
    439 			 path);
    440 		homedirfp = fopen(homedir_path, "r");
    441 
    442 		snprintf(local_path, sizeof(local_path), "%s.local", path);
    443 		localfp = fopen(local_path, "r");
    444 	}
    445 
    446 	/*
    447 	 * Perform two passes over the specification file.
    448 	 * The first pass counts the number of specifications and
    449 	 * performs simple validation of the input.  At the end
    450 	 * of the first pass, the spec array is allocated.
    451 	 * The second pass performs detailed validation of the input
    452 	 * and fills in the spec array.
    453 	 */
    454 	maxnspec = UINT_MAX / sizeof(spec_t);
    455 	for (pass = 0; pass < 2; pass++) {
    456 		lineno = 0;
    457 		data->nspec = 0;
    458 		data->ncomp = 0;
    459 		while (fgets(line_buf, sizeof line_buf - 1, fp)
    460 		       && data->nspec < maxnspec) {
    461 			if (process_line(rec, path, prefix, line_buf,
    462 					 pass, ++lineno) != 0)
    463 				goto finish;
    464 		}
    465 		if (pass == 1) {
    466 			status = nodups_specs(data, path);
    467 			if (status)
    468 				goto finish;
    469 		}
    470 		lineno = 0;
    471 		if (homedirfp)
    472 			while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
    473 			       && data->nspec < maxnspec) {
    474 				if (process_line
    475 				    (rec, homedir_path, prefix,
    476 				     line_buf, pass, ++lineno) != 0)
    477 					goto finish;
    478 			}
    479 
    480 		lineno = 0;
    481 		if (localfp)
    482 			while (fgets(line_buf, sizeof line_buf - 1, localfp)
    483 			       && data->nspec < maxnspec) {
    484 				if (process_line
    485 				    (rec, local_path, prefix, line_buf,
    486 				     pass, ++lineno) != 0)
    487 					goto finish;
    488 			}
    489 
    490 		if (pass == 0) {
    491 			if (data->nspec == 0) {
    492 				status = 0;
    493 				goto finish;
    494 			}
    495 			if (NULL == (data->spec_arr =
    496 				     malloc(sizeof(spec_t) * data->nspec)))
    497 				goto finish;
    498 			memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
    499 			maxnspec = data->nspec;
    500 			rewind(fp);
    501 			if (homedirfp)
    502 				rewind(homedirfp);
    503 			if (localfp)
    504 				rewind(localfp);
    505 		}
    506 	}
    507 
    508 	/* Move exact pathname specifications to the end. */
    509 	spec_copy = malloc(sizeof(spec_t) * data->nspec);
    510 	if (!spec_copy)
    511 		goto finish;
    512 	j = 0;
    513 	for (i = 0; i < data->nspec; i++)
    514 		if (data->spec_arr[i].hasMetaChars)
    515 			memcpy(&spec_copy[j++],
    516 			       &data->spec_arr[i], sizeof(spec_t));
    517 	for (i = 0; i < data->nspec; i++)
    518 		if (!data->spec_arr[i].hasMetaChars)
    519 			memcpy(&spec_copy[j++],
    520 			       &data->spec_arr[i], sizeof(spec_t));
    521 	free(data->spec_arr);
    522 	data->spec_arr = spec_copy;
    523 
    524 	status = 0;
    525 finish:
    526 	fclose(fp);
    527 	if (data->spec_arr != spec_copy)
    528 		free(data->spec_arr);
    529 	if (homedirfp)
    530 		fclose(homedirfp);
    531 	if (localfp)
    532 		fclose(localfp);
    533 	return status;
    534 }
    535 
    536 /*
    537  * Backend interface routines
    538  */
    539 static void closef(struct selabel_handle *rec)
    540 {
    541 	struct saved_data *data = (struct saved_data *)rec->data;
    542 	struct spec *spec;
    543 	struct stem *stem;
    544 	unsigned int i;
    545 
    546 	for (i = 0; i < data->nspec; i++) {
    547 		spec = &data->spec_arr[i];
    548 		free(spec->regex_str);
    549 		free(spec->type_str);
    550 		free(spec->lr.ctx_raw);
    551 		free(spec->lr.ctx_trans);
    552 		if (spec->regcomp)
    553 			regfree(&spec->regex);
    554 	}
    555 
    556 	for (i = 0; i < (unsigned int)data->num_stems; i++) {
    557 		stem = &data->stem_arr[i];
    558 		free(stem->buf);
    559 	}
    560 
    561 	if (data->spec_arr)
    562 		free(data->spec_arr);
    563 	if (data->stem_arr)
    564 		free(data->stem_arr);
    565 
    566 	free(data);
    567 }
    568 
    569 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
    570 					 const char *key, int type)
    571 {
    572 	struct saved_data *data = (struct saved_data *)rec->data;
    573 	spec_t *spec_arr = data->spec_arr;
    574 	int i, rc, file_stem;
    575 	mode_t mode = (mode_t)type;
    576 	const char *buf;
    577 	struct selabel_lookup_rec *ret = NULL;
    578 	char *clean_key = NULL;
    579 	const char *prev_slash, *next_slash;
    580 	unsigned int sofar = 0;
    581 
    582 	if (!data->nspec) {
    583 		errno = ENOENT;
    584 		goto finish;
    585 	}
    586 
    587 	/* Remove duplicate slashes */
    588 	if ((next_slash = strstr(key, "//"))) {
    589 		clean_key = malloc(strlen(key) + 1);
    590 		if (!clean_key)
    591 			goto finish;
    592 		prev_slash = key;
    593 		while (next_slash) {
    594 			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
    595 			sofar += next_slash - prev_slash;
    596 			prev_slash = next_slash + 1;
    597 			next_slash = strstr(prev_slash, "//");
    598 		}
    599 		strcpy(clean_key + sofar, prev_slash);
    600 		key = clean_key;
    601 	}
    602 
    603 	buf = key;
    604 	file_stem = find_stem_from_file(data, &buf);
    605 	mode &= S_IFMT;
    606 
    607 	/*
    608 	 * Check for matching specifications in reverse order, so that
    609 	 * the last matching specification is used.
    610 	 */
    611 	for (i = data->nspec - 1; i >= 0; i--) {
    612 		/* if the spec in question matches no stem or has the same
    613 		 * stem as the file AND if the spec in question has no mode
    614 		 * specified or if the mode matches the file mode then we do
    615 		 * a regex check        */
    616 		if ((spec_arr[i].stem_id == -1
    617 		     || spec_arr[i].stem_id == file_stem)
    618 		    && (!mode || !spec_arr[i].mode
    619 			|| mode == spec_arr[i].mode)) {
    620 			if (compile_regex(data, &spec_arr[i], NULL) < 0)
    621 				goto finish;
    622 			if (spec_arr[i].stem_id == -1)
    623 				rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
    624 			else
    625 				rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
    626 
    627 			if (rc == 0) {
    628 				spec_arr[i].matches++;
    629 				break;
    630 			}
    631 			if (rc == REG_NOMATCH)
    632 				continue;
    633 			/* else it's an error */
    634 			goto finish;
    635 		}
    636 	}
    637 
    638 	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
    639 		/* No matching specification. */
    640 		errno = ENOENT;
    641 		goto finish;
    642 	}
    643 
    644 	ret = &spec_arr[i].lr;
    645 
    646 finish:
    647 	free(clean_key);
    648 	return ret;
    649 }
    650 
    651 static void stats(struct selabel_handle *rec)
    652 {
    653 	struct saved_data *data = (struct saved_data *)rec->data;
    654 	unsigned int i, nspec = data->nspec;
    655 	spec_t *spec_arr = data->spec_arr;
    656 
    657 	for (i = 0; i < nspec; i++) {
    658 		if (spec_arr[i].matches == 0) {
    659 			if (spec_arr[i].type_str) {
    660 				selinux_log(SELINUX_WARNING,
    661 				    "Warning!  No matches for (%s, %s, %s)\n",
    662 				    spec_arr[i].regex_str,
    663 				    spec_arr[i].type_str,
    664 				    spec_arr[i].lr.ctx_raw);
    665 			} else {
    666 				selinux_log(SELINUX_WARNING,
    667 				    "Warning!  No matches for (%s, %s)\n",
    668 				    spec_arr[i].regex_str,
    669 				    spec_arr[i].lr.ctx_raw);
    670 			}
    671 		}
    672 	}
    673 }
    674 
    675 int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts,
    676 		      unsigned nopts)
    677 {
    678 	struct saved_data *data;
    679 
    680 	data = (struct saved_data *)malloc(sizeof(*data));
    681 	if (!data)
    682 		return -1;
    683 	memset(data, 0, sizeof(*data));
    684 
    685 	rec->data = data;
    686 	rec->func_close = &closef;
    687 	rec->func_stats = &stats;
    688 	rec->func_lookup = &lookup;
    689 
    690 	return init(rec, opts, nopts);
    691 }
    692