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