xref: /csrg-svn/bin/pax/sel_subs.c (revision 57501)
1 /*-
2  * Copyright (c) 1992 Keith Muller.
3  * Copyright (c) 1992 The Regents of the University of California.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Keith Muller of the University of California, San Diego.
8  *
9  * %sccs.include.redist.c%
10  */
11 
12 #ifndef lint
13 static char sccsid[] = "@(#)sel_subs.c	1.2 (Berkeley) 01/12/93";
14 #endif /* not lint */
15 
16 #include <sys/types.h>
17 #include <sys/time.h>
18 #include <sys/stat.h>
19 #include <sys/param.h>
20 #include <pwd.h>
21 #include <grp.h>
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include <strings.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include "pax.h"
29 #include "sel_subs.h"
30 #include "extern.h"
31 
32 static int str_sec __P((register char *, time_t *));
33 static int usr_match __P((register ARCHD *));
34 static int grp_match __P((register ARCHD *));
35 static int trng_match __P((register ARCHD *));
36 
37 static TIME_RNG *trhead = NULL;		/* time range list head */
38 static TIME_RNG *trtail = NULL;		/* time range list tail */
39 static USRT **usrtb = NULL;		/* user selection table */
40 static GRPT **grptb = NULL;		/* group selection table */
41 
42 /*
43  * Routines for selection of archive members
44  */
45 
46 /*
47  * sel_chk()
48  *	check if this file matches a specfied uid, gid or time range
49  * Return:
50  *	0 if this archive member should be processed, 1 if it should be skipped
51  */
52 
53 #if __STDC__
54 int
55 sel_chk(register ARCHD *arcn)
56 #else
57 int
58 sel_chk(arcn)
59 	register ARCHD *arcn;
60 #endif
61 {
62 	if (((usrtb != NULL) && usr_match(arcn)) ||
63 	    ((grptb != NULL) && grp_match(arcn)) ||
64 	    ((trhead != NULL) && trng_match(arcn)))
65 		return(1);
66 	return(0);
67 }
68 
69 /*
70  * User/group selection routines
71  *
72  * Routines to handle user selection of files based on the file uid/gid. To
73  * add an entry, the user supplies either then name or the uid/gid starting with
74  * a # on the command line. A \# will eascape the #.
75  */
76 
77 /*
78  * usr_add()
79  *	add a user match to the user match hash table
80  * Return:
81  *	0 if added ok, -1 otherwise;
82  */
83 
84 #if __STDC__
85 int
86 usr_add(register char *str)
87 #else
88 int
89 usr_add(str)
90 	register char *str;
91 #endif
92 {
93 	register u_int indx;
94 	register USRT *pt;
95 	register struct passwd *pw;
96 	register uid_t uid;
97 
98 	/*
99 	 * create the table if it doesn't exist
100 	 */
101 	if ((str == NULL) || (*str == '\0'))
102 		return(-1);
103 	if ((usrtb == NULL) &&
104  	    ((usrtb = (USRT **)calloc(USR_TB_SZ, sizeof(USRT *))) == NULL)) {
105                 warn(1, "Unable to allocate memory for user selection table");
106                 return(-1);
107 	}
108 
109 	/*
110 	 * figure out user spec
111 	 */
112 	if (str[0] != '#') {
113 		/*
114 		 * it is a user name, \# escapes # as first char in user name
115 		 */
116 		if ((str[0] == '\\') && (str[1] == '#'))
117 			++str;
118 		if ((pw = getpwnam(str)) == NULL) {
119                 	warn(1, "Unable to find uid for user: %s", str);
120                 	return(-1);
121 		}
122 		uid = (uid_t)pw->pw_uid;
123         } else
124 #		ifdef NET2_STAT
125 		uid = (uid_t)atoi(str+1);
126 #		else
127 		uid = (uid_t)strtoul(str+1, (char **)NULL, 10);
128 #		endif
129 	endpwent();
130 
131 	/*
132 	 * hash it and go down the hash chain (if any) looking for it
133 	 */
134 	indx = ((unsigned)uid) % USR_TB_SZ;
135 	if ((pt = usrtb[indx]) != NULL) {
136                 while (pt != NULL) {
137                         if (pt->uid == uid)
138 				return(0);
139                         pt = pt->fow;
140                 }
141 	}
142 
143 	/*
144 	 * uid is not yet in the table, add it to the front of the chain
145 	 */
146 	if ((pt = (USRT *)malloc(sizeof(USRT))) != NULL) {
147 		pt->uid = uid;
148 		pt->fow = usrtb[indx];
149 		usrtb[indx] = pt;
150 		return(0);
151 	}
152         warn(1, "User selection table out of memory");
153         return(-1);
154 }
155 
156 /*
157  * usr_match()
158  *	check if this files uid matches a selected uid.
159  * Return:
160  *	0 if this archive member should be processed, 1 if it should be skipped
161  */
162 
163 #if __STDC__
164 static int
165 usr_match(register ARCHD *arcn)
166 #else
167 static int
168 usr_match(arcn)
169 	register ARCHD *arcn;
170 #endif
171 {
172 	register USRT *pt;
173 
174 	/*
175 	 * hash and look for it in the table
176 	 */
177 	pt = usrtb[((unsigned)arcn->sb.st_uid) % USR_TB_SZ];
178 	while (pt != NULL) {
179 		if (pt->uid == arcn->sb.st_uid)
180 			return(0);
181 		pt = pt->fow;
182 	}
183 
184 	/*
185 	 * not found
186 	 */
187 	return(1);
188 }
189 
190 /*
191  * grp_add()
192  *	add a group match to the group match hash table
193  * Return:
194  *	0 if added ok, -1 otherwise;
195  */
196 
197 #if __STDC__
198 int
199 grp_add(register char *str)
200 #else
201 int
202 grp_add(str)
203 	register char *str;
204 #endif
205 {
206 	register u_int indx;
207 	register GRPT *pt;
208 	register struct group *gr;
209 	register gid_t gid;
210 
211 	/*
212 	 * create the table if it doesn't exist
213 	 */
214 	if ((str == NULL) || (*str == '\0'))
215 		return(-1);
216 	if ((grptb == NULL) &&
217  	    ((grptb = (GRPT **)calloc(GRP_TB_SZ, sizeof(GRPT *))) == NULL)) {
218                 warn(1, "Unable to allocate memory fo group selection table");
219                 return(-1);
220 	}
221 
222 	/*
223 	 * figure out user spec
224 	 */
225 	if (str[0] != '#') {
226 		/*
227 		 * it is a group name, \# escapes # as first char in group name
228 		 */
229 		if ((str[0] == '\\') && (str[1] == '#'))
230 			++str;
231 		if ((gr = getgrnam(str)) == NULL) {
232                 	warn(1,"Cannot determine gid for group name: %s", str);
233                 	return(-1);
234 		}
235 		gid = (gid_t)gr->gr_gid;
236         } else
237 #		ifdef NET2_STAT
238 		gid = (gid_t)atoi(str+1);
239 #		else
240 		gid = (gid_t)strtoul(str+1, (char **)NULL, 10);
241 #		endif
242 	endgrent();
243 
244 	/*
245 	 * hash it and go down the hash chain (if any) looking for it
246 	 */
247 	indx = ((unsigned)gid) % GRP_TB_SZ;
248 	if ((pt = grptb[indx]) != NULL) {
249                 while (pt != NULL) {
250                         if (pt->gid == gid)
251 				return(0);
252                         pt = pt->fow;
253                 }
254 	}
255 
256 	/*
257 	 * gid not in the table, add it to the front of the chain
258 	 */
259 	if ((pt = (GRPT *)malloc(sizeof(GRPT))) != NULL) {
260 		pt->gid = gid;
261 		pt->fow = grptb[indx];
262 		grptb[indx] = pt;
263 		return(0);
264 	}
265         warn(1, "Group selection table out of memory");
266         return(-1);
267 }
268 
269 /*
270  * grp_match()
271  *	check if this files gid matches a selected gid.
272  * Return:
273  *	0 if this archive member should be processed, 1 if it should be skipped
274  */
275 
276 #if __STDC__
277 static int
278 grp_match(register ARCHD *arcn)
279 #else
280 static int
281 grp_match(arcn)
282 	register ARCHD *arcn;
283 #endif
284 {
285 	register GRPT *pt;
286 
287 	/*
288 	 * hash and look for it in the table
289 	 */
290 	pt = grptb[((unsigned)arcn->sb.st_gid) % GRP_TB_SZ];
291 	while (pt != NULL) {
292 		if (pt->gid == arcn->sb.st_gid)
293 			return(0);
294 		pt = pt->fow;
295 	}
296 
297 	/*
298 	 * not found
299 	 */
300 	return(1);
301 }
302 
303 /*
304  * Time range selection routines
305  *
306  * Routines to handle user selection of files based on the modification and/or
307  * inode change time falling within a specified time range (the non-standard
308  * -T flag). The user may specify any number of different file time ranges.
309  * Time ranges are checked one at a time until a match is found (if at all).
310  * If the file has a mtime (and/or ctime) which lies within one of the time
311  * ranges, the file is selected. Time ranges may have a lower and/or a upper
312  * value. These ranges are inclusive. When no time ranges are supplied to pax
313  * with the -T option, all members in the archive will be selected by the time
314  * range routines. When only a lower range is supplied, only files with a
315  * mtime (and/or ctime) equal to or younger are selected. When only a upper
316  * range is supplied, only files with a mtime (and/or ctime) equal to or older
317  * are selected. When the lower time range is equal to the upper time range,
318  * only files with a mtime (or ctime) of exactly that time are selected.
319  */
320 
321 /*
322  * trng_add()
323  *	add a time range match to the time range list.
324  *	This is a non-standard pax option. Lower and upper ranges are in the
325  *	format: [yy[mm[dd[hh]]]]mm[.ss] and are comma separated.
326  *	Time ranges are based on current time, so 1234 would specify a time of
327  *	12:34 today.
328  * Return:
329  *	0 if the time range was added to the list, -1 otherwise
330  */
331 
332 #if __STDC__
333 int
334 trng_add(register char *str)
335 #else
336 int
337 trng_add(str)
338 	register char *str;
339 #endif
340 {
341 	register TIME_RNG *pt;
342 	register char *up_pt = NULL;
343 	register char *stpt;
344 	register char *flgpt;
345 	register int dot = 0;
346 
347 	/*
348 	 * throw out the badly formed time ranges
349 	 */
350 	if ((str == NULL) || (*str == '\0')) {
351 		warn(1, "Empty time range string");
352 		return(-1);
353 	}
354 
355 	/*
356 	 * locate optional flags suffix /{cm}. We only allow a flag suffix(s)
357 	 * in write and copy (as none of the formats stores inode change time;
358 	 * currently inode change time cannot be set to a specific value by
359 	 * any system call).
360 	 */
361 	if ((flgpt = rindex(str, '/')) != NULL) {
362 		*flgpt++ = '\0';
363 		if ((act == LIST) || (act == EXTRACT)) {
364 			warn(1,"Time suffix only valid in write or copy modes");
365 			return(-1);
366 		}
367 	}
368 
369 	for (stpt = str; *stpt != '\0'; ++stpt) {
370 		if ((*stpt >= '0') && (*stpt <= '9'))
371 			continue;
372 		if ((*stpt == ',') && (up_pt == NULL)) {
373 			*stpt = '\0';
374 			up_pt = stpt + 1;
375 			dot = 0;
376 			continue;
377 		}
378 
379 		/*
380 		 * allow only one dot per range (secs)
381 		 */
382 		if ((*stpt == '.') && (!dot)) {
383 			++dot;
384 			continue;
385 		}
386 		warn(1, "Improperly specified time range: %s", str);
387 		goto out;
388 	}
389 
390 	/*
391 	 * allocate space for the time range and store the limits
392 	 */
393 	if ((pt = (TIME_RNG *)malloc(sizeof(TIME_RNG))) == NULL) {
394 		warn(1, "Unable to allocate memory for time range");
395 		return(-1);
396 	}
397 
398 	/*
399 	 * by default we only will check file mtime, but usee can specify
400 	 * mtime, ctime (inode change time) or both.
401 	 */
402 	if ((flgpt == NULL) || (*flgpt == '\0'))
403 		pt->flgs = CMPMTME;
404 	else {
405 		pt->flgs = 0;
406 		while (*flgpt != '\0') {
407 			switch(*flgpt) {
408 			case 'M':
409 			case 'm':
410 				pt->flgs |= CMPMTME;
411 				break;
412 			case 'C':
413 			case 'c':
414 				pt->flgs |= CMPCTME;
415 				break;
416 			default:
417 				warn(1, "Bad option %c with time range %s",
418 				    *flgpt, str);
419 				goto out;
420 			}
421 			++flgpt;
422 		}
423 	}
424 
425 	/*
426 	 * start off with the current time
427 	 */
428 	pt->low_time = pt->high_time = time((time_t *)NULL);
429 	if (*str != '\0') {
430 		/*
431 		 * add lower limit
432 		 */
433 		if (str_sec(str, &(pt->low_time)) < 0) {
434 			warn(1, "Illegal lower time range %s", str);
435 			(void)free((char *)pt);
436 			goto out;
437 		}
438 		pt->flgs |= HASLOW;
439 	}
440 
441 	if ((up_pt != NULL) && (*up_pt != '\0')) {
442 		/*
443 		 * add upper limit
444 		 */
445 		if (str_sec(up_pt, &(pt->high_time)) < 0) {
446 			warn(1, "Illegal upper time range %s", up_pt);
447 			(void)free((char *)pt);
448 			goto out;
449 		}
450 		pt->flgs |= HASHIGH;
451 
452 		/*
453 		 * check that the upper and lower do not overlap
454 		 */
455 		if (pt->flgs & HASLOW) {
456 			if (pt->low_time > pt->high_time) {
457 				warn(1, "Upper %s and lower %s time overlap",
458 					up_pt, str);
459 				(void)free((char *)pt);
460 				return(-1);
461 			}
462 		}
463 	}
464 
465 	pt->fow = NULL;
466 	if (trhead == NULL) {
467 		trtail = trhead = pt;
468 		return(0);
469 	}
470 	trtail->fow = pt;
471 	trtail = pt;
472 	return(0);
473 
474     out:
475 	warn(1, "Time range format is: [yy[mm[dd[hh]]]]mm[.ss][/[c][m]]");
476 	return(-1);
477 }
478 
479 /*
480  * trng_match()
481  *	check if this files mtime/ctime falls within any supplied time range.
482  * Return:
483  *	0 if this archive member should be processed, 1 if it should be skipped
484  */
485 
486 #if __STDC__
487 static int
488 trng_match(register ARCHD *arcn)
489 #else
490 static int
491 trng_match(arcn)
492 	register ARCHD *arcn;
493 #endif
494 {
495 	register TIME_RNG *pt;
496 
497 	/*
498 	 * have to search down the list one at a time looking for a match.
499 	 * remember time range limits are inclusive.
500 	 */
501 	pt = trhead;
502 	while (pt != NULL) {
503 		switch(pt->flgs & CMPBOTH) {
504 		case CMPBOTH:
505 			/*
506 			 * user wants both mtime and ctime checked for this
507 			 * time range
508 			 */
509 			if (((pt->flgs & HASLOW) &&
510 			    (arcn->sb.st_mtime < pt->low_time) &&
511 			    (arcn->sb.st_ctime < pt->low_time)) ||
512 			    ((pt->flgs & HASHIGH) &&
513 			    (arcn->sb.st_mtime > pt->high_time) &&
514 			    (arcn->sb.st_ctime > pt->high_time))) {
515 				pt = pt->fow;
516 				continue;
517 			}
518 			break;
519 		case CMPCTME:
520 			/*
521 			 * user wants only ctime checked for this time range
522 			 */
523 			if (((pt->flgs & HASLOW) &&
524 			    (arcn->sb.st_ctime < pt->low_time)) ||
525 			    ((pt->flgs & HASHIGH) &&
526 			    (arcn->sb.st_ctime > pt->high_time))) {
527 				pt = pt->fow;
528 				continue;
529 			}
530 			break;
531 		case CMPMTME:
532 		default:
533 			/*
534 			 * user wants only mtime checked for this time range
535 			 */
536 			if (((pt->flgs & HASLOW) &&
537 			    (arcn->sb.st_mtime < pt->low_time)) ||
538 			    ((pt->flgs & HASHIGH) &&
539 			    (arcn->sb.st_mtime > pt->high_time))) {
540 				pt = pt->fow;
541 				continue;
542 			}
543 			break;
544 		}
545 		break;
546 	}
547 
548 	if (pt == NULL)
549 		return(1);
550 	return(0);
551 }
552 
553 /*
554  * str_sec()
555  *	Convert a time string in the format of [yy[mm[dd[hh]]]]mm[.ss] to gmt
556  *	seconds. Tval already has current time loaded into it at entry.
557  * Return:
558  *	0 if converted ok, -1 otherwise
559  */
560 
561 #if __STDC__
562 static int
563 str_sec(register char *str, time_t *tval)
564 #else
565 static int
566 str_sec(str, tval)
567 	register char *str;
568 	time_t *tval;
569 #endif
570 {
571 	register struct tm *lt;
572 	register char *dot = NULL;
573 
574 	lt = localtime(tval);
575 	if ((dot = index(str, '.')) != NULL) {
576 		/*
577 		 * seconds (.ss)
578 		 */
579 		*dot++ = '\0';
580 		if (strlen(dot) != 2)
581 			return(-1);
582 		if ((lt->tm_sec = ATOI2(dot)) > 61)
583 			return(-1);
584 	} else
585 		lt->tm_sec = 0;
586 
587 	switch (strlen(str)) {
588 	case 10:
589 		/*
590 		 * year (yy)
591 		 * watch out for year 2000
592 		 */
593 		if ((lt->tm_year = ATOI2(str)) < 69)
594 			lt->tm_year += 100;
595 		str += 2;
596 		/* FALLTHROUGH */
597 	case 8:
598 		/*
599 		 * month (mm)
600 		 * watch out months are from 0 - 11 internally
601 		 */
602 		if ((lt->tm_mon = ATOI2(str)) > 12)
603 			return(-1);
604 		--lt->tm_mon;
605 		str += 2;
606 		/* FALLTHROUGH */
607 	case 6:
608 		/*
609 		 * day (dd)
610 		 */
611 		if ((lt->tm_mday = ATOI2(str)) > 31)
612 			return(-1);
613 		str += 2;
614 		/* FALLTHROUGH */
615 	case 4:
616 		/*
617 		 * hour (hh)
618 		 */
619 		if ((lt->tm_hour = ATOI2(str)) > 23)
620 			return(-1);
621 		str += 2;
622 		/* FALLTHROUGH */
623 	case 2:
624 		/*
625 		 * minute (mm)
626 		 */
627 		if ((lt->tm_min = ATOI2(str)) > 59)
628 			return(-1);
629 		break;
630 	default:
631 		return(-1);
632 	}
633 	/*
634 	 * convert broken-down time to GMT clock time seconds
635 	 */
636 	if ((*tval = mktime(lt)) == -1)
637 		return(-1);
638 	return(0);
639 }
640