xref: /illumos-gate/usr/src/cmd/logadm/conf.c (revision 0dee7919e2f2a6479d16b370af93747b9416b242)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 /*
31  * logadm/conf.c -- configuration file module
32  */
33 
34 #include <stdio.h>
35 #include <libintl.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/mman.h>
40 #include <ctype.h>
41 #include <strings.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include "err.h"
45 #include "lut.h"
46 #include "fn.h"
47 #include "opts.h"
48 #include "conf.h"
49 
50 /* forward declarations of functions private to this module */
51 static void fillconflist(int lineno, const char *entry, char **args,
52     struct opts *opts, const char *com, int flags);
53 static void fillargs(char *arg);
54 static char *nexttok(char **ptrptr);
55 static void conf_print(FILE *stream);
56 
57 static const char *Confname;	/* name of the confile file */
58 static char *Confbuf;		/* copy of the config file (a la mmap()) */
59 static int Conflen;		/* length of mmap'd area */
60 static int Conffd = -1;		/* file descriptor for config file */
61 static boolean_t Confchanged;	/* true if we need to write changes back */
62 
63 /*
64  * our structured representation of the configuration file
65  * is made up of a list of these
66  */
67 struct confinfo {
68 	struct confinfo *cf_next;
69 	int cf_lineno;		/* line number in file */
70 	const char *cf_entry;	/* name of entry, if line has an entry */
71 	char **cf_args;		/* raw rhs of entry */
72 	struct opts *cf_opts;	/* parsed rhs of entry */
73 	const char *cf_com;	/* any comment text found */
74 	int cf_flags;
75 };
76 
77 #define	CONFF_DELETED	1	/* entry should be deleted on write back */
78 
79 static struct confinfo *Confinfo;	/* the entries in the config file */
80 static struct confinfo *Confinfolast;	/* end of list */
81 static struct lut *Conflut;		/* lookup table keyed by entry name */
82 static struct fn_list *Confentries;	/* list of valid entry names */
83 
84 /* allocate & fill in another entry in our list */
85 static void
86 fillconflist(int lineno, const char *entry, char **args,
87     struct opts *opts, const char *com, int flags)
88 {
89 	struct confinfo *cp = MALLOC(sizeof (*cp));
90 
91 	cp->cf_next = NULL;
92 	cp->cf_lineno = lineno;
93 	cp->cf_entry = entry;
94 	cp->cf_args = args;
95 	cp->cf_opts = opts;
96 	cp->cf_com = com;
97 	cp->cf_flags = flags;
98 	if (entry) {
99 		Conflut = lut_add(Conflut, entry, cp);
100 		fn_list_adds(Confentries, entry);
101 	}
102 	if (Confinfo == NULL)
103 		Confinfo = Confinfolast = cp;
104 	else {
105 		Confinfolast->cf_next = cp;
106 		Confinfolast = cp;
107 	}
108 }
109 
110 static char **Args;	/* static buffer for args */
111 static int ArgsN;	/* size of our static buffer */
112 static int ArgsI;	/* index into Cmdargs as we walk table */
113 #define	CONF_ARGS_INC	1024
114 
115 /* callback for lut_walk to build a cmdargs vector */
116 static void
117 fillargs(char *arg)
118 {
119 	if (ArgsI >= ArgsN) {
120 		/* need bigger table */
121 		Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC));
122 		ArgsN += CONF_ARGS_INC;
123 	}
124 	Args[ArgsI++] = arg;
125 }
126 
127 /* isolate and return the next token */
128 static char *
129 nexttok(char **ptrptr)
130 {
131 	char *ptr = *ptrptr;
132 	char *eptr;
133 	char *quote = NULL;
134 
135 	while (*ptr && isspace(*ptr))
136 		ptr++;
137 
138 	if (*ptr == '"' || *ptr == '\'')
139 		quote = ptr++;
140 
141 	for (eptr = ptr; *eptr; eptr++)
142 		if (quote && *eptr == *quote) {
143 			/* found end quote */
144 			*eptr++ = '\0';
145 			*ptrptr = eptr;
146 			return (ptr);
147 		} else if (!quote && isspace(*eptr)) {
148 			/* found end of unquoted area */
149 			*eptr++ = '\0';
150 			*ptrptr = eptr;
151 			return (ptr);
152 		}
153 
154 	if (quote)
155 		err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote);
156 		/*NOTREACHED*/
157 
158 	*ptrptr = eptr;
159 
160 	if (ptr == eptr)
161 		return (NULL);
162 	else
163 		return (ptr);
164 }
165 
166 /*
167  * conf_open -- open the configuration file, lock it if we have write perms
168  */
169 void
170 conf_open(const char *fname, int needwrite)
171 {
172 	struct stat stbuf;
173 	int lineno = 0;
174 	char *line;
175 	char *eline;
176 	char *ebuf;
177 	char *comment;
178 
179 	Confname = fname;
180 	Confentries = fn_list_new(NULL);
181 
182 	/* special case this so we don't even try locking the file */
183 	if (strcmp(Confname, "/dev/null") == 0)
184 		return;
185 
186 	if ((Conffd = open(Confname, (needwrite) ? O_RDWR : O_RDONLY)) < 0)
187 		err(EF_SYS, "%s", Confname);
188 
189 	if (fstat(Conffd, &stbuf) < 0)
190 		err(EF_SYS, "fstat on %s", Confname);
191 
192 	if (needwrite && lockf(Conffd, F_LOCK, 0) < 0)
193 		err(EF_SYS, "lockf on %s", Confname);
194 
195 	if (stbuf.st_size == 0)
196 		return;	/* empty file, don't bother parsing it */
197 
198 	if ((Confbuf = (char *)mmap(0, stbuf.st_size,
199 	    PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1)
200 		err(EF_SYS, "mmap on %s", Confname);
201 
202 	Conflen = stbuf.st_size;
203 	Confchanged = B_FALSE;
204 
205 	ebuf = &Confbuf[Conflen];
206 
207 	if (Confbuf[Conflen - 1] != '\n')
208 		err(EF_WARN|EF_FILE, "config file doesn't end with "
209 		    "newline, last line ignored.");
210 
211 	line = Confbuf;
212 	while (line < ebuf) {
213 		lineno++;
214 		err_fileline(Confname, lineno);
215 		eline = line;
216 		comment = NULL;
217 		for (; eline < ebuf; eline++) {
218 			/* check for continued lines */
219 			if (comment == NULL && *eline == '\\' &&
220 			    eline + 1 < ebuf && *(eline + 1) == '\n') {
221 				*eline = ' ';
222 				*(eline + 1) = ' ';
223 				lineno++;
224 				err_fileline(Confname, lineno);
225 				continue;
226 			}
227 
228 			/* check for comments */
229 			if (comment == NULL && *eline == '#') {
230 				*eline = '\0';
231 				comment = (eline + 1);
232 				continue;
233 			}
234 
235 			/* check for end of line */
236 			if (*eline == '\n')
237 				break;
238 		}
239 		if (comment >= ebuf)
240 			comment = NULL;
241 		if (eline < ebuf) {
242 			char *entry;
243 
244 			*eline++ = '\0';
245 
246 			/*
247 			 * now we have the entry, if any, at "line"
248 			 * and the comment, if any, at "comment"
249 			 */
250 
251 			/* entry is first token */
252 			if ((entry = nexttok(&line)) != NULL &&
253 			    strcmp(entry, "logadm-version") == 0) {
254 				/*
255 				 * we somehow opened some future format
256 				 * conffile that we likely don't understand.
257 				 * if the given version is "1" then go on,
258 				 * otherwise someone is mixing versions
259 				 * and we can't help them other than to
260 				 * print an error and exit.
261 				 */
262 				if ((entry = nexttok(&line)) != NULL &&
263 				    strcmp(entry, "1") != 0)
264 					err(0, "%s version not "
265 					    "supported by "
266 					    "this version of logadm.",
267 					    Confname);
268 			} else if (entry) {
269 				char *ap;
270 				char **args;
271 				int i;
272 
273 				ArgsI = 0;
274 				while (ap = nexttok(&line))
275 					fillargs(ap);
276 				if (ArgsI == 0) {
277 					/* short entry allowed */
278 					fillconflist(lineno, entry,
279 					    NULL, NULL, comment, 0);
280 				} else {
281 					Args[ArgsI++] = NULL;
282 					args = MALLOC(sizeof (char *) * ArgsI);
283 					for (i = 0; i < ArgsI; i++)
284 						args[i] = Args[i];
285 					fillconflist(lineno, entry,
286 					    args, NULL, comment, 0);
287 				}
288 			} else
289 				fillconflist(lineno, entry, NULL, NULL,
290 				    comment, 0);
291 		}
292 		line = eline;
293 	}
294 	/*
295 	 * possible future enhancement:  go through and mark any entries:
296 	 * 		logfile -P <date>
297 	 * as DELETED if the logfile doesn't exist
298 	 */
299 }
300 
301 /*
302  * conf_close -- close the configuration file
303  */
304 void
305 conf_close(struct opts *opts)
306 {
307 	FILE *fp;
308 
309 	if (Confchanged && opts_count(opts, "n") == 0 && Conffd != -1) {
310 		if (opts_count(opts, "v"))
311 			(void) out("# writing changes to %s\n", Confname);
312 		if (Debug > 1) {
313 			(void) fprintf(stderr, "conf_close, %s changed to:\n",
314 			    Confname);
315 			conf_print(stderr);
316 		}
317 		if (lseek(Conffd, (off_t)0, SEEK_SET) < 0)
318 			err(EF_SYS, "lseek on %s", Confname);
319 		if (ftruncate(Conffd, (off_t)0) < 0)
320 			err(EF_SYS, "ftruncate on %s", Confname);
321 		if ((fp = fdopen(Conffd, "w")) == NULL)
322 			err(EF_SYS, "fdopen on %s", Confname);
323 		conf_print(fp);
324 		if (fclose(fp) < 0)
325 			err(EF_SYS, "fclose on %s", Confname);
326 		Conffd = -1;
327 		Confchanged = B_FALSE;
328 	} else if (opts_count(opts, "v")) {
329 		(void) out("# %s unchanged\n", Confname);
330 	}
331 
332 	if (Conffd != -1) {
333 		(void) close(Conffd);
334 		Conffd = -1;
335 	}
336 	if (Conflut) {
337 		lut_free(Conflut, free);
338 		Conflut = NULL;
339 	}
340 	if (Confentries) {
341 		fn_list_free(Confentries);
342 		Confentries = NULL;
343 	}
344 }
345 
346 /*
347  * conf_lookup -- lookup an entry in the config file
348  */
349 char **
350 conf_lookup(const char *lhs)
351 {
352 	struct confinfo *cp = lut_lookup(Conflut, lhs);
353 
354 	if (cp) {
355 		err_fileline(Confname, cp->cf_lineno);
356 		return (cp->cf_args);
357 	} else
358 		return (NULL);
359 }
360 
361 /*
362  * conf_opts -- return the parsed opts for an entry
363  */
364 struct opts *
365 conf_opts(const char *lhs)
366 {
367 	struct confinfo *cp = lut_lookup(Conflut, lhs);
368 
369 	if (cp) {
370 		if (cp->cf_opts)
371 			return (cp->cf_opts);	/* already parsed */
372 		err_fileline(Confname, cp->cf_lineno);
373 		cp->cf_opts = opts_parse(cp->cf_args, OPTF_CONF);
374 		return (cp->cf_opts);
375 	}
376 	return (opts_parse(NULL, OPTF_CONF));
377 }
378 
379 /*
380  * conf_replace -- replace an entry in the config file
381  */
382 void
383 conf_replace(const char *lhs, struct opts *newopts)
384 {
385 	struct confinfo *cp = lut_lookup(Conflut, lhs);
386 
387 	if (Conffd == -1)
388 		return;
389 
390 	if (cp) {
391 		cp->cf_opts = newopts;
392 		cp->cf_args = NULL;
393 		if (newopts == NULL)
394 			cp->cf_flags |= CONFF_DELETED;
395 	} else
396 		fillconflist(0, lhs, NULL, newopts, NULL, 0);
397 	Confchanged = B_TRUE;
398 }
399 
400 /*
401  * conf_set -- set options for an entry in the config file
402  */
403 void
404 conf_set(const char *entry, char *o, const char *optarg)
405 {
406 	struct confinfo *cp = lut_lookup(Conflut, entry);
407 
408 	if (Conffd == -1)
409 		return;
410 
411 	if (cp) {
412 		if (cp->cf_opts == NULL)
413 			cp->cf_opts = opts_parse(cp->cf_args, OPTF_CONF);
414 		cp->cf_flags &= ~CONFF_DELETED;
415 	} else {
416 		fillconflist(0, STRDUP(entry), NULL,
417 		    opts_parse(NULL, OPTF_CONF), NULL, 0);
418 		if ((cp = lut_lookup(Conflut, entry)) == NULL)
419 			err(0, "conf_set internal error");
420 	}
421 	(void) opts_set(cp->cf_opts, o, optarg);
422 	Confchanged = B_TRUE;
423 }
424 
425 /*
426  * conf_entries -- list all the entry names
427  */
428 struct fn_list *
429 conf_entries(void)
430 {
431 	return (Confentries);
432 }
433 
434 /* print the config file */
435 static void
436 conf_print(FILE *stream)
437 {
438 	struct confinfo *cp;
439 
440 	for (cp = Confinfo; cp; cp = cp->cf_next) {
441 		if (cp->cf_flags & CONFF_DELETED)
442 			continue;
443 		if (cp->cf_entry) {
444 			char **p;
445 
446 			opts_printword(cp->cf_entry, stream);
447 			if (cp->cf_opts) {
448 				/* existence of opts overrides args */
449 				opts_print(cp->cf_opts, stream, "fhnrvVw");
450 			} else if (cp->cf_args) {
451 				for (p = cp->cf_args; *p; p++) {
452 					(void) fprintf(stream, " ");
453 					opts_printword(*p, stream);
454 				}
455 			}
456 		}
457 		if (cp->cf_com) {
458 			if (cp->cf_entry)
459 				(void) fprintf(stream, " ");
460 			(void) fprintf(stream, "#%s", cp->cf_com);
461 		}
462 		(void) fprintf(stream, "\n");
463 	}
464 }
465 
466 #ifdef	TESTMODULE
467 
468 /*
469  * test main for conf module, usage: a.out conffile
470  */
471 main(int argc, char *argv[])
472 {
473 	err_init(argv[0]);
474 	setbuf(stdout, NULL);
475 
476 	if (argc != 2)
477 		err(EF_RAW, "usage: %s conffile\n", argv[0]);
478 
479 	conf_open(argv[1], 1);
480 
481 	printf("conffile <%s>:\n", argv[1]);
482 	conf_print(stdout);
483 
484 	conf_close(opts_parse(NULL, 0));
485 
486 	err_done(0);
487 }
488 
489 #endif	/* TESTMODULE */
490