1 /* Miscellaneous utility routines for GNATS.
2 Copyright (C) 2000, 2001 Milan Zamazal
3 Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
4 Contributed by Tim Wicinski (wicinski@barn.com)
5 and Brendan Kehoe (brendan@cygnus.com).
6 Further hacked by Milan Zamazal (pdm@zamazal.org).
7
8 This file is part of GNU GNATS.
9
10 GNU GNATS is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2, or (at your option)
13 any later version.
14
15 GNU GNATS is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with GNU GNATS; see the file COPYING. If not, write to the Free
22 Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
23
24 #include "gnats.h"
25
26
27 /* Exit constants. All GNATS C sources should use only the symbolic names
28 defined here as exit codes. */
29 const int EXIT_OK = 0;
30 const int EXIT_PROGRAM_ERROR = 127;
31
32
33 void program_error (const char *file, int line);
34
35
36
37 /* Logging */
38
39 /* TODO: The current logging facilities are quite primitive. They should be
40 improved in the future. However the primary goal now is to unify logging in
41 all the GNATS components. For that purpose the current logging facility is
42 sufficient. */
43
44 /* Debugging level. */
45 static int debug_level = LOG_ERR;
46
47 /* File to log all messages to. */
48 static FILE *gnats_logfile = NULL;
49
50 /* Current logging method. */
51 static Logging_Methods log_method = NOLOG;
52
53 /* Set logging level to LOG_INFO.
54 Currently we support only two levels of debugging, thus it makes sense. */
55 void
enable_debugging(void)56 enable_debugging (void)
57 {
58 debug_level = LOG_INFO;
59 }
60
61 /* Log NUM_ARG messages given in `...' with logging SEVERITY.
62 Actually at most two messages in `...' are supported. */
63 void
64 #if defined(__STDC__) || defined(_AIX)
log_msg(int severity,int num_arg,...)65 log_msg (int severity, int num_arg, ...)
66 #else
67 log_msg (int severity, int num_arg, va_dcl va_alist)
68 #endif
69 {
70 va_list args;
71
72 VA_START (args, num_arg);
73
74 if (debug_level >= severity)
75 {
76 char *message;
77 char *buf;
78
79 message = va_arg (args, char *);
80 /* The following is incredibly stupid and broken, but I don't know how to
81 do it better in C. */
82 if (num_arg > 0)
83 {
84 char *message2 = va_arg (args, char *);
85 asprintf (&buf, "%s: %s %s\n", program_name, message, message2);
86 }
87 else
88 {
89 asprintf (&buf, "%s: %s\n", program_name, message);
90 }
91
92 switch (log_method)
93 {
94 #ifdef HAVE_SYSLOG_H
95 case SYSLOG:
96 syslog (severity, "%s", buf);
97 break;
98 #endif
99 case MAIL:
100 /* This is currently not used, because it could send a lot of mails
101 to someone. */
102 program_error (__FILE__, __LINE__);
103 break;
104 case LOGFILE:
105 if (gnats_logfile != (FILE *)NULL)
106 {
107 fprintf (gnats_logfile, "%s", buf);
108 break;
109 }
110 /* No log file, log to stderr. */
111 case STDERR:
112 fprintf (stderr, "%s", buf);
113 break;
114 case NOLOG:
115 /* Do nothing. */
116 break;
117 default:
118 program_error (__FILE__, __LINE__);
119 }
120
121 free (buf);
122 }
123
124 va_end (args);
125 }
126
127 /* Set log_method appropriately.
128 `logfile' is the file to log to. If it is NULL, we use another logging
129 destination by guessing, considering various criteria.
130 TODO: This method is very rudimentary and should be improved. */
131 void
init_logging(const char * logfile)132 init_logging (const char *logfile)
133 {
134 if (logfile)
135 {
136 /* A log file explicitly requested. Let's try to open it. */
137 gnats_logfile = fopen (logfile, "at");
138 if (gnats_logfile != NULL)
139 {
140 log_method = LOGFILE;
141 return;
142 }
143 }
144
145 if (isatty (fileno (stderr)))
146 /* If the program is invoked from a terminal, it is good to present the
147 messages directly to the user. */
148 log_method = STDERR;
149 else
150 {
151 /* Maybe this is a daemon. */
152 #ifdef HAVE_SYSLOG_H
153 closelog ();
154 #ifdef LOG_DAEMON
155 openlog ("gnatsd", (LOG_PID|LOG_CONS), LOG_DAEMON);
156 #else
157 openlog ("gnatsd", LOG_PID);
158 #endif
159 log_method = SYSLOG;
160 #else
161 /* This should be MAIL, but I don't know how to use it well. */
162 log_method = NOLOG;
163 #endif
164 }
165
166 if (logfile)
167 /* Log file requested, but couldn't be open. Now we can report this
168 fact. */
169 log_msg (LOG_ERR, 1, "Log file couldn't be open:", logfile);
170 }
171
172 /* Open the file (actually the pipe) to which the mail message will
173 be written. */
174 FILE *
open_mail_file(const DatabaseInfo database)175 open_mail_file (const DatabaseInfo database)
176 {
177 FILE *fp = NULL;
178 char *mailAgentPath = mailAgent (database);
179
180 if (mailAgentPath != NULL)
181 {
182 /* Can't use log_msg here, since gnats_logfile is being set by this first
183 thing. */
184
185 fp = popen (mailAgentPath, "w");
186 free (mailAgentPath);
187
188 if (debugMode (database))
189 {
190 fprintf (fp, "From: %s (GNATS Management)\n",
191 gnatsAdminMailAddr (database));
192 fprintf (fp, "To: %s\n", gnatsAdminMailAddr (database));
193 fprintf (fp, "Subject: mail output from %s\n", program_name);
194 fprintf (fp, "\n\n");
195 }
196 }
197
198 return fp;
199 }
200
201 void
close_mail_file(fp)202 close_mail_file (fp)
203 FILE *fp;
204 {
205
206 if (fp)
207 {
208 fflush (fp);
209 pclose (fp);
210 }
211 }
212
213
214 /* Report a program error at `line' in `file' and exit with an error code. */
215 void
program_error(const char * filename,int line)216 program_error (const char *filename, int line)
217 {
218 char *message;
219 asprintf (&message, "Program error at %s:%d", filename, line);
220 log_msg (LOG_ERR, 0, message);
221 free (message);
222 exit (EXIT_PROGRAM_ERROR);
223 }
224
225 /* Initialize the system and load the database DATABASE_NAME.
226 Return the basic database access structure.
227 PROGRAM_NAME is the name of the calling program.
228 If any error occurs, NULL is returned and `err' is set appropriately. */
229 DatabaseInfo
init_gnats(const char * program_name,const char * database_name,ErrorDesc * err)230 init_gnats (const char *program_name, const char *database_name,
231 ErrorDesc *err)
232 {
233 init_logging (NULL);
234 re_set_syntax ((RE_SYNTAX_POSIX_EXTENDED | RE_BK_PLUS_QM) & ~RE_DOT_NEWLINE);
235 if (initDatabaseList (err) != 0)
236 {
237 return NULL;
238 }
239 return findOrLoadDatabase (program_name, database_name, err);
240 }
241
242 StringList *
new_string_list_ent(char * name,StringList * next)243 new_string_list_ent (char *name, StringList *next)
244 {
245 StringList *res = (StringList *) xmalloc (sizeof (StringList));
246 res->name = name;
247 res->next = next;
248 return res;
249 }
250
251 /* Scan down LINE, returning the next token. We can't use strtok, since
252 we often deal with constant strings like "foo | bar" for the default
253 values for a PR.
254
255 Returns the returned token as a malloc()ed string, and changes
256 *LINE to point to the next place to begin parsing for the next
257 token, or sets it to NULL if the token being returned is the last
258 one. */
259 char *
get_next_field(const char ** line_ptr,int delim)260 get_next_field (const char **line_ptr, int delim)
261 {
262 const char *line = *line_ptr;
263 const char *end_line = line;
264 const char *next_token;
265 char *res;
266
267 while (*end_line != delim && *end_line != '\0')
268 {
269 end_line++;
270 }
271
272 next_token = end_line;
273
274 /* skip whitespace at the end of the token */
275 while (end_line > line && isspace ((int)(unsigned char) end_line[-1]))
276 {
277 end_line--;
278 }
279
280 res = xstrndup (line, end_line - line);
281 if (*next_token == delim)
282 {
283 *line_ptr = next_token + 1;
284 }
285 else
286 {
287 *line_ptr = NULL;
288 }
289
290 return res;
291 }
292
293 /* Adds quote-marks (") around the string, and escapes any quotes that
294 appear within the string with '\'. */
295 char *
quote_string(const char * string)296 quote_string (const char *string)
297 {
298 const char *p = string;
299 char *rp;
300 char *res;
301 int len = strlen (string) + 2;
302
303 for (p = string; *p != '\0'; p++)
304 {
305 if (*p == '"')
306 {
307 len++;
308 }
309 }
310
311 res = xmalloc (len + 1);
312 rp = res + 1;
313
314 res[0] = '"';
315 res[len - 1] = '"';
316 res[len] = '\0';
317
318 for (p = string; *p != '\0'; p++)
319 {
320 if (*p == '"')
321 {
322 *(rp++) = '\\';
323 }
324 *(rp++) = *p;
325 }
326 return res;
327 }
328
329 #define MYMIN(a,b) ((a) < (b) ? (a) : (b))
330
331 /* Read a newline-terminated line of text from INPUT. If MAXLENARG is
332 non-NULL and has a value greater than zero, a maximum of *MAXLENARG
333 characters will be read. (Since the string is terminated with a
334 '\0', *MAXLENARG + 1 chars will be allocated.)
335
336 The returned line is allocated with malloc (), and is the property
337 of the caller. If MAXLENARG is non-NULL, the number of characters
338 read is stored there.
339
340 A NULL value is returned if no text was read. */
341 char *
read_line(FILE * input,size_t * max_len_arg)342 read_line (FILE *input, size_t *max_len_arg)
343 {
344 size_t len = 0;
345 size_t max_len = (max_len_arg != NULL ? *max_len_arg : 0);
346 const size_t default_len = 512;
347 char *res = xmalloc (max_len>0 ? max_len+1 : default_len);
348
349 while (fgets (res+len, (max_len>0 ? max_len : default_len), input)
350 != NULL)
351 {
352 size_t slen = strlen (res + len);
353 len += slen;
354 if (res[len - 1] == '\n')
355 break;
356
357 if (max_len > 0)
358 {
359 max_len -= slen;
360 if (max_len == 0)
361 break;
362 }
363 else
364 {
365 res = xrealloc (res, len + default_len);
366 }
367 }
368
369 if (len == 0)
370 {
371 free (res);
372 res = NULL;
373 }
374 else if (max_len_arg == NULL || *max_len_arg == 0)
375 {
376 res = xrealloc (res, len + 1);
377 }
378
379 if (max_len_arg != NULL)
380 {
381 *max_len_arg = len;
382 }
383 return res;
384 }
385
386 /*
387
388 @deftypefn Supplemental char * xstrndup(const char *@var{str}, size_t @var{len})
389
390 Convenience function to copy @var{len} bytes of @var{str} to a newly allocated
391 string. Returns char pointer to new string, or returns NULL if @var{len} < 1
392 or @var{str} == NULL.
393
394 @end deftypefn
395
396 */
397 char *
xstrndup(const char * str,size_t len)398 xstrndup (const char *str, size_t len)
399 {
400 if (str == NULL)
401 {
402 return NULL;
403 }
404 else
405 {
406 char *res;
407
408 if (len < 1) {
409 len = 0;
410 }
411 res = xmalloc (len + 1);
412 memcpy (res, str, len);
413 res[len] = '\0';
414 return res;
415 }
416 }
417
418 /* Allocate a memory with internal error handling. */
419 PTR
xmalloc(size_t size)420 xmalloc(size_t size)
421 {
422 void *ptr;
423
424 if (size == (size_t)NULL)
425 size = 1;
426 if ((ptr = malloc(size)) == NULL) {
427 (void)fprintf(stderr, "xmalloc: unable to allocate memory");
428 program_error(__FILE__, __LINE__);
429 }
430 return (ptr);
431 }
432
433 /* Safely change the size of previously allocated memory area. */
434 PTR
xrealloc(PTR ptr,size_t size)435 xrealloc(PTR ptr, size_t size)
436 {
437 PTR ptr2;
438
439 if (size == (size_t)NULL)
440 size = 1;
441 if ((ptr2 = realloc(ptr, size)) == NULL) {
442 if (ptr != (PTR)NULL)
443 free(ptr);
444 (void)fprintf(stderr, "xrealloc: unable to relocate memory");
445 program_error(__FILE__, __LINE__);
446 }
447 return(ptr2);
448 }
449
450 /* Save a copy of a string with internal error handling. */
451 char *
xstrdup(const char * str)452 xstrdup(const char *str)
453 {
454 char *strc;
455
456 if ((strc = strdup(str)) == NULL) {
457 (void)fprintf(stderr, "xstrdup: unable to duplicate a string");
458 program_error(__FILE__, __LINE__);
459 }
460 return(strc);
461 }
462
463 #ifndef HAVE_MKDIR
464 /* mkdir adapted from GNU tar. */
465
466 /* Make directory DPATH, with permission mode DMODE.
467
468 Written by Robert Rother, Mariah Corporation, August 1985
469 (sdcsvax!rmr or rmr@@uscd). If you want it, it's yours.
470
471 Severely hacked over by John Gilmore to make a 4.2BSD compatible
472 subroutine. 11Mar86; hoptoad!gnu
473
474 Modified by rmtodd@@uokmax 6-28-87 -- when making an already existing dir,
475 subroutine didn't return EEXIST. It does now. */
476
477 int
mkdir(dpath,dmode)478 mkdir (dpath, dmode)
479 char *dpath;
480 int dmode;
481 {
482 int cpid, status;
483 struct stat statbuf;
484
485 if (stat (dpath, &statbuf) == 0)
486 {
487 errno = EEXIST; /* stat worked, so it already exists. */
488 return -1;
489 }
490
491 /* If stat fails for a reason other than non-existence, return error. */
492 if (errno != ENOENT)
493 return -1;
494
495 cpid = fork ();
496 switch (cpid)
497 {
498 case -1: /* Cannot fork. */
499 return -1; /* errno is set already. */
500
501 case 0: /* Child process. */
502 /* Cheap hack to set mode of new directory. Since this child
503 process is going away anyway, we zap its umask.
504 This won't suffice to set SUID, SGID, etc. on this
505 directory, so the parent process calls chmod afterward. */
506 status = umask (0); /* Get current umask. */
507 umask (status | (0777 & ~dmode)); /* Set for mkdir. */
508 execl ("/bin/mkdir", "mkdir", dpath, (char *) 0);
509 _exit (1);
510
511 default: /* Parent process. */
512 while (wait (&status) != cpid) /* Wait for kid to finish. */
513 /* Do nothing. */ ;
514
515 if (status & 0xFFFF)
516 {
517 errno = EIO; /* /bin/mkdir failed. */
518 return -1;
519 }
520 return chmod (dpath, dmode);
521 }
522 }
523 #endif /* HAVE_MKDIR */
524
525 /* Return open file descriptor of the file specified by `template' suitable to
526 `mktemp'. The file is open for reading and writing.
527 If the stream can't be opened, a negative value is returned. */
open_temporary_file(char * template,int mode)528 int open_temporary_file (char *template, int mode)
529 {
530 int fd = -1;
531 #ifndef HAVE_MKSTEMP
532 const int NUMBER_OF_TRIALS = 3;
533 #endif
534
535 #ifdef HAVE_MKSTEMP
536 fd = mkstemp (template);
537 chmod (template, mode);
538 #else
539 {
540 int i;
541 for (i = 0; i < NUMBER_OF_TRIALS && fd < 0; i++)
542 {
543 #ifdef HAVE_MKTEMP
544 mktemp (template);
545 #else
546 mkstemps (template, 0);
547 #endif
548 fd = open (template, O_RDWR | O_CREAT | O_EXCL, mode);
549 }
550 }
551 #endif
552 return fd;
553 }
554
555 /* Return the name of the directory to use for general temporary files. */
556 const char *
temporary_directory(void)557 temporary_directory (void)
558 {
559 #ifdef P_tmpdir
560 return P_tmpdir;
561 #else
562 char *tmpdir = getenv ("TMPDIR");
563 if (tmpdir == NULL)
564 {
565 tmpdir = "/tmp";
566 }
567 return tmpdir;
568 #endif
569 }
570
571 /* Return open temporary file specified by `template' suitable to `mktemp'.
572 `fopen_mode' is the mode string to be given to fopen.
573 If the file can't be opened, return NULL. */
574 FILE *
fopen_temporary_file(char * template,const char * fopen_mode,const int mode)575 fopen_temporary_file (char *template, const char *fopen_mode, const int mode)
576 {
577 int fd = open_temporary_file (template, mode);
578 return (fd < 0 ? NULL : fdopen (fd, fopen_mode));
579 }
580
581 /* Auxiliary for gnats_strftime. */
582 static int
minutes_gmt_offset(const struct tm * local_time_pointer)583 minutes_gmt_offset (const struct tm *local_time_pointer)
584 {
585 const int MINUTES_PER_DAY = 24*60;
586 time_t unix_time;
587 struct tm local;
588 struct tm gmt;
589 int offset;
590
591 /* Make local copies of the return values */
592 local = *local_time_pointer;
593 unix_time = mktime (&local);
594 gmt = *gmtime (&unix_time);
595
596 /* mktime() not portably reliable; calculate minutes offset ourselves */
597 offset = ((local.tm_hour - gmt.tm_hour) * 60 +
598 (local.tm_min - gmt.tm_min));
599
600 /* Adjust backwards/forwards if the day is different */
601 if (local.tm_year < gmt.tm_year) offset -= MINUTES_PER_DAY;
602 else if (local.tm_year > gmt.tm_year) offset += MINUTES_PER_DAY;
603 else if (local.tm_yday < gmt.tm_yday) offset -= MINUTES_PER_DAY;
604 else if (local.tm_yday > gmt.tm_yday) offset += MINUTES_PER_DAY;
605
606 return offset;
607 }
608
609 /* The same as `strftime' except it handles the case when `%z' is unsupported
610 by libc. */
611 size_t
gnats_strftime(char * s,size_t size,const char * template,const struct tm * brokentime)612 gnats_strftime (char *s, size_t size, const char *template,
613 const struct tm *brokentime)
614 {
615 static short have_strftime_with_z = -1;
616 if (have_strftime_with_z < 0)
617 {
618 char buf[16];
619 strftime (buf, 16, "%z", brokentime);
620 /* jonm@alchemetrics.co.uk - added check for +/- at the start
621 ** of the string to support SCO OpenServer. The undocumented
622 ** %z does not have a '+' on for positive offsets, so the
623 ** return from get_curr_date() cannot be parsed by get_date().
624 */
625 have_strftime_with_z = ((int)buf[0] == '+' || (int)buf[0] == '-') &&
626 isdigit ((int)(buf[1]));
627 }
628
629 if (have_strftime_with_z)
630 return strftime (s, size, template, brokentime);
631 else
632 {
633 int padding = 0;
634 const char *in = template;
635 char *fixed_template = 0;
636 char *out = 0;
637 /* Because brokentime points to static data (allocated
638 * by localtime()), it cannot be passed to a subroutine
639 * and then later be relied on to point to the same data. */
640 struct tm btime = *brokentime;
641 int result;
642
643 /* Count number of %z so we know how much characters to add to the
644 * template. We actually count the number of additional characters.
645 * As we are going to replace each "%z" by "+hhmm" (sign, hours,
646 * minutes), this is 3 extra chars for each %z. */
647 while (*in)
648 {
649 if (*in == '%' && *(in+1) == 'z')
650 {
651 in += 2;
652 padding += 3; /* 3 extra chars for each %z */
653 }
654 else
655 {
656 in++;
657 }
658 }
659
660 /* Now allocate enough space. */
661 fixed_template = (char*)xmalloc (strlen(template)+padding+1);
662 in = template; /* Inspect it again, this time replacing all %z. */
663 out = fixed_template;
664
665 while (*in != '\0')
666 {
667 char c = *in++;
668 if (c != '%')
669 {
670 *out++ = c;
671 }
672 else if (*in != 'z')
673 {
674 *out++ = c; /* the '%' */
675 *out++ = *in++;
676 }
677 else
678 {
679 int offset = minutes_gmt_offset (brokentime);
680 char offset_buf[6];
681 char sign = '+';
682 unsigned int i, hours, minutes;
683
684 if (offset < 0)
685 {
686 sign = '-';
687 offset = -offset;
688 }
689 hours = offset / 60;
690 minutes = offset % 60;
691 sprintf (offset_buf, "%c%02d%02d", sign, hours, minutes);
692 for (i = 0; i < strlen (offset_buf); i++)
693 *out++ = offset_buf[i];
694 in++; /* skip over 'z' */
695 }
696 }
697 *out = '\0';
698
699 result = strftime (s, size, fixed_template, &btime);
700 free (fixed_template);
701 return result;
702 }
703 }
704
705 /* Print usage information and exit with EXIT_CODE.
706 `texts' contains the output strings to be concatenated and printed; its last
707 element must be NULL.
708 (This is not a single string, because ISO C guarantees string length only to
709 about 509 bytes.) */
710 void
usage(const char * const texts[],int exit_code)711 usage (const char *const texts[], int exit_code)
712 {
713 FILE *output = (exit_code ? stderr : stdout);
714 const char *const *t;
715 for (t = texts; *t != NULL; t++)
716 fprintf (output, "%s", *t);
717 exit (exit_code);
718 }
719
720 /* Output the version information for PROGRAM_NAME and exit. */
721 void
version(const char * const program_name)722 version (const char *const program_name)
723 {
724 printf ("%s %s\n", program_name, version_string);
725 exit (EXIT_OK);
726 }
727
728 /* Return true iff STRING is either NULL or doesn't contain any non-whitespace
729 character. */
730 bool
value_is_empty(const char * string)731 value_is_empty (const char *string)
732 {
733 if (string == NULL)
734 return TRUE;
735 {
736 unsigned int i;
737 for (i = 0; i < strlen (string); i++)
738 if (! isspace ((int)(unsigned char)(string[i])))
739 return FALSE;
740 return TRUE;
741 }
742 }
743
744 /* The following functions are stolen from libiberty library (-liberty) */
745
746 #ifndef HAVE_ASPRINTF
747 /*
748 @deftypefn Extension int asprintf (char **@var{resptr}, const char *@var{format}, ...)
749
750 Like @code{sprintf}, but instead of passing a pointer to a buffer, you
751 pass a pointer to a pointer. This function will compute the size of
752 the buffer needed, allocate memory with @code{malloc}, and store a
753 pointer to the allocated memory in @code{*@var{resptr}}. The value
754 returned is the same as @code{sprintf} would return. If memory could
755 not be allocated, minus one is returned and @code{NULL} is stored in
756 @code{*@var{resptr}}.
757
758 @end deftypefn
759 */
760
761 int
asprintf(char ** buf,const char * fmt,...)762 asprintf VPARAMS ((char **buf, const char *fmt, ...))
763 {
764 int status;
765 VA_OPEN (ap, fmt);
766 VA_FIXEDARG (ap, char **, buf);
767 VA_FIXEDARG (ap, const char *, fmt);
768 status = vasprintf (buf, fmt, ap);
769 VA_CLOSE (ap);
770 return status;
771 }
772 #endif /* HAVE_ASPRINTF */
773
774 #ifndef HAVE_VASPRINTF
775 /*
776 @deftypefn Extension int vasprintf (char **@var{resptr}, const char *@var{format}, va_list @var{args})
777
778 Like @code{vsprintf}, but instead of passing a pointer to a buffer,
779 you pass a pointer to a pointer. This function will compute the size
780 of the buffer needed, allocate memory with @code{malloc}, and store a
781 pointer to the allocated memory in @code{*@var{resptr}}. The value
782 returned is the same as @code{vsprintf} would return. If memory could
783 not be allocated, minus one is returned and @code{NULL} is stored in
784 @code{*@var{resptr}}.
785
786 @end deftypefn
787 */
788
789 /* This is a vasprintf helper */
790 static int int_vasprintf PARAMS ((char **, const char *, va_list));
791
792 static int
int_vasprintf(result,format,args)793 int_vasprintf (result, format, args)
794 char **result;
795 const char *format;
796 va_list args;
797 {
798 const char *p = format;
799 /* Add one to make sure that it is never zero, which might cause malloc
800 to return NULL. */
801 int total_width = strlen (format) + 1;
802 va_list ap;
803
804 #ifdef va_copy
805 va_copy (ap, args);
806 #else
807 memcpy ((PTR) &ap, (PTR) &args, sizeof (va_list));
808 #endif
809
810 while (*p != '\0')
811 {
812 if (*p++ == '%')
813 {
814 while (strchr ("-+ #0", *p))
815 ++p;
816 if (*p == '*')
817 {
818 ++p;
819 total_width += abs (va_arg (ap, int));
820 }
821 else
822 total_width += strtoul (p, (char **) &p, 10);
823 if (*p == '.')
824 {
825 ++p;
826 if (*p == '*')
827 {
828 ++p;
829 total_width += abs (va_arg (ap, int));
830 }
831 else
832 total_width += strtoul (p, (char **) &p, 10);
833 }
834 while (strchr ("hlL", *p))
835 ++p;
836 /* Should be big enough for any format specifier except %s and floats. */
837 total_width += 30;
838 switch (*p)
839 {
840 case 'd':
841 case 'i':
842 case 'o':
843 case 'u':
844 case 'x':
845 case 'X':
846 case 'c':
847 (void) va_arg (ap, int);
848 break;
849 case 'f':
850 case 'e':
851 case 'E':
852 case 'g':
853 case 'G':
854 (void) va_arg (ap, double);
855 /* Since an ieee double can have an exponent of 307, we'll
856 make the buffer wide enough to cover the gross case. */
857 total_width += 307;
858 break;
859 case 's':
860 total_width += strlen (va_arg (ap, char *));
861 break;
862 case 'p':
863 case 'n':
864 (void) va_arg (ap, char *);
865 break;
866 }
867 p++;
868 }
869 }
870 #ifdef va_copy
871 va_end (ap);
872 #endif
873 *result = (char *) malloc (total_width);
874 if (*result != NULL)
875 return vsprintf (*result, format, args);
876 else
877 return -1;
878 }
879
880 int
vasprintf(result,format,args)881 vasprintf (result, format, args)
882 char **result;
883 const char *format;
884 #if defined (_BSD_VA_LIST_) && defined (__FreeBSD__)
885 _BSD_VA_LIST_ args;
886 #else
887 va_list args;
888 #endif
889 {
890 return int_vasprintf (result, format, args);
891 }
892 #endif /* HAVE_VASPRINTF */
893
894 #ifndef HAVE_BASENAME
895 /*
896 @deftypefn Supplemental char* basename (const char *@var{name})
897
898 Returns a pointer to the last component of pathname @var{name}.
899 Behavior is undefined if the pathname ends in a directory separator.
900
901 @end deftypefn
902 */
903
904 #ifndef DIR_SEPARATOR
905 #define DIR_SEPARATOR '/'
906 #endif
907
908 #if defined (_WIN32) || defined (__MSDOS__) || defined (__DJGPP__) || \
909 defined (__OS2__)
910 #define HAVE_DOS_BASED_FILE_SYSTEM
911 #ifndef DIR_SEPARATOR_2
912 #define DIR_SEPARATOR_2 '\\'
913 #endif
914 #endif
915
916 /* Define IS_DIR_SEPARATOR. */
917 #ifndef DIR_SEPARATOR_2
918 # define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
919 #else /* DIR_SEPARATOR_2 */
920 # define IS_DIR_SEPARATOR(ch) \
921 (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
922 #endif /* DIR_SEPARATOR_2 */
923
924 /* Note: to be POSIX-2 compliant "name" have a "char *" type. */
925 char *
basename(name)926 basename (name)
927 char *name;
928 {
929 const char *base;
930
931 #if defined (HAVE_DOS_BASED_FILE_SYSTEM)
932 /* Skip over the disk name in MSDOS pathnames. */
933 if (ISALPHA (name[0]) && name[1] == ':')
934 name += 2;
935 #endif
936
937 for (base = name; *name; name++)
938 {
939 if (IS_DIR_SEPARATOR (*name))
940 {
941 base = name + 1;
942 }
943 }
944 return (char *) base;
945 }
946 #endif /* HAVE_BASENAME */
947