smartd.cpp 148 KB
Newer Older
1
/*
2
3
 * Home page of code is: http://smartmontools.sourceforge.net
 *
4
5
6
7
 * Copyright (C) 2002-10 Bruce Allen <smartmontools-support@lists.sourceforge.net>
 * Copyright (C) 2000    Michael Cornwell <cornwell@acm.org>
 * Copyright (C) 2008    Oliver Bock <brevilo@users.sourceforge.net>
 * Copyright (C) 2008-10 Christian Franke <smartmontools-support@lists.sourceforge.net>
ballen4705's avatar
ballen4705 committed
8
9
10
11
12
13
14
 *
 * 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
15
 * (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
ballen4705's avatar
ballen4705 committed
16
17
18
19
20
21
 *
 * 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/
 *
ballen4705's avatar
ballen4705 committed
22
 */
23

24
#ifndef _GNU_SOURCE
25
// TODO: Why is this define necessary?
26
#define _GNU_SOURCE
27
28
29
#endif

// unconditionally included files
ballen4705's avatar
ballen4705 committed
30
31
#include <stdio.h>
#include <sys/types.h>
32
33
#include <sys/stat.h>   // umask
#include <signal.h>
ballen4705's avatar
ballen4705 committed
34
35
36
37
#include <fcntl.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>
38
39
#include <stdlib.h>
#include <errno.h>
40
#include <time.h>
41
#include <limits.h>
42
#include <getopt.h>
43

44
#include <stdexcept>
45
#include <string>
46
47
#include <vector>
#include <algorithm> // std::replace()
48

49
50
51
52
// see which system files to conditionally include
#include "config.h"

// conditionally included files
53
54
55
56
#ifndef _WIN32
#include <sys/wait.h>
#endif
#ifdef HAVE_UNISTD_H
57
#include <unistd.h>
58
#endif
59
#ifdef HAVE_NETDB_H
guidog's avatar
guidog committed
60
61
62
#include <netdb.h>
#endif

chrfranke's avatar
chrfranke committed
63
64
65
66
67
#ifdef _WIN32
#ifdef _MSC_VER
#pragma warning(disable:4761) // "conversion supplied"
typedef unsigned short mode_t;
typedef int pid_t;
68
#endif
chrfranke's avatar
chrfranke committed
69
70
71
72
#include <io.h> // umask()
#include <process.h> // getpid()
#endif // _WIN32

73
74
75
#ifdef __CYGWIN__
// From <windows.h>:
// BOOL WINAPI FreeConsole(void);
chrfranke's avatar
chrfranke committed
76
extern "C" int __stdcall FreeConsole(void);
77
#include <io.h> // setmode()
78
79
#endif // __CYGWIN__

80
81
82
83
#ifdef HAVE_LIBCAP_NG
#include <cap-ng.h>
#endif // LIBCAP_NG

84
// locally included files
85
#include "int64.h"
ballen4705's avatar
ballen4705 committed
86
#include "atacmds.h"
87
#include "dev_interface.h"
88
#include "extern.h"
89
#include "knowndrives.h"
90
#include "scsicmds.h"
91
#include "utility.h"
92
93
94
95
96
97
98
99

// 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
ballen4705's avatar
ballen4705 committed
100

101
#ifdef _WIN32
102
103
104
#include "hostname_win32.h" // gethost/domainname()
#define HAVE_GETHOSTNAME   1
#define HAVE_GETDOMAINNAME 1
105
106
107
108
109
110
// fork()/signal()/initd simulation for native Windows
#include "daemon_win32.h" // daemon_main/detach/signal()
#undef SIGNALFN
#define SIGNALFN  daemon_signal
#define strsignal daemon_strsignal
#define sleep     daemon_sleep
111
// SIGQUIT does not exist, CONTROL-Break signals SIGBREAK.
112
113
114
#define SIGQUIT SIGBREAK
#define SIGQUIT_KEYNAME "CONTROL-Break"
#else  // _WIN32
115
116
117
118
#ifdef __CYGWIN__
// 2x CONTROL-C simulates missing SIGQUIT via keyboard
#define SIGQUIT_KEYNAME "2x CONTROL-C"
#else // __CYGWIN__
119
#define SIGQUIT_KEYNAME "CONTROL-\\"
120
#endif // __CYGWIN__
121
122
#endif // _WIN32

123
#if defined (__SVR4) && defined (__sun)
124
extern "C" int getdomainname(char *, int); // no declaration in header files!
125
126
#endif

chrfranke's avatar
chrfranke committed
127
128
#define ARGUSED(x) ((void)(x))

129
130
const char * smartd_cpp_cvsid = "$Id$"
                                CONFIG_H_CVSID EXTERN_H_CVSID;
131

132
extern const char *reportbug;
133

134
extern unsigned char debugmode;
ballen4705's avatar
ballen4705 committed
135

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// smartd exit codes
#define EXIT_BADCMD    1   // command line did not parse
#define EXIT_BADCONF   2   // syntax error in config file
#define EXIT_STARTUP   3   // problem forking daemon
#define EXIT_PID       4   // problem creating pid file
#define EXIT_NOCONF    5   // config file does not exist
#define EXIT_READCONF  6   // config file exists but cannot be read

#define EXIT_NOMEM     8   // out of memory
#define EXIT_BADCODE   10  // internal error - should NEVER happen

#define EXIT_BADDEV    16  // we can't monitor this device
#define EXIT_NODEV     17  // no devices to monitor

#define EXIT_SIGNAL    254 // abort on signal

152
// command-line: how long to sleep between checks
153
#define CHECKTIME 1800
154
static int checktime=CHECKTIME;
ballen4705's avatar
ballen4705 committed
155

156
157
// command-line: name of PID file (empty for no pid file)
static std::string pid_file;
158

159
// command-line: path prefix of persistent state file, empty if no persistence.
160
161
162
163
164
static std::string state_path_prefix
#ifdef SMARTMONTOOLS_SAVESTATES
          = SMARTMONTOOLS_SAVESTATES
#endif
                                    ;
165

166
167
168
169
170
171
172
// command-line: path prefix of attribute log file, empty if no logs.
static std::string attrlog_path_prefix
#ifdef SMARTMONTOOLS_ATTRIBUTELOG
          = SMARTMONTOOLS_ATTRIBUTELOG
#endif
                                    ;

173
// configuration file name
174
175
#define CONFIGFILENAME "smartd.conf"

chrfranke's avatar
chrfranke committed
176
#ifndef _WIN32
177
static const char *configfile = SMARTMONTOOLS_SYSCONFDIR "/" CONFIGFILENAME ;
chrfranke's avatar
chrfranke committed
178
#else
179
static const char *configfile = "./" CONFIGFILENAME ;
chrfranke's avatar
chrfranke committed
180
#endif
181
// configuration file "name" if read from stdin
182
static const char * const configfile_stdin = "<stdin>";
183
184
// path of alternate configuration file
static std::string configfile_alt;
185

186
// command-line: when should we exit?
187
188
static int quit=0;

189
// command-line; this is the default syslog(3) log facility to use.
190
static int facility=LOG_DAEMON;
191

192
193
194
#ifndef _WIN32
// command-line: fork into background?
static bool do_fork=true;
195
196
#endif

197
198
199
200
201
#ifdef HAVE_LIBCAP_NG
// command-line: enable capabilities?
static bool enable_capabilities = false;
#endif

202
// used for control of printing, passing arguments to atacmds.c
203
smartmonctrl *con=NULL;
204

205
206
207
// set to one if we catch a USR1 (check devices now)
volatile int caughtsigUSR1=0;

208
209
210
211
212
#ifdef _WIN32
// set to one if we catch a USR2 (toggle debug mode)
volatile int caughtsigUSR2=0;
#endif

213
// set to one if we catch a HUP (reload config file). In debug mode,
214
// set to two, if we catch INT (also reload config file).
215
216
volatile int caughtsigHUP=0;

217
218
219
// set to signal value if we catch INT, QUIT, or TERM
volatile int caughtsigEXIT=0;

220
221
// Attribute monitoring flags.
// See monitor_attr_flags below.
222
enum {
223
224
225
226
227
228
  MONITOR_IGN_FAILUSE = 0x01,
  MONITOR_IGNORE      = 0x02,
  MONITOR_RAW_PRINT   = 0x04,
  MONITOR_RAW         = 0x08,
  MONITOR_AS_CRIT     = 0x10,
  MONITOR_RAW_AS_CRIT = 0x20,
229
230
};

231
232
233
234
235
236
237
238
// Array of flags for each attribute.
class attribute_flags
{
public:
  attribute_flags()
    { memset(m_flags, 0, sizeof(m_flags)); }

  bool is_set(int id, unsigned char flag) const
239
    { return (0 < id && id < (int)sizeof(m_flags) && (m_flags[id] & flag)); }
240
241
242

  void set(int id, unsigned char flags)
    {
243
      if (0 < id && id < (int)sizeof(m_flags))
244
245
246
247
        m_flags[id] |= flags;
    }

private:
248
  unsigned char m_flags[256];
249
250
251
};


252
253
254
255
256
257
258
/// Configuration data for a device. Read from smartd.conf.
/// Supports copy & assignment and is compatible with STL containers.
struct dev_config
{
  int lineno;                             // Line number of entry in file
  std::string name;                       // Device name
  std::string dev_type;                   // Device type argument from -d directive, empty if none
259
  std::string state_file;                 // Path of the persistent state file, empty if none
260
  std::string attrlog_file;               // Path of the persistent attrlog file, empty if none
261
262
263
264
265
266
267
268
269
  bool smartcheck;                        // Check SMART status
  bool usagefailed;                       // Check for failed Usage Attributes
  bool prefail;                           // Track changes in Prefail Attributes
  bool usage;                             // Track changes in Usage Attributes
  bool selftest;                          // Monitor number of selftest errors
  bool errorlog;                          // Monitor number of ATA errors
  bool permissive;                        // Ignore failed SMART commands
  char autosave;                          // 1=disable, 2=enable Autosave Attributes
  char autoofflinetest;                   // 1=disable, 2=enable Auto Offline Test
270
  unsigned char fix_firmwarebug;          // FIX_*, see atacmds.h
271
272
273
274
275
  bool ignorepresets;                     // Ignore database of -v options
  bool showpresets;                       // Show database entry for this device
  bool removable;                         // Device may disappear (not be present)
  char powermode;                         // skip check, if disk in idle or standby mode
  bool powerquiet;                        // skip powermode 'skipping checks' message
276
  int powerskipmax;                       // how many times can be check skipped
277
278
279
280
281
282
283
284
285
286
287
  unsigned char tempdiff;                 // Track Temperature changes >= this limit
  unsigned char tempinfo, tempcrit;       // Track Temperatures >= these limits as LOG_INFO, LOG_CRIT+mail
  regular_expression test_regex;          // Regex for scheduled testing

  // Configuration of email warning messages
  std::string emailcmdline;               // script to execute, empty if no messages
  std::string emailaddress;               // email address, or empty
  unsigned char emailfreq;                // Emails once (1) daily (2) diminishing (3)
  bool emailtest;                         // Send test email?

  // ATA ONLY
288
289
290
291
292
  unsigned char curr_pending_id;          // ID of current pending sector count, 0 if none
  unsigned char offl_pending_id;          // ID of offline uncorrectable sector count, 0 if none
  bool curr_pending_incr, offl_pending_incr; // True if current/offline pending values increase
  bool curr_pending_set,  offl_pending_set;  // True if '-C', '-U' set in smartd.conf

293
  attribute_flags monitor_attr_flags;     // MONITOR_* flags for each attribute
294

295
  ata_vendor_attr_defs attribute_defs;    // -v options
296
297
298

  dev_config();
};
299

300
dev_config::dev_config()
301
302
303
304
305
306
307
308
309
310
: lineno(0),
  smartcheck(false),
  usagefailed(false),
  prefail(false),
  usage(false),
  selftest(false),
  errorlog(false),
  permissive(false),
  autosave(0),
  autoofflinetest(0),
311
  fix_firmwarebug(FIX_NOTSPECIFIED),
312
313
314
  ignorepresets(false),
  showpresets(false),
  removable(false),
315
  powermode(0),
316
  powerquiet(false),
317
  powerskipmax(0),
318
319
  tempdiff(0),
  tempinfo(0), tempcrit(0),
320
  emailfreq(0),
321
322
323
324
  emailtest(false),
  curr_pending_id(0), offl_pending_id(0),
  curr_pending_incr(false), offl_pending_incr(false),
  curr_pending_set(false),  offl_pending_set(false)
325
326
327
{
}

328
329
330

// Number of allowed mail message types
const int SMARTD_NMAIL = 13;
331
332
333
// Type for '-M test' mails (state not persistent)
const int MAILTYPE_TEST = 0;
// TODO: Add const or enum for all mail types.
334
335
336
337
338
339
340
341
342
343

struct mailinfo {
  int logged;// number of times an email has been sent
  time_t firstsent;// time first email was sent, as defined by time(2)
  time_t lastsent; // time last email was sent, as defined by time(2)

  mailinfo()
    : logged(0), firstsent(0), lastsent(0) { }
};

344
345
/// Persistent state data for a device.
struct persistent_dev_state
346
347
348
349
350
351
{
  unsigned char tempmin, tempmax;         // Min/Max Temperatures

  unsigned char selflogcount;             // total number of self-test errors
  unsigned short selfloghour;             // lifetime hours of last self-test error

352
353
354
355
356
357
358
359
360
361
362
  time_t scheduled_test_next_check;       // Time of next check for scheduled self-tests

  mailinfo maillog[SMARTD_NMAIL];         // log info on when mail sent

  // ATA ONLY
  int ataerrorcount;                      // Total number of ATA errors

  // Persistent part of ata_smart_values:
  struct ata_attribute {
    unsigned char id;
    unsigned char val;
363
    unsigned char worst; // Byte needed for 'raw64' attribute only.
364
365
    uint64_t raw;

366
    ata_attribute() : id(0), val(0), worst(0), raw(0) { }
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
  };
  ata_attribute ata_attributes[NUMBER_ATA_SMART_ATTRIBUTES];

  persistent_dev_state();
};

persistent_dev_state::persistent_dev_state()
: tempmin(0), tempmax(0),
  selflogcount(0),
  selfloghour(0),
  scheduled_test_next_check(0),
  ataerrorcount(0)
{
}

/// Non-persistent state data for a device.
struct temp_dev_state
{
  bool must_write;                        // true if persistent part should be written

387
388
389
390
  bool not_cap_offline;                   // true == not capable of offline testing
  bool not_cap_conveyance;
  bool not_cap_short;
  bool not_cap_long;
391
  bool not_cap_selective;
392
393

  unsigned char temperature;              // last recorded Temperature (in Celsius)
394
  time_t tempmin_delay;                   // time where Min Temperature tracking will start
395
396
397
398
399
400
401
402
403
404
405
406

  bool powermodefail;                     // true if power mode check failed
  int powerskipcnt;                       // Number of checks skipped due to idle or standby mode

  // SCSI ONLY
  unsigned char SmartPageSupported;       // has log sense IE page (0x2f)
  unsigned char TempPageSupported;        // has log sense temperature page (0xd)
  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
407
  uint64_t num_sectors;                   // Number of sectors (for selective self-test only)
408
409
  ata_smart_values smartval;              // SMART data
  ata_smart_thresholds_pvt smartthres;    // SMART thresholds
410

411
  temp_dev_state();
412
};
413

414
415
temp_dev_state::temp_dev_state()
: must_write(false),
416
417
418
419
  not_cap_offline(false),
  not_cap_conveyance(false),
  not_cap_short(false),
  not_cap_long(false),
420
  not_cap_selective(false),
421
  temperature(0),
422
  tempmin_delay(0),
423
  powermodefail(false),
424
425
426
427
  powerskipcnt(0),
  SmartPageSupported(false),
  TempPageSupported(false),
  SuppressReport(false),
428
429
  modese_len(0),
  num_sectors(0)
430
431
432
433
434
{
  memset(&smartval, 0, sizeof(smartval));
  memset(&smartthres, 0, sizeof(smartthres));
}

435
436
437
438
439
440
441
442
/// Runtime state data for a device.
struct dev_state
: public persistent_dev_state,
  public temp_dev_state
{
  void update_persistent_state();
  void update_temp_state();
};
443

444
445
/// Container for configuration info for each device.
typedef std::vector<dev_config> dev_config_vector;
446

447
448
/// Container for state info for each device.
typedef std::vector<dev_state> dev_state_vector;
449

450
451
452
453
454
455
456
457
// Copy ATA attributes to persistent state.
void dev_state::update_persistent_state()
{
  for (int i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) {
    const ata_smart_attribute & ta = smartval.vendor_attributes[i];
    ata_attribute & pa = ata_attributes[i];
    pa.id = ta.id;
    if (ta.id == 0) {
458
      pa.val = pa.worst = 0; pa.raw = 0;
459
460
461
      continue;
    }
    pa.val = ta.current;
462
    pa.worst = ta.worst;
463
464
465
    pa.raw =            ta.raw[0]
           | (          ta.raw[1] <<  8)
           | (          ta.raw[2] << 16)
466
           | ((uint64_t)ta.raw[3] << 24)
467
468
469
470
471
472
473
474
475
476
477
478
479
           | ((uint64_t)ta.raw[4] << 32)
           | ((uint64_t)ta.raw[5] << 40);
  }
}

// Copy ATA from persistent to temp state.
void dev_state::update_temp_state()
{
  for (int i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) {
    const ata_attribute & pa = ata_attributes[i];
    ata_smart_attribute & ta = smartval.vendor_attributes[i];
    ta.id = pa.id;
    if (pa.id == 0) {
480
481
      ta.current = ta.worst = 0;
      memset(ta.raw, 0, sizeof(ta.raw));
482
483
484
      continue;
    }
    ta.current = pa.val;
485
    ta.worst = pa.worst;
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
    ta.raw[0] = (unsigned char) pa.raw;
    ta.raw[1] = (unsigned char)(pa.raw >>  8);
    ta.raw[2] = (unsigned char)(pa.raw >> 16);
    ta.raw[3] = (unsigned char)(pa.raw >> 24);
    ta.raw[4] = (unsigned char)(pa.raw >> 32);
    ta.raw[5] = (unsigned char)(pa.raw >> 40);
  }
}

// Parse a line from a state file.
static bool parse_dev_state_line(const char * line, persistent_dev_state & state)
{
  static regular_expression regex(
    "^ *"
     "((temperature-min)" // (1 (2)
     "|(temperature-max)" // (3)
     "|(self-test-errors)" // (4)
     "|(self-test-last-err-hour)" // (5)
     "|(scheduled-test-next-check)" // (6)
     "|(ata-error-count)"  // (7)
     "|(mail\\.([0-9]+)\\." // (8 (9)
       "((count)" // (10 (11)
       "|(first-sent-time)" // (12)
       "|(last-sent-time)" // (13)
510
       ")" // 10)
511
512
      ")" // 8)
     "|(ata-smart-attribute\\.([0-9]+)\\." // (14 (15)
513
514
515
516
517
       "((id)" // (16 (17)
       "|(val)" // (18)
       "|(worst)" // (19)
       "|(raw)" // (20)
       ")" // 16)
518
519
      ")" // 14)
     ")" // 1)
520
     " *= *([0-9]+)[ \n]*$", // (21)
521
522
523
524
525
    REG_EXTENDED
  );
  if (regex.empty())
    throw std::logic_error("parse_dev_state_line: invalid regex");

526
  const int nmatch = 1+21;
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
  regmatch_t match[nmatch];
  if (!regex.execute(line, nmatch, match))
    return false;
  if (match[nmatch-1].rm_so < 0)
    return false;

  uint64_t val = strtoull(line + match[nmatch-1].rm_so, (char **)0, 10);

  int m = 1;
  if (match[++m].rm_so >= 0)
    state.tempmin = (unsigned char)val;
  else if (match[++m].rm_so >= 0)
    state.tempmax = (unsigned char)val;
  else if (match[++m].rm_so >= 0)
    state.selflogcount = (unsigned char)val;
  else if (match[++m].rm_so >= 0)
    state.selfloghour = (unsigned short)val;
  else if (match[++m].rm_so >= 0)
    state.scheduled_test_next_check = (time_t)val;
  else if (match[++m].rm_so >= 0)
    state.ataerrorcount = (int)val;
  else if (match[m+=2].rm_so >= 0) {
    int i = atoi(line+match[m].rm_so);
    if (!(0 <= i && i < SMARTD_NMAIL))
      return false;
552
553
    if (i == MAILTYPE_TEST) // Don't suppress test mails
      return true;
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
    if (match[m+=2].rm_so >= 0)
      state.maillog[i].logged = (int)val;
    else if (match[++m].rm_so >= 0)
      state.maillog[i].firstsent = (time_t)val;
    else if (match[++m].rm_so >= 0)
      state.maillog[i].lastsent = (time_t)val;
    else
      return false;
  }
  else if (match[m+=5+1].rm_so >= 0) {
    int i = atoi(line+match[m].rm_so);
    if (!(0 <= i && i < NUMBER_ATA_SMART_ATTRIBUTES))
      return false;
    if (match[m+=2].rm_so >= 0)
      state.ata_attributes[i].id = (unsigned char)val;
    else if (match[++m].rm_so >= 0)
      state.ata_attributes[i].val = (unsigned char)val;
571
572
    else if (match[++m].rm_so >= 0)
      state.ata_attributes[i].worst = (unsigned char)val;
573
574
575
576
577
578
579
580
581
582
583
584
585
    else if (match[++m].rm_so >= 0)
      state.ata_attributes[i].raw = val;
    else
      return false;
  }
  else
    return false;
  return true;
}

// Read a state file.
static bool read_dev_state(const char * path, persistent_dev_state & state)
{
chrfranke's avatar
chrfranke committed
586
  stdio_file f(path, "r");
587
588
589
590
591
  if (!f) {
    if (errno != ENOENT)
      pout("Cannot read state file \"%s\"\n", path);
    return false;
  }
chrfranke's avatar
chrfranke committed
592
593
594
#ifdef __CYGWIN__
  setmode(fileno(f), O_TEXT); // Allow files with \r\n
#endif
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634

  int good = 0, bad = 0;
  char line[256];
  while (fgets(line, sizeof(line), f)) {
    const char * s = line + strspn(line, " \t");
    if (!*s || *s == '#')
      continue;
    if (!parse_dev_state_line(line, state))
      bad++;
    else
      good++;
  }

  if (bad) {
    if (!good) {
      pout("%s: format error\n", path);
      return false;
    }
    pout("%s: %d invalid line(s) ignored\n", path, bad);
  }
  return true;
}

static void write_dev_state_line(FILE * f, const char * name, uint64_t val)
{
  if (val)
    fprintf(f, "%s = %"PRIu64"\n", name, val);
}

static void write_dev_state_line(FILE * f, const char * name1, int id, const char * name2, uint64_t val)
{
  if (val)
    fprintf(f, "%s.%d.%s = %"PRIu64"\n", name1, id, name2, val);
}

// Write a state file
static bool write_dev_state(const char * path, const persistent_dev_state & state)
{
  // Rename old "file" to "file~"
  std::string pathbak = path; pathbak += '~';
chrfranke's avatar
chrfranke committed
635
  unlink(pathbak.c_str());
636
637
  rename(path, pathbak.c_str());

chrfranke's avatar
chrfranke committed
638
  stdio_file f(path, "w");
639
640
641
642
643
644
645
646
647
648
649
650
651
652
  if (!f) {
    pout("Cannot create state file \"%s\"\n", path);
    return false;
  }

  fprintf(f, "# smartd state file\n");
  write_dev_state_line(f, "temperature-min", state.tempmin);
  write_dev_state_line(f, "temperature-max", state.tempmax);
  write_dev_state_line(f, "self-test-errors", state.selflogcount);
  write_dev_state_line(f, "self-test-last-err-hour", state.selfloghour);
  write_dev_state_line(f, "scheduled-test-next-check", state.scheduled_test_next_check);

  int i;
  for (i = 0; i < SMARTD_NMAIL; i++) {
653
654
    if (i == MAILTYPE_TEST) // Don't suppress test mails
      continue;
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
    const mailinfo & mi = state.maillog[i];
    if (!mi.logged)
      continue;
    write_dev_state_line(f, "mail", i, "count", mi.logged);
    write_dev_state_line(f, "mail", i, "first-sent-time", mi.firstsent);
    write_dev_state_line(f, "mail", i, "last-sent-time", mi.lastsent);
  }

  // ATA ONLY
  write_dev_state_line(f, "ata-error-count", state.ataerrorcount);

  for (i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) {
    const persistent_dev_state::ata_attribute & pa = state.ata_attributes[i];
    if (!pa.id)
      continue;
    write_dev_state_line(f, "ata-smart-attribute", i, "id", pa.id);
    write_dev_state_line(f, "ata-smart-attribute", i, "val", pa.val);
672
    write_dev_state_line(f, "ata-smart-attribute", i, "worst", pa.worst);
673
674
675
676
677
678
    write_dev_state_line(f, "ata-smart-attribute", i, "raw", pa.raw);
  }

  return true;
}

679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
// 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;
}

705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
// 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,
                                 dev_state_vector & states,
                                 bool write_always = true)
{
  for (unsigned i = 0; i < states.size(); i++) {
    const dev_config & cfg = configs.at(i);
    if (cfg.state_file.empty())
      continue;
    dev_state & state = states[i];
    if (!write_always && !state.must_write)
      continue;
    if (!write_dev_state(cfg.state_file.c_str(), state))
      continue;
    state.must_write = false;
    if (write_always || debugmode)
      PrintOut(LOG_INFO, "Device: %s, state written to %s\n",
               cfg.name.c_str(), cfg.state_file.c_str());
  }
}

727
728
729
730
731
732
733
734
735
736
737
738
739
// 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);
  }
}

740
// remove the PID file
741
void RemovePidFile(){
742
743
  if (!pid_file.empty()) {
    if (unlink(pid_file.c_str()))
744
      PrintOut(LOG_CRIT,"Can't unlink PID file %s (%s).\n", 
745
746
               pid_file.c_str(), strerror(errno));
    pid_file.clear();
747
748
749
750
  }
  return;
}

751
extern "C" { // signal handlers require C-linkage
chrfranke's avatar
chrfranke committed
752

753
754
//  Note if we catch a SIGUSR1
void USR1handler(int sig){
755
756
  if (SIGUSR1==sig)
    caughtsigUSR1=1;
757
758
  return;
}
ballen4705's avatar
ballen4705 committed
759

760
761
762
763
764
765
766
767
768
#ifdef _WIN32
//  Note if we catch a SIGUSR2
void USR2handler(int sig){
  if (SIGUSR2==sig)
    caughtsigUSR2=1;
  return;
}
#endif

769
770
771
772
773
774
775
776
// Note if we catch a HUP (or INT in debug mode)
void HUPhandler(int sig){
  if (sig==SIGHUP)
    caughtsigHUP=1;
  else
    caughtsigHUP=2;
  return;
}
ballen4705's avatar
ballen4705 committed
777

778
// signal handler for TERM, QUIT, and INT (if not in debug mode)
779
void sighandler(int sig){
780
781
782
  if (!caughtsigEXIT)
    caughtsigEXIT=sig;
  return;
783
784
}

785
} // extern "C"
chrfranke's avatar
chrfranke committed
786

787
788
789
// Cleanup, print Goodbye message and remove pidfile
static int Goodbye(int status)
{
790
  // delete PID file, if one was created
791
  RemovePidFile();
792

793
  // if we are exiting because of a code bug, tell user
794
  if (status==EXIT_BADCODE)
795
796
        PrintOut(LOG_CRIT, "Please inform " PACKAGE_BUGREPORT ", including output of smartd -V.\n");

797
  // and this should be the final output from smartd before it exits
798
  PrintOut(status?LOG_CRIT:LOG_INFO, "smartd is exiting (exit status %d)\n", status);
799

800
  return status;
801
802
}

803
#define ENVLENGTH 1024
804
805
806
807
808
809
810

// 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
811
// as long as the popen() call is underway.
812
813
814
815
int exportenv(char* stackspace, const char *name, const char *value){
  snprintf(stackspace,ENVLENGTH, "%s=%s", name, value);
  return putenv(stackspace);
}
ballen4705's avatar
ballen4705 committed
816

ballen4705's avatar
ballen4705 committed
817
818
char* dnsdomain(const char* hostname) {
  char *p = NULL;
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
#ifdef HAVE_GETADDRINFO
  static char canon_name[NI_MAXHOST];
  struct addrinfo *info = NULL;
  struct addrinfo hints;
  int err;

  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
ballen4705's avatar
ballen4705 committed
840
841
  struct hostent *hp;
  if ((hp = gethostbyname(hostname))) {
842
843
    // Does this work if gethostbyname() returns an IPv6 name in
    // colon/dot notation?  [BA]
ballen4705's avatar
ballen4705 committed
844
845
846
    if ((p = strchr(hp->h_name, '.')))
      p++; // skip "."
  }
847
848
#else
  ARGUSED(hostname);
guidog's avatar
guidog committed
849
#endif
ballen4705's avatar
ballen4705 committed
850
  return p;
guidog's avatar
guidog committed
851
852
}

853
854
#define EBUFLEN 1024

855
856
857
static void MailWarning(const dev_config & cfg, dev_state & state, int which, const char *fmt, ...)
                        __attribute__ ((format (printf, 4, 5)));

858
859
// If either address or executable path is non-null then send and log
// a warning email, or execute executable
860
static void MailWarning(const dev_config & cfg, dev_state & state, int which, const char *fmt, ...){
861
  char command[2048], message[256], hostname[256], domainname[256], additional[256],fullmessage[1024];
guidog's avatar
guidog committed
862
  char original[256], further[256], nisdomain[256], subject[256],dates[DATEANDEPOCHLEN];
863
  char environ_strings[11][ENVLENGTH];
864
  time_t epoch;
865
  va_list ap;
866
867
  const int day=24*3600;
  int days=0;
868
  const char * const whichfail[]={
ballen4705's avatar
ballen4705 committed
869
870
871
872
873
874
875
876
    "EmailTest",                  // 0
    "Health",                     // 1
    "Usage",                      // 2
    "SelfTest",                   // 3
    "ErrorCount",                 // 4
    "FailedHealthCheck",          // 5
    "FailedReadSmartData",        // 6
    "FailedReadSmartErrorLog",    // 7
ballen4705's avatar
Typo    
ballen4705 committed
877
    "FailedReadSmartSelfTestLog", // 8
ballen4705's avatar
ballen4705 committed
878
    "FailedOpenDevice",           // 9
879
    "CurrentPendingSector",       // 10
880
881
    "OfflineUncorrectableSector", // 11
    "Temperature"                 // 12
882
883
  };
  
884
  const char *unknown="[Unknown]";
885

886
  // See if user wants us to send mail
887
  if (cfg.emailaddress.empty() && cfg.emailcmdline.empty())
888
    return;
889

890
891
892
  std::string address = cfg.emailaddress;
  const char * executable = cfg.emailcmdline.c_str();

893
  // which type of mail are we sending?
894
  mailinfo * mail=(state.maillog)+which;
895

896
  // checks for sanity
897
898
  if (cfg.emailfreq<1 || cfg.emailfreq>3) {
    PrintOut(LOG_CRIT,"internal error in MailWarning(): cfg.mailwarn->emailfreq=%d\n",cfg.emailfreq);
899
900
    return;
  }
901
902
  if (which<0 || which>=SMARTD_NMAIL || sizeof(whichfail)!=SMARTD_NMAIL*sizeof(char *)) {
    PrintOut(LOG_CRIT,"Contact " PACKAGE_BUGREPORT "; internal error in MailWarning(): which=%d, size=%d\n",
903
             which, (int)sizeof(whichfail));
904
905
    return;
  }
906
907
  
  // Return if a single warning mail has been sent.
908
  if ((cfg.emailfreq==1) && mail->logged)
909
    return;
910
911
912
913

  // Return if this is an email test and one has already been sent.
  if (which == 0 && mail->logged)
    return;
914
915
916
917
918
  
  // To decide if to send mail, we need to know what time it is.
  epoch=time(NULL);

  // Return if less than one day has gone by
919
  if (cfg.emailfreq==2 && mail->logged && epoch<(mail->lastsent+day))
920
921
922
    return;

  // Return if less than 2^(logged-1) days have gone by
923
  if (cfg.emailfreq==3 && mail->logged) {
924
925
926
927
928
929
    days=0x01<<(mail->logged-1);
    days*=day;
    if  (epoch<(mail->lastsent+days))
      return;
  }

930
931
932
933
934
935
936
937
#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

938
939
940
941
  // record the time of this mail message, and the first mail message
  if (!mail->logged)
    mail->firstsent=epoch;
  mail->lastsent=epoch;
942
  
943
  // get system host & domain names (not null terminated if length=MAX) 
944
#ifdef HAVE_GETHOSTNAME
945
  if (gethostname(hostname, 256))
946
    strcpy(hostname, unknown);
guidog's avatar
guidog committed
947
  else {
ballen4705's avatar
ballen4705 committed
948
    char *p=NULL;
949
    hostname[255]='\0';
ballen4705's avatar
ballen4705 committed
950
    p = dnsdomain(hostname);
951
    if (p && *p) {
ballen4705's avatar
ballen4705 committed
952
953
      strncpy(domainname, p, 255);
      domainname[255]='\0';
guidog's avatar
guidog committed
954
    } else
955
      strcpy(domainname, unknown);
guidog's avatar
guidog committed
956
  }
957
#else
958
  strcpy(hostname, unknown);
959
  strcpy(domainname, unknown);
960
#endif
ballen4705's avatar
ballen4705 committed
961
  
962
#ifdef HAVE_GETDOMAINNAME
guidog's avatar
guidog committed
963
  if (getdomainname(nisdomain, 256))
964
    strcpy(nisdomain, unknown);
965
  else
guidog's avatar
guidog committed
966
    nisdomain[255]='\0';
967
#else
968
  strcpy(nisdomain, unknown);
969
#endif
970
  
971
972
973
974
  // print warning string into message
  va_start(ap, fmt);
  vsnprintf(message, 256, fmt, ap);
  va_end(ap);
975
976
977
978
979
980

  // 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");

981
    switch (cfg.emailfreq) {
982
983
984
985
    case 1:
      sprintf(additional,"No additional email messages about this problem will be sent.\n");
      break;
    case 2:
ballen4705's avatar
ballen4705 committed
986
      sprintf(additional,"Another email message will be sent in 24 hours if the problem persists.\n");
987
988
989
      break;
    case 3:
      sprintf(additional,"Another email message will be sent in %d days if the problem persists\n",
990
              (0x01)<<mail->logged);
991
992
      break;
    }
993
    if (cfg.emailfreq>1 && mail->logged) {
994
995
996
      dateandtimezoneepoch(dates, mail->firstsent);
      sprintf(original,"The original email about this issue was sent at %s\n", dates);
    }
997
  }
998
  
999
  snprintf(subject, 256,"SMART error (%s) detected on host: %s", whichfail[which], hostname);
1000

1001
  // If the user has set cfg.emailcmdline, use that as mailer, else "mail" or "mailx".
1002
  if (!*executable)
1003
1004
#ifdef DEFAULT_MAILER
    executable = DEFAULT_MAILER ;
1005
#else
1006
#ifndef _WIN32
1007
    executable = "mail";
1008
1009
1010
#else
    executable = "blat"; // http://blat.sourceforge.net/
#endif
1011
1012
#endif

1013
#ifndef _WIN32 // blat mailer needs comma
1014
1015
  // replace commas by spaces to separate recipients
  std::replace(address.begin(), address.end(), ',', ' ');
1016
#endif
1017
1018
  // Export information in environment variables that will be useful
  // for user scripts
1019
1020
1021
  exportenv(environ_strings[0], "SMARTD_MAILER", executable);
  exportenv(environ_strings[1], "SMARTD_MESSAGE", message);
  exportenv(environ_strings[2], "SMARTD_SUBJECT", subject);
1022
  dateandtimezoneepoch(dates, mail->firstsent);
1023
  exportenv(environ_strings[3], "SMARTD_TFIRST", dates);
1024
  snprintf(dates, DATEANDEPOCHLEN,"%d", (int)mail->firstsent);
1025
1026
  exportenv(environ_strings[4], "SMARTD_TFIRSTEPOCH", dates);
  exportenv(environ_strings[5], "SMARTD_FAILTYPE", whichfail[which]);
1027
1028
  if (!address.empty())
    exportenv(environ_strings[6], "SMARTD_ADDRESS", address.c_str());
1029
  exportenv(environ_strings[7], "SMARTD_DEVICESTRING", cfg.name.c_str());
1030

1031
1032
  exportenv(environ_strings[8], "SMARTD_DEVICETYPE", cfg.dev_type.c_str());
  exportenv(environ_strings[9], "SMARTD_DEVICE", cfg.name.c_str());
1033

1034
  snprintf(fullmessage, 1024,
1035
             "This email was generated by the smartd daemon running on:\n\n"
1036
             "   host name: %s\n"
1037
             "  DNS domain: %s\n"
1038
             "  NIS domain: %s\n\n"
1039
             "The following warning/error was logged by the smartd daemon:\n\n"
1040
             "%s\n\n"
1041
             "For details see host's SYSLOG (default: /var/log/messages).\n\n"
1042
1043
1044
1045
1046
1047
             "%s%s%s",
	     hostname, domainname, nisdomain, message, further, original, additional);
  exportenv(environ_strings[10], "SMARTD_FULLMESSAGE", fullmessage);

  // now construct a command to send this as EMAIL
#ifndef _WIN32
1048
  if (!address.empty())
1049
1050
    snprintf(command, 2048, 
             "$SMARTD_MAILER -s '%s' %s 2>&1 << \"ENDMAIL\"\n"
1051
	     "%sENDMAIL\n", subject, address.c_str(), fullmessage);
1052
  else
1053
    snprintf(command, 2048, "%s 2>&1", executable);
1054
  
1055
  // tell SYSLOG what we are about to do...
1056
1057
  const char * newadd = (!address.empty()? address.c_str() : "<nomailer>");
  const char * newwarn = (which? "Warning via" : "Test of");
1058

1059
  PrintOut(LOG_INFO,"%s %s to %s ...\n",