]> git.proxmox.com Git - mirror_smartmontools-debian.git/blame - utility.cpp
document changes
[mirror_smartmontools-debian.git] / utility.cpp
CommitLineData
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 47const char* utility_c_cvsid="$Id: utility.cpp,v 1.62 2006/08/09 20:40:20 chrfranke Exp $"
832b75ed
GG
48CONFIG_H_CVSID INT64_H_CVSID UTILITY_H_CVSID;
49
50const 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
70const 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
75int exitstatus = 0;
76
77// command-line argument: are we running in debug mode?.
78unsigned 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)
84static const char *TIMEZONE_FILE = "/etc/TIMEZONE";
85
86static 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.
117void 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().
184static 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.
207const 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.
222int 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).
231void 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
279void 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
290int 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
323void 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.
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// Prints a warning message for a failed regular expression compilation from
365// regcomp().
366void 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.
389static 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.
402int 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.
425int 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
447int 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
471static 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.
519int 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
542int64_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 546void *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 559void *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
571void *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.
583char *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
612int 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.
624void 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
660int 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
672int 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