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