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