]> git.proxmox.com Git - mirror_smartmontools-debian.git/blob - utility.cpp
Merge branch 'upstream'
[mirror_smartmontools-debian.git] / utility.cpp
1 /*
2 * utility.cpp
3 *
4 * Home page of code is: http://smartmontools.sourceforge.net
5 *
6 * Copyright (C) 2002-8 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.
47 const char* utility_c_cvsid="$Id: utility.cpp,v 1.65 2008/03/04 22:09:47 ballen4705 Exp $"
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);
368 char *buffer = (char*)malloc(length);
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, int *mode)
521 {
522 char *tailptr;
523 if (!(s = strchr(s, ',')))
524 return 1;
525 bool add = false;
526 if (!isdigit((int)(*++s))) {
527 *start = *stop = 0;
528 if (!strncmp(s, "redo", 4))
529 *mode = SEL_REDO;
530 else if (!strncmp(s, "next", 4))
531 *mode = SEL_NEXT;
532 else if (!strncmp(s, "cont", 4))
533 *mode = SEL_CONT;
534 else
535 return 1;
536 s += 4;
537 if (!*s)
538 return 0;
539 if (*s != '+')
540 return 1;
541 }
542 else {
543 *mode = SEL_RANGE;
544 errno = 0;
545 // Last argument to strtoull (the base) is 0 meaning that decimal is assumed
546 // unless prefixes of "0" (for octal) or "0x"/"0X" (for hex) are used.
547 *start = strtoull(s, &tailptr, 0);
548 s = tailptr;
549 add = (*s == '+');
550 if (!(!errno && (add || *s == '-')))
551 return 1;
552 if (!strcmp(s, "-max")) {
553 *stop = ~(uint64_t)0; // replaced by max LBA later
554 return 0;
555 }
556 }
557 *stop = strtoull(s+1, &tailptr, 0);
558 if (errno || *tailptr != '\0')
559 return 1;
560 if (add) {
561 if (*stop > 0)
562 (*stop)--;
563 *stop += *start; // -t select,N+M => -t select,N,(N+M-1)
564 }
565 return 0;
566 }
567
568 int64_t bytes = 0;
569 // Helps debugging. If the second argument is non-negative, then
570 // decrement bytes by that amount. Else decrement bytes by (one plus)
571 // length of null terminated string.
572 void *FreeNonZero1(void *address, int size, int line, const char* file){
573 if (address) {
574 if (size<0)
575 bytes-=1+strlen((char*)address);
576 else
577 bytes-=size;
578 return CheckFree1(address, line, file);
579 }
580 return NULL;
581 }
582
583 // To help with memory checking. Use when it is known that address is
584 // NOT null.
585 void *CheckFree1(void *address, int whatline, const char* file){
586 if (address){
587 free(address);
588 return NULL;
589 }
590
591 PrintOut(LOG_CRIT, "Internal error in CheckFree() at line %d of file %s\n%s",
592 whatline, file, reportbug);
593 EXIT(EXIT_BADCODE);
594 }
595
596 // A custom version of calloc() that tracks memory use
597 void *Calloc(size_t nmemb, size_t size) {
598 void *ptr=calloc(nmemb, size);
599
600 if (ptr)
601 bytes+=nmemb*size;
602
603 return ptr;
604 }
605
606 // A custom version of strdup() that keeps track of how much memory is
607 // being allocated. If mustexist is set, it also throws an error if we
608 // try to duplicate a NULL string.
609 char *CustomStrDup(const char *ptr, int mustexist, int whatline, const char* file){
610 char *tmp;
611
612 // report error if ptr is NULL and mustexist is set
613 if (ptr==NULL){
614 if (mustexist) {
615 PrintOut(LOG_CRIT, "Internal error in CustomStrDup() at line %d of file %s\n%s",
616 whatline, file, reportbug);
617 EXIT(EXIT_BADCODE);
618 }
619 else
620 return NULL;
621 }
622
623 // make a copy of the string...
624 tmp=strdup(ptr);
625
626 if (!tmp) {
627 PrintOut(LOG_CRIT, "No memory to duplicate string %s at line %d of file %s\n", ptr, whatline, file);
628 EXIT(EXIT_NOMEM);
629 }
630
631 // and track memory usage
632 bytes+=1+strlen(ptr);
633
634 return tmp;
635 }
636
637 // Returns nonzero if region of memory contains non-zero entries
638 int nonempty(unsigned char *testarea,int n){
639 int i;
640 for (i=0;i<n;i++)
641 if (testarea[i])
642 return 1;
643 return 0;
644 }
645
646
647 // This routine converts an integer number of milliseconds into a test
648 // string of the form Xd+Yh+Zm+Ts.msec. The resulting text string is
649 // written to the array.
650 void MsecToText(unsigned int msec, char *txt){
651 int start=0;
652 unsigned int days, hours, min, sec;
653
654 days = msec/86400000U;
655 msec -= days*86400000U;
656
657 hours = msec/3600000U;
658 msec -= hours*3600000U;
659
660 min = msec/60000U;
661 msec -= min*60000U;
662
663 sec = msec/1000U;
664 msec -= sec*1000U;
665
666 if (days) {
667 txt += sprintf(txt, "%2dd+", (int)days);
668 start=1;
669 }
670
671 sprintf(txt, "%02d:%02d:%02d.%03d", (int)hours, (int)min, (int)sec, (int)msec);
672 return;
673 }
674
675
676 #ifndef HAVE_WORKING_SNPRINTF
677 // Some versions of (v)snprintf() don't append null char on overflow (MSVCRT.DLL),
678 // and/or return -1 on overflow (old Linux).
679 // Below are sane replacements substituted by #define in utility.h.
680
681 #undef vsnprintf
682 #if defined(_WIN32) && defined(_MSC_VER)
683 #define vsnprintf _vsnprintf
684 #endif
685
686 int safe_vsnprintf(char *buf, int size, const char *fmt, va_list ap)
687 {
688 int i;
689 if (size <= 0)
690 return 0;
691 i = vsnprintf(buf, size, fmt, ap);
692 if (0 <= i && i < size)
693 return i;
694 buf[size-1] = 0;
695 return strlen(buf); // Note: cannot detect for overflow, not necessary here.
696 }
697
698 int safe_snprintf(char *buf, int size, const char *fmt, ...)
699 {
700 int i; va_list ap;
701 va_start(ap, fmt);
702 i = safe_vsnprintf(buf, size, fmt, ap);
703 va_end(ap);
704 return i;
705 }
706
707 #endif