X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=smartd.cpp;h=14163dc1fd24a240e43c65b8927b5d6f346d5c45;hb=HEAD;hp=2f1cdc86bd97ea3b2ecb61d91b22b52b7efc0f40;hpb=4beda62ad07a302ded72d60f701949dd9085a7ce;p=mirror_smartmontools-debian.git diff --git a/smartd.cpp b/smartd.cpp index 2f1cdc8..14163dc 100644 --- a/smartd.cpp +++ b/smartd.cpp @@ -2,29 +2,18 @@ * Home page of code is: http://www.smartmontools.org * * Copyright (C) 2002-11 Bruce Allen - * Copyright (C) 2008-17 Christian Franke + * Copyright (C) 2008-18 Christian Franke * Copyright (C) 2000 Michael Cornwell * Copyright (C) 2008 Oliver Bock * - * 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 - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * You should have received a copy of the GNU General Public License - * (for example COPYING); If not, see . - * - * This code was originally developed as a Senior Thesis by Michael Cornwell - * at the Concurrent Systems Laboratory (now part of the Storage Systems - * Research Center), Jack Baskin School of Engineering, University of - * California, Santa Cruz. http://ssrc.soe.ucsc.edu/ - * + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" -#include "int64.h" +#define __STDC_FORMAT_MACROS 1 // enable PRI* for C++ // unconditionally included files +#include #include #include #include // umask @@ -53,6 +42,7 @@ #endif #ifdef _WIN32 +#include "os_win32/popen.h" // popen/pclose() #ifdef _MSC_VER #pragma warning(disable:4761) // "conversion supplied" typedef unsigned short mode_t; @@ -70,6 +60,10 @@ typedef int pid_t; #include #endif // LIBCAP_NG +#ifdef HAVE_LIBSYSTEMD +#include +#endif // HAVE_LIBSYSTEMD + // locally included files #include "atacmds.h" #include "dev_interface.h" @@ -78,19 +72,9 @@ typedef int pid_t; #include "nvmecmds.h" #include "utility.h" -// This is for solaris, where signal() resets the handler to SIG_DFL -// after the first signal is caught. -#ifdef HAVE_SIGSET -#define SIGNALFN sigset -#else -#define SIGNALFN signal -#endif - #ifdef _WIN32 // fork()/signal()/initd simulation for native Windows -#include "daemon_win32.h" // daemon_main/detach/signal() -#undef SIGNALFN -#define SIGNALFN daemon_signal +#include "os_win32/daemon_win32.h" // daemon_main/detach/signal() #define strsignal daemon_strsignal #define sleep daemon_sleep // SIGQUIT does not exist, CONTROL-Break signals SIGBREAK. @@ -100,9 +84,46 @@ typedef int pid_t; #define SIGQUIT_KEYNAME "CONTROL-\\" #endif // _WIN32 -const char * smartd_cpp_cvsid = "$Id: smartd.cpp 4556 2017-10-15 17:31:30Z chrfranke $" +const char * smartd_cpp_cvsid = "$Id: smartd.cpp 4864 2018-12-20 13:02:39Z chrfranke $" CONFIG_H_CVSID; +extern "C" { + typedef void (*signal_handler_type)(int); +} + +static void set_signal_if_not_ignored(int sig, signal_handler_type handler) +{ +#if defined(_WIN32) + // signal() emulation + daemon_signal(sig, handler); + +#elif defined(HAVE_SIGACTION) + // SVr4, POSIX.1-2001, POSIX.1-2008 + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigaction(sig, (struct sigaction *)0, &sa); + if (sa.sa_handler == SIG_IGN) + return; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sa.sa_flags = SA_RESTART; // BSD signal() semantics + sigaction(sig, &sa, (struct sigaction *)0); + +#elif defined(HAVE_SIGSET) + // SVr4, POSIX.1-2001, obsoleted in POSIX.1-2008 + if (sigset(sig, handler) == SIG_IGN) + sigset(sig, SIG_IGN); + +#else + // POSIX.1-2001, POSIX.1-2008, C89, C99, undefined semantics. + // Important: BSD semantics is required. Traditional signal() + // resets the handler to SIG_DFL after the first signal is caught. + if (signal(sig, handler) == SIG_IGN) + signal(sig, SIG_IGN); +#endif +} + using namespace smartmontools; // smartd exit codes @@ -171,12 +192,7 @@ static int facility=LOG_DAEMON; static bool do_fork=true; #endif -#ifdef HAVE_LIBCAP_NG -// command-line: enable capabilities? -static bool enable_capabilities = false; -#endif - -// TODO: This smartctl only variable is also used in os_win32.cpp +// TODO: This smartctl only variable is also used in some os_*.cpp unsigned char failuretest_permissive = 0; // set to one if we catch a USR1 (check devices now) @@ -198,6 +214,102 @@ static volatile int caughtsigEXIT=0; static void PrintOut(int priority, const char *fmt, ...) __attribute_format_printf(2, 3); +#ifdef HAVE_LIBSYSTEMD +// systemd notify support + +static bool notify_enabled = false; + +static inline void notify_init() +{ + if (!getenv("NOTIFY_SOCKET")) + return; + notify_enabled = true; +} + +static inline bool notify_post_init() +{ + if (!notify_enabled) + return true; + if (do_fork) { + PrintOut(LOG_CRIT, "Option -n (--no-fork) is required if 'Type=notify' is set.\n"); + return false; + } + return true; +} + +static void notify_msg(const char * msg, bool ready = false) +{ + if (!notify_enabled) + return; + if (debugmode) { + pout("sd_notify(0, \"%sSTATUS=%s\")\n", (ready ? "READY=1\\n" : ""), msg); + return; + } + sd_notifyf(0, "%sSTATUS=%s", (ready ? "READY=1\n" : ""), msg); +} + +static void notify_check(int numdev) +{ + if (!notify_enabled) + return; + char msg[32]; + snprintf(msg, sizeof(msg), "Checking %d device%s ...", + numdev, (numdev != 1 ? "s" : "")); + notify_msg(msg); +} + +static void notify_wait(time_t wakeuptime, int numdev) +{ + if (!notify_enabled) + return; + char ts[16], msg[64]; + strftime(ts, sizeof(ts), "%H:%M:%S", localtime(&wakeuptime)); + snprintf(msg, sizeof(msg), "Next check of %d device%s will start at %s", + numdev, (numdev != 1 ? "s" : ""), ts); + static bool ready = true; // first call notifies READY=1 + notify_msg(msg, ready); + ready = false; +} + +static void notify_exit(int status) +{ + if (!notify_enabled) + return; + const char * msg; + switch (status) { + case 0: msg = "Exiting ..."; break; + case EXIT_BADCMD: msg = "Error in command line (see SYSLOG)"; break; + case EXIT_BADCONF: case EXIT_NOCONF: + case EXIT_READCONF: msg = "Error in config file (see SYSLOG)"; break; + case EXIT_BADDEV: msg = "Unable to register a device (see SYSLOG)"; break; + case EXIT_NODEV: msg = "No devices to monitor"; break; + default: msg = "Error (see SYSLOG)"; break; + } + notify_msg(msg); +} + +#else // HAVE_LIBSYSTEMD +// No systemd notify support + +static inline bool notify_post_init() +{ +#ifdef __linux__ + if (getenv("NOTIFY_SOCKET")) { + PrintOut(LOG_CRIT, "This version of smartd was build without 'Type=notify' support.\n"); + return false; + } +#endif + return true; +} + +static inline void notify_init() { } +static inline void notify_msg(const char *) { } +static inline void notify_check(int) { } +static inline void notify_wait(time_t, int) { } +static inline void notify_exit(int) { } + +#endif // HAVE_LIBSYSTEMD + // Attribute monitoring flags. // See monitor_attr_flags below. enum { @@ -582,12 +694,11 @@ static bool parse_dev_state_line(const char * line, persistent_dev_state & state ")" // 16) "|(nvme-err-log-entries)" // (24) ")" // 1) - " *= *([0-9]+)[ \n]*$", // (25) - REG_EXTENDED + " *= *([0-9]+)[ \n]*$" // (25) ); const int nmatch = 1+25; - regmatch_t match[nmatch]; + regular_expression::match_range match[nmatch]; if (!regex.execute(line, nmatch, match)) return false; if (match[nmatch-1].rm_so < 0) @@ -846,18 +957,6 @@ static void write_all_dev_attrlogs(const dev_config_vector & configs, } } -// remove the PID file -static void RemovePidFile() -{ - if (!pid_file.empty()) { - if (unlink(pid_file.c_str())) - PrintOut(LOG_CRIT,"Can't unlink PID file %s (%s).\n", - pid_file.c_str(), strerror(errno)); - pid_file.clear(); - } - return; -} - extern "C" { // signal handlers require C-linkage // Note if we catch a SIGUSR1 @@ -898,18 +997,43 @@ static void sighandler(int sig) } // extern "C" -// Cleanup, print Goodbye message and remove pidfile -static int Goodbye(int status) -{ - // delete PID file, if one was created - RemovePidFile(); +#ifdef HAVE_LIBCAP_NG +// capabilities(7) support - // and this should be the final output from smartd before it exits - PrintOut(status?LOG_CRIT:LOG_INFO, "smartd is exiting (exit status %d)\n", status); +static bool capabilities_enabled = false; - return status; +static void capabilities_drop_now() +{ + if (!capabilities_enabled) + return; + capng_clear(CAPNG_SELECT_BOTH); + capng_updatev(CAPNG_ADD, (capng_type_t)(CAPNG_EFFECTIVE|CAPNG_PERMITTED), + CAP_SYS_ADMIN, CAP_MKNOD, CAP_SYS_RAWIO, -1); + capng_apply(CAPNG_SELECT_BOTH); +} + +static void capabilities_check_config(dev_config_vector & configs) +{ + if (!capabilities_enabled) + return; + for (unsigned i = 0; i < configs.size(); i++) { + dev_config & cfg = configs[i]; + if (!cfg.emailaddress.empty() || !cfg.emailcmdline.empty()) { + PrintOut(LOG_INFO, "Device: %s, --capabilites is set, mail will be suppressed.\n", + cfg.name.c_str()); + cfg.emailaddress.clear(); cfg.emailcmdline.clear(); + } + } } +#else // HAVE_LIBCAP_NG +// No capabilities(7) support + +static inline void capabilities_drop_now() { } +static inline void capabilities_check_config(dev_config_vector &) { } + +#endif // HAVE_LIBCAP_NG + // 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 @@ -1019,14 +1143,6 @@ static void MailWarning(const dev_config & cfg, dev_state & state, int which, co return; } -#ifdef HAVE_LIBCAP_NG - if (enable_capabilities) { - PrintOut(LOG_ERR, "Sending a mail was supressed. " - "Mails can't be send when capabilites are enabled\n"); - return; - } -#endif - // record the time of this mail message, and the first mail message if (!mail->logged) mail->firstsent=epoch; @@ -1077,10 +1193,14 @@ static void MailWarning(const dev_config & cfg, dev_state & state, int which, co const char * newadd = (!address.empty()? address.c_str() : ""); const char * newwarn = (which? "Warning via" : "Test of"); -#ifndef _WIN32 - char command[2048]; + char command[256]; +#ifdef _WIN32 + // Path may contain spaces + snprintf(command, sizeof(command), "\"%s\" 2>&1", warning_script.c_str()); +#else snprintf(command, sizeof(command), "%s 2>&1", warning_script.c_str()); - +#endif + // 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); @@ -1093,7 +1213,7 @@ static void MailWarning(const dev_config & cfg, dev_state & state, int which, co PrintOut(LOG_CRIT,"%s %s to %s: failed (fork or pipe failed, or no memory) %s\n", newwarn, executable, newadd, errno?strerror(errno):""); else { - // pipe suceeded! + // pipe succeeded! int len, status; char buffer[EBUFLEN]; @@ -1150,29 +1270,6 @@ static void MailWarning(const dev_config & cfg, dev_state & state, int which, co } } - -#else // _WIN32 - { - char command[2048]; - snprintf(command, sizeof(command), "cmd /c \"%s\"", warning_script.c_str()); - - 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, "", 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); - if (rc != 0) - PrintOut(LOG_CRIT,"%s %s to %s: failed, exit status %d\n", - newwarn, executable, newadd, rc); - else - PrintOut(LOG_INFO,"%s %s to %s: successful\n", newwarn, executable, newadd); - } - -#endif // _WIN32 // increment mail sent counter mail->logged++; @@ -1317,11 +1414,11 @@ static bool WaitForPidFile() #endif // _WIN32 -// Forks new process, closes ALL file descriptors, redirects stdin, -// stdout, and stderr. Not quite daemon(). See -// http://www.linuxjournal.com/article/2335 +// Forks new process if needed, closes ALL file descriptors, +// redirects stdin, stdout, and stderr. Not quite daemon(). +// See https://www.linuxjournal.com/article/2335 // for a good description of why we do things this way. -static void DaemonInit() +static int daemon_init() { #ifndef _WIN32 @@ -1334,15 +1431,15 @@ static void DaemonInit() if ((pid=fork()) < 0) { // unable to fork! PrintOut(LOG_CRIT,"smartd unable to fork daemon process!\n"); - EXIT(EXIT_STARTUP); + return EXIT_STARTUP; } - else if (pid) { + if (pid) { // we are the parent process, wait for pid file, then exit cleanly if(!WaitForPidFile()) { PrintOut(LOG_CRIT,"PID file %s didn't show up!\n", pid_file.c_str()); - EXIT(EXIT_STARTUP); - } else - EXIT(0); + return EXIT_STARTUP; + } + return 0; } // from here on, we are the child process. @@ -1352,11 +1449,11 @@ static void DaemonInit() if ((pid=fork()) < 0) { // unable to fork! PrintOut(LOG_CRIT,"smartd unable to fork daemon process!\n"); - EXIT(EXIT_STARTUP); + return EXIT_STARTUP; } - else if (pid) + if (pid) // we are the parent process -- exit cleanly - EXIT(0); + return 0; // Now we are the child's child... } @@ -1369,7 +1466,7 @@ static void DaemonInit() int fd = open("/dev/null", O_RDWR); if (!(fd == 0 && dup(fd) == 1 && dup(fd) == 2 && !chdir("/"))) { PrintOut(LOG_CRIT, "smartd unable to redirect to /dev/null or to chdir to root!\n"); - EXIT(EXIT_STARTUP); + return EXIT_STARTUP; } umask(0022); @@ -1383,16 +1480,18 @@ static void DaemonInit() fflush(NULL); if (daemon_detach("smartd")) { PrintOut(LOG_CRIT,"smartd unable to detach from console!\n"); - EXIT(EXIT_STARTUP); + return EXIT_STARTUP; } // stdin/out/err now closed if not redirected #endif // _WIN32 - return; + + // No error, continue in main_worker() + return -1; } // create a PID file containing the current process id -static void WritePidFile() +static bool write_pid_file() { if (!pid_file.empty()) { pid_t pid = getpid(); @@ -1408,10 +1507,11 @@ static void WritePidFile() umask(old_umask); if (!(f && fprintf(f, "%d\n", (int)pid) > 0 && f.close())) { PrintOut(LOG_CRIT, "unable to write PID file %s - exiting.\n", pid_file.c_str()); - EXIT(EXIT_PID); + return false; } PrintOut(LOG_INFO, "file %s written containing PID %d\n", pid_file.c_str(), (int)pid); } + return true; } // Prints header identifying version of code and home @@ -1536,8 +1636,12 @@ static void Usage() #endif #ifndef _WIN32 PrintOut(LOG_INFO," -n, --no-fork\n"); - PrintOut(LOG_INFO," Do not fork into background\n\n"); -#endif // _WIN32 + PrintOut(LOG_INFO," Do not fork into background\n"); +#ifdef HAVE_LIBSYSTEMD + PrintOut(LOG_INFO," (systemd 'Type=notify' is assumed if $NOTIFY_SOCKET is set)\n"); +#endif // HAVE_LIBSYSTEMD + PrintOut(LOG_INFO,"\n"); +#endif // WIN32 PrintOut(LOG_INFO," -p NAME, --pidfile=NAME\n"); PrintOut(LOG_INFO," Write PID file NAME\n\n"); PrintOut(LOG_INFO," -q WHEN, --quit=WHEN\n"); @@ -1574,7 +1678,7 @@ static int CloseDevice(smart_device * device, const char * name) PrintOut(LOG_INFO,"Device: %s, %s, close() failed\n", name, device->get_errmsg()); return 1; } - // device sucessfully closed + // device successfully closed return 0; } @@ -1621,9 +1725,34 @@ static int SelfTestErrorCount(ata_device * device, const char * name, 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, firmwarebugs); + + if (!log.mostrecenttest) + // No tests logged + return 0; + + // Count failed self-tests + int errcnt = 0, hours = 0; + for (int i = 20; i >= 0; i--) { + int j = (i + log.mostrecenttest) % 21; + const ata_smart_selftestlog_struct & entry = log.selftest_struct[j]; + if (!nonempty(&entry, sizeof(entry))) + continue; + + int status = entry.selfteststatus >> 4; + if (status == 0x0 && (entry.selftestnumber & 0x7f) == 0x02) + // First successful extended self-test, stop count + break; + + if (0x3 <= status && status <= 0x8) { + // Self-test showed an error + errcnt++; + // Keep track of time of most recent error + if (!hours) + hours = entry.timestamp; + } + } + + return ((hours << 8) | errcnt); } #define SELFTEST_ERRORCOUNT(x) (x & 0xff) @@ -1759,7 +1888,7 @@ static bool is_duplicate_dev_idinfo(const dev_config & cfg, const dev_config_vec for (unsigned i = 0; i < prev_cfgs.size(); i++) { if (!prev_cfgs[i].id_is_unique) continue; - if (cfg.dev_idinfo != prev_cfgs[i].dev_idinfo.c_str()) + if (cfg.dev_idinfo != prev_cfgs[i].dev_idinfo) continue; PrintOut(LOG_INFO, "Device: %s, same identity as %s, ignored\n", @@ -1894,10 +2023,16 @@ static int ATADeviceScan(dev_config & cfg, dev_state & state, ata_device * atade PrintOut(LOG_INFO,"Device: %s, could not enable SMART capability\n",name); if (ataIsSmartEnabled(&drive) <= 0) { - CloseDevice(atadev, name); - return 2; + if (!cfg.permissive) { + PrintOut(LOG_INFO, "Device: %s, to proceed anyway, use '-T permissive' Directive.\n", name); + CloseDevice(atadev, name); + return 2; + } + PrintOut(LOG_INFO, "Device: %s, proceeding since '-T permissive' Directive given.\n", name); + } + else { + PrintOut(LOG_INFO, "Device: %s, proceeding since SMART is already enabled\n", name); } - PrintOut(LOG_INFO, "Device: %s, proceeding since SMART is already enabled\n", name); } // disable device attribute autosave... @@ -2217,9 +2352,9 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state & state, scsi_device * scs int err, req_len, avail_len, version, len; const char *device = cfg.name.c_str(); struct scsi_iec_mode_page iec; - UINT8 tBuf[64]; - UINT8 inqBuf[96]; - UINT8 vpdBuf[252]; + uint8_t tBuf[64]; + uint8_t inqBuf[96]; + uint8_t vpdBuf[252]; char lu_id[64], serial[256], vendor[40], model[40]; // Device must be open @@ -2273,12 +2408,12 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state & state, scsi_device * scs vpdBuf, sizeof(vpdBuf))) { len = vpdBuf[3]; vpdBuf[4 + len] = '\0'; - scsi_format_id_string(serial, (const unsigned char *)&vpdBuf[4], len); + scsi_format_id_string(serial, &vpdBuf[4], len); } - unsigned int lb_size; char si_str[64]; - uint64_t capacity = scsiGetSize(scsidev, &lb_size, NULL); + struct scsi_readcap_resp srr; + uint64_t capacity = scsiGetSize(scsidev, scsidev->use_rcap16(), &srr); if (capacity) format_capacity(si_str, sizeof(si_str), capacity, "."); @@ -2294,8 +2429,8 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state & state, scsi_device * scs cfg.id_is_unique = (lu_id[0] || serial[0]); // 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); + scsi_format_id_string(vendor, &inqBuf[8], 8); + scsi_format_id_string(model, &inqBuf[16], 16); PrintOut(LOG_INFO, "Device: %s, %s\n", device, cfg.dev_idinfo.c_str()); // Check for duplicates @@ -2381,10 +2516,10 @@ static int SCSIDeviceScan(dev_config & cfg, dev_state & state, scsi_device * scs // Check if scsiCheckIE() is going to work { - UINT8 asc = 0; - UINT8 ascq = 0; - UINT8 currenttemp = 0; - UINT8 triptemp = 0; + uint8_t asc = 0; + uint8_t ascq = 0; + uint8_t currenttemp = 0; + uint8_t triptemp = 0; if (scsiCheckIE(scsidev, state.SmartPageSupported, state.TempPageSupported, &asc, &ascq, ¤ttemp, &triptemp)) { @@ -3422,7 +3557,7 @@ static int ATACheckDevice(const dev_config & cfg, dev_state & state, ata_device if (cfg.selfteststs) { if ( curval.self_test_exec_status != state.smartval.self_test_exec_status || state.selftest_started // test was started in previous call - || (firstpass && (debugmode || curval.self_test_exec_status != 0x00))) + || (firstpass && (debugmode || (curval.self_test_exec_status & 0xf0)))) log_self_test_exec_status(name, curval.self_test_exec_status); } @@ -3492,8 +3627,8 @@ static int SCSICheckDevice(const dev_config & cfg, dev_state & state, scsi_devic const char * name = cfg.name.c_str(); - UINT8 asc = 0, ascq = 0; - UINT8 currenttemp = 0, triptemp = 0; + uint8_t asc = 0, ascq = 0; + uint8_t currenttemp = 0, triptemp = 0; if (!state.SuppressReport) { if (scsiCheckIE(scsidev, state.SmartPageSupported, state.TempPageSupported, &asc, &ascq, ¤ttemp, &triptemp)) { @@ -3531,7 +3666,7 @@ static int SCSICheckDevice(const dev_config & cfg, dev_state & state, scsi_devic } if (!cfg.attrlog_file.empty()){ // saving error counters to state - UINT8 tBuf[252]; + uint8_t tBuf[252]; if (state.ReadECounterPageSupported && (0 == scsiLogSense(scsidev, READ_ERROR_COUNTER_LPAGE, 0, tBuf, sizeof(tBuf), 0))) { scsiDecodeErrCounterPage(tBuf, &state.scsi_error_counters[0].errCounter); @@ -3729,47 +3864,22 @@ static void CheckDevicesOnce(const dev_config_vector & configs, dev_state_vector do_disable_standby_check(configs, states); } -// Set if Initialize() was called -static bool is_initialized = false; - -// Does initialization right after fork to daemon mode -static void Initialize(time_t *wakeuptime) +// Install all signal handlers +static void install_signal_handlers() { - // Call Goodbye() on exit - is_initialized = true; - - // write PID file - if (!debugmode) - WritePidFile(); - - // install signal handlers. On Solaris, can't use signal() because - // it resets the handler to SIG_DFL after each call. So use sigset() - // instead. So SIGNALFN()==signal() or SIGNALFN()==sigset(). - // normal and abnormal exit - if (SIGNALFN(SIGTERM, sighandler)==SIG_IGN) - SIGNALFN(SIGTERM, SIG_IGN); - if (SIGNALFN(SIGQUIT, sighandler)==SIG_IGN) - SIGNALFN(SIGQUIT, SIG_IGN); + set_signal_if_not_ignored(SIGTERM, sighandler); + set_signal_if_not_ignored(SIGQUIT, sighandler); // in debug mode, ==> HUP - if (SIGNALFN(SIGINT, debugmode?HUPhandler:sighandler)==SIG_IGN) - SIGNALFN(SIGINT, SIG_IGN); + set_signal_if_not_ignored(SIGINT, (debugmode ? HUPhandler : sighandler)); // Catch HUP and USR1 - if (SIGNALFN(SIGHUP, HUPhandler)==SIG_IGN) - SIGNALFN(SIGHUP, SIG_IGN); - if (SIGNALFN(SIGUSR1, USR1handler)==SIG_IGN) - SIGNALFN(SIGUSR1, SIG_IGN); + set_signal_if_not_ignored(SIGHUP, HUPhandler); + set_signal_if_not_ignored(SIGUSR1, USR1handler); #ifdef _WIN32 - if (SIGNALFN(SIGUSR2, USR2handler)==SIG_IGN) - SIGNALFN(SIGUSR2, SIG_IGN); + set_signal_if_not_ignored(SIGUSR2, USR2handler); #endif - - // initialize wakeup time to CURRENT time - *wakeuptime=time(NULL); - - return; } #ifdef _WIN32 @@ -3798,7 +3908,7 @@ static void ToggleDebugMode() } #endif -static time_t dosleep(time_t wakeuptime, bool & sigwakeup) +static time_t dosleep(time_t wakeuptime, bool & sigwakeup, int numdev) { // If past wake-up-time, compute next wake-up-time time_t timenow=time(NULL); @@ -3806,7 +3916,9 @@ static time_t dosleep(time_t wakeuptime, bool & sigwakeup) int intervals=1+(timenow-wakeuptime)/checktime; wakeuptime+=intervals*checktime; } - + + notify_wait(wakeuptime, numdev); + // sleep until we catch SIGUSR1 or have completed sleeping int addtime = 0; while (timenow < wakeuptime+addtime && !caughtsigUSR1 && !caughtsigHUP && !caughtsigEXIT) { @@ -4225,7 +4337,7 @@ static int ParseToken(char * token, dev_config & cfg, smart_devtype_list & scan_ } // Compile regex else { - if (!cfg.test_regex.compile(arg, REG_EXTENDED)) { + if (!cfg.test_regex.compile(arg)) { // not a valid regular expression! PrintOut(LOG_CRIT, "File %s line %d (drive %s): -s argument \"%s\" is INVALID extended regular expression. %s.\n", configfile, lineno, name, arg, cfg.test_regex.get_errmsg()); @@ -4247,14 +4359,6 @@ static int ParseToken(char * token, dev_config & cfg, smart_devtype_list & scan_ 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 // TODO: Remove after smartmontools 6.5 - if ( !strcmp(arg, "msgbox") || !strcmp(arg, "sysmsgbox") - || str_starts_with(arg, "msgbox,") || str_starts_with(arg, "sysmsgbox,")) { - PrintOut(LOG_CRIT, "File %s line %d (drive %s): -m %s is no longer supported, use -m console[,...] instead\n", - configfile, lineno, name, arg); - return -1; - } -#endif cfg.emailaddress = arg; } break; @@ -4568,7 +4672,7 @@ static int ParseConfigLine(dev_config_vector & conf_entries, dev_config & defaul // -3: config file exists but cannot be read // // In the case where the return value is 0, there are three -// possiblities: +// possibilities: // Empty configuration file ==> conf_entries.empty() // No configuration file ==> conf_entries[0].lineno == 0 // SCANDIRECTIVE found ==> conf_entries.back().lineno != 0 (size >= 1) @@ -4720,23 +4824,23 @@ static void PrintValidArgs(char opt) } #ifndef _WIN32 -// Report error and exit if specified path is not absolute. -static void check_abs_path(char option, const std::string & path) +// Report error and return false if specified path is not absolute. +static bool check_abs_path(char option, const std::string & path) { if (path.empty() || path[0] == '/') - return; + return true; debugmode = 1; PrintHead(); PrintOut(LOG_CRIT, "=======> INVALID ARGUMENT TO -%c: %s <=======\n\n", option, path.c_str()); PrintOut(LOG_CRIT, "Error: relative path names are not allowed\n\n"); - EXIT(EXIT_BADCMD); + return false; } #endif // !_WIN32 // Parses input line, prints usage message and // version/license/copyright messages -static void ParseOpts(int argc, char **argv) +static int parse_options(int argc, char **argv) { // Init default path names #ifndef _WIN32 @@ -4855,8 +4959,7 @@ static void ParseOpts(int argc, char **argv) // print summary of all valid directives debugmode = 1; Directives(); - EXIT(0); - break; + return 0; case 'i': // Period (time interval) for checking // strtol will set errno in the event of overflow, so we'll check it. @@ -4868,7 +4971,7 @@ static void ParseOpts(int argc, char **argv) PrintOut(LOG_CRIT, "======> INVALID INTERVAL: %s <=======\n", optarg); PrintOut(LOG_CRIT, "======> INTERVAL MUST BE INTEGER BETWEEN %d AND %d <=======\n", 10, INT_MAX); PrintOut(LOG_CRIT, "\nUse smartd -h to get a usage summary\n\n"); - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; } checktime = (int)lchecktime; break; @@ -4921,7 +5024,7 @@ static void ParseOpts(int argc, char **argv) use_default_db = false; unsigned char savedebug = debugmode; debugmode = 1; if (!read_drive_database(path)) - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; debugmode = savedebug; } break; @@ -4932,12 +5035,11 @@ static void ParseOpts(int argc, char **argv) // print version and CVS info debugmode = 1; PrintOut(LOG_INFO, "%s", format_version_info("smartd", true /*full*/).c_str()); - EXIT(0); - break; + return 0; #ifdef HAVE_LIBCAP_NG case 'C': // enable capabilities - enable_capabilities = true; + capabilities_enabled = true; break; #endif case 'h': @@ -4945,8 +5047,7 @@ static void ParseOpts(int argc, char **argv) debugmode=1; PrintHead(); Usage(); - EXIT(0); - break; + return 0; case '?': default: // unrecognized option @@ -4964,7 +5065,7 @@ static void ParseOpts(int argc, char **argv) PrintOut(LOG_CRIT, "=======> UNRECOGNIZED OPTION: %s <=======\n\n",arg+2); } PrintOut(LOG_CRIT, "\nUse smartd --help to get a usage summary\n\n"); - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; } if (optopt) { // Iff optopt holds a valid option then argument must be missing. @@ -4975,10 +5076,10 @@ static void ParseOpts(int argc, char **argv) PrintOut(LOG_CRIT, "=======> UNRECOGNIZED OPTION: %c <=======\n\n",optopt); } PrintOut(LOG_CRIT, "\nUse smartd -h to get a usage summary\n\n"); - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; } Usage(); - EXIT(0); + return 0; } // Check to see if option had an unrecognized or incorrect argument. @@ -4991,7 +5092,7 @@ static void ParseOpts(int argc, char **argv) PrintOut(LOG_CRIT, "=======> INVALID ARGUMENT TO -%c: %s <======= \n", optchar, optarg); PrintValidArgs(optchar); PrintOut(LOG_CRIT, "\nUse smartd -h to get a usage summary\n\n"); - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; } } @@ -5001,7 +5102,7 @@ static void ParseOpts(int argc, char **argv) PrintHead(); PrintOut(LOG_CRIT, "=======> UNRECOGNIZED ARGUMENT: %s <=======\n\n", argv[optind]); PrintOut(LOG_CRIT, "\nUse smartd -h to get a usage summary\n\n"); - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; } // no pidfile in debug mode @@ -5010,15 +5111,16 @@ static void ParseOpts(int argc, char **argv) PrintHead(); PrintOut(LOG_CRIT, "=======> INVALID CHOICE OF OPTIONS: -d and -p <======= \n\n"); PrintOut(LOG_CRIT, "Error: pid file %s not written in debug (-d) mode\n\n", pid_file.c_str()); - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; } #ifndef _WIN32 if (!debugmode) { - // absolute path names are required due to chdir('/') after fork(). - check_abs_path('p', pid_file); - check_abs_path('s', state_path_prefix); - check_abs_path('A', attrlog_path_prefix); + // absolute path names are required due to chdir('/') in daemon_init() + if (!( check_abs_path('p', pid_file) + && check_abs_path('s', state_path_prefix) + && check_abs_path('A', attrlog_path_prefix))) + return EXIT_BADCMD; } #endif @@ -5026,12 +5128,19 @@ static void ParseOpts(int argc, char **argv) { unsigned char savedebug = debugmode; debugmode = 1; if (!init_drive_database(use_default_db)) - EXIT(EXIT_BADCMD); + return EXIT_BADCMD; debugmode = savedebug; } + // Check option compatibility of notify support + if (!notify_post_init()) + return EXIT_BADCMD; + // print header PrintHead(); + + // No error, continue in main_worker() + return -1; } // Function we call if no configuration file was found or if the @@ -5243,8 +5352,8 @@ static bool register_device(dev_config & cfg, dev_state & state, smart_device_au // 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. -static void RegisterDevices(const dev_config_vector & conf_entries, smart_device_list & scanned_devs, - dev_config_vector & configs, dev_state_vector & states, smart_device_list & devices) +static bool register_devices(const dev_config_vector & conf_entries, smart_device_list & scanned_devs, + dev_config_vector & configs, dev_state_vector & states, smart_device_list & devices) { // start by clearing lists/memory of ALL existing devices configs.clear(); @@ -5289,12 +5398,12 @@ static void RegisterDevices(const dev_config_vector & conf_entries, smart_device // If scanning, pass dev_idinfo of previous devices for duplicate check dev_state state; if (!register_device(cfg, state, dev, (scanning ? &configs : 0))) { - // if device is explictly listed and we can't register it, then + // if device is explicitly listed and we can't register it, then // exit unless the user has specified that the device is removable if (!scanning) { if (!(cfg.removable || quit == QUIT_NEVER)) { PrintOut(LOG_CRIT, "Unable to register device %s (no Directive -d removable). Exiting.\n", cfg.name.c_str()); - EXIT(EXIT_BADDEV); + return false; } PrintOut(LOG_INFO, "Device: %s, not available\n", cfg.name.c_str()); // Prevent retry of registration @@ -5312,6 +5421,7 @@ static void RegisterDevices(const dev_config_vector & conf_entries, smart_device } init_disable_standby_check(configs); + return true; } @@ -5323,14 +5433,13 @@ static int main_worker(int argc, char **argv) if (!smi()) return 1; - // is it our first pass through? - bool firstpass = true; - - // next time to wake up - time_t wakeuptime = 0; + // Check whether systemd notify is supported and enabled + notify_init(); // parse input and print header and usage info if needed - ParseOpts(argc,argv); + int status = parse_options(argc,argv); + if (status >= 0) + return status; // Configuration for each device dev_config_vector configs; @@ -5339,41 +5448,16 @@ static int main_worker(int argc, char **argv) // Devices to monitor smart_device_list devices; - bool write_states_always = true; + // Drop capabilities if supported and enabled + capabilities_drop_now(); -#ifdef HAVE_LIBCAP_NG - // Drop capabilities - if (enable_capabilities) { - capng_clear(CAPNG_SELECT_BOTH); - capng_updatev(CAPNG_ADD, (capng_type_t)(CAPNG_EFFECTIVE|CAPNG_PERMITTED), - CAP_SYS_ADMIN, CAP_MKNOD, CAP_SYS_RAWIO, -1); - capng_apply(CAPNG_SELECT_BOTH); - } -#endif + notify_msg("Initializing ..."); // the main loop of the code - for (;;) { - - // are we exiting from a signal? - if (caughtsigEXIT) { - // are we exiting with SIGTERM? - int isterm=(caughtsigEXIT==SIGTERM); - int isquit=(caughtsigEXIT==SIGQUIT); - int isok=debugmode?isterm || isquit:isterm; - - PrintOut(isok?LOG_INFO:LOG_CRIT, "smartd received signal %d: %s\n", - caughtsigEXIT, strsignal(caughtsigEXIT)); - - if (!isok) - return EXIT_SIGNAL; - - // Write state files - if (!state_path_prefix.empty()) - write_all_dev_states(configs, states); - - return 0; - } - + bool firstpass = true, write_states_always = true; + time_t wakeuptime = 0; + // assert(status < 0); + do { // Should we (re)read the config file? if (firstpass || caughtsigHUP){ if (!firstpass) { @@ -5386,6 +5470,7 @@ static int main_worker(int argc, char **argv) "Signal HUP - rereading configuration file %s\n": "\a\nSignal INT - rereading configuration file %s (" SIGQUIT_KEYNAME " quits)\n\n", configfile); + notify_msg("Reloading ..."); } { @@ -5396,9 +5481,14 @@ static int main_worker(int argc, char **argv) if (entries>=0) { // checks devices, then moves onto ata/scsi list or deallocates. - RegisterDevices(conf_entries, scanned_devs, configs, states, devices); + if (!register_devices(conf_entries, scanned_devs, configs, states, devices)) { + status = EXIT_BADDEV; + break; + } if (!(configs.size() == devices.size() && configs.size() == states.size())) throw std::logic_error("Invalid result from RegisterDevices"); + // Handle limitations if capabilities are dropped + capabilities_check_config(configs); } else if ( quit == QUIT_NEVER || ((quit == QUIT_NODEV || quit == QUIT_NODEVSTARTUP) && !firstpass)) { @@ -5408,45 +5498,38 @@ static int main_worker(int argc, char **argv) } else { // exit with configuration file error status - return (entries==-3 ? EXIT_READCONF : entries==-2 ? EXIT_NOCONF : EXIT_BADCONF); + status = (entries == -3 ? EXIT_READCONF : entries == -2 ? EXIT_NOCONF : EXIT_BADCONF); + break; } } - // Log number of devices we are monitoring... - if (devices.size() > 0 || quit == QUIT_NEVER || (quit == QUIT_NODEVSTARTUP && !firstpass)) { - int numata = 0, numscsi = 0; - for (unsigned i = 0; i < devices.size(); i++) { - const smart_device * dev = devices.at(i); - if (dev->is_ata()) - numata++; - else if (dev->is_scsi()) - numscsi++; - } - PrintOut(LOG_INFO,"Monitoring %d ATA/SATA, %d SCSI/SAS and %d NVMe devices\n", - numata, numscsi, (int)devices.size() - numata - numscsi); + if (!( devices.size() > 0 || quit == QUIT_NEVER + || (quit == QUIT_NODEVSTARTUP && !firstpass))) { + PrintOut(LOG_INFO, "Unable to monitor any SMART enabled devices. %sExiting...\n", + (!debugmode ? "Try debug (-d) option. " : "")); + status = EXIT_NODEV; + break; } - else { - PrintOut(LOG_INFO,"Unable to monitor any SMART enabled devices. Try debug (-d) option. Exiting...\n"); - return EXIT_NODEV; + + // Log number of devices we are monitoring... + int numata = 0, numscsi = 0; + for (unsigned i = 0; i < devices.size(); i++) { + const smart_device * dev = devices.at(i); + if (dev->is_ata()) + numata++; + else if (dev->is_scsi()) + numscsi++; } + PrintOut(LOG_INFO, "Monitoring %d ATA/SATA, %d SCSI/SAS and %d NVMe devices\n", + numata, numscsi, (int)devices.size() - numata - numscsi); if (quit == QUIT_SHOWTESTS) { // user has asked to print test schedule PrintTestSchedule(configs, states, devices); + // assert(firstpass); return 0; } -#ifdef HAVE_LIBCAP_NG - if (enable_capabilities) { - for (unsigned i = 0; i < configs.size(); i++) { - if (!configs[i].emailaddress.empty() || !configs[i].emailcmdline.empty()) { - PrintOut(LOG_WARNING, "Mail can't be enabled together with --capabilities. All mail will be suppressed.\n"); - break; - } - } - } -#endif - // reset signal caughtsigHUP=0; @@ -5456,6 +5539,7 @@ static int main_worker(int argc, char **argv) // check all devices once, // self tests are not started in first pass unless '-q onecheck' is specified + notify_check((int)devices.size()); CheckDevicesOnce(configs, states, devices, firstpass, (!firstpass || quit == QUIT_ONECHECK)); // Write state files @@ -5469,25 +5553,75 @@ static int main_worker(int argc, char **argv) // user has asked us to exit after first check if (quit == QUIT_ONECHECK) { - PrintOut(LOG_INFO,"Started with '-q onecheck' option. All devices sucessfully checked once.\n" + PrintOut(LOG_INFO,"Started with '-q onecheck' option. All devices successfully checked once.\n" "smartd is exiting (exit status 0)\n"); + // assert(firstpass); return 0; } - // fork into background if needed - if (firstpass && !debugmode) { - DaemonInit(); - } + if (firstpass) { + if (!debugmode) { + // fork() into background if needed, close ALL file descriptors, + // redirect stdin, stdout, and stderr, chdir to "/". + status = daemon_init(); + if (status >= 0) + return status; + + // Write PID file if configured + if (!write_pid_file()) + return EXIT_PID; + } + + // Set exit and signal handlers + install_signal_handlers(); + + // Initialize wakeup time to CURRENT time + wakeuptime = time(0); - // set exit and signal handlers, write PID file, set wake-up time - if (firstpass){ - Initialize(&wakeuptime); firstpass = false; } - + // sleep until next check time, or a signal arrives - wakeuptime = dosleep(wakeuptime, write_states_always); + wakeuptime = dosleep(wakeuptime, write_states_always, (int)devices.size()); + + } while (!caughtsigEXIT); + + if (caughtsigEXIT && status < 0) { + // Loop exited on signal + if (caughtsigEXIT == SIGTERM || (debugmode && caughtsigEXIT == SIGQUIT)) { + PrintOut(LOG_INFO, "smartd received signal %d: %s\n", + caughtsigEXIT, strsignal(caughtsigEXIT)); + } + else { + // Unexpected SIGINT or SIGQUIT + PrintOut(LOG_CRIT, "smartd received unexpected signal %d: %s\n", + caughtsigEXIT, strsignal(caughtsigEXIT)); + status = EXIT_SIGNAL; + } } + + // Status unset above implies success + if (status < 0) + status = 0; + + if (!firstpass) { + // Loop exited after daemon_init() and write_pid_file() + + // Write state files only on normal exit + if (!status && !state_path_prefix.empty()) + write_all_dev_states(configs, states); + + // Delete PID file, if one was created + if (!pid_file.empty() && unlink(pid_file.c_str())) + PrintOut(LOG_CRIT,"Can't unlink PID file %s (%s).\n", + pid_file.c_str(), strerror(errno)); + + // and this should be the final output from smartd before it exits + PrintOut((status ? LOG_CRIT : LOG_INFO), "smartd is exiting (exit status %d)\n", + status); + } + + return status; } @@ -5504,10 +5638,6 @@ static int smartd_main(int argc, char **argv) // Do the real work ... status = main_worker(argc, argv); } - catch (int ex) { - // EXIT(status) arrives here - status = ex; - } catch (const std::bad_alloc & /*ex*/) { // Memory allocation failed (also thrown by std::operator new) PrintOut(LOG_CRIT, "Smartd: Out of memory\n"); @@ -5529,9 +5659,7 @@ static int smartd_main(int argc, char **argv) if (status == EXIT_BADCODE) PrintOut(LOG_CRIT, "Please inform " PACKAGE_BUGREPORT ", including output of smartd -V.\n"); - if (is_initialized) - status = Goodbye(status); - + notify_exit(status); #ifdef _WIN32 daemon_winsvc_exitcode = status; #endif