xref: /original-bsd/bin/pax/sel_subs.c (revision 0cad3712)
1 /*-
2  * Copyright (c) 1992 Keith Muller.
3  * Copyright (c) 1992, 1993
4  *	The Regents of the University of California.  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	8.1 (Berkeley) 05/31/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}.
357 	 */
358 	if ((flgpt = rindex(str, '/')) != NULL)
359 		*flgpt++ = '\0';
360 
361 	for (stpt = str; *stpt != '\0'; ++stpt) {
362 		if ((*stpt >= '0') && (*stpt <= '9'))
363 			continue;
364 		if ((*stpt == ',') && (up_pt == NULL)) {
365 			*stpt = '\0';
366 			up_pt = stpt + 1;
367 			dot = 0;
368 			continue;
369 		}
370 
371 		/*
372 		 * allow only one dot per range (secs)
373 		 */
374 		if ((*stpt == '.') && (!dot)) {
375 			++dot;
376 			continue;
377 		}
378 		warn(1, "Improperly specified time range: %s", str);
379 		goto out;
380 	}
381 
382 	/*
383 	 * allocate space for the time range and store the limits
384 	 */
385 	if ((pt = (TIME_RNG *)malloc(sizeof(TIME_RNG))) == NULL) {
386 		warn(1, "Unable to allocate memory for time range");
387 		return(-1);
388 	}
389 
390 	/*
391 	 * by default we only will check file mtime, but usee can specify
392 	 * mtime, ctime (inode change time) or both.
393 	 */
394 	if ((flgpt == NULL) || (*flgpt == '\0'))
395 		pt->flgs = CMPMTME;
396 	else {
397 		pt->flgs = 0;
398 		while (*flgpt != '\0') {
399 			switch(*flgpt) {
400 			case 'M':
401 			case 'm':
402 				pt->flgs |= CMPMTME;
403 				break;
404 			case 'C':
405 			case 'c':
406 				pt->flgs |= CMPCTME;
407 				break;
408 			default:
409 				warn(1, "Bad option %c with time range %s",
410 				    *flgpt, str);
411 				goto out;
412 			}
413 			++flgpt;
414 		}
415 	}
416 
417 	/*
418 	 * start off with the current time
419 	 */
420 	pt->low_time = pt->high_time = time((time_t *)NULL);
421 	if (*str != '\0') {
422 		/*
423 		 * add lower limit
424 		 */
425 		if (str_sec(str, &(pt->low_time)) < 0) {
426 			warn(1, "Illegal lower time range %s", str);
427 			(void)free((char *)pt);
428 			goto out;
429 		}
430 		pt->flgs |= HASLOW;
431 	}
432 
433 	if ((up_pt != NULL) && (*up_pt != '\0')) {
434 		/*
435 		 * add upper limit
436 		 */
437 		if (str_sec(up_pt, &(pt->high_time)) < 0) {
438 			warn(1, "Illegal upper time range %s", up_pt);
439 			(void)free((char *)pt);
440 			goto out;
441 		}
442 		pt->flgs |= HASHIGH;
443 
444 		/*
445 		 * check that the upper and lower do not overlap
446 		 */
447 		if (pt->flgs & HASLOW) {
448 			if (pt->low_time > pt->high_time) {
449 				warn(1, "Upper %s and lower %s time overlap",
450 					up_pt, str);
451 				(void)free((char *)pt);
452 				return(-1);
453 			}
454 		}
455 	}
456 
457 	pt->fow = NULL;
458 	if (trhead == NULL) {
459 		trtail = trhead = pt;
460 		return(0);
461 	}
462 	trtail->fow = pt;
463 	trtail = pt;
464 	return(0);
465 
466     out:
467 	warn(1, "Time range format is: [yy[mm[dd[hh]]]]mm[.ss][/[c][m]]");
468 	return(-1);
469 }
470 
471 /*
472  * trng_match()
473  *	check if this files mtime/ctime falls within any supplied time range.
474  * Return:
475  *	0 if this archive member should be processed, 1 if it should be skipped
476  */
477 
478 #if __STDC__
479 static int
480 trng_match(register ARCHD *arcn)
481 #else
482 static int
483 trng_match(arcn)
484 	register ARCHD *arcn;
485 #endif
486 {
487 	register TIME_RNG *pt;
488 
489 	/*
490 	 * have to search down the list one at a time looking for a match.
491 	 * remember time range limits are inclusive.
492 	 */
493 	pt = trhead;
494 	while (pt != NULL) {
495 		switch(pt->flgs & CMPBOTH) {
496 		case CMPBOTH:
497 			/*
498 			 * user wants both mtime and ctime checked for this
499 			 * time range
500 			 */
501 			if (((pt->flgs & HASLOW) &&
502 			    (arcn->sb.st_mtime < pt->low_time) &&
503 			    (arcn->sb.st_ctime < pt->low_time)) ||
504 			    ((pt->flgs & HASHIGH) &&
505 			    (arcn->sb.st_mtime > pt->high_time) &&
506 			    (arcn->sb.st_ctime > pt->high_time))) {
507 				pt = pt->fow;
508 				continue;
509 			}
510 			break;
511 		case CMPCTME:
512 			/*
513 			 * user wants only ctime checked for this time range
514 			 */
515 			if (((pt->flgs & HASLOW) &&
516 			    (arcn->sb.st_ctime < pt->low_time)) ||
517 			    ((pt->flgs & HASHIGH) &&
518 			    (arcn->sb.st_ctime > pt->high_time))) {
519 				pt = pt->fow;
520 				continue;
521 			}
522 			break;
523 		case CMPMTME:
524 		default:
525 			/*
526 			 * user wants only mtime checked for this time range
527 			 */
528 			if (((pt->flgs & HASLOW) &&
529 			    (arcn->sb.st_mtime < pt->low_time)) ||
530 			    ((pt->flgs & HASHIGH) &&
531 			    (arcn->sb.st_mtime > pt->high_time))) {
532 				pt = pt->fow;
533 				continue;
534 			}
535 			break;
536 		}
537 		break;
538 	}
539 
540 	if (pt == NULL)
541 		return(1);
542 	return(0);
543 }
544 
545 /*
546  * str_sec()
547  *	Convert a time string in the format of [yy[mm[dd[hh]]]]mm[.ss] to gmt
548  *	seconds. Tval already has current time loaded into it at entry.
549  * Return:
550  *	0 if converted ok, -1 otherwise
551  */
552 
553 #if __STDC__
554 static int
555 str_sec(register char *str, time_t *tval)
556 #else
557 static int
558 str_sec(str, tval)
559 	register char *str;
560 	time_t *tval;
561 #endif
562 {
563 	register struct tm *lt;
564 	register char *dot = NULL;
565 
566 	lt = localtime(tval);
567 	if ((dot = index(str, '.')) != NULL) {
568 		/*
569 		 * seconds (.ss)
570 		 */
571 		*dot++ = '\0';
572 		if (strlen(dot) != 2)
573 			return(-1);
574 		if ((lt->tm_sec = ATOI2(dot)) > 61)
575 			return(-1);
576 	} else
577 		lt->tm_sec = 0;
578 
579 	switch (strlen(str)) {
580 	case 10:
581 		/*
582 		 * year (yy)
583 		 * watch out for year 2000
584 		 */
585 		if ((lt->tm_year = ATOI2(str)) < 69)
586 			lt->tm_year += 100;
587 		str += 2;
588 		/* FALLTHROUGH */
589 	case 8:
590 		/*
591 		 * month (mm)
592 		 * watch out months are from 0 - 11 internally
593 		 */
594 		if ((lt->tm_mon = ATOI2(str)) > 12)
595 			return(-1);
596 		--lt->tm_mon;
597 		str += 2;
598 		/* FALLTHROUGH */
599 	case 6:
600 		/*
601 		 * day (dd)
602 		 */
603 		if ((lt->tm_mday = ATOI2(str)) > 31)
604 			return(-1);
605 		str += 2;
606 		/* FALLTHROUGH */
607 	case 4:
608 		/*
609 		 * hour (hh)
610 		 */
611 		if ((lt->tm_hour = ATOI2(str)) > 23)
612 			return(-1);
613 		str += 2;
614 		/* FALLTHROUGH */
615 	case 2:
616 		/*
617 		 * minute (mm)
618 		 */
619 		if ((lt->tm_min = ATOI2(str)) > 59)
620 			return(-1);
621 		break;
622 	default:
623 		return(-1);
624 	}
625 	/*
626 	 * convert broken-down time to GMT clock time seconds
627 	 */
628 	if ((*tval = mktime(lt)) == -1)
629 		return(-1);
630 	return(0);
631 }
632