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