root/trunk/smartmontools/utility.cpp @ 3191

Revision 3191, 20.3 KB (checked in by chrfranke, 3 years ago)

Make 'debugmode' variable local to smartd.cpp.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * utility.cpp
3 *
4 * Home page of code is: http://smartmontools.sourceforge.net
5 *
6 * Copyright (C) 2002-10 Bruce Allen <smartmontools-support@lists.sourceforge.net>
7 * Copyright (C) 2008-10 Christian Franke <smartmontools-support@lists.sourceforge.net>
8 * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org>
9 *
10 * This program 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 * You should have received a copy of the GNU General Public License
16 * (for example COPYING); if not, write to the Free
17 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *
19 * This code was originally developed as a Senior Thesis by Michael Cornwell
20 * at the Concurrent Systems Laboratory (now part of the Storage Systems
21 * Research Center), Jack Baskin School of Engineering, University of
22 * California, Santa Cruz. http://ssrc.soe.ucsc.edu/
23 *
24 */
25
26// THIS FILE IS INTENDED FOR UTILITY ROUTINES THAT ARE APPLICABLE TO
27// BOTH SCSI AND ATA DEVICES, AND THAT MAY BE USED IN SMARTD,
28// SMARTCTL, OR BOTH.
29
30#include <stdio.h>
31#include <string.h>
32#include <time.h>
33#include <errno.h>
34#include <stdlib.h>
35#include <ctype.h>
36#include <syslog.h>
37#include <stdarg.h>
38#include <sys/stat.h>
39#ifdef _WIN32
40#include <mbstring.h> // _mbsinc()
41#endif
42
43#include <stdexcept>
44
45#include "config.h"
46#include "svnversion.h"
47#include "int64.h"
48#include "utility.h"
49
50#include "atacmds.h"
51#include "dev_interface.h"
52
53const char * utility_cpp_cvsid = "$Id$"
54                                 UTILITY_H_CVSID INT64_H_CVSID;
55
56const char * packet_types[] = {
57        "Direct-access (disk)",
58        "Sequential-access (tape)",
59        "Printer",
60        "Processor",
61        "Write-once (optical disk)",
62        "CD/DVD",
63        "Scanner",
64        "Optical memory (optical disk)",
65        "Medium changer",
66        "Communications",
67        "Graphic arts pre-press (10)",
68        "Graphic arts pre-press (11)",
69        "Array controller",
70        "Enclosure services",
71        "Reduced block command (simplified disk)",
72        "Optical card reader/writer"
73};
74
75// BUILD_INFO can be provided by package maintainers
76#ifndef BUILD_INFO
77#define BUILD_INFO "(local build)"
78#endif
79
80// Make version information string
81std::string format_version_info(const char * prog_name, bool full /*= false*/)
82{
83  std::string info = strprintf(
84    "%s "PACKAGE_VERSION" "
85#ifdef SMARTMONTOOLS_SVN_REV
86      SMARTMONTOOLS_SVN_DATE" r"SMARTMONTOOLS_SVN_REV
87#else
88      "(build date "__DATE__")" // checkout without expansion of Id keywords
89#endif
90      " [%s] "BUILD_INFO"\n"
91    "Copyright (C) 2002-10 by Bruce Allen, http://smartmontools.sourceforge.net\n",
92    prog_name, smi()->get_os_version_str().c_str()
93  );
94  if (!full)
95    return info;
96
97  info += strprintf(
98    "\n"
99    "%s comes with ABSOLUTELY NO WARRANTY. This is free\n"
100    "software, and you are welcome to redistribute it under\n"
101    "the terms of the GNU General Public License Version 2.\n"
102    "See http://www.gnu.org for further details.\n"
103    "\n",
104    prog_name
105  );
106  info += strprintf(
107    "smartmontools release "PACKAGE_VERSION
108      " dated "SMARTMONTOOLS_RELEASE_DATE" at "SMARTMONTOOLS_RELEASE_TIME"\n"
109#ifdef SMARTMONTOOLS_SVN_REV
110    "smartmontools SVN rev "SMARTMONTOOLS_SVN_REV
111      " dated "SMARTMONTOOLS_SVN_DATE" at "SMARTMONTOOLS_SVN_TIME"\n"
112#else
113    "smartmontools SVN rev is unknown\n"
114#endif
115    "smartmontools build host: "SMARTMONTOOLS_BUILD_HOST"\n"
116    "smartmontools build configured: "SMARTMONTOOLS_CONFIGURE_DATE "\n"
117    "%s compile dated "__DATE__" at "__TIME__"\n"
118    "smartmontools configure arguments: ",
119    prog_name
120  );
121  info += (sizeof(SMARTMONTOOLS_CONFIGURE_ARGS) > 1 ?
122           SMARTMONTOOLS_CONFIGURE_ARGS : "[no arguments given]");
123  info += '\n';
124
125  return info;
126}
127
128// Solaris only: Get site-default timezone. This is called from
129// UpdateTimezone() when TZ environment variable is unset at startup.
130#if defined (__SVR4) && defined (__sun)
131static const char *TIMEZONE_FILE = "/etc/TIMEZONE";
132
133static char *ReadSiteDefaultTimezone(){
134  FILE *fp;
135  char buf[512], *tz;
136  int n;
137
138  tz = NULL;
139  fp = fopen(TIMEZONE_FILE, "r");
140  if(fp == NULL) return NULL;
141  while(fgets(buf, sizeof(buf), fp)) {
142    if (strncmp(buf, "TZ=", 3))    // searches last "TZ=" line
143      continue;
144    n = strlen(buf) - 1;
145    if (buf[n] == '\n') buf[n] = 0;
146    if (tz) free(tz);
147    tz = strdup(buf);
148  }
149  fclose(fp);
150  return tz;
151}
152#endif
153
154// Make sure that this executable is aware if the user has changed the
155// time-zone since the last time we polled devices. The cannonical
156// example is a user who starts smartd on a laptop, then flies across
157// time-zones with a laptop, and then changes the timezone, WITHOUT
158// restarting smartd. This is a work-around for a bug in
159// GLIBC. Yuk. See bug number 48184 at http://bugs.debian.org and
160// thanks to Ian Redfern for posting a workaround.
161
162// Please refer to the smartd manual page, in the section labeled LOG
163// TIMESTAMP TIMEZONE.
164void FixGlibcTimeZoneBug(){
165#if __GLIBC__ 
166  if (!getenv("TZ")) {
167    putenv((char *)"TZ=GMT"); // POSIX prototype is 'int putenv(char *)'
168    tzset();
169    putenv((char *)"TZ");
170    tzset();
171  }
172#elif _WIN32
173  if (!getenv("TZ")) {
174    putenv("TZ=GMT");
175    tzset();
176    putenv("TZ=");  // empty value removes TZ, putenv("TZ") does nothing
177    tzset();
178  }
179#elif defined (__SVR4) && defined (__sun)
180  // In Solaris, putenv("TZ=") sets null string and invalid timezone.
181  // putenv("TZ") does nothing.  With invalid TZ, tzset() do as if
182  // TZ=GMT.  With TZ unset, /etc/TIMEZONE will be read only _once_ at
183  // first tzset() call.  Conclusion: Unlike glibc, dynamic
184  // configuration of timezone can be done only by changing actual
185  // value of TZ environment value.
186  enum tzstate { NOT_CALLED_YET, USER_TIMEZONE, TRACK_TIMEZONE };
187  static enum tzstate state = NOT_CALLED_YET;
188
189  static struct stat prev_stat;
190  static char *prev_tz;
191  struct stat curr_stat;
192  char *curr_tz;
193
194  if(state == NOT_CALLED_YET) {
195    if(getenv("TZ")) {
196      state = USER_TIMEZONE; // use supplied timezone
197    } else {
198      state = TRACK_TIMEZONE;
199      if(stat(TIMEZONE_FILE, &prev_stat)) {
200        state = USER_TIMEZONE;  // no TZ, no timezone file; use GMT forever
201      } else {
202        prev_tz = ReadSiteDefaultTimezone(); // track timezone file change
203        if(prev_tz) putenv(prev_tz);
204      }
205    }
206    tzset();
207  } else if(state == TRACK_TIMEZONE) {
208    if(stat(TIMEZONE_FILE, &curr_stat) == 0
209       && (curr_stat.st_ctime != prev_stat.st_ctime
210            || curr_stat.st_mtime != prev_stat.st_mtime)) {
211      // timezone file changed
212      curr_tz = ReadSiteDefaultTimezone();
213      if(curr_tz) {
214        putenv(curr_tz);
215        if(prev_tz) free(prev_tz);
216        prev_tz = curr_tz; prev_stat = curr_stat; 
217      }
218    }
219    tzset();
220  }
221#endif
222  // OTHER OS/LIBRARY FIXES SHOULD GO HERE, IF DESIRED.  PLEASE TRY TO
223  // KEEP THEM INDEPENDENT.
224  return;
225}
226
227#ifdef _WIN32
228// Fix strings in tzname[] to avoid long names with non-ascii characters.
229// If TZ is not set, tzset() in the MSVC runtime sets tzname[] to the
230// national language timezone names returned by GetTimezoneInformation().
231static char * fixtzname(char * dest, int destsize, const char * src)
232{
233  int i = 0, j = 0;
234  while (src[i] && j < destsize-1) {
235    int i2 = (const char *)_mbsinc((const unsigned char *)src+i) - src;
236    if (i2 > i+1)
237      i = i2; // Ignore multibyte chars
238    else {
239      if ('A' <= src[i] && src[i] <= 'Z')
240        dest[j++] = src[i]; // "Pacific Standard Time" => "PST"
241      i++;
242    }
243  }
244  if (j < 2)
245    j = 0;
246  dest[j] = 0;
247  return dest;
248}
249#endif // _WIN32
250
251// This value follows the peripheral device type value as defined in
252// SCSI Primary Commands, ANSI INCITS 301:1997.  It is also used in
253// the ATA standard for packet devices to define the device type.
254const char *packetdevicetype(int type){
255  if (type<0x10)
256    return packet_types[type];
257 
258  if (type<0x20)
259    return "Reserved";
260 
261  return "Unknown";
262}
263
264// Runtime check of byte ordering, throws if different from isbigendian().
265void check_endianness()
266{
267  union {
268    // Force compile error if int type is not 32bit.
269    unsigned char c[sizeof(unsigned) == 4 ? 4 : -1];
270    unsigned i;
271  } x = {{1,2,3,4}};
272
273  int big = -1;
274  switch (x.i) {
275    case 0x01020304: big = 1; break;
276    case 0x04030201: big = 0; break;
277  }
278
279  if (big != (isbigendian() ? 1 : 0))
280    throw std::logic_error("CPU endianness does not match compile time test");
281}
282
283// Utility function prints date and time and timezone into a character
284// buffer of length>=64.  All the fuss is needed to get the right
285// timezone info (sigh).
286void dateandtimezoneepoch(char *buffer, time_t tval){
287  struct tm *tmval;
288  const char *timezonename;
289  char datebuffer[DATEANDEPOCHLEN];
290  int lenm1;
291#ifdef _WIN32
292  char tzfixbuf[6+1];
293#endif
294
295  FixGlibcTimeZoneBug();
296 
297  // Get the time structure.  We need this to determine if we are in
298  // daylight savings time or not.
299  tmval=localtime(&tval);
300 
301  // Convert to an ASCII string, put in datebuffer
302  // same as: asctime_r(tmval, datebuffer);
303  strncpy(datebuffer, asctime(tmval), DATEANDEPOCHLEN);
304  datebuffer[DATEANDEPOCHLEN-1]='\0';
305 
306  // Remove newline
307  lenm1=strlen(datebuffer)-1;
308  datebuffer[lenm1>=0?lenm1:0]='\0';
309 
310  // correct timezone name
311  if (tmval->tm_isdst==0)
312    // standard time zone
313    timezonename=tzname[0];
314  else if (tmval->tm_isdst>0)
315    // daylight savings in effect
316    timezonename=tzname[1];
317  else
318    // unable to determine if daylight savings in effect
319    timezonename="";
320
321#ifdef _WIN32
322  // Fix long non-ascii timezone names
323  if (!getenv("TZ"))
324    timezonename=fixtzname(tzfixbuf, sizeof(tzfixbuf), timezonename);
325#endif
326 
327  // Finally put the information into the buffer as needed.
328  snprintf(buffer, DATEANDEPOCHLEN, "%s %s", datebuffer, timezonename);
329 
330  return;
331}
332
333// Date and timezone gets printed into string pointed to by buffer
334void dateandtimezone(char *buffer){
335 
336  // Get the epoch (time in seconds since Jan 1 1970)
337  time_t tval=time(NULL);
338 
339  dateandtimezoneepoch(buffer, tval);
340  return;
341}
342
343// A replacement for perror() that sends output to our choice of
344// printing. If errno not set then just print message.
345void syserror(const char *message){
346 
347  if (errno) {
348    // Get the correct system error message:
349    const char *errormessage=strerror(errno);
350   
351    // Check that caller has handed a sensible string, and provide
352    // appropriate output. See perrror(3) man page to understand better.
353    if (message && *message)
354      pout("%s: %s\n",message, errormessage);
355    else
356      pout("%s\n",errormessage);
357  }
358  else if (message && *message)
359    pout("%s\n",message);
360 
361  return;
362}
363
364// POSIX extended regular expressions interpret unmatched ')' ordinary:
365// "The close-parenthesis shall be considered special in this context
366//  only if matched with a preceding open-parenthesis."
367//
368// Actual '(...)' nesting errors remain undetected on strict POSIX
369// implementations (glibc) but an error is reported on others (Cygwin).
370//
371// The check below is rather incomplete because it does not handle
372// e.g. '\)' '[)]'.
373// But it should work for the regex subset used in drive database
374// and smartd '-s' directives.
375static int check_regex_nesting(const char * pattern)
376{
377  int level = 0, i;
378  for (i = 0; pattern[i] && level >= 0; i++) {
379    switch (pattern[i]) {
380      case '(': level++; break;
381      case ')': level--; break;
382    }
383  }
384  return level;
385}
386
387// Wrapper class for regex(3)
388
389regular_expression::regular_expression()
390: m_flags(0)
391{
392  memset(&m_regex_buf, 0, sizeof(m_regex_buf));
393}
394
395regular_expression::regular_expression(const char * pattern, int flags)
396{
397  memset(&m_regex_buf, 0, sizeof(m_regex_buf));
398  compile(pattern, flags);
399}
400
401regular_expression::~regular_expression()
402{
403  free_buf();
404}
405
406regular_expression::regular_expression(const regular_expression & x)
407{
408  memset(&m_regex_buf, 0, sizeof(m_regex_buf));
409  copy(x);
410}
411
412regular_expression & regular_expression::operator=(const regular_expression & x)
413{
414  free_buf();
415  copy(x);
416  return *this;
417}
418
419void regular_expression::free_buf()
420{
421  if (nonempty(&m_regex_buf, sizeof(m_regex_buf))) {
422    regfree(&m_regex_buf);
423    memset(&m_regex_buf, 0, sizeof(m_regex_buf));
424  }
425}
426
427void regular_expression::copy(const regular_expression & x)
428{
429  m_pattern = x.m_pattern;
430  m_flags = x.m_flags;
431  m_errmsg = x.m_errmsg;
432
433  if (!m_pattern.empty() && m_errmsg.empty()) {
434    // There is no POSIX compiled-regex-copy command.
435    if (!compile())
436      throw std::runtime_error(strprintf(
437        "Unable to recompile regular expression \"%s\": %s",
438        m_pattern.c_str(), m_errmsg.c_str()));
439  }
440}
441
442bool regular_expression::compile(const char * pattern, int flags)
443{
444  free_buf();
445  m_pattern = pattern;
446  m_flags = flags;
447  return compile();
448}
449
450bool regular_expression::compile()
451{
452  int errcode = regcomp(&m_regex_buf, m_pattern.c_str(), m_flags);
453  if (errcode) {
454    char errmsg[512];
455    regerror(errcode, &m_regex_buf, errmsg, sizeof(errmsg));
456    m_errmsg = errmsg;
457    free_buf();
458    return false;
459  }
460
461  if (check_regex_nesting(m_pattern.c_str()) < 0) {
462    m_errmsg = "Unmatched ')'";
463    free_buf();
464    return false;
465  }
466
467  m_errmsg.clear();
468  return true;
469}
470
471// Splits an argument to the -r option into a name part and an (optional)
472// positive integer part.  s is a pointer to a string containing the
473// argument.  After the call, s will point to the name part and *i the
474// integer part if there is one or 1 otherwise.  Note that the string s may
475// be changed by this function.  Returns zero if successful and non-zero
476// otherwise.
477int split_report_arg(char *s, int *i)
478{
479  if ((s = strchr(s, ','))) {
480    // Looks like there's a name part and an integer part.
481    char *tailptr;
482
483    *s++ = '\0';
484    if (*s == '0' || !isdigit((int)*s))  // The integer part must be positive
485      return 1;
486    errno = 0;
487    *i = (int) strtol(s, &tailptr, 10);
488    if (errno || *tailptr != '\0')
489      return 1;
490  } else {
491    // There's no integer part.
492    *i = 1;
493  }
494
495  return 0;
496}
497
498// same as above but sets *i to -1 if missing , argument
499int split_report_arg2(char *s, int *i){
500  char *tailptr;
501  s+=6;
502
503  if (*s=='\0' || !isdigit((int)*s)) { 
504    // What's left must be integer
505    *i=-1;
506    return 1;
507  }
508
509  errno = 0;
510  *i = (int) strtol(s, &tailptr, 10);
511  if (errno || *tailptr != '\0') {
512    *i=-1;
513    return 1;
514  }
515
516  return 0;
517}
518
519#ifndef HAVE_STRTOULL
520// Replacement for missing strtoull() (Linux with libc < 6, MSVC)
521// Functionality reduced to requirements of smartd and split_selective_arg().
522
523uint64_t strtoull(const char * p, char * * endp, int base)
524{
525  uint64_t result, maxres;
526  int i = 0;
527  char c = p[i++];
528
529  if (!base) {
530    if (c == '0') {
531      if (p[i] == 'x' || p[i] == 'X') {
532        base = 16; i++;
533      }
534      else
535        base = 8;
536      c = p[i++];
537    }
538    else
539      base = 10;
540  }
541
542  result = 0;
543  maxres = ~(uint64_t)0 / (unsigned)base;
544  for (;;) {
545    unsigned digit;
546    if ('0' <= c && c <= '9')
547      digit = c - '0';
548    else if ('A' <= c && c <= 'Z')
549      digit = c - 'A' + 10;
550    else if ('a' <= c && c <= 'z')
551      digit = c - 'a' + 10;
552    else
553      break;
554    if (digit >= (unsigned)base)
555      break;
556    if (!(   result < maxres
557          || (result == maxres && digit <= ~(uint64_t)0 % (unsigned)base))) {
558      result = ~(uint64_t)0; errno = ERANGE; // return on overflow
559      break;
560    }
561    result = result * (unsigned)base + digit;
562    c = p[i++];
563  }
564  if (endp)
565    *endp = (char *)p + i - 1;
566  return result;
567}
568#endif // HAVE_STRTOLL
569
570// Splits an argument to the -t option that is assumed to be of the form
571// "selective,%lld-%lld" (prefixes of "0" (for octal) and "0x"/"0X" (for hex)
572// are allowed).  The first long long int is assigned to *start and the second
573// to *stop.  Returns zero if successful and non-zero otherwise.
574int split_selective_arg(char *s, uint64_t *start,
575                        uint64_t *stop, int *mode)
576{
577  char *tailptr;
578  if (!(s = strchr(s, ',')))
579    return 1;
580  bool add = false;
581  if (!isdigit((int)(*++s))) {
582    *start = *stop = 0;
583    if (!strncmp(s, "redo", 4))
584      *mode = SEL_REDO;
585    else if (!strncmp(s, "next", 4))
586      *mode = SEL_NEXT;
587    else if (!strncmp(s, "cont", 4))
588      *mode = SEL_CONT;
589    else
590      return 1;
591    s += 4;
592    if (!*s)
593      return 0;
594    if (*s != '+')
595      return 1;
596  }
597  else {
598    *mode = SEL_RANGE;
599    errno = 0;
600    // Last argument to strtoull (the base) is 0 meaning that decimal is assumed
601    // unless prefixes of "0" (for octal) or "0x"/"0X" (for hex) are used.
602    *start = strtoull(s, &tailptr, 0);
603    s = tailptr;
604    add = (*s == '+');
605    if (!(!errno && (add || *s == '-')))
606      return 1;
607    if (!strcmp(s, "-max")) {
608      *stop = ~(uint64_t)0; // replaced by max LBA later
609      return 0;
610    }
611  }
612  *stop = strtoull(s+1, &tailptr, 0);
613  if (errno || *tailptr != '\0')
614    return 1;
615  if (add) {
616    if (*stop > 0)
617      (*stop)--;
618    *stop += *start; // -t select,N+M => -t select,N,(N+M-1)
619  }
620  return 0;
621}
622
623#ifdef OLD_INTERFACE
624
625int64_t bytes = 0;
626
627// Helps debugging.  If the second argument is non-negative, then
628// decrement bytes by that amount.  Else decrement bytes by (one plus)
629// length of null terminated string.
630void *FreeNonZero1(void *address, int size, int line, const char* file){
631  if (address) {
632    if (size<0)
633      bytes-=1+strlen((char*)address);
634    else
635      bytes-=size;
636    return CheckFree1(address, line, file);
637  }
638  return NULL;
639}
640
641// To help with memory checking.  Use when it is known that address is
642// NOT null.
643void *CheckFree1(void *address, int /*whatline*/, const char* /*file*/){
644  if (address){
645    free(address);
646    return NULL;
647  }
648  throw std::runtime_error("Internal error in CheckFree()");
649}
650
651// A custom version of calloc() that tracks memory use
652void *Calloc(size_t nmemb, size_t size) { 
653  void *ptr=calloc(nmemb, size);
654 
655  if (ptr)
656    bytes+=nmemb*size;
657
658  return ptr;
659}
660
661// A custom version of strdup() that keeps track of how much memory is
662// being allocated. If mustexist is set, it also throws an error if we
663// try to duplicate a NULL string.
664char *CustomStrDup(const char *ptr, int mustexist, int /*whatline*/, const char* /*file*/){
665  char *tmp;
666
667  // report error if ptr is NULL and mustexist is set
668  if (ptr==NULL){
669    if (mustexist)
670      throw std::runtime_error("Internal error in CustomStrDup()");
671    else
672      return NULL;
673  }
674
675  // make a copy of the string...
676  tmp=strdup(ptr);
677 
678  if (!tmp)
679    throw std::bad_alloc();
680 
681  // and track memory usage
682  bytes+=1+strlen(ptr);
683 
684  return tmp;
685}
686
687#endif // OLD_INTERFACE
688
689
690// Returns true if region of memory contains non-zero entries
691bool nonempty(const void * data, int size)
692{
693  for (int i = 0; i < size; i++)
694    if (((const unsigned char *)data)[i])
695      return true;
696  return false;
697}
698
699
700// This routine converts an integer number of milliseconds into a test
701// string of the form Xd+Yh+Zm+Ts.msec.  The resulting text string is
702// written to the array.
703void MsecToText(unsigned int msec, char *txt){
704  int start=0;
705  unsigned int days, hours, min, sec;
706
707  days       = msec/86400000U;
708  msec      -= days*86400000U;
709
710  hours      = msec/3600000U; 
711  msec      -= hours*3600000U;
712
713  min        = msec/60000U;
714  msec      -= min*60000U;
715
716  sec        = msec/1000U;
717  msec      -= sec*1000U;
718
719  if (days) {
720    txt += sprintf(txt, "%2dd+", (int)days);
721    start=1;
722  }
723
724  sprintf(txt, "%02d:%02d:%02d.%03d", (int)hours, (int)min, (int)sec, (int)msec); 
725  return;
726}
727
728// return (v)sprintf() formatted std::string
729
730std::string vstrprintf(const char * fmt, va_list ap)
731{
732  char buf[512];
733  vsnprintf(buf, sizeof(buf), fmt, ap);
734  buf[sizeof(buf)-1] = 0;
735  return buf;
736}
737
738std::string strprintf(const char * fmt, ...)
739{
740  va_list ap; va_start(ap, fmt);
741  std::string str = vstrprintf(fmt, ap);
742  va_end(ap);
743  return str;
744}
745
746
747#ifndef HAVE_WORKING_SNPRINTF
748// Some versions of (v)snprintf() don't append null char on overflow (MSVCRT.DLL),
749// and/or return -1 on overflow (old Linux).
750// Below are sane replacements substituted by #define in utility.h.
751
752#undef vsnprintf
753#if defined(_WIN32) && defined(_MSC_VER)
754#define vsnprintf _vsnprintf
755#endif
756
757int safe_vsnprintf(char *buf, int size, const char *fmt, va_list ap)
758{
759  int i;
760  if (size <= 0)
761    return 0;
762  i = vsnprintf(buf, size, fmt, ap);
763  if (0 <= i && i < size)
764    return i;
765  buf[size-1] = 0;
766  return strlen(buf); // Note: cannot detect for overflow, not necessary here.
767}
768
769int safe_snprintf(char *buf, int size, const char *fmt, ...)
770{
771  int i; va_list ap;
772  va_start(ap, fmt);
773  i = safe_vsnprintf(buf, size, fmt, ap);
774  va_end(ap);
775  return i;
776}
777
778#endif
779
Note: See TracBrowser for help on using the browser.