1 /* ====================================================================
2  * Copyright (c) 1995-1999 The Apache Group.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * 3. All advertising materials mentioning features or use of this
17  *    software must display the following acknowledgment:
18  *    "This product includes software developed by the Apache Group
19  *    for use in the Apache HTTP server project (http://www.apache.org/)."
20  *
21  * 4. The names "Apache Server" and "Apache Group" must not be used to
22  *    endorse or promote products derived from this software without
23  *    prior written permission. For written permission, please contact
24  *    apache@apache.org.
25  *
26  * 5. Products derived from this software may not be called "Apache"
27  *    nor may "Apache" appear in their names without prior written
28  *    permission of the Apache Group.
29  *
30  * 6. Redistributions of any form whatsoever must retain the following
31  *    acknowledgment:
32  *    "This product includes software developed by the Apache Group
33  *    for use in the Apache HTTP server project (http://www.apache.org/)."
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46  * OF THE POSSIBILITY OF SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Group and was originally based
51  * on public domain software written at the National Center for
52  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53  * For more information on the Apache Group and the Apache HTTP server
54  * project, please see <http://www.apache.org/>.
55  *
56  */
57 /*
58  * cronoutils -- utilities for the cronolog program
59  *
60  * Copyright (c) 1996-1999 by Ford & Mason Ltd
61  *
62  * This software was submitted by Ford & Mason Ltd to the Apache
63  * Software Foundation in December 1999.  Future revisions and
64  * derivatives of this source code must acknowledge Ford & Mason Ltd
65  * as the original contributor of this module.  All other licensing
66  * and usage conditions are those of the Apache Software Foundation.
67  *
68  * Originally written by Andrew Ford <A.Ford@ford-mason.co.uk>
69  *
70  */
71 
72 #include "cronoutils.h"
73 extern char *tzname[2];
74 
75 
76 #ifdef _WIN32
77 #include "strptime.h"
78 #endif
79 /* debug_file is the file to output debug messages to.  No debug
80  * messages are output if it is set to NULL.
81  */
82 FILE	*debug_file = NULL;
83 
84 
85 /* America and Europe disagree on whether weeks start on Sunday or
86  * Monday - weeks_start_on_mondays is set if a %U specifier is encountered.
87  */
88 int	weeks_start_on_mondays = 0;
89 
90 
91 /* periods[] is an array of the names of the periods.
92  */
93 char	*periods[] =
94 {
95     "second",
96     "minute",
97     "hour",
98     "day",
99     "week",
100     "month",
101     "year",
102     "aeon"	/* i.e. once only */
103 };
104 
105 /* period_seconds[] is an array of the number of seconds in a period.
106  */
107 int	period_seconds[] =
108 {
109     1,
110     60,
111     60 * 60,
112     60 * 60 * 24,
113     60 * 60 * 24 * 7,
114     60 * 60 * 24 * 31,
115     60 * 60 * 24 * 36
116 };
117 
118 /* Try to create missing directories on the path of filename.
119  *
120  * Note that on a busy server there may theoretically be many cronolog
121  * processes trying simultaneously to create the same subdirectories
122  * so ignore any EEXIST errors on mkdir -- they probably just mean
123  * that another process got there first.
124  *
125  * Unless CHECK_ALL_PREFIX_DIRS is defined, we save the directory of
126  * the last file tested -- any common prefix should exist.  This
127  * probably only saves a few stat system calls at the start of each
128  * log period, but it might as well be done.
129  */
130 void
create_subdirs(char * filename)131 create_subdirs(char *filename)
132 {
133 #ifndef CHECK_ALL_PREFIX_DIRS
134     static char	lastpath[MAX_PATH] = "";
135 #endif
136     struct stat stat_buf;
137     char	dirname[MAX_PATH];
138     char	*p;
139 
140     DEBUG(("Creating missing components of \"%s\"\n", filename));
141     for (p = filename; (p = strchr(p, '/')); p++)
142     {
143 	if (p == filename)
144 	{
145 	    continue;	    /* Don't bother with the root directory */
146 	}
147 
148 	memcpy(dirname, filename, p - filename);
149 	dirname[p-filename] = '\0';
150 
151 #ifndef CHECK_ALL_PREFIX_DIRS
152 	if (strncmp(dirname, lastpath, strlen(dirname)) == 0)
153 	{
154 	    DEBUG(("Initial prefix \"%s\" known to exist\n", dirname));
155 	    continue;
156 	}
157 #endif
158 
159 	DEBUG(("Testing directory \"%s\"\n", dirname));
160 	if (stat(dirname, &stat_buf) < 0)
161 	{
162 	    if (errno != ENOENT)
163 	    {
164 		perror(dirname);
165 		exit(2);
166 	    }
167 	    else
168 	    {
169 		DEBUG(("Directory \"%s\" does not exist -- creating\n", dirname));
170 		if ((mkdir(dirname, DIR_MODE) < 0) && (errno != EEXIST))
171 #ifndef _WIN32
172 		{
173 #else
174                 if ((mkdir(dirname) < 0) && (errno != EEXIST))
175 #endif
176 		    perror(dirname);
177 		    exit(2);
178 		}
179 	    }
180 	}
181     }
182 #ifndef CHECK_ALL_PREFIX_DIRS
183     strcpy(lastpath, dirname);
184 #endif
185 }
186 
187 /* Create a hard or symbolic link to a filename according to the type specified.
188  *
189  * This function could do with more error checking!
190  */
191 void
create_link(char * pfilename,const char * linkname,mode_t linktype,const char * prevlinkname)192 create_link(char *pfilename,
193 	    const char *linkname, mode_t linktype,
194 	    const char *prevlinkname)
195 {
196     struct stat		stat_buf;
197 
198     if (stat(prevlinkname, &stat_buf) == 0)
199     {
200 	unlink(prevlinkname);
201     }
202     if (lstat(linkname, &stat_buf) == 0)
203     {
204 	if (prevlinkname) {
205 	    rename(linkname, prevlinkname);
206 	}
207 	else {
208 	    unlink(linkname);
209 	}
210     }
211 #ifndef _WIN32
212     if (linktype == S_IFLNK)
213     {
214 	symlink(pfilename, linkname);
215     }
216     else
217     {
218 	link(pfilename, linkname);
219     }
220 #else
221     fprintf(stderr, "Creating link from %s to %s not supported", pfilename, linkname);
222 #endif
223 }
224 
225 /* Examine the log file name specifier for strftime conversion
226  * specifiers and determine the period between log files.
227  * Smallest period allowed is per minute.
228  */
229 PERIODICITY
determine_periodicity(char * spec)230 determine_periodicity(char *spec)
231 {
232     PERIODICITY	periodicity = ONCE_ONLY;
233     char 	ch;
234 
235     DEBUG(("Determining periodicity of \"%s\"\n", spec));
236     while ((ch = *spec++) != 0)
237     {
238 	if (ch == '%')
239 	{
240 	    ch = *spec++;
241 	    if (!ch) break;
242 
243 	    switch (ch)
244 	    {
245 	    case 'y':		/* two digit year */
246 	    case 'Y':		/* four digit year */
247 		if (periodicity > YEARLY)
248 		{
249 		    DEBUG(("%%%c -> yearly\n", ch));
250 		    periodicity = YEARLY;
251 		}
252 		break;
253 
254 	    case 'b':		/* abbreviated month name */
255 	    case 'h':		/* abbreviated month name (non-standard) */
256 	    case 'B':		/* full month name */
257 	    case 'm':		/* month as two digit number (with
258 				   leading zero) */
259 		if (periodicity > MONTHLY)
260 		{
261 		    DEBUG(("%%%c -> monthly\n", ch));
262 		    periodicity = MONTHLY;
263 		}
264   	        break;
265 
266 	    case 'U':		/* week number (weeks start on Sunday) */
267 	    case 'W':		/* week number (weeks start on Monday) */
268 	        if (periodicity > WEEKLY)
269 		{
270 		    DEBUG(("%%%c -> weeky\n", ch));
271 		    periodicity = WEEKLY;
272 		    weeks_start_on_mondays = (ch == 'W');
273 		}
274 		break;
275 
276 	    case 'a':		/* abbreviated weekday name */
277 	    case 'A':		/* full weekday name */
278 	    case 'd':		/* day of the month (with leading zero) */
279 	    case 'e':		/* day of the month (with leading space -- non-standard) */
280 	    case 'j':		/* day of the year (with leading zeroes) */
281 	    case 'w':		/* day of the week (0-6) */
282 	    case 'D':		/* full date spec (non-standard) */
283 	    case 'x':		/* full date spec */
284 	        if (periodicity > DAILY)
285 		{
286 		    DEBUG(("%%%c -> daily\n", ch));
287 		    periodicity = DAILY;
288 		}
289   	        break;
290 
291 	    case 'H':		/* hour (24 hour clock) */
292 	    case 'I':		/* hour (12 hour clock) */
293 	    case 'p':		/* AM/PM indicator */
294 	        if (periodicity > HOURLY)
295 		{
296 		    DEBUG(("%%%c -> hourly\n", ch));
297 		    periodicity = HOURLY;
298 		}
299 		break;
300 
301 	    case 'M':		/* minute */
302 	        if (periodicity > PER_MINUTE)
303 		{
304 		    DEBUG(("%%%c -> per minute\n", ch));
305 		    periodicity = PER_MINUTE;
306 		}
307 		break;
308 
309 	    case 'S':		/* second */
310 	    case 's':		/* seconds since the epoch (GNU non-standard) */
311 	    case 'c':		/* full time and date spec */
312 	    case 'T':		/* full time spec */
313 	    case 'r':		/* full time spec (non-standard) */
314 	    case 'R':		/* full time spec (non-standard) */
315 		DEBUG(("%%%c -> per second", ch));
316 		periodicity = PER_SECOND;
317 
318 	    default:		/* ignore anything else */
319 		DEBUG(("ignoring %%%c\n", ch));
320 	        break;
321 	    }
322 	}
323     }
324     return periodicity;
325 }
326 
327 /*
328  */
329 PERIODICITY
parse_timespec(char * optarg,int * p_period_multiple)330 parse_timespec(char *optarg, int *p_period_multiple)
331 {
332     PERIODICITY		periodicity	= INVALID_PERIOD;
333     int			period_multiple = 1;
334     char 		*p = optarg;
335 
336     /* Skip leading whitespace */
337 
338     while (isspace(*p)) { p++; }
339 
340 
341     /* Parse a digit string */
342 
343     if (isdigit(*p)) {
344 	period_multiple = *p++ - '0';
345 
346 	while (isdigit(*p)) {
347 	    period_multiple *= 10;
348 	    period_multiple += (*p++ - '0');
349 	}
350     }
351 
352 
353     /* Skip whitespace */
354 
355     while (isspace(*p)) { p++; }
356 
357     if (strncasecmp(p, "sec", 3) == 0) {
358 	if (period_multiple < 60) {
359 	    periodicity = PER_SECOND;
360 	}
361     }
362     else if (strncasecmp(p, "min", 3) == 0) {
363 	if (period_multiple < 60) {
364 	    periodicity = PER_MINUTE;
365 	}
366     }
367     else if (strncasecmp(p, "hour", 4) == 0) {
368 	if (period_multiple < 24) {
369 	    periodicity = HOURLY;
370 	}
371     }
372     else if (strncasecmp(p, "day", 3) == 0) {
373 	if (period_multiple <= 31) {
374 	    periodicity = DAILY;
375 	}
376     }
377     else if (strncasecmp(p, "week", 4) == 0) {
378 	if (period_multiple < 53) {
379 	    periodicity = WEEKLY;
380 	}
381     }
382     else if (strncasecmp(p, "mon", 3) == 0) {
383 	if (period_multiple <= 12) {
384 	    periodicity = MONTHLY;
385 	}
386     }
387     *p_period_multiple = period_multiple;
388     return periodicity;
389 }
390 
391 /* To determine the time of the start of the next period add just
392  * enough to move beyond the start of the next period and then
393  * determine the time of the start of that period.
394  *
395  * There is a potential problem if the start or end of daylight saving
396  * time occurs during the current period.
397  */
398 time_t
start_of_next_period(time_t time_now,PERIODICITY periodicity,int period_multiple)399 start_of_next_period(time_t time_now, PERIODICITY periodicity, int period_multiple)
400 {
401     time_t	start_time;
402 
403     switch (periodicity)
404     {
405     case YEARLY:
406 	start_time = (time_now + 366 * SECS_PER_DAY + DST_ALLOWANCE);
407 	break;
408 
409     case MONTHLY:
410 	start_time = (time_now + 31 * SECS_PER_DAY + DST_ALLOWANCE);
411 	break;
412 
413     case WEEKLY:
414 	start_time = (time_now + SECS_PER_WEEK + DST_ALLOWANCE);
415 	break;
416 
417     case DAILY:
418 	start_time = (time_now + SECS_PER_DAY + DST_ALLOWANCE);
419 	break;
420 
421     case HOURLY:
422 	start_time = time_now + period_multiple * SECS_PER_HOUR + LEAP_SECOND_ALLOWANCE;
423 	break;
424 
425     case PER_MINUTE:
426 	start_time = time_now + period_multiple * SECS_PER_MIN + LEAP_SECOND_ALLOWANCE;
427 	break;
428 
429     case PER_SECOND:
430 	start_time = time_now + 1;
431 	break;
432 
433     default:
434 	start_time = FAR_DISTANT_FUTURE;
435 	break;
436     }
437     return start_of_this_period(start_time, periodicity, period_multiple);
438 }
439 
440 /* Determine the time of the start of the period containing a given time.
441  * Break down the time with localtime and subtract the number of
442  * seconds since the start of the period.  If the length of period is
443  * equal or longer than a day then we have to check tht the
444  * calculation is not thrown out by the start or end of daylight
445  * saving time.
446  */
447 time_t
start_of_this_period(time_t start_time,PERIODICITY periodicity,int period_multiple)448 start_of_this_period(time_t start_time, PERIODICITY periodicity, int period_multiple)
449 {
450     struct tm	tm_initial;
451     struct tm	tm_adjusted;
452     int		expected_mday;
453 
454 #ifndef _WIN32
455     localtime_r(&start_time, &tm_initial);
456 #else
457     struct tm * tempTime;
458 
459     tempTime = localtime(&start_time);
460     if (NULL != tempTime)
461     {
462         memcpy(&tm_initial, tempTime, sizeof(struct tm));
463 
464         free(tempTime);
465         tempTime = NULL;
466     }
467 #endif
468     switch (periodicity)
469     {
470     case YEARLY:
471     case MONTHLY:
472     case WEEKLY:
473     case DAILY:
474 	switch (periodicity)
475 	{
476 	case YEARLY:
477 	    start_time -= (  (tm_initial.tm_yday * SECS_PER_DAY)
478 			   + (tm_initial.tm_hour * SECS_PER_HOUR)
479 			   + (tm_initial.tm_min  * SECS_PER_MIN)
480 			   + (tm_initial.tm_sec));
481 	    expected_mday = 1;
482 	    break;
483 
484 	case MONTHLY:
485 	    start_time -= (  ((tm_initial.tm_mday - 1) * SECS_PER_DAY)
486 			   + ( tm_initial.tm_hour      * SECS_PER_HOUR)
487 			   + ( tm_initial.tm_min       * SECS_PER_MIN)
488 			   + ( tm_initial.tm_sec));
489 	    expected_mday = 1;
490 	    break;
491 
492 	case WEEKLY:
493 	    if (weeks_start_on_mondays)
494 	    {
495 		tm_initial.tm_wday = (6 + tm_initial.tm_wday) % 7;
496 	    }
497 	    start_time -= (  (tm_initial.tm_wday * SECS_PER_DAY)
498 			   + (tm_initial.tm_hour * SECS_PER_HOUR)
499 			   + (tm_initial.tm_min  * SECS_PER_MIN)
500 			   + (tm_initial.tm_sec));
501 	    expected_mday = tm_initial.tm_mday;
502 	    break;
503 
504 	case DAILY:
505 	    start_time -= (  (tm_initial.tm_hour * SECS_PER_HOUR)
506 			   + (tm_initial.tm_min  * SECS_PER_MIN )
507 			   +  tm_initial.tm_sec);
508 	    expected_mday = tm_initial.tm_mday;
509 	    break;
510 
511 	default:
512 	    fprintf(stderr, "software fault in start_of_this_period()\n");
513 	    exit(1);
514 	}
515 
516 	/* If the time of day is not equal to midnight then we need to
517 	 * adjust for daylight saving time.  Adjust the time backwards
518 	 * by the value of the hour, minute and second fields.  If the
519 	 * day of the month is not as expected one then we must have
520 	 * adjusted back to the previous day so add 24 hours worth of
521 	 * seconds.
522 	 */
523 #ifndef _WIN32
524 	localtime_r(&start_time, &tm_adjusted);
525 #else
526 	tempTime = localtime(&start_time);
527 	if (NULL != tempTime)
528 	{
529 	    memcpy(&tm_adjusted, tempTime, sizeof(struct tm));
530 
531 	    free(tempTime);
532 	    tempTime = NULL;
533 	}
534 #endif
535 	if (   (tm_adjusted.tm_hour != 0)
536 	    || (tm_adjusted.tm_min  != 0)
537 	    || (tm_adjusted.tm_sec  != 0))
538 	{
539 	    char	sign   = '-';
540 	    time_t      adjust = - (  (tm_adjusted.tm_hour * SECS_PER_HOUR)
541 				    + (tm_adjusted.tm_min  * SECS_PER_MIN)
542 				    + (tm_adjusted.tm_sec));
543 
544 	    if (tm_adjusted.tm_mday != expected_mday)
545 	    {
546 		adjust += SECS_PER_DAY;
547 		sign = '+';
548 	    }
549 	    start_time += adjust;
550 
551 	    if (adjust < 0)
552 	    {
553 		adjust = -adjust;
554 	    }
555 
556 	    DEBUG(("Adjust for dst: %02d/%02d/%04d %02d:%02d:%02d -- %c%0d:%02d:%02d\n",
557 		   tm_initial.tm_mday, tm_initial.tm_mon+1, tm_initial.tm_year+1900,
558 		   tm_initial.tm_hour, tm_initial.tm_min,   tm_initial.tm_sec, sign,
559 		   adjust / SECS_PER_HOUR, (adjust / 60) % 60, adjust % SECS_PER_HOUR));
560 	}
561 	break;
562 
563     case HOURLY:
564 	start_time -= (tm_initial.tm_sec + tm_initial.tm_min * SECS_PER_MIN);
565 	if (period_multiple > 1) {
566 	    start_time -= SECS_PER_HOUR * (tm_initial.tm_hour -
567 					   period_multiple * (tm_initial.tm_hour / period_multiple));
568 	}
569 	break;
570 
571     case PER_MINUTE:
572 	start_time -= tm_initial.tm_sec;
573 	if (period_multiple > 1) {
574 	    start_time -= SECS_PER_MIN * (tm_initial.tm_min -
575 					  period_multiple * (tm_initial.tm_min / period_multiple));
576 	}
577 	break;
578 
579     case PER_SECOND:	/* No adjustment needed */
580     default:
581 	break;
582     }
583     return start_time;
584 }
585 
586 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
587  * than local timezone (as mktime assumes).
588  *
589  * Contributed by Roger Beeman <beeman@cisco.com>.
590  */
591 time_t
mktime_from_utc(struct tm * t)592 mktime_from_utc(struct tm *t)
593 {
594    time_t tl, tb;
595 
596    tl = mktime(t);
597    tb = mktime(gmtime(&tl));
598    return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
599 }
600 
601 /* Check whether the string is processed well.  It is processed if the
602  * pointer is non-NULL, and it is either at the `GMT', or at the end
603  * of the string.
604  */
605 static int
check_end(const char * p)606 check_end(const char *p)
607 {
608    if (!p)
609       return 0;
610    while (isspace(*p))
611       ++p;
612    if (!*p || (p[0] == 'G' && p[1] == 'M' && p[2] == 'T'))
613       return 1;
614    else
615       return 0;
616 }
617 
618 /* NOTE: We don't use `%n' for white space, as OSF's strptime uses
619    it to eat all white space up to (and including) a newline, and
620    the function fails (!) if there is no newline.
621 
622    Let's hope all strptime-s use ` ' to skipp *all* whitespace
623    instead of just one (it works that way on all the systems I've
624    tested it on). */
625 
626 static char *european_date_formats[] =
627 {
628     "%d %b %Y %T",       	/* dd mmm yyyy HH:MM:SS */
629     "%d %b %Y %H:%M",       	/* dd mmm yyyy HH:MM    */
630     "%d %b %Y",       		/* dd mmm yyyy          */
631     "%d-%b-%Y %T",       	/* dd-mmm-yyyy HH:MM:SS */
632     "%d-%b-%Y %H:%M",       	/* dd-mmm-yyyy HH:MM    */
633     "%d-%b-%y %T",       	/* dd-mmm-yy   HH:MM:SS */
634     "%d-%b-%y %H:%M",  		/* dd-mmm-yy   HH:MM    */
635     "%d-%b-%Y",
636     "%b %d %T %Y",
637     "%b %d %Y",
638     NULL
639 };
640 
641 static char *american_date_formats[] =
642 {
643     "%b %d %Y %T",       	/* dd mmm yyyy HH:MM:SS */
644     "%b %d %Y %H:%M",       	/* dd mmm yyyy HH:MM    */
645     "%b %d %Y",       		/* dd mmm yyyy          */
646     "%b-%d-%Y %T",       	/* dd-mmm-yyyy HH:MM:SS */
647     "%b-%d-%Y %H:%M",       	/* dd-mmm-yyyy HH:MM    */
648     "%b-%d-%Y",
649     "%b/%d/%Y %T",
650     "%b/%d/%Y %H:%M",
651     "%b/%d/%Y",
652     NULL
653 };
654 
655 
656 
657 time_t
parse_time(char * time_str,int use_american_date_formats)658 parse_time(char *time_str, int use_american_date_formats)
659 {
660    struct tm    tm;
661    char		**date_formats;
662 
663    memset(&tm, 0, sizeof (tm));
664    tm.tm_isdst = -1;
665 
666 
667    for (date_formats = (use_american_date_formats
668 			? american_date_formats
669 			: european_date_formats);
670 	*date_formats;
671 	date_formats++)
672    {
673        if (check_end((const char *)strptime(time_str, *date_formats, &tm)))
674 	   return mktime_from_utc(&tm);
675    }
676 
677    return -1;
678 }
679 
680 
681 
682 /* Simple debugging print function.
683  */
684 void
print_debug_msg(char * msg,...)685 print_debug_msg(char *msg, ...)
686 {
687     va_list	ap;
688 
689     va_start(ap, msg);
690     vfprintf(debug_file, msg, ap);
691 }
692 
693 
694 /* Build a timestamp and return a pointer to it.
695  * (has a number of static buffers that are rotated).
696  */
697 char *
timestamp(time_t thetime)698 timestamp(time_t thetime)
699 {
700     static int	index = 0;
701     static char	buffer[4][80];
702     struct tm	*tm;
703     char	*retval;
704 
705     retval = buffer[index++];
706     index %= 4;
707 
708     tm = localtime(&thetime);
709     strftime(retval, 80, "%Y/%m/%d-%H:%M:%S %Z", tm);
710     return retval;
711 }
712 
713 
714 #ifndef _WIN32
715 /* Turn a string specifying either a username or UID into an actual
716  * uid_t for use in setuid(). A string is assumed to be a UID if
717  * it contains only decimal digits. */
718 uid_t
parse_uid(char * user,char * argv0)719 parse_uid(char *user, char *argv0)
720 {
721     char		*probe = user;
722     struct passwd	*ent;
723 
724     while (*probe && isdigit(*probe)) {
725 	probe++;
726     }
727     if (!(*probe)) {
728 	return atoi(user);
729     }
730     if (!(ent = getpwnam(user))) {
731 	fprintf(stderr, "%s: Bad username %s\n", argv0, user);
732 	exit(1);
733     }
734     return (ent->pw_uid);
735 }
736 
737 
738 /* Turn a string specifying either a group name or GID into an actual
739  * gid_t for use in setgid(). A string is assumed to be a GID if
740  * it contains only decimal digits. */
741 gid_t
parse_gid(char * group,char * argv0)742 parse_gid(char *group, char *argv0)
743 {
744     char		*probe = group;
745     struct group	*ent;
746 
747     while (*probe && isdigit(*probe)) {
748 	probe++;
749     }
750     if (!(*probe)) {
751 	return atoi(group);
752     }
753     if (!(ent = getgrnam(group))) {
754 	fprintf(stderr, "%s: Bad group name %s\n", argv0, group);
755 	exit(1);
756     }
757     return (ent->gr_gid);
758 }
759 #endif /* _WIN32 */
760