--- embedaddon/smartmontools/smartd.cpp 2012/10/09 09:36:45 1.1.1.2 +++ embedaddon/smartmontools/smartd.cpp 2013/07/22 01:17:36 1.1.1.3 @@ -4,7 +4,7 @@ * Copyright (C) 2002-11 Bruce Allen * Copyright (C) 2000 Michael Cornwell * Copyright (C) 2008 Oliver Bock - * Copyright (C) 2008-12 Christian Franke + * Copyright (C) 2008-13 Christian Franke * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,11 +21,6 @@ * */ -#ifndef _GNU_SOURCE -// TODO: Why is this define necessary? -#define _GNU_SOURCE -#endif - // unconditionally included files #include #include @@ -95,9 +90,6 @@ typedef int pid_t; #endif #ifdef _WIN32 -#include "hostname_win32.h" // gethost/domainname() -#define HAVE_GETHOSTNAME 1 -#define HAVE_GETDOMAINNAME 1 // fork()/signal()/initd simulation for native Windows #include "daemon_win32.h" // daemon_main/detach/signal() #undef SIGNALFN @@ -115,9 +107,7 @@ typedef int pid_t; extern "C" int getdomainname(char *, int); // no declaration in header files! #endif -#define ARGUSED(x) ((void)(x)) - -const char * smartd_cpp_cvsid = "$Id: smartd.cpp,v 1.1.1.2 2012/10/09 09:36:45 misho Exp $" +const char * smartd_cpp_cvsid = "$Id: smartd.cpp,v 1.1.1.3 2013/07/22 01:17:36 misho Exp $" CONFIG_H_CVSID; // smartd exit codes @@ -168,6 +158,9 @@ static const char * const configfile_stdin = "" // path of alternate configuration file static std::string configfile_alt; +// warning script file +static std::string warning_script; + // command-line: when should we exit? static int quit=0; @@ -184,10 +177,8 @@ static bool do_fork=true; static bool enable_capabilities = false; #endif -#if defined(_WIN32) || defined(__CYGWIN__) // TODO: This smartctl only variable is also used in os_win32.cpp unsigned char failuretest_permissive = 0; -#endif // set to one if we catch a USR1 (check devices now) static volatile int caughtsigUSR1=0; @@ -248,8 +239,10 @@ struct dev_config std::string name; // Device name (with optional extra info) std::string dev_name; // Device name (plain, for SMARTD_DEVICE variable) std::string dev_type; // Device type argument from -d directive, empty if none + std::string dev_idinfo; // Device identify info for warning emails std::string state_file; // Path of the persistent state file, empty if none std::string attrlog_file; // Path of the persistent attrlog file, empty if none + bool ignore; // Ignore this entry bool smartcheck; // Check SMART status bool usagefailed; // Check for failed Usage Attributes bool prefail; // Track changes in Prefail Attributes @@ -264,7 +257,7 @@ struct dev_config bool permissive; // Ignore failed SMART commands char autosave; // 1=disable, 2=enable Autosave Attributes char autoofflinetest; // 1=disable, 2=enable Auto Offline Test - unsigned char fix_firmwarebug; // FIX_*, see atacmds.h + firmwarebug_defs firmwarebugs; // -F directives from drivedb or smartd.conf bool ignorepresets; // Ignore database of -v options bool showpresets; // Show database entry for this device bool removable; // Device may disappear (not be present) @@ -282,6 +275,7 @@ struct dev_config bool emailtest; // Send test email? // ATA ONLY + int dev_rpm; // rotation rate, 0 = unknown, 1 = SSD, >1 = HDD int set_aam; // disable(-1), enable(1..255->0..254) Automatic Acoustic Management int set_apm; // disable(-1), enable(2..255->1..254) Advanced Power Management int set_lookahead; // disable(-1), enable(1) read look-ahead @@ -307,6 +301,7 @@ struct dev_config dev_config::dev_config() : lineno(0), + ignore(false), smartcheck(false), usagefailed(false), prefail(false), @@ -319,7 +314,6 @@ dev_config::dev_config() permissive(false), autosave(0), autoofflinetest(0), - fix_firmwarebug(FIX_NOTSPECIFIED), ignorepresets(false), showpresets(false), removable(false), @@ -330,6 +324,7 @@ dev_config::dev_config() tempinfo(0), tempcrit(0), emailfreq(0), emailtest(false), + dev_rpm(0), set_aam(0), set_apm(0), set_lookahead(0), set_standby(0), @@ -388,7 +383,23 @@ struct persistent_dev_state ata_attribute() : id(0), val(0), worst(0), raw(0), resvd(0) { } }; ata_attribute ata_attributes[NUMBER_ATA_SMART_ATTRIBUTES]; + + // SCSI ONLY + struct scsi_error_counter { + struct scsiErrorCounter errCounter; + unsigned char found; + scsi_error_counter() : found(0) { } + }; + scsi_error_counter scsi_error_counters[3]; + + struct scsi_nonmedium_error { + struct scsiNonMediumError nme; + unsigned char found; + scsi_nonmedium_error() : found(0) { } + }; + scsi_nonmedium_error scsi_nonmedium_error; + persistent_dev_state(); }; @@ -423,10 +434,13 @@ struct temp_dev_state // SCSI ONLY unsigned char SmartPageSupported; // has log sense IE page (0x2f) unsigned char TempPageSupported; // has log sense temperature page (0xd) + unsigned char ReadECounterPageSupported; + unsigned char WriteECounterPageSupported; + unsigned char VerifyECounterPageSupported; + unsigned char NonMediumErrorPageSupported; unsigned char SuppressReport; // minimize nuisance reports unsigned char modese_len; // mode sense/select cmd len: 0 (don't // know yet) 6 or 10 - // ATA ONLY uint64_t num_sectors; // Number of sectors ata_smart_values smartval; // SMART data @@ -450,6 +464,10 @@ temp_dev_state::temp_dev_state() powerskipcnt(0), SmartPageSupported(false), TempPageSupported(false), + ReadECounterPageSupported(false), + WriteECounterPageSupported(false), + VerifyECounterPageSupported(false), + NonMediumErrorPageSupported(false), SuppressReport(false), modese_len(0), num_sectors(0), @@ -721,7 +739,7 @@ static bool write_dev_state(const char * path, const p } // Write to the attrlog file -static bool write_dev_attrlog(const char * path, const persistent_dev_state & state) +static bool write_dev_attrlog(const char * path, const dev_state & state) { stdio_file f(path, "a"); if (!f) { @@ -729,20 +747,48 @@ static bool write_dev_attrlog(const char * path, const return false; } - // ATA ONLY + time_t now = time(0); struct tm * tms = gmtime(&now); fprintf(f, "%d-%02d-%02d %02d:%02d:%02d;", 1900+tms->tm_year, 1+tms->tm_mon, tms->tm_mday, tms->tm_hour, tms->tm_min, tms->tm_sec); + // ATA ONLY for (int i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) { const persistent_dev_state::ata_attribute & pa = state.ata_attributes[i]; if (!pa.id) continue; fprintf(f, "\t%d;%d;%"PRIu64";", pa.id, pa.val, pa.raw); } + // SCSI ONLY + const struct scsiErrorCounter * ecp; + const char * pageNames[3] = {"read", "write", "verify"}; + for (int k = 0; k < 3; ++k) { + if ( !state.scsi_error_counters[k].found ) continue; + ecp = &state.scsi_error_counters[k].errCounter; + fprintf(f, "\t%s-corr-by-ecc-fast;%"PRIu64";" + "\t%s-corr-by-ecc-delayed;%"PRIu64";" + "\t%s-corr-by-retry;%"PRIu64";" + "\t%s-total-err-corrected;%"PRIu64";" + "\t%s-corr-algorithm-invocations;%"PRIu64";" + "\t%s-gb-processed;%.3f;" + "\t%s-total-unc-errors;%"PRIu64";", + pageNames[k], ecp->counter[0], + pageNames[k], ecp->counter[1], + pageNames[k], ecp->counter[2], + pageNames[k], ecp->counter[3], + pageNames[k], ecp->counter[4], + pageNames[k], (ecp->counter[5] / 1000000000.0), + pageNames[k], ecp->counter[6]); + } + if(state.scsi_nonmedium_error.found && state.scsi_nonmedium_error.nme.gotPC0) { + fprintf(f, "\tnon-medium-errors;%"PRIu64";", state.scsi_nonmedium_error.nme.counterPC0); + } + // write SCSI current temperature if it is monitored + if(state.TempPageSupported && state.temperature) + fprintf(f, "\ttemperature;%d;", state.temperature); + // end of line fprintf(f, "\n"); - return true; } @@ -849,56 +895,43 @@ static int Goodbye(int status) return status; } -#define ENVLENGTH 1024 - // a replacement for setenv() which is not available on all platforms. // Note that the string passed to putenv must not be freed or made // invalid, since a pointer to it is kept by putenv(). This means that // it must either be a static buffer or allocated off the heap. The -// string can be freed if the environment variable is redefined or -// deleted via another call to putenv(). So we keep these on the stack -// as long as the popen() call is underway. -static int exportenv(char *stackspace, const char *name, const char *value) +// string can be freed if the environment variable is redefined via +// another call to putenv(). There is no portable way to unset a variable +// with putenv(). So we manage the buffer in a static object. +// Using setenv() if available is not considered because some +// implementations may produce memory leaks. + +class env_buffer { - snprintf(stackspace,ENVLENGTH, "%s=%s", name, value); - return putenv(stackspace); -} +public: + env_buffer() + : m_buf((char *)0) { } -static char *dnsdomain(const char *hostname) + void set(const char * name, const char * value); + +private: + char * m_buf; + + env_buffer(const env_buffer &); + void operator=(const env_buffer &); +}; + +void env_buffer::set(const char * name, const char * value) { - char *p = NULL; -#ifdef HAVE_GETADDRINFO - static char canon_name[NI_MAXHOST]; - struct addrinfo *info = NULL; - struct addrinfo hints; - int err; + int size = strlen(name) + 1 + strlen(value) + 1; + char * newbuf = new char[size]; + snprintf(newbuf, size, "%s=%s", name, value); - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_CANONNAME; - if ((err = getaddrinfo(hostname, NULL, &hints, &info)) || (!info)) { - PrintOut(LOG_CRIT, "Error retrieving getaddrinfo(%s): %s\n", hostname, gai_strerror(err)); - return NULL; - } - if (info->ai_canonname) { - strncpy(canon_name, info->ai_canonname, sizeof(canon_name)); - canon_name[NI_MAXHOST - 1] = '\0'; - p = canon_name; - if ((p = strchr(canon_name, '.'))) - p++; - } - freeaddrinfo(info); -#elif HAVE_GETHOSTBYNAME - struct hostent *hp; - if ((hp = gethostbyname(hostname))) { - // Does this work if gethostbyname() returns an IPv6 name in - // colon/dot notation? [BA] - if ((p = strchr(hp->h_name, '.'))) - p++; // skip "." - } -#else - ARGUSED(hostname); -#endif - return p; + if (putenv(newbuf)) + throw std::runtime_error("putenv() failed"); + + // This assumes that the same NAME is passed on each call + delete [] m_buf; + m_buf = newbuf; } #define EBUFLEN 1024 @@ -908,15 +941,9 @@ static void MailWarning(const dev_config & cfg, dev_st // If either address or executable path is non-null then send and log // a warning email, or execute executable -static void MailWarning(const dev_config & cfg, dev_state & state, int which, const char *fmt, ...){ - char command[2048], message[256], hostname[256], domainname[256], additional[256],fullmessage[1024]; - char original[256], further[256], nisdomain[256], subject[256],dates[DATEANDEPOCHLEN]; - char environ_strings[11][ENVLENGTH]; - time_t epoch; - va_list ap; - const int day=24*3600; - int days=0; - const char * const whichfail[]={ +static void MailWarning(const dev_config & cfg, dev_state & state, int which, const char *fmt, ...) +{ + static const char * const whichfail[] = { "EmailTest", // 0 "Health", // 1 "Usage", // 2 @@ -932,8 +959,6 @@ static void MailWarning(const dev_config & cfg, dev_st "Temperature" // 12 }; - const char *unknown="[Unknown]"; - // See if user wants us to send mail if (cfg.emailaddress.empty() && cfg.emailcmdline.empty()) return; @@ -964,15 +989,16 @@ static void MailWarning(const dev_config & cfg, dev_st return; // To decide if to send mail, we need to know what time it is. - epoch=time(NULL); + time_t epoch = time(0); // Return if less than one day has gone by + const int day = 24*3600; if (cfg.emailfreq==2 && mail->logged && epoch<(mail->lastsent+day)) return; // Return if less than 2^(logged-1) days have gone by if (cfg.emailfreq==3 && mail->logged) { - days=0x01<<(mail->logged-1); + int days = 0x01 << (mail->logged - 1); days*=day; if (epoch<(mail->lastsent+days)) return; @@ -990,125 +1016,57 @@ static void MailWarning(const dev_config & cfg, dev_st if (!mail->logged) mail->firstsent=epoch; mail->lastsent=epoch; - - // get system host & domain names (not null terminated if length=MAX) -#ifdef HAVE_GETHOSTNAME - if (gethostname(hostname, 256)) - strcpy(hostname, unknown); - else { - char *p=NULL; - hostname[255]='\0'; - p = dnsdomain(hostname); - if (p && *p) { - strncpy(domainname, p, 255); - domainname[255]='\0'; - } else - strcpy(domainname, unknown); - } -#else - strcpy(hostname, unknown); - strcpy(domainname, unknown); -#endif - -#ifdef HAVE_GETDOMAINNAME - if (getdomainname(nisdomain, 256)) - strcpy(nisdomain, unknown); - else - nisdomain[255]='\0'; -#else - strcpy(nisdomain, unknown); -#endif - + // print warning string into message + char message[256]; + va_list ap; va_start(ap, fmt); - vsnprintf(message, 256, fmt, ap); + vsnprintf(message, sizeof(message), fmt, ap); va_end(ap); - // appropriate message about further information - additional[0]=original[0]=further[0]='\0'; - if (which) { - sprintf(further,"You can also use the smartctl utility for further investigation.\n"); - - switch (cfg.emailfreq) { - case 1: - sprintf(additional,"No additional email messages about this problem will be sent.\n"); - break; - case 2: - sprintf(additional,"Another email message will be sent in 24 hours if the problem persists.\n"); - break; - case 3: - sprintf(additional,"Another email message will be sent in %d days if the problem persists\n", - (0x01)<logged); - break; - } - if (cfg.emailfreq>1 && mail->logged) { - dateandtimezoneepoch(dates, mail->firstsent); - sprintf(original,"The original email about this issue was sent at %s\n", dates); - } - } - - snprintf(subject, 256,"SMART error (%s) detected on host: %s", whichfail[which], hostname); - - // If the user has set cfg.emailcmdline, use that as mailer, else "mail" or "mailx". - if (!*executable) -#ifdef DEFAULT_MAILER - executable = DEFAULT_MAILER ; -#else -#ifndef _WIN32 - executable = "mail"; -#else - executable = "blat"; // http://blat.sourceforge.net/ -#endif -#endif - -#ifndef _WIN32 // blat mailer needs comma // replace commas by spaces to separate recipients std::replace(address.begin(), address.end(), ',', ' '); -#endif + // Export information in environment variables that will be useful // for user scripts - exportenv(environ_strings[0], "SMARTD_MAILER", executable); - exportenv(environ_strings[1], "SMARTD_MESSAGE", message); - exportenv(environ_strings[2], "SMARTD_SUBJECT", subject); + static env_buffer env[12]; + env[0].set("SMARTD_MAILER", executable); + env[1].set("SMARTD_MESSAGE", message); + char dates[DATEANDEPOCHLEN]; + snprintf(dates, sizeof(dates), "%d", mail->logged); + env[2].set("SMARTD_PREVCNT", dates); dateandtimezoneepoch(dates, mail->firstsent); - exportenv(environ_strings[3], "SMARTD_TFIRST", dates); + env[3].set("SMARTD_TFIRST", dates); snprintf(dates, DATEANDEPOCHLEN,"%d", (int)mail->firstsent); - exportenv(environ_strings[4], "SMARTD_TFIRSTEPOCH", dates); - exportenv(environ_strings[5], "SMARTD_FAILTYPE", whichfail[which]); - if (!address.empty()) - exportenv(environ_strings[6], "SMARTD_ADDRESS", address.c_str()); - exportenv(environ_strings[7], "SMARTD_DEVICESTRING", cfg.name.c_str()); + env[4].set("SMARTD_TFIRSTEPOCH", dates); + env[5].set("SMARTD_FAILTYPE", whichfail[which]); + env[6].set("SMARTD_ADDRESS", address.c_str()); + env[7].set("SMARTD_DEVICESTRING", cfg.name.c_str()); // Allow 'smartctl ... -d $SMARTD_DEVICETYPE $SMARTD_DEVICE' - exportenv(environ_strings[8], "SMARTD_DEVICETYPE", - (!cfg.dev_type.empty() ? cfg.dev_type.c_str() : "auto")); - exportenv(environ_strings[9], "SMARTD_DEVICE", cfg.dev_name.c_str()); + env[8].set("SMARTD_DEVICETYPE", + (!cfg.dev_type.empty() ? cfg.dev_type.c_str() : "auto")); + env[9].set("SMARTD_DEVICE", cfg.dev_name.c_str()); - snprintf(fullmessage, 1024, - "This email was generated by the smartd daemon running on:\n\n" - " host name: %s\n" - " DNS domain: %s\n" - " NIS domain: %s\n\n" - "The following warning/error was logged by the smartd daemon:\n\n" - "%s\n\n" - "For details see host's SYSLOG.\n\n" - "%s%s%s", - hostname, domainname, nisdomain, message, further, original, additional); - exportenv(environ_strings[10], "SMARTD_FULLMESSAGE", fullmessage); + env[10].set("SMARTD_DEVICEINFO", cfg.dev_idinfo.c_str()); + dates[0] = 0; + if (which) switch (cfg.emailfreq) { + case 2: dates[0] = '1'; dates[1] = 0; break; + case 3: snprintf(dates, sizeof(dates), "%d", (0x01)<logged); + } + env[11].set("SMARTD_NEXTDAYS", dates); // now construct a command to send this as EMAIL -#ifndef _WIN32 - if (!address.empty()) - snprintf(command, 2048, - "$SMARTD_MAILER -s '%s' %s 2>&1 << \"ENDMAIL\"\n" - "%sENDMAIL\n", subject, address.c_str(), fullmessage); - else - snprintf(command, 2048, "%s 2>&1", executable); - - // tell SYSLOG what we are about to do... + char command[2048]; + if (!*executable) + executable = ""; const char * newadd = (!address.empty()? address.c_str() : ""); const char * newwarn = (which? "Warning via" : "Test of"); +#ifndef _WIN32 + snprintf(command, sizeof(command), "%s 2>&1", warning_script.c_str()); + + // tell SYSLOG what we are about to do... PrintOut(LOG_INFO,"%s %s to %s ...\n", which?"Sending warning via":"Executing test of", executable, newadd); @@ -1182,65 +1140,15 @@ static void MailWarning(const dev_config & cfg, dev_st } #else // _WIN32 + { + snprintf(command, sizeof(command), "cmd /c \"%s\"", warning_script.c_str()); - // No "here-documents" on Windows, so must use separate commandline and stdin - char stdinbuf[1024]; - command[0] = stdinbuf[0] = 0; - int boxtype = -1, boxmsgoffs = 0; - const char * newadd = ""; - if (!address.empty()) { - // address "[sys]msgbox ..." => show warning (also) as [system modal ]messagebox - char addr1[9+1+13] = ""; int n1 = -1, n2 = -1; - if (sscanf(address.c_str(), "%9[a-z]%n,%n", addr1, &n1, &n2) == 1 && (n1 == (int)address.size() || n2 > 0)) { - if (!strcmp(addr1, "msgbox")) - boxtype = 0; - else if (!strcmp(addr1, "sysmsgbox")) - boxtype = 1; - if (boxtype >= 0) - address.erase(0, (n2 > n1 ? n2 : n1)); - } - - if (!address.empty()) { - // Use "blat" parameter syntax (TODO: configure via -M for other mailers) - snprintf(command, sizeof(command), - "%s - -q -subject \"%s\" -to \"%s\"", - executable, subject, address.c_str()); - newadd = address.c_str(); - } - - // Message for mail [0...] and messagebox [boxmsgoffs...] - snprintf(stdinbuf, sizeof(stdinbuf), - "This email was generated by the smartd daemon running on:\n\n" - " host name: %s\n" - " DNS domain: %s\n" -// " NIS domain: %s\n" - "\n", - hostname, /*domainname, */ nisdomain); - boxmsgoffs = strlen(stdinbuf); - snprintf(stdinbuf+boxmsgoffs, sizeof(stdinbuf)-boxmsgoffs, - "The following warning/error was logged by the smartd daemon:\n\n" - "%s\n\n" - "For details see the event log or log file of smartd.\n\n" - "%s%s%s" - "\n", - message, further, original, additional); - } - else - snprintf(command, sizeof(command), "%s", executable); - - const char * newwarn = (which ? "Warning via" : "Test of"); - if (boxtype >= 0) { - // show message box - daemon_messagebox(boxtype, subject, stdinbuf+boxmsgoffs); - PrintOut(LOG_INFO,"%s message box\n", newwarn); - } - if (command[0]) { char stdoutbuf[800]; // < buffer in syslog_win32::vsyslog() int rc; // run command PrintOut(LOG_INFO,"%s %s to %s ...\n", (which?"Sending warning via":"Executing test of"), executable, newadd); - rc = daemon_spawn(command, stdinbuf, strlen(stdinbuf), stdoutbuf, sizeof(stdoutbuf)); + rc = daemon_spawn(command, "", 0, stdoutbuf, sizeof(stdoutbuf)); if (rc >= 0 && stdoutbuf[0]) PrintOut(LOG_CRIT,"%s %s to %s produced unexpected output (%d bytes) to STDOUT/STDERR:\n%s\n", newwarn, executable, newadd, (int)strlen(stdoutbuf), stdoutbuf); @@ -1321,13 +1229,15 @@ void pout(const char *fmt, ...){ // initialize variable argument list va_start(ap,fmt); // in debugmode==1 mode we will print the output from the ataprint.o functions! - if (debugmode && debugmode!=2) + if (debugmode && debugmode != 2) { + FILE * f = stdout; #ifdef _WIN32 - if (facility == LOG_LOCAL1) // logging to stdout - vfprintf(stderr,fmt,ap); - else + if (facility == LOG_LOCAL1) // logging to stdout + f = stderr; #endif - vprintf(fmt,ap); + vfprintf(f, fmt, ap); + fflush(f); + } // in debugmode==2 mode we print output from knowndrives.o functions else if (debugmode==2 || ata_debugmode || scsi_debugmode) { openlog("smartd", LOG_PID, facility); @@ -1335,7 +1245,6 @@ void pout(const char *fmt, ...){ closelog(); } va_end(ap); - fflush(NULL); return; } @@ -1347,13 +1256,15 @@ static void PrintOut(int priority, const char *fmt, .. FixGlibcTimeZoneBug(); // initialize variable argument list va_start(ap,fmt); - if (debugmode) + if (debugmode) { + FILE * f = stdout; #ifdef _WIN32 - if (facility == LOG_LOCAL1) // logging to stdout - vfprintf(stderr,fmt,ap); - else + if (facility == LOG_LOCAL1) // logging to stdout + f = stderr; #endif - vprintf(fmt,ap); + vfprintf(f, fmt, ap); + fflush(f); + } else { openlog("smartd", LOG_PID, facility); vsyslog_lines(priority, fmt, ap); @@ -1506,7 +1417,8 @@ static void Directives() { PrintOut(LOG_INFO, "Configuration file (%s) Directives (after device name):\n" - " -d TYPE Set the device type: %s, auto, removable\n" + " -d TYPE Set the device type: auto, ignore, removable,\n" + " %s\n" " -T TYPE Set the tolerance to one of: normal, permissive\n" " -o VAL Enable/disable automatic offline tests (on/off)\n" " -S VAL Enable/disable attribute autosave (on/off)\n" @@ -1534,14 +1446,16 @@ static void Directives() " -v N,ST Modifies labeling of Attribute N (see man page) \n" " -P TYPE Drive-specific presets: use, ignore, show, showall\n" " -a Default: -H -f -t -l error -l selftest -l selfteststs -C 197 -U 198\n" - " -F TYPE Firmware bug workaround: none, samsung, samsung2, samsung3\n" + " -F TYPE Use firmware bug workaround:\n" + " %s\n" " # Comment: text after a hash sign is ignored\n" " \\ Line continuation character\n" "Attribute ID is a decimal integer 1 <= ID <= 255\n" - "Use ID = 0 to turn off -C and/or -U Directives\n" - "Example: /dev/hda -a\n", - configfile, smi()->get_valid_dev_types_str().c_str()); - return; + "Use ID = 0 to turn off -C and/or -U Directives\n" + "Example: /dev/sda -a\n", + configfile, + smi()->get_valid_dev_types_str().c_str(), + get_valid_firmwarebug_args()); } /* Returns a pointer to a static string containing a formatted list of the valid @@ -1562,6 +1476,7 @@ static const char *GetValidArgList(char opt) return "ioctl[,N], ataioctl[,N], scsiioctl[,N]"; case 'B': case 'p': + case 'w': return ""; case 'i': return ""; @@ -1626,6 +1541,13 @@ static void Usage() PrintOut(LOG_INFO," [default is "SMARTMONTOOLS_SAVESTATES"MODEL-SERIAL.TYPE.state]\n"); #endif PrintOut(LOG_INFO,"\n"); + PrintOut(LOG_INFO," -w NAME, --warnexec=NAME\n"); + PrintOut(LOG_INFO," Run executable NAME on warnings\n"); +#ifndef _WIN32 + PrintOut(LOG_INFO," [default is "SMARTMONTOOLS_SYSCONFDIR"/smartd_warning.sh]\n\n"); +#else + PrintOut(LOG_INFO," [default is %s/smartd_warning.cmd]\n\n", get_exe_dir().c_str()); +#endif #ifdef _WIN32 PrintOut(LOG_INFO," --service\n"); PrintOut(LOG_INFO," Running as windows service (see man page), install with:\n"); @@ -1658,11 +1580,11 @@ static bool not_allowed_in_filename(char c) // Read error count from Summary or Extended Comprehensive SMART error log // Return -1 on error static int read_ata_error_count(ata_device * device, const char * name, - unsigned char fix_firmwarebug, bool extended) + firmwarebug_defs firmwarebugs, bool extended) { if (!extended) { ata_smart_errorlog log; - if (ataReadErrorLog(device, &log, fix_firmwarebug)){ + if (ataReadErrorLog(device, &log, firmwarebugs)){ PrintOut(LOG_INFO,"Device: %s, Read Summary SMART Error Log failed\n",name); return -1; } @@ -1670,7 +1592,7 @@ static int read_ata_error_count(ata_device * device, c } else { ata_smart_exterrlog logx; - if (!ataReadExtErrorLog(device, &logx, 1 /*first sector only*/)) { + if (!ataReadExtErrorLog(device, &logx, 1 /*first sector only*/, firmwarebugs)) { PrintOut(LOG_INFO,"Device: %s, Read Extended Comprehensive SMART Error Log failed\n",name); return -1; } @@ -1682,17 +1604,17 @@ static int read_ata_error_count(ata_device * device, c // returns <0 if problem. Otherwise, bottom 8 bits are the self test // error count, and top bits are the power-on hours of the last error. static int SelfTestErrorCount(ata_device * device, const char * name, - unsigned char fix_firmwarebug) + firmwarebug_defs firmwarebugs) { struct ata_smart_selftestlog log; - if (ataReadSelfTestLog(device, &log, fix_firmwarebug)){ + if (ataReadSelfTestLog(device, &log, firmwarebugs)){ PrintOut(LOG_INFO,"Device: %s, Read SMART Self Test Log Failed\n",name); return -1; } // return current number of self-test errors - return ataPrintSmartSelfTestlog(&log, false, fix_firmwarebug); + return ataPrintSmartSelfTestlog(&log, false, firmwarebugs); } #define SELFTEST_ERRORCOUNT(x) (x & 0xff) @@ -1845,7 +1767,7 @@ static int ATADeviceScan(dev_config & cfg, dev_state & return 2; } - // Log drive identity and size + // Get drive identity, size and rotation rate (HDD/SSD) char model[40+1], serial[20+1], firmware[8+1]; ata_format_id_string(model, drive.model, sizeof(model)-1); ata_format_id_string(serial, drive.serial_no, sizeof(serial)-1); @@ -1854,6 +1776,7 @@ static int ATADeviceScan(dev_config & cfg, dev_state & ata_size_info sizes; ata_get_size_info(&drive, sizes); state.num_sectors = sizes.sectors; + cfg.dev_rpm = ata_get_rotation_rate(&drive); char wwn[30]; wwn[0] = 0; unsigned oui = 0; uint64_t unique_id = 0; @@ -1861,11 +1784,13 @@ static int ATADeviceScan(dev_config & cfg, dev_state & if (naa >= 0) snprintf(wwn, sizeof(wwn), "WWN:%x-%06x-%09"PRIx64", ", naa, oui, unique_id); + // Format device id string for warning emails char cap[32]; - PrintOut(LOG_INFO, "Device: %s, %s, S/N:%s, %sFW:%s, %s\n", name, - model, serial, wwn, firmware, - format_capacity(cap, sizeof(cap), sizes.capacity, ".")); + cfg.dev_idinfo = strprintf("%s, S/N:%s, %sFW:%s, %s", model, serial, wwn, firmware, + format_capacity(cap, sizeof(cap), sizes.capacity, ".")); + PrintOut(LOG_INFO, "Device: %s, %s\n", name, cfg.dev_idinfo.c_str()); + // Show if device in database, and use preset vendor attribute // options unless user has requested otherwise. if (cfg.ignorepresets) @@ -1873,7 +1798,7 @@ static int ATADeviceScan(dev_config & cfg, dev_state & else { // Apply vendor specific presets, print warning if present const drive_settings * dbentry = lookup_drive_apply_presets( - &drive, cfg.attribute_defs, cfg.fix_firmwarebug); + &drive, cfg.attribute_defs, cfg.firmwarebugs); if (!dbentry) PrintOut(LOG_INFO, "Device: %s, not found in smartd database.\n", name); else { @@ -1997,7 +1922,8 @@ static int ATADeviceScan(dev_config & cfg, dev_state & if ( (cfg.tempdiff || cfg.tempinfo || cfg.tempcrit) && !ata_return_temperature_value(&state.smartval, cfg.attribute_defs)) { - PrintOut(LOG_CRIT, "Device: %s, can't monitor Temperature, ignoring -W Directive\n", name); + PrintOut(LOG_INFO, "Device: %s, can't monitor Temperature, ignoring -W %d,%d,%d\n", + name, cfg.tempdiff, cfg.tempinfo, cfg.tempcrit); cfg.tempdiff = cfg.tempinfo = cfg.tempcrit = 0; } @@ -2044,12 +1970,13 @@ static int ATADeviceScan(dev_config & cfg, dev_state & bool smart_logdir_ok = false, gp_logdir_ok = false; if ( isGeneralPurposeLoggingCapable(&drive) - && (cfg.errorlog || cfg.selftest) ) { + && (cfg.errorlog || cfg.selftest) + && !cfg.firmwarebugs.is_set(BUG_NOLOGDIR)) { if (!ataReadLogDirectory(atadev, &smart_logdir, false)) smart_logdir_ok = true; } - if (cfg.xerrorlog) { + if (cfg.xerrorlog && !cfg.firmwarebugs.is_set(BUG_NOLOGDIR)) { if (!ataReadLogDirectory(atadev, &gp_logdir, true)) gp_logdir_ok = true; } @@ -2064,7 +1991,7 @@ static int ATADeviceScan(dev_config & cfg, dev_state & PrintOut(LOG_INFO, "Device: %s, no SMART Self-test Log, ignoring -l selftest (override with -T permissive)\n", name); cfg.selftest = false; } - else if ((retval = SelfTestErrorCount(atadev, name, cfg.fix_firmwarebug)) < 0) { + else if ((retval = SelfTestErrorCount(atadev, name, cfg.firmwarebugs)) < 0) { PrintOut(LOG_INFO, "Device: %s, no SMART Self-test Log, ignoring -l selftest\n", name); cfg.selftest = false; } @@ -2084,7 +2011,7 @@ static int ATADeviceScan(dev_config & cfg, dev_state & PrintOut(LOG_INFO, "Device: %s, no SMART Error Log, ignoring -l error (override with -T permissive)\n", name); cfg.errorlog = false; } - else if ((errcnt1 = read_ata_error_count(atadev, name, cfg.fix_firmwarebug, false)) < 0) { + else if ((errcnt1 = read_ata_error_count(atadev, name, cfg.firmwarebugs, false)) < 0) { PrintOut(LOG_INFO, "Device: %s, no SMART Error Log, ignoring -l error\n", name); cfg.errorlog = false; } @@ -2094,12 +2021,13 @@ static int ATADeviceScan(dev_config & cfg, dev_state & if (cfg.xerrorlog) { int errcnt2; - if (!(cfg.permissive || (gp_logdir_ok && gp_logdir.entry[0x03-1].numsectors))) { + if (!( cfg.permissive || cfg.firmwarebugs.is_set(BUG_NOLOGDIR) + || (gp_logdir_ok && gp_logdir.entry[0x03-1].numsectors) )) { PrintOut(LOG_INFO, "Device: %s, no Extended Comprehensive SMART Error Log, ignoring -l xerror (override with -T permissive)\n", name); cfg.xerrorlog = false; } - else if ((errcnt2 = read_ata_error_count(atadev, name, cfg.fix_firmwarebug, true)) < 0) { + else if ((errcnt2 = read_ata_error_count(atadev, name, cfg.firmwarebugs, true)) < 0) { PrintOut(LOG_INFO, "Device: %s, no Extended Comprehensive SMART Error Log, ignoring -l xerror\n", name); cfg.xerrorlog = false; } @@ -2235,7 +2163,7 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state UINT8 tBuf[64]; UINT8 inqBuf[96]; UINT8 vpdBuf[252]; - char lu_id[64]; + char lu_id[64], serial[256], vendor[40], model[40]; // Device must be open memset(inqBuf, 0, 96); @@ -2249,7 +2177,8 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state return 2; } } - version = inqBuf[2]; + version = (inqBuf[2] & 0x7f); /* Accept old ISO/IEC 9316:1995 variants */ + avail_len = inqBuf[4] + 5; len = (avail_len < req_len) ? avail_len : req_len; if (len < 36) { @@ -2266,29 +2195,51 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state "skip\n", device, pdt); return 2; } + + if (supported_vpd_pages_p) { + delete supported_vpd_pages_p; + supported_vpd_pages_p = NULL; + } + supported_vpd_pages_p = new supported_vpd_pages(scsidev); + lu_id[0] = '\0'; - if ((version >= 0x4) && (version < 0x8)) { - /* SPC-2 to SPC-5 */ - if (0 == (err = scsiInquiryVpd(scsidev, 0x83, vpdBuf, sizeof(vpdBuf)))) { + if ((version >= 0x3) && (version < 0x8)) { + /* SPC to SPC-5 */ + if (0 == scsiInquiryVpd(scsidev, SCSI_VPD_DEVICE_IDENTIFICATION, + vpdBuf, sizeof(vpdBuf))) { len = vpdBuf[3]; scsi_decode_lu_dev_id(vpdBuf + 4, len, lu_id, sizeof(lu_id), NULL); } - } + } + serial[0] = '\0'; + if (0 == scsiInquiryVpd(scsidev, SCSI_VPD_UNIT_SERIAL_NUMBER, + vpdBuf, sizeof(vpdBuf))) { + len = vpdBuf[3]; + vpdBuf[4 + len] = '\0'; + scsi_format_id_string(serial, (const unsigned char *)&vpdBuf[4], len); + } unsigned int lb_size; char si_str[64]; - uint64_t capacity = scsiGetSize(scsidev, &lb_size); + uint64_t capacity = scsiGetSize(scsidev, &lb_size, NULL); if (capacity) format_capacity(si_str, sizeof(si_str), capacity); else si_str[0] = '\0'; - PrintOut(LOG_INFO, "Device: %s, [%.8s %.16s %.4s]%s%s%s%s\n", - device, (char *)&inqBuf[8], (char *)&inqBuf[16], - (char *)&inqBuf[32], - (lu_id[0] ? ", lu id: " : ""), (lu_id[0] ? lu_id : ""), - (si_str[0] ? ", " : ""), (si_str[0] ? si_str : "")); + // Format device id string for warning emails + cfg.dev_idinfo = strprintf("[%.8s %.16s %.4s]%s%s%s%s%s%s", + (char *)&inqBuf[8], (char *)&inqBuf[16], (char *)&inqBuf[32], + (lu_id[0] ? ", lu id: " : ""), (lu_id[0] ? lu_id : ""), + (serial[0] ? ", S/N: " : ""), (serial[0] ? serial : ""), + (si_str[0] ? ", " : ""), (si_str[0] ? si_str : "")); + + // format "model" string + scsi_format_id_string(vendor, (const unsigned char *)&inqBuf[8], 8); + scsi_format_id_string(model, (const unsigned char *)&inqBuf[16], 16); + PrintOut(LOG_INFO, "Device: %s, %s\n", device, cfg.dev_idinfo.c_str()); + // check that device is ready for commands. IE stores its stuff on // the media. if ((err = scsiTestUnitReady(scsidev))) { @@ -2343,6 +2294,18 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state case IE_LPAGE: state.SmartPageSupported = 1; break; + case READ_ERROR_COUNTER_LPAGE: + state.ReadECounterPageSupported = 1; + break; + case WRITE_ERROR_COUNTER_LPAGE: + state.WriteECounterPageSupported = 1; + break; + case VERIFY_ERROR_COUNTER_LPAGE: + state.VerifyECounterPageSupported = 1; + break; + case NON_MEDIUM_ERROR_LPAGE: + state.NonMediumErrorPageSupported = 1; + break; default: break; } @@ -2361,7 +2324,8 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state PrintOut(LOG_INFO, "Device: %s, unexpectedly failed to read SMART values\n", device); state.SuppressReport = 1; if (cfg.tempdiff || cfg.tempinfo || cfg.tempcrit) { - PrintOut(LOG_CRIT, "Device: %s, can't monitor Temperature, ignoring -W Directive\n", device); + PrintOut(LOG_INFO, "Device: %s, can't monitor Temperature, ignoring -W %d,%d,%d\n", + device, cfg.tempdiff, cfg.tempinfo, cfg.tempcrit); cfg.tempdiff = cfg.tempinfo = cfg.tempcrit = 0; } } @@ -2403,21 +2367,29 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state // tell user we are registering device PrintOut(LOG_INFO, "Device: %s, is SMART capable. Adding to \"monitor\" list.\n", device); - // TODO: Build file name for state file - if (!state_path_prefix.empty()) { - PrintOut(LOG_INFO, "Device: %s, persistence not yet supported for SCSI; ignoring -s option.\n", device); - } - // TODO: Build file name for attribute log file - if (!attrlog_path_prefix.empty()) { - PrintOut(LOG_INFO, "Device: %s, attribute log not yet supported for SCSI; ignoring -A option.\n", device); - } - // Make sure that init_standby_check() ignores SCSI devices cfg.offlinests_ns = cfg.selfteststs_ns = false; // close file descriptor CloseDevice(scsidev, device); + if (!state_path_prefix.empty() || !attrlog_path_prefix.empty()) { + // Build file name for state file + std::replace_if(model, model+strlen(model), not_allowed_in_filename, '_'); + std::replace_if(serial, serial+strlen(serial), not_allowed_in_filename, '_'); + if (!state_path_prefix.empty()) { + cfg.state_file = strprintf("%s%s-%s-%s.scsi.state", state_path_prefix.c_str(), vendor, model, serial); + // Read previous state + if (read_dev_state(cfg.state_file.c_str(), state)) { + PrintOut(LOG_INFO, "Device: %s, state read from %s\n", device, cfg.state_file.c_str()); + // Copy ATA attribute values to temp state + state.update_temp_state(); + } + } + if (!attrlog_path_prefix.empty()) + cfg.attrlog_file = strprintf("%s%s-%s-%s.scsi.csv", attrlog_path_prefix.c_str(), vendor, model, serial); + } + finish_device_scan(cfg, state); return 0; @@ -2461,7 +2433,7 @@ static void CheckSelfTestLogs(const dev_config & cfg, // new failure. PrintOut(LOG_CRIT, "Device: %s, new Self-Test Log error at hour timestamp %d\n", name, newh); - MailWarning(cfg, state, 3, "Device: %s, new Self-Test Log error at hour timestamp %d\n", + MailWarning(cfg, state, 3, "Device: %s, new Self-Test Log error at hour timestamp %d", name, newh); state.must_write = true; } @@ -2755,7 +2727,7 @@ static int DoATASelfTest(const dev_config & cfg, dev_s // If currently running a self-test, do not interrupt it to start another. if (15==(data.self_test_exec_status >> 4)) { - if (cfg.fix_firmwarebug == FIX_SAMSUNG3 && data.self_test_exec_status == 0xf0) { + if (cfg.firmwarebugs.is_set(BUG_SAMSUNG3) && data.self_test_exec_status == 0xf0) { PrintOut(LOG_INFO, "Device: %s, will not skip scheduled %sTest " "despite unclear Self-Test byte (SAMSUNG Firmware bug).\n", name, testname); } else { @@ -2834,17 +2806,16 @@ static void check_pending(const dev_config & cfg, dev_ s += strprintf(" (changed %+"PRId64")", rawval - prev_rawval); PrintOut(LOG_CRIT, "%s\n", s.c_str()); - MailWarning(cfg, state, mailtype, "%s\n", s.c_str()); + MailWarning(cfg, state, mailtype, "%s", s.c_str()); state.must_write = true; } // Format Temperature value -static const char * fmt_temp(unsigned char x, char * buf) +static const char * fmt_temp(unsigned char x, char (& buf)[20]) { if (!x) // unset - strcpy(buf, "??"); - else - sprintf(buf, "%u", x); + return "??"; + snprintf(buf, sizeof(buf), "%u", x); return buf; } @@ -2908,7 +2879,7 @@ static void CheckTemperature(const dev_config & cfg, d if (cfg.tempcrit && currtemp >= cfg.tempcrit) { PrintOut(LOG_CRIT, "Device: %s, Temperature %u Celsius reached critical limit of %u Celsius (Min/Max %s%s/%u%s)\n", cfg.name.c_str(), currtemp, cfg.tempcrit, fmt_temp(state.tempmin, buf), minchg, state.tempmax, maxchg); - MailWarning(cfg, state, 12, "Device: %s, Temperature %d Celsius reached critical limit of %u Celsius (Min/Max %s%s/%u%s)\n", + MailWarning(cfg, state, 12, "Device: %s, Temperature %d Celsius reached critical limit of %u Celsius (Min/Max %s%s/%u%s)", cfg.name.c_str(), currtemp, cfg.tempcrit, fmt_temp(state.tempmin, buf), minchg, state.tempmax, maxchg); } else if (cfg.tempinfo && currtemp >= cfg.tempinfo) { @@ -2937,7 +2908,7 @@ static void check_attribute(const dev_config & cfg, de // If requested, check for usage attributes that have failed. if ( cfg.usagefailed && attrstate == ATTRSTATE_FAILED_NOW && !cfg.monitor_attr_flags.is_set(attr.id, MONITOR_IGN_FAILUSE)) { - std::string attrname = ata_get_smart_attr_name(attr.id, cfg.attribute_defs); + std::string attrname = ata_get_smart_attr_name(attr.id, cfg.attribute_defs, cfg.dev_rpm); PrintOut(LOG_CRIT, "Device: %s, Failed SMART usage Attribute: %d %s.\n", cfg.name.c_str(), attr.id, attrname.c_str()); MailWarning(cfg, state, 2, "Device: %s, Failed SMART usage Attribute: %d %s.", cfg.name.c_str(), attr.id, attrname.c_str()); state.must_write = true; @@ -3004,7 +2975,7 @@ static void check_attribute(const dev_config & cfg, de // Format message std::string msg = strprintf("Device: %s, SMART %s Attribute: %d %s changed from %s to %s", cfg.name.c_str(), (prefail ? "Prefailure" : "Usage"), attr.id, - ata_get_smart_attr_name(attr.id, cfg.attribute_defs).c_str(), + ata_get_smart_attr_name(attr.id, cfg.attribute_defs, cfg.dev_rpm).c_str(), prevstr.c_str(), currstr.c_str()); // Report this change as critical ? @@ -3195,16 +3166,16 @@ static int ATACheckDevice(const dev_config & cfg, dev_ // check if number of selftest errors has increased (note: may also DECREASE) if (cfg.selftest) - CheckSelfTestLogs(cfg, state, SelfTestErrorCount(atadev, name, cfg.fix_firmwarebug)); + CheckSelfTestLogs(cfg, state, SelfTestErrorCount(atadev, name, cfg.firmwarebugs)); // check if number of ATA errors has increased if (cfg.errorlog || cfg.xerrorlog) { int errcnt1 = -1, errcnt2 = -1; if (cfg.errorlog) - errcnt1 = read_ata_error_count(atadev, name, cfg.fix_firmwarebug, false); + errcnt1 = read_ata_error_count(atadev, name, cfg.firmwarebugs, false); if (cfg.xerrorlog) - errcnt2 = read_ata_error_count(atadev, name, cfg.fix_firmwarebug, true); + errcnt2 = read_ata_error_count(atadev, name, cfg.firmwarebugs, true); // new number of errors is max of both logs int newc = (errcnt1 >= errcnt2 ? errcnt1 : errcnt2); @@ -3251,6 +3222,7 @@ static int SCSICheckDevice(const dev_config & cfg, dev UINT8 asc, ascq; UINT8 currenttemp; UINT8 triptemp; + UINT8 tBuf[252]; const char * name = cfg.name.c_str(); const char *cp; @@ -3266,6 +3238,7 @@ static int SCSICheckDevice(const dev_config & cfg, dev return 1; } else if (debugmode) PrintOut(LOG_INFO,"Device: %s, opened SCSI device\n", name); + reset_warning_mail(cfg, state, 9, "open device worked again"); currenttemp = 0; asc = 0; ascq = 0; @@ -3283,6 +3256,8 @@ static int SCSICheckDevice(const dev_config & cfg, dev if (cp) { PrintOut(LOG_CRIT, "Device: %s, SMART Failure: %s\n", name, cp); MailWarning(cfg, state, 1,"Device: %s, SMART Failure: %s", name, cp); + } else if (asc == 4 && ascq == 9) { + PrintOut(LOG_INFO,"Device: %s, self-test in progress\n", name); } else if (debugmode) PrintOut(LOG_INFO,"Device: %s, non-SMART asc,ascq: %d,%d\n", name, (int)asc, (int)ascq); @@ -3290,7 +3265,7 @@ static int SCSICheckDevice(const dev_config & cfg, dev PrintOut(LOG_INFO,"Device: %s, SMART health: passed\n", name); // check temperature limits - if (cfg.tempdiff || cfg.tempinfo || cfg.tempcrit) + if (cfg.tempdiff || cfg.tempinfo || cfg.tempcrit || !cfg.attrlog_file.empty()) CheckTemperature(cfg, state, currenttemp, triptemp); // check if number of selftest errors has increased (note: may also DECREASE) @@ -3302,6 +3277,29 @@ static int SCSICheckDevice(const dev_config & cfg, dev if (testtype) DoSCSISelfTest(cfg, state, scsidev, testtype); } + if (!cfg.attrlog_file.empty()){ + // saving error counters to state + if (state.ReadECounterPageSupported && (0 == scsiLogSense(scsidev, + READ_ERROR_COUNTER_LPAGE, 0, tBuf, sizeof(tBuf), 0))) { + scsiDecodeErrCounterPage(tBuf, &state.scsi_error_counters[0].errCounter); + state.scsi_error_counters[0].found=1; + } + if (state.WriteECounterPageSupported && (0 == scsiLogSense(scsidev, + WRITE_ERROR_COUNTER_LPAGE, 0, tBuf, sizeof(tBuf), 0))) { + scsiDecodeErrCounterPage(tBuf, &state.scsi_error_counters[1].errCounter); + state.scsi_error_counters[1].found=1; + } + if (state.VerifyECounterPageSupported && (0 == scsiLogSense(scsidev, + VERIFY_ERROR_COUNTER_LPAGE, 0, tBuf, sizeof(tBuf), 0))) { + scsiDecodeErrCounterPage(tBuf, &state.scsi_error_counters[2].errCounter); + state.scsi_error_counters[2].found=1; + } + if (state.NonMediumErrorPageSupported && (0 == scsiLogSense(scsidev, + NON_MEDIUM_ERROR_LPAGE, 0, tBuf, sizeof(tBuf), 0))) { + scsiDecodeNonMediumErrPage(tBuf, &state.scsi_nonmedium_error.nme); + state.scsi_nonmedium_error.found=1; + } + } CloseDevice(scsidev, name); return 0; } @@ -3561,7 +3559,8 @@ static void printoutvaliddirectiveargs(int priority, c PrintOut(priority, "use, ignore, show, showall"); break; case 'F': - PrintOut(priority, "none, samsung, samsung2, samsung3"); + PrintOut(priority, "%s", get_valid_firmwarebug_args()); + break; case 'e': PrintOut(priority, "aam,[N|off], apm,[N|off], lookahead,[on|off], " "security-freeze, standby,[N|off], wcache,[on|off]"); @@ -3627,6 +3626,35 @@ static int Get3Integers(const char *arg, const char *n } +#ifdef _WIN32 + +// Concatenate strtok() results if quoted with "..." +static const char * strtok_dequote(const char * delimiters) +{ + const char * t = strtok(0, delimiters); + if (!t || t[0] != '"') + return t; + + static std::string token; + token = t+1; + for (;;) { + t = strtok(0, delimiters); + if (!t || !*t) + return "\""; + token += ' '; + int len = strlen(t); + if (t[len-1] == '"') { + token += std::string(t, len-1); + break; + } + token += t; + } + return token.c_str(); +} + +#endif // _WIN32 + + // This function returns 1 if it has correctly parsed one token (and // any arguments), else zero if no tokens remain. It returns -1 if an // error was encountered. @@ -3696,6 +3724,8 @@ static int ParseToken(char * token, dev_config & cfg) // specify the device type if ((arg = strtok(NULL, delim)) == NULL) { missingarg = 1; + } else if (!strcmp(arg, "ignore")) { + cfg.ignore = true; } else if (!strcmp(arg, "removable")) { cfg.removable = true; } else if (!strcmp(arg, "auto")) { @@ -3706,19 +3736,10 @@ static int ParseToken(char * token, dev_config & cfg) break; case 'F': // fix firmware bug - if ((arg = strtok(NULL, delim)) == NULL) { + if (!(arg = strtok(0, delim))) missingarg = 1; - } else if (!strcmp(arg, "none")) { - cfg.fix_firmwarebug = FIX_NONE; - } else if (!strcmp(arg, "samsung")) { - cfg.fix_firmwarebug = FIX_SAMSUNG; - } else if (!strcmp(arg, "samsung2")) { - cfg.fix_firmwarebug = FIX_SAMSUNG2; - } else if (!strcmp(arg, "samsung3")) { - cfg.fix_firmwarebug = FIX_SAMSUNG3; - } else { + else if (!parse_firmwarebug_def(arg, cfg.firmwarebugs)) badarg = 1; - } break; case 'H': // check SMART status @@ -3894,6 +3915,18 @@ static int ParseToken(char * token, dev_config & cfg) if (!cfg.emailaddress.empty()) PrintOut(LOG_INFO, "File %s line %d (drive %s): ignoring previous Address Directive -m %s\n", configfile, lineno, name, cfg.emailaddress.c_str()); +#ifdef _WIN32 + if ( !strcmp(arg, "msgbox") || !strcmp(arg, "sysmsgbox") + || str_starts_with(arg, "msgbox,") || str_starts_with(arg, "sysmsgbox,")) { + cfg.emailaddress = "console"; + const char * arg2 = strchr(arg, ','); + if (arg2) + cfg.emailaddress += arg2; + PrintOut(LOG_INFO, "File %s line %d (drive %s): Deprecated -m %s changed to -m %s\n", + configfile, lineno, name, arg, cfg.emailaddress.c_str()); + } + else +#endif cfg.emailaddress = arg; } break; @@ -3911,7 +3944,18 @@ static int ParseToken(char * token, dev_config & cfg) cfg.emailtest = 1; else if (!strcmp(arg, "exec")) { // Get the next argument (the command line) - if (!(arg = strtok(NULL, delim))) { +#ifdef _WIN32 + // Allow "/path name/with spaces/..." on Windows + arg = strtok_dequote(delim); + if (arg && arg[0] == '"') { + PrintOut(LOG_CRIT, "File %s line %d (drive %s): Directive %s 'exec' argument: missing closing quote\n", + configfile, lineno, name, token); + return -1; + } +#else + arg = strtok(0, delim); +#endif + if (!arg) { PrintOut(LOG_CRIT, "File %s line %d (drive %s): Directive %s 'exec' argument must be followed by executable path.\n", configfile, lineno, name, token); return -1; @@ -4166,7 +4210,7 @@ static int ParseConfigLine(dev_config_vector & conf_en cfg.name.c_str(), cfg.lineno, configfile); return -2; } - // From here on the sign of is address.empty() and !cfg.emailcmdline.empty() + // From here on the sign of is cfg.emailaddress.empty() and !cfg.emailcmdline.empty() cfg.emailaddress.clear(); } @@ -4289,7 +4333,7 @@ static int ParseConfigFile(dev_config_vector & conf_en } // copy string so far into fullline, and increment length - strcpy(fullline+cont,line); + snprintf(fullline+cont, sizeof(fullline)-cont, "%s" ,line); cont+=len; // is this a continuation line. If so, replace \ by space and look at next line @@ -4350,16 +4394,19 @@ static void check_abs_path(char option, const std::str // version/license/copyright messages static void ParseOpts(int argc, char **argv) { - // Init default configfile path + // Init default path names #ifndef _WIN32 configfile = SMARTMONTOOLS_SYSCONFDIR"/smartd.conf"; + warning_script = SMARTMONTOOLS_SYSCONFDIR"/smartd_warning.sh"; #else - static std::string configfile_str = get_exe_dir() + "/smartd.conf"; + std::string exedir = get_exe_dir(); + static std::string configfile_str = exedir + "/smartd.conf"; configfile = configfile_str.c_str(); + warning_script = exedir + "/smartd_warning.cmd"; #endif // Please update GetValidArgList() if you edit shortopts - static const char shortopts[] = "c:l:q:dDni:p:r:s:A:B:Vh?" + static const char shortopts[] = "c:l:q:dDni:p:r:s:A:B:w:Vh?" #ifdef HAVE_LIBCAP_NG "C" #endif @@ -4382,6 +4429,7 @@ static void ParseOpts(int argc, char **argv) { "savestates", required_argument, 0, 's' }, { "attributelog", required_argument, 0, 'A' }, { "drivedb", required_argument, 0, 'B' }, + { "warnexec", required_argument, 0, 'w' }, { "version", no_argument, 0, 'V' }, { "license", no_argument, 0, 'V' }, { "copyright", no_argument, 0, 'V' }, @@ -4543,6 +4591,9 @@ static void ParseOpts(int argc, char **argv) debugmode = savedebug; } break; + case 'w': + warning_script = optarg; + break; case 'V': // print version and CVS info debugmode = 1; @@ -4744,7 +4795,44 @@ static int ReadOrMakeConfigEntries(dev_config_vector & return conf_entries.size(); } +// Return true if TYPE contains a RAID drive number +static bool is_raid_type(const char * type) +{ + if (str_starts_with(type, "sat,")) + return false; + int i; + if (sscanf(type, "%*[^,],%d", &i) != 1) + return false; + return true; +} +// Return true if DEV is already in DEVICES[0..NUMDEVS) or IGNORED[*] +static bool is_duplicate_device(const smart_device * dev, + const smart_device_list & devices, unsigned numdevs, + const dev_config_vector & ignored) +{ + const smart_device::device_info & info1 = dev->get_info(); + bool is_raid1 = is_raid_type(info1.dev_type.c_str()); + + for (unsigned i = 0; i < numdevs; i++) { + const smart_device::device_info & info2 = devices.at(i)->get_info(); + // -d TYPE options must match if RAID drive number is specified + if ( info1.dev_name == info2.dev_name + && ( info1.dev_type == info2.dev_type + || !is_raid1 || !is_raid_type(info2.dev_type.c_str()))) + return true; + } + + for (unsigned i = 0; i < ignored.size(); i++) { + const dev_config & cfg2 = ignored.at(i); + if ( info1.dev_name == cfg2.dev_name + && ( info1.dev_type == cfg2.dev_type + || !is_raid1 || !is_raid_type(cfg2.dev_type.c_str()))) + return true; + } + return false; +} + // This function tries devices from conf_entries. Each one that can be // registered is moved onto the [ata|scsi]devices lists and removed // from the conf_entries list. @@ -4757,10 +4845,22 @@ static void RegisterDevices(const dev_config_vector & states.clear(); // Register entries + dev_config_vector ignored_entries; + unsigned numnoscan = 0; for (unsigned i = 0; i < conf_entries.size(); i++){ dev_config cfg = conf_entries[i]; + if (cfg.ignore) { + // Store for is_duplicate_device() check and ignore + PrintOut(LOG_INFO, "Device: %s%s%s%s, ignored\n", cfg.name.c_str(), + (!cfg.dev_type.empty() ? " [" : ""), + cfg.dev_type.c_str(), + (!cfg.dev_type.empty() ? "]" : "")); + ignored_entries.push_back(cfg); + continue; + } + // get device of appropriate type smart_device_auto_ptr dev; bool scanning = false; @@ -4768,8 +4868,15 @@ static void RegisterDevices(const dev_config_vector & // Device may already be detected during devicescan if (i < scanned_devs.size()) { dev = scanned_devs.release(i); - if (dev) + if (dev) { + // Check for a preceding non-DEVICESCAN entry for the same device + if ( (numnoscan || !ignored_entries.empty()) + && is_duplicate_device(dev.get(), devices, numnoscan, ignored_entries)) { + PrintOut(LOG_INFO, "Device: %s, duplicate, ignored\n", dev->get_info_name()); + continue; + } scanning = true; + } } if (!dev) { @@ -4834,6 +4941,8 @@ static void RegisterDevices(const dev_config_vector & configs.push_back(cfg); states.push_back(state); devices.push_back(dev); + if (!scanning) + numnoscan = devices.size(); } // if device is explictly listed and we can't register it, then // exit unless the user has specified that the device is removable