]>
Commit | Line | Data |
---|---|---|
832b75ed | 1 | /* |
4d59bff9 | 2 | * utility.cpp |
832b75ed GG |
3 | * |
4 | * Home page of code is: http://smartmontools.sourceforge.net | |
5 | * | |
6 | * Copyright (C) 2002-6 Bruce Allen <smartmontools-support@lists.sourceforge.net> | |
7 | * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2, or (at your option) | |
12 | * any later version. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * (for example COPYING); if not, write to the Free | |
16 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
17 | * | |
18 | * This code was originally developed as a Senior Thesis by Michael Cornwell | |
19 | * at the Concurrent Systems Laboratory (now part of the Storage Systems | |
20 | * Research Center), Jack Baskin School of Engineering, University of | |
21 | * California, Santa Cruz. http://ssrc.soe.ucsc.edu/ | |
22 | * | |
23 | */ | |
24 | ||
25 | // THIS FILE IS INTENDED FOR UTILITY ROUTINES THAT ARE APPLICABLE TO | |
26 | // BOTH SCSI AND ATA DEVICES, AND THAT MAY BE USED IN SMARTD, | |
27 | // SMARTCTL, OR BOTH. | |
28 | ||
29 | #include <stdio.h> | |
30 | #include <string.h> | |
31 | #include <time.h> | |
32 | #include <errno.h> | |
33 | #include <stdlib.h> | |
34 | #include <ctype.h> | |
35 | #include <syslog.h> | |
36 | #include <stdarg.h> | |
37 | #include <sys/stat.h> | |
38 | #ifdef _WIN32 | |
39 | #include <mbstring.h> // _mbsinc() | |
40 | #endif | |
41 | ||
42 | #include "config.h" | |
43 | #include "int64.h" | |
44 | #include "utility.h" | |
45 | ||
46 | // Any local header files should be represented by a CVSIDX just below. | |
4d59bff9 | 47 | const char* utility_c_cvsid="$Id: utility.cpp,v 1.62 2006/08/09 20:40:20 chrfranke Exp $" |
832b75ed GG |
48 | CONFIG_H_CVSID INT64_H_CVSID UTILITY_H_CVSID; |
49 | ||
50 | const char * packet_types[] = { | |
51 | "Direct-access (disk)", | |
52 | "Sequential-access (tape)", | |
53 | "Printer", | |
54 | "Processor", | |
55 | "Write-once (optical disk)", | |
56 | "CD/DVD", | |
57 | "Scanner", | |
58 | "Optical memory (optical disk)", | |
59 | "Medium changer", | |
60 | "Communications", | |
61 | "Graphic arts pre-press (10)", | |
62 | "Graphic arts pre-press (11)", | |
63 | "Array controller", | |
64 | "Enclosure services", | |
65 | "Reduced block command (simplified disk)", | |
66 | "Optical card reader/writer" | |
67 | }; | |
68 | ||
69 | // Whenever exit() status is EXIT_BADCODE, please print this message | |
70 | const char *reportbug="Please report this bug to the Smartmontools developers at " PACKAGE_BUGREPORT ".\n"; | |
71 | ||
72 | ||
73 | // hang on to exit code, so we can make use of more generic 'atexit()' | |
74 | // functionality and still check our exit code | |
75 | int exitstatus = 0; | |
76 | ||
77 | // command-line argument: are we running in debug mode?. | |
78 | unsigned char debugmode = 0; | |
79 | ||
80 | ||
81 | // Solaris only: Get site-default timezone. This is called from | |
82 | // UpdateTimezone() when TZ environment variable is unset at startup. | |
83 | #if defined (__SVR4) && defined (__sun) | |
84 | static const char *TIMEZONE_FILE = "/etc/TIMEZONE"; | |
85 | ||
86 | static char *ReadSiteDefaultTimezone(){ | |
87 | FILE *fp; | |
88 | char buf[512], *tz; | |
89 | int n; | |
90 | ||
91 | tz = NULL; | |
92 | fp = fopen(TIMEZONE_FILE, "r"); | |
93 | if(fp == NULL) return NULL; | |
94 | while(fgets(buf, sizeof(buf), fp)) { | |
95 | if (strncmp(buf, "TZ=", 3)) // searches last "TZ=" line | |
96 | continue; | |
97 | n = strlen(buf) - 1; | |
98 | if (buf[n] == '\n') buf[n] = 0; | |
99 | if (tz) free(tz); | |
100 | tz = strdup(buf); | |
101 | } | |
102 | fclose(fp); | |
103 | return tz; | |
104 | } | |
105 | #endif | |
106 | ||
107 | // Make sure that this executable is aware if the user has changed the | |
108 | // time-zone since the last time we polled devices. The cannonical | |
109 | // example is a user who starts smartd on a laptop, then flies across | |
110 | // time-zones with a laptop, and then changes the timezone, WITHOUT | |
111 | // restarting smartd. This is a work-around for a bug in | |
112 | // GLIBC. Yuk. See bug number 48184 at http://bugs.debian.org and | |
113 | // thanks to Ian Redfern for posting a workaround. | |
114 | ||
115 | // Please refer to the smartd manual page, in the section labeled LOG | |
116 | // TIMESTAMP TIMEZONE. | |
117 | void FixGlibcTimeZoneBug(){ | |
118 | #if __GLIBC__ | |
119 | if (!getenv("TZ")) { | |
120 | putenv("TZ=GMT"); | |
121 | tzset(); | |
122 | putenv("TZ"); | |
123 | tzset(); | |
124 | } | |
125 | #elif _WIN32 | |
126 | if (!getenv("TZ")) { | |
127 | putenv("TZ=GMT"); | |
128 | tzset(); | |
129 | putenv("TZ="); // empty value removes TZ, putenv("TZ") does nothing | |
130 | tzset(); | |
131 | } | |
132 | #elif defined (__SVR4) && defined (__sun) | |
133 | // In Solaris, putenv("TZ=") sets null string and invalid timezone. | |
134 | // putenv("TZ") does nothing. With invalid TZ, tzset() do as if | |
135 | // TZ=GMT. With TZ unset, /etc/TIMEZONE will be read only _once_ at | |
136 | // first tzset() call. Conclusion: Unlike glibc, dynamic | |
137 | // configuration of timezone can be done only by changing actual | |
138 | // value of TZ environment value. | |
139 | enum tzstate { NOT_CALLED_YET, USER_TIMEZONE, TRACK_TIMEZONE }; | |
140 | static enum tzstate state = NOT_CALLED_YET; | |
141 | ||
142 | static struct stat prev_stat; | |
143 | static char *prev_tz; | |
144 | struct stat curr_stat; | |
145 | char *curr_tz; | |
146 | ||
147 | if(state == NOT_CALLED_YET) { | |
148 | if(getenv("TZ")) { | |
149 | state = USER_TIMEZONE; // use supplied timezone | |
150 | } else { | |
151 | state = TRACK_TIMEZONE; | |
152 | if(stat(TIMEZONE_FILE, &prev_stat)) { | |
153 | state = USER_TIMEZONE; // no TZ, no timezone file; use GMT forever | |
154 | } else { | |
155 | prev_tz = ReadSiteDefaultTimezone(); // track timezone file change | |
156 | if(prev_tz) putenv(prev_tz); | |
157 | } | |
158 | } | |
159 | tzset(); | |
160 | } else if(state == TRACK_TIMEZONE) { | |
161 | if(stat(TIMEZONE_FILE, &curr_stat) == 0 | |
162 | && (curr_stat.st_ctime != prev_stat.st_ctime | |
163 | || curr_stat.st_mtime != prev_stat.st_mtime)) { | |
164 | // timezone file changed | |
165 | curr_tz = ReadSiteDefaultTimezone(); | |
166 | if(curr_tz) { | |
167 | putenv(curr_tz); | |
168 | if(prev_tz) free(prev_tz); | |
169 | prev_tz = curr_tz; prev_stat = curr_stat; | |
170 | } | |
171 | } | |
172 | tzset(); | |
173 | } | |
174 | #endif | |
175 | // OTHER OS/LIBRARY FIXES SHOULD GO HERE, IF DESIRED. PLEASE TRY TO | |
176 | // KEEP THEM INDEPENDENT. | |
177 | return; | |
178 | } | |
179 | ||
180 | #ifdef _WIN32 | |
181 | // Fix strings in tzname[] to avoid long names with non-ascii characters. | |
182 | // If TZ is not set, tzset() in the MSVC runtime sets tzname[] to the | |
183 | // national language timezone names returned by GetTimezoneInformation(). | |
184 | static char * fixtzname(char * dest, int destsize, const char * src) | |
185 | { | |
186 | int i = 0, j = 0; | |
187 | while (src[i] && j < destsize-1) { | |
188 | int i2 = (const char *)_mbsinc((const unsigned char *)src+i) - src; | |
189 | if (i2 > i+1) | |
190 | i = i2; // Ignore multibyte chars | |
191 | else { | |
192 | if ('A' <= src[i] && src[i] <= 'Z') | |
193 | dest[j++] = src[i]; // "Pacific Standard Time" => "PST" | |
194 | i++; | |
195 | } | |
196 | } | |
197 | if (j < 2) | |
198 | j = 0; | |
199 | dest[j] = 0; | |
200 | return dest; | |
201 | } | |
202 | #endif // _WIN32 | |
203 | ||
204 | // This value follows the peripheral device type value as defined in | |
205 | // SCSI Primary Commands, ANSI INCITS 301:1997. It is also used in | |
206 | // the ATA standard for packet devices to define the device type. | |
207 | const char *packetdevicetype(int type){ | |
208 | if (type<0x10) | |
209 | return packet_types[type]; | |
210 | ||
211 | if (type<0x20) | |
212 | return "Reserved"; | |
213 | ||
214 | return "Unknown"; | |
215 | } | |
216 | ||
217 | ||
218 | // Returns 1 if machine is big endian, else zero. This is a run-time | |
219 | // rather than a compile-time function. We could do it at | |
220 | // compile-time but in principle there are architectures that can run | |
221 | // with either byte-ordering. | |
222 | int isbigendian(){ | |
223 | short i=0x0100; | |
224 | char *tmp=(char *)&i; | |
225 | return *tmp; | |
226 | } | |
227 | ||
228 | // Utility function prints date and time and timezone into a character | |
229 | // buffer of length>=64. All the fuss is needed to get the right | |
230 | // timezone info (sigh). | |
231 | void dateandtimezoneepoch(char *buffer, time_t tval){ | |
232 | struct tm *tmval; | |
233 | char *timezonename; | |
234 | char datebuffer[DATEANDEPOCHLEN]; | |
235 | int lenm1; | |
236 | #ifdef _WIN32 | |
237 | char tzfixbuf[6+1]; | |
238 | #endif | |
239 | ||
240 | FixGlibcTimeZoneBug(); | |
241 | ||
242 | // Get the time structure. We need this to determine if we are in | |
243 | // daylight savings time or not. | |
244 | tmval=localtime(&tval); | |
245 | ||
246 | // Convert to an ASCII string, put in datebuffer | |
247 | // same as: asctime_r(tmval, datebuffer); | |
248 | strncpy(datebuffer, asctime(tmval), DATEANDEPOCHLEN); | |
249 | datebuffer[DATEANDEPOCHLEN-1]='\0'; | |
250 | ||
251 | // Remove newline | |
252 | lenm1=strlen(datebuffer)-1; | |
253 | datebuffer[lenm1>=0?lenm1:0]='\0'; | |
254 | ||
255 | // correct timezone name | |
256 | if (tmval->tm_isdst==0) | |
257 | // standard time zone | |
258 | timezonename=tzname[0]; | |
259 | else if (tmval->tm_isdst>0) | |
260 | // daylight savings in effect | |
261 | timezonename=tzname[1]; | |
262 | else | |
263 | // unable to determine if daylight savings in effect | |
264 | timezonename=""; | |
265 | ||
266 | #ifdef _WIN32 | |
267 | // Fix long non-ascii timezone names | |
268 | if (!getenv("TZ")) | |
269 | timezonename=fixtzname(tzfixbuf, sizeof(tzfixbuf), timezonename); | |
270 | #endif | |
271 | ||
272 | // Finally put the information into the buffer as needed. | |
273 | snprintf(buffer, DATEANDEPOCHLEN, "%s %s", datebuffer, timezonename); | |
274 | ||
275 | return; | |
276 | } | |
277 | ||
278 | // Date and timezone gets printed into string pointed to by buffer | |
279 | void dateandtimezone(char *buffer){ | |
280 | ||
281 | // Get the epoch (time in seconds since Jan 1 1970) | |
282 | time_t tval=time(NULL); | |
283 | ||
284 | dateandtimezoneepoch(buffer, tval); | |
285 | return; | |
286 | } | |
287 | ||
288 | // These are two utility functions for printing CVS IDs. Massagecvs() | |
289 | // returns distance that it has moved ahead in the input string | |
290 | int massagecvs(char *out, const char *cvsid){ | |
291 | char *copy,*filename,*date,*version; | |
292 | int retVal=0; | |
293 | const char delimiters[] = " ,$"; | |
294 | ||
295 | // make a copy on the heap, go to first token, | |
296 | if (!(copy=strdup(cvsid))) | |
297 | return 0; | |
298 | ||
299 | if (!(filename=strtok(copy, delimiters))) | |
300 | goto endmassage; | |
301 | ||
302 | // move to first instance of "Id:" | |
303 | while (strcmp(filename,"Id:")) | |
304 | if (!(filename=strtok(NULL, delimiters))) | |
305 | goto endmassage; | |
306 | ||
307 | // get filename, skip "v", get version and date | |
308 | if (!( filename=strtok(NULL, delimiters) ) || | |
309 | !( strtok(NULL, delimiters) ) || | |
310 | !( version=strtok(NULL, delimiters) ) || | |
311 | !( date=strtok(NULL, delimiters) ) ) | |
312 | goto endmassage; | |
313 | ||
314 | sprintf(out,"%-16s revision: %-5s date: %-15s", filename, version, date); | |
315 | retVal = (date-copy)+strlen(date); | |
316 | ||
317 | endmassage: | |
318 | free(copy); | |
319 | return retVal; | |
320 | } | |
321 | ||
322 | // prints a single set of CVS ids | |
323 | void printone(char *block, const char *cvsid){ | |
324 | char strings[CVSMAXLEN]; | |
325 | const char *here=cvsid; | |
326 | int bi=0, len=strlen(cvsid)+1; | |
327 | ||
328 | // check that the size of the output block is sufficient | |
329 | if (len>=CVSMAXLEN) { | |
330 | pout("CVSMAXLEN=%d must be at least %d\n",CVSMAXLEN,len+1); | |
331 | EXIT(1); | |
332 | } | |
333 | ||
334 | // loop through the different strings | |
335 | while (bi<CVSMAXLEN && (len=massagecvs(strings,here))){ | |
336 | bi+=snprintf(block+bi,CVSMAXLEN-bi,"%s %s\n",(bi==0?"Module:":" uses:"),strings); | |
337 | here+=len; | |
338 | } | |
339 | return; | |
340 | } | |
341 | ||
342 | ||
343 | // A replacement for perror() that sends output to our choice of | |
344 | // printing. If errno not set then just print message. | |
345 | void 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 | // Prints a warning message for a failed regular expression compilation from | |
365 | // regcomp(). | |
366 | void printregexwarning(int errcode, regex_t *compiled){ | |
367 | size_t length = regerror(errcode, compiled, NULL, 0); | |
4d59bff9 | 368 | char *buffer = (char*)malloc(length); |
832b75ed GG |
369 | if (!buffer){ |
370 | pout("Out of memory in printregexwarning()\n"); | |
371 | return; | |
372 | } | |
373 | regerror(errcode, compiled, buffer, length); | |
374 | pout("%s\n", buffer); | |
375 | free(buffer); | |
376 | return; | |
377 | } | |
378 | ||
379 | // POSIX extended regular expressions interpret unmatched ')' ordinary: | |
380 | // "The close-parenthesis shall be considered special in this context | |
381 | // only if matched with a preceding open-parenthesis." | |
382 | // | |
383 | // Actual '(...)' nesting errors remain undetected on strict POSIX | |
384 | // implementations (glibc) but an error is reported on others (Cygwin). | |
385 | // | |
386 | // The check below is rather incomplete because it does not handle | |
387 | // e.g. '\)' '[)]'. | |
388 | // But it should work for the regex subset used in drive database. | |
389 | static int check_regex_nesting(const char * pattern) | |
390 | { | |
391 | int level = 0, i; | |
392 | for (i = 0; pattern[i] && level >= 0; i++) { | |
393 | switch (pattern[i]) { | |
394 | case '(': level++; break; | |
395 | case ')': level--; break; | |
396 | } | |
397 | } | |
398 | return level; | |
399 | } | |
400 | ||
401 | // A wrapper for regcomp(). Returns zero for success, non-zero otherwise. | |
402 | int compileregex(regex_t *compiled, const char *pattern, int cflags) | |
403 | { | |
404 | int errorcode; | |
405 | ||
406 | if ( (errorcode = regcomp(compiled, pattern, cflags)) | |
407 | || check_regex_nesting(pattern) < 0 ) { | |
408 | pout("Internal error: unable to compile regular expression \"%s\" ", pattern); | |
409 | if (errorcode) | |
410 | printregexwarning(errorcode, compiled); | |
411 | else | |
412 | pout("Unmatched ')'\n"); | |
413 | pout("Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n"); | |
414 | return 1; | |
415 | } | |
416 | return 0; | |
417 | } | |
418 | ||
419 | // Splits an argument to the -r option into a name part and an (optional) | |
420 | // positive integer part. s is a pointer to a string containing the | |
421 | // argument. After the call, s will point to the name part and *i the | |
422 | // integer part if there is one or 1 otherwise. Note that the string s may | |
423 | // be changed by this function. Returns zero if successful and non-zero | |
424 | // otherwise. | |
425 | int split_report_arg(char *s, int *i) | |
426 | { | |
427 | if ((s = strchr(s, ','))) { | |
428 | // Looks like there's a name part and an integer part. | |
429 | char *tailptr; | |
430 | ||
431 | *s++ = '\0'; | |
432 | if (*s == '0' || !isdigit((int)*s)) // The integer part must be positive | |
433 | return 1; | |
434 | errno = 0; | |
435 | *i = (int) strtol(s, &tailptr, 10); | |
436 | if (errno || *tailptr != '\0') | |
437 | return 1; | |
438 | } else { | |
439 | // There's no integer part. | |
440 | *i = 1; | |
441 | } | |
442 | ||
443 | return 0; | |
444 | } | |
445 | ||
446 | // same as above but sets *i to -1 if missing , argument | |
447 | int split_report_arg2(char *s, int *i){ | |
448 | char *tailptr; | |
449 | s+=6; | |
450 | ||
451 | if (*s=='\0' || !isdigit((int)*s)) { | |
452 | // What's left must be integer | |
453 | *i=-1; | |
454 | return 1; | |
455 | } | |
456 | ||
457 | errno = 0; | |
458 | *i = (int) strtol(s, &tailptr, 10); | |
459 | if (errno || *tailptr != '\0') { | |
460 | *i=-1; | |
461 | return 1; | |
462 | } | |
463 | ||
464 | return 0; | |
465 | } | |
466 | ||
467 | #ifndef HAVE_STRTOULL | |
468 | // Replacement for missing strtoull() (Linux with libc < 6, MSVC 6.0) | |
469 | // Functionality reduced to split_selective_arg()'s requirements. | |
470 | ||
471 | static uint64_t strtoull(const char * p, char * * endp, int base) | |
472 | { | |
473 | uint64_t result, maxres; | |
474 | int i = 0; | |
475 | char c = p[i++]; | |
476 | // assume base == 0 | |
477 | if (c == '0') { | |
478 | if (p[i] == 'x' || p[i] == 'X') { | |
479 | base = 16; i++; | |
480 | } | |
481 | else | |
482 | base = 8; | |
483 | c = p[i++]; | |
484 | } | |
485 | else | |
486 | base = 10; | |
487 | ||
488 | result = 0; | |
489 | maxres = ~(uint64_t)0 / (unsigned)base; | |
490 | for (;;) { | |
491 | unsigned digit; | |
492 | if ('0' <= c && c <= '9') | |
493 | digit = c - '0'; | |
494 | else if ('A' <= c && c <= 'Z') | |
495 | digit = c - 'A' + 10; | |
496 | else if ('a' <= c && c <= 'z') | |
497 | digit = c - 'a' + 10; | |
498 | else | |
499 | break; | |
500 | if (digit >= (unsigned)base) | |
501 | break; | |
502 | if (!( result < maxres | |
503 | || (result == maxres && digit <= ~(uint64_t)0 % (unsigned)base))) { | |
504 | result = ~(uint64_t)0; errno = ERANGE; // return on overflow | |
505 | break; | |
506 | } | |
507 | result = result * (unsigned)base + digit; | |
508 | c = p[i++]; | |
509 | } | |
510 | *endp = (char *)p + i - 1; | |
511 | return result; | |
512 | } | |
513 | #endif // HAVE_STRTOLL | |
514 | ||
515 | // Splits an argument to the -t option that is assumed to be of the form | |
516 | // "selective,%lld-%lld" (prefixes of "0" (for octal) and "0x"/"0X" (for hex) | |
517 | // are allowed). The first long long int is assigned to *start and the second | |
518 | // to *stop. Returns zero if successful and non-zero otherwise. | |
519 | int split_selective_arg(char *s, uint64_t *start, | |
520 | uint64_t *stop) | |
521 | { | |
522 | char *tailptr; | |
523 | ||
524 | if (!(s = strchr(s, ','))) | |
525 | return 1; | |
526 | if (!isdigit((int)(*++s))) | |
527 | return 1; | |
528 | errno = 0; | |
529 | // Last argument to strtoull (the base) is 0 meaning that decimal is assumed | |
530 | // unless prefixes of "0" (for octal) or "0x"/"0X" (for hex) are used. | |
531 | *start = strtoull(s, &tailptr, 0); | |
532 | ||
533 | s = tailptr; | |
534 | if (errno || *s++ != '-') | |
535 | return 1; | |
536 | *stop = strtoull(s, &tailptr, 0); | |
537 | if (errno || *tailptr != '\0') | |
538 | return 1; | |
539 | return 0; | |
540 | } | |
541 | ||
542 | int64_t bytes = 0; | |
543 | // Helps debugging. If the second argument is non-negative, then | |
544 | // decrement bytes by that amount. Else decrement bytes by (one plus) | |
545 | // length of null terminated string. | |
4d59bff9 | 546 | void *FreeNonZero1(void *address, int size, int line, const char* file){ |
832b75ed GG |
547 | if (address) { |
548 | if (size<0) | |
4d59bff9 | 549 | bytes-=1+strlen((char*)address); |
832b75ed GG |
550 | else |
551 | bytes-=size; | |
4d59bff9 | 552 | return CheckFree1(address, line, file); |
832b75ed GG |
553 | } |
554 | return NULL; | |
555 | } | |
556 | ||
557 | // To help with memory checking. Use when it is known that address is | |
558 | // NOT null. | |
4d59bff9 | 559 | void *CheckFree1(void *address, int whatline, const char* file){ |
832b75ed GG |
560 | if (address){ |
561 | free(address); | |
562 | return NULL; | |
563 | } | |
564 | ||
565 | PrintOut(LOG_CRIT, "Internal error in CheckFree() at line %d of file %s\n%s", | |
566 | whatline, file, reportbug); | |
567 | EXIT(EXIT_BADCODE); | |
568 | } | |
569 | ||
570 | // A custom version of calloc() that tracks memory use | |
571 | void *Calloc(size_t nmemb, size_t size) { | |
572 | void *ptr=calloc(nmemb, size); | |
573 | ||
574 | if (ptr) | |
575 | bytes+=nmemb*size; | |
576 | ||
577 | return ptr; | |
578 | } | |
579 | ||
580 | // A custom version of strdup() that keeps track of how much memory is | |
581 | // being allocated. If mustexist is set, it also throws an error if we | |
582 | // try to duplicate a NULL string. | |
583 | char *CustomStrDup(char *ptr, int mustexist, int whatline, const char* file){ | |
584 | char *tmp; | |
585 | ||
586 | // report error if ptr is NULL and mustexist is set | |
587 | if (ptr==NULL){ | |
588 | if (mustexist) { | |
589 | PrintOut(LOG_CRIT, "Internal error in CustomStrDup() at line %d of file %s\n%s", | |
590 | whatline, file, reportbug); | |
591 | EXIT(EXIT_BADCODE); | |
592 | } | |
593 | else | |
594 | return NULL; | |
595 | } | |
596 | ||
597 | // make a copy of the string... | |
598 | tmp=strdup(ptr); | |
599 | ||
600 | if (!tmp) { | |
601 | PrintOut(LOG_CRIT, "No memory to duplicate string %s at line %d of file %s\n", ptr, whatline, file); | |
602 | EXIT(EXIT_NOMEM); | |
603 | } | |
604 | ||
605 | // and track memory usage | |
606 | bytes+=1+strlen(ptr); | |
607 | ||
608 | return tmp; | |
609 | } | |
610 | ||
611 | // Returns nonzero if region of memory contains non-zero entries | |
612 | int nonempty(unsigned char *testarea,int n){ | |
613 | int i; | |
614 | for (i=0;i<n;i++) | |
615 | if (testarea[i]) | |
616 | return 1; | |
617 | return 0; | |
618 | } | |
619 | ||
620 | ||
621 | // This routine converts an integer number of milliseconds into a test | |
622 | // string of the form Xd+Yh+Zm+Ts.msec. The resulting text string is | |
623 | // written to the array. | |
624 | void MsecToText(unsigned int msec, char *txt){ | |
625 | int start=0; | |
626 | unsigned int days, hours, min, sec; | |
627 | ||
628 | days = msec/86400000U; | |
629 | msec -= days*86400000U; | |
630 | ||
631 | hours = msec/3600000U; | |
632 | msec -= hours*3600000U; | |
633 | ||
634 | min = msec/60000U; | |
635 | msec -= min*60000U; | |
636 | ||
637 | sec = msec/1000U; | |
638 | msec -= sec*1000U; | |
639 | ||
640 | if (days) { | |
641 | txt += sprintf(txt, "%2dd+", (int)days); | |
642 | start=1; | |
643 | } | |
644 | ||
645 | sprintf(txt, "%02d:%02d:%02d.%03d", (int)hours, (int)min, (int)sec, (int)msec); | |
646 | return; | |
647 | } | |
648 | ||
649 | ||
650 | #ifndef HAVE_WORKING_SNPRINTF | |
651 | // Some versions of (v)snprintf() don't append null char on overflow (MSVCRT.DLL), | |
652 | // and/or return -1 on overflow (old Linux). | |
653 | // Below are sane replacements substituted by #define in utility.h. | |
654 | ||
655 | #undef vsnprintf | |
656 | #if defined(_WIN32) && defined(_MSC_VER) | |
657 | #define vsnprintf _vsnprintf | |
658 | #endif | |
659 | ||
660 | int safe_vsnprintf(char *buf, int size, const char *fmt, va_list ap) | |
661 | { | |
662 | int i; | |
663 | if (size <= 0) | |
664 | return 0; | |
665 | i = vsnprintf(buf, size, fmt, ap); | |
666 | if (0 <= i && i < size) | |
667 | return i; | |
668 | buf[size-1] = 0; | |
669 | return strlen(buf); // Note: cannot detect for overflow, not necessary here. | |
670 | } | |
671 | ||
672 | int safe_snprintf(char *buf, int size, const char *fmt, ...) | |
673 | { | |
674 | int i; va_list ap; | |
675 | va_start(ap, fmt); | |
676 | i = safe_vsnprintf(buf, size, fmt, ap); | |
677 | va_end(ap); | |
678 | return i; | |
679 | } | |
680 | ||
681 | #endif |