diff --git a/smartmontools/CHANGELOG b/smartmontools/CHANGELOG index 4e77d866a4904fe3cd984c8b973c32e68cbc6286..475cf28aea4577c95f6e6ef76b6d3ea9788be501 100644 --- a/smartmontools/CHANGELOG +++ b/smartmontools/CHANGELOG @@ -42,6 +42,12 @@ NOTES FOR FUTURE RELEASES: see TODO file. <DEVELOPERS: ADDITIONS TO THE CHANGE LOG GO JUST BELOW HERE, PLEASE> + [MS] Add experimental feature to log attribute values at each check + cycle (ATA only), activated with the smartd option + "-A PREFIX" / "--attributelog=PREFIX". + Introduce configure options "--enable-attributelog" and + "--with-attributelog=PREFIX" to enable feature by default. + [DG] [SAT] Heads up about a non backwardly compatible change introduced in draft SAT-2 (sat2r8b.pdf) that will break our existing SAT processing code. Action needed if change stands. diff --git a/smartmontools/Makefile.am b/smartmontools/Makefile.am index ab5a199f0473c60af7f438a6d3ce9355cbe3131a..3b4f484088646b493f1fdb354f2c53b190f4fcb8 100644 --- a/smartmontools/Makefile.am +++ b/smartmontools/Makefile.am @@ -18,6 +18,9 @@ endif if ENABLE_SAVESTATES AM_CPPFLAGS += -DSMARTMONTOOLS_SAVESTATES='"$(savestates)"' endif +if ENABLE_ATTRIBUTELOG +AM_CPPFLAGS += -DSMARTMONTOOLS_ATTRIBUTELOG='"$(attributelog)"' +endif sbin_PROGRAMS = smartd \ smartctl @@ -312,6 +315,11 @@ if ENABLE_SAVESTATES savestates_DATA = endif +if ENABLE_ATTRIBUTELOG +# Create $(attributelogdir) only +attributelog_DATA = +endif + smartd.conf.5.in: smartd.8.in sed '1,/STARTINCLUDE/ D;/ENDINCLUDE/,$$D' < $(srcdir)/smartd.8.in > $(top_builddir)/tmp.directives sed '/STARTINCLUDE/,$$D' < $(srcdir)/smartd.conf.5.in > $(top_builddir)/tmp.head @@ -413,6 +421,12 @@ else MAN_SAVESTATES = sed '/BEGIN ENABLE_SAVESTATES/,/END ENABLE_SAVESTATES/d' endif +if ENABLE_ATTRIBUTELOG +MAN_ATTRIBUTELOG = sed "s|/usr/local/var/lib/smartmontools/attrlog\\.|$(attributelog)|g" +else +MAN_ATTRIBUTELOG = sed '/BEGIN ENABLE_ATTRIBUTELOG/,/END ENABLE_ATTRIBUTELOG/d' +endif + if OS_FREEBSD .for file in $(man_MANS) ${file}: $(srcdir)/${file}.in Makefile svnversion.h @@ -426,7 +440,8 @@ ${file}: $(srcdir)/${file}.in Makefile svnversion.h s|/usr/local/etc/smartd\\.conf|$(sysconfdir)/smartd.conf|g; \ s|/usr/local/etc/smart_drivedb\\.h|$(sysconfdir)/smart_drivedb\\.h|g" ${.ALLSRC:M*.in} | \ $(MAN_DRIVEDB) | \ - $(MAN_SAVESTATES) > $@ + $(MAN_SAVESTATES) | \ + $(MAN_ATTRIBUTELOG) > $@ .endfor else smart%: $(srcdir)/smart%.in Makefile svnversion.h @@ -440,7 +455,8 @@ smart%: $(srcdir)/smart%.in Makefile svnversion.h sed "s|/usr/local/etc/smartd\\.conf|$(sysconfdir)/smartd.conf|g" | \ sed "s|/usr/local/etc/smart_drivedb\\.h|$(sysconfdir)/smart_drivedb\\.h|g" | \ $(MAN_DRIVEDB) | \ - $(MAN_SAVESTATES) > $@ + $(MAN_SAVESTATES) | \ + $(MAN_ATTRIBUTELOG) > $@ endif # Commands to convert man pages into .html and .txt diff --git a/smartmontools/configure.in b/smartmontools/configure.in index 1c4377325c28dfb1510c381e81e8ed880cd0b2fe..a3f5ee1d98d9d0ee724a749c200220b30e5e5284 100644 --- a/smartmontools/configure.in +++ b/smartmontools/configure.in @@ -169,6 +169,16 @@ AC_SUBST(savestates) AC_SUBST(savestatesdir) AM_CONDITIONAL(ENABLE_SAVESTATES, [test "$enable_savestates" = "yes"]) +AC_ARG_ENABLE(attributelog, [AC_HELP_STRING([--enable-attributelog],[Enables default smartd attribute log files])]) + +AC_ARG_WITH(attributelog, + [AC_HELP_STRING([--with-attributelog=PREFIX],[Prefix for default smartd attribute log files (implies --enable-attributelog) [LOCALSTATEDIR/lib/smartmontools/attrlog.]])], + [attributelog="$withval"; enable_attributelog="yes"], + [attributelog=; test "$enable_attributelog" = "yes" && attributelog='${localstatedir}/lib/${PACKAGE}/attrlog.']) +attributelogdir="${attributelog%/*}" +AC_SUBST(attributelog) +AC_SUBST(attributelogdir) +AM_CONDITIONAL(ENABLE_ATTRIBUTELOG, [test "$enable_attributelog" = "yes"]) AC_ARG_ENABLE(sample,[AC_HELP_STRING([--enable-sample],[Enables appending .sample to the installed smartd rc script and configuration file])],[smartd_suffix='.sample'],[smartd_suffix='']) AC_SUBST(smartd_suffix) diff --git a/smartmontools/smartd.8.in b/smartmontools/smartd.8.in index 4bc1eaa305a8e8115844ac7ce3379df0652dfe40..b1eff75b36eb61a080e931f068311924cc70580d 100644 --- a/smartmontools/smartd.8.in +++ b/smartmontools/smartd.8.in @@ -128,6 +128,31 @@ below). OPTIONS Long options are not supported on all systems. Use \fB\'smartd \-h\'\fP to see the available options. + +.TP +.B \-A PREFIX, \-\-attributelog=PREFIX +[NEW EXPERIMENTAL SMARTD FEATURE] [ATA ONLY] +Writes \fBsmartd\fP attribute information (normalized and raw attribute values) +to files \'PREFIX\'\'MODEL\-SERIAL.ata.csv\'. At each check cycle attributes +are logged as a line of semicolon separated triplets of the form +"attribute-ID;attribute-norm-value;attribute-raw-value;". Each line is +led by a date string of the form "yyyy-mm-dd HH:MM:SS" (in UTC). + +.\" BEGIN ENABLE_ATTRIBUTELOG +If this option is not specified, attribute information is written to files +\'/usr/local/var/lib/smartmontools/attrlog.MODEL\-SERIAL.ata.csv\'. +To disable attribute log files, specify this option with an empty string +argument: \'-A ""\'. +.\" END ENABLE_ATTRIBUTELOG +MODEL and SERIAL are build from drive identify information, invalid +characters are replaced by underline. + +If the PREFIX has the form \'/path/dir/\' (e.g. \'/var/lib/smartd/\'), then +files \'MODEL\-SERIAL.ata.csv\' are created in directory \'/path/dir\'. +If the PREFIX has the form \'/path/name\' (e.g. \'/var/lib/misc/attrlog\-\'), +then files 'nameMODEL\-SERIAL.ata.csv' are created in directory '/path/'. +The path must be absolute, except if debug mode is enabled. + .TP .B \-B [+]FILE, \-\-drivedb=[+]FILE [NEW EXPERIMENTAL SMARTD FEATURE] Read the drive database from FILE. diff --git a/smartmontools/smartd.cpp b/smartmontools/smartd.cpp index cc4792ce6600e063f79b5f537253857ad1f3300d..00737f7b0a60647b32ca945154e652064adf3db6 100644 --- a/smartmontools/smartd.cpp +++ b/smartmontools/smartd.cpp @@ -159,6 +159,13 @@ static std::string state_path_prefix #endif ; +// command-line: path prefix of attribute log file, empty if no logs. +static std::string attrlog_path_prefix +#ifdef SMARTMONTOOLS_ATTRIBUTELOG + = SMARTMONTOOLS_ATTRIBUTELOG +#endif + ; + // configuration file name #define CONFIGFILENAME "smartd.conf" @@ -241,6 +248,7 @@ struct dev_config std::string name; // Device name std::string dev_type; // Device type argument from -d directive, empty if none 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 smartcheck; // Check SMART status bool usagefailed; // Check for failed Usage Attributes bool prefail; // Track changes in Prefail Attributes @@ -653,6 +661,32 @@ static bool write_dev_state(const char * path, const persistent_dev_state & stat return true; } +// Write to the attrlog file +static bool write_dev_attrlog(const char * path, const persistent_dev_state & state) +{ + stdio_file f(path, "a"); + if (!f) { + pout("Cannot create attribute log file \"%s\"\n", path); + 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); + 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); + } + fprintf(f, "\n"); + + return true; +} + // Write all state files. If write_always is false, don't write // unless must_write is set. static void write_all_dev_states(const dev_config_vector & configs, @@ -675,6 +709,19 @@ static void write_all_dev_states(const dev_config_vector & configs, } } +// Write to all attrlog files +static void write_all_dev_attrlogs(const dev_config_vector & configs, + dev_state_vector & states) +{ + for (unsigned i = 0; i < states.size(); i++) { + const dev_config & cfg = configs.at(i); + if (cfg.attrlog_file.empty()) + continue; + dev_state & state = states[i]; + write_dev_attrlog(cfg.attrlog_file.c_str(), state); + } +} + // remove the PID file void RemovePidFile(){ if (!pid_file.empty()) { @@ -1769,21 +1816,24 @@ static int ATADeviceScan(dev_config & cfg, dev_state & state, ata_device * atade // close file descriptor CloseDevice(atadev, name); - if (!state_path_prefix.empty()) { + if (!state_path_prefix.empty() || !attrlog_path_prefix.empty()) { // Build file name for state file char model[40+1], serial[20+1]; format_ata_string(model, drive.model, sizeof(model)-1, fix_swapped_id); format_ata_string(serial, drive.serial_no, sizeof(serial)-1, fix_swapped_id); std::replace_if(model, model+strlen(model), not_allowed_in_filename, '_'); std::replace_if(serial, serial+strlen(serial), not_allowed_in_filename, '_'); - cfg.state_file = strprintf("%s%s-%s.ata.state", state_path_prefix.c_str(), 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", name, cfg.state_file.c_str()); - // Copy ATA attribute values to temp state - state.update_temp_state(); + if (!state_path_prefix.empty()) { + cfg.state_file = strprintf("%s%s-%s.ata.state", state_path_prefix.c_str(), 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", name, 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.ata.csv", attrlog_path_prefix.c_str(), model, serial); } // Start self-test regex check now if time was not read from state file @@ -1926,6 +1976,10 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state & state, scsi_device * scs 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); + } // close file descriptor CloseDevice(scsidev, device); @@ -3677,7 +3731,7 @@ void ParseOpts(int argc, char **argv){ char *tailptr; long lchecktime; // Please update GetValidArgList() if you edit shortopts - const char *shortopts = "c:l:q:dDni:p:r:s:B:Vh?"; + const char *shortopts = "c:l:q:dDni:p:r:s:A:B:Vh?"; char *arg; // Please update GetValidArgList() if you edit longopts struct option longopts[] = { @@ -3693,6 +3747,7 @@ void ParseOpts(int argc, char **argv){ { "pidfile", required_argument, 0, 'p' }, { "report", required_argument, 0, 'r' }, { "savestates", required_argument, 0, 's' }, + { "attributelog", required_argument, 0, 'A' }, { "drivedb", required_argument, 0, 'B' }, #if defined(_WIN32) || defined(__CYGWIN__) { "service", no_argument, 0, 'n' }, @@ -3837,6 +3892,10 @@ void ParseOpts(int argc, char **argv){ // path prefix of persistent state file state_path_prefix = optarg; break; + case 'A': + // path prefix of attribute log file + attrlog_path_prefix = optarg; + break; case 'B': { const char * path = optarg; @@ -3939,6 +3998,16 @@ void ParseOpts(int argc, char **argv){ EXIT(EXIT_BADCMD); } + // absolute path is required due to chdir('/') after fork(). + if (!attrlog_path_prefix.empty() && !debugmode && !is_abs_path(attrlog_path_prefix.c_str())) { + debugmode=1; + PrintHead(); + PrintOut(LOG_CRIT, "=======> INVALID CHOICE OF OPTIONS: -s <======= \n\n"); + PrintOut(LOG_CRIT, "Error: relative path %s is only allowed in debug (-d) mode\n\n", + attrlog_path_prefix.c_str()); + EXIT(EXIT_BADCMD); + } + // Read or init drive database if (!no_defaultdb) { unsigned char savedebug = debugmode; debugmode = 1; @@ -4301,6 +4370,10 @@ int main_worker(int argc, char **argv) write_all_dev_states(configs, states, write_states_always); write_states_always = false; + // Write attribute logs + if (!attrlog_path_prefix.empty()) + write_all_dev_attrlogs(configs, states); + // user has asked us to exit after first check if (quit==3) { PrintOut(LOG_INFO,"Started with '-q onecheck' option. All devices sucessfully checked once.\n"