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