#include #include #include #include #include #include #include #include #include #include "configfile.h" #include "event.h" #include "list.h" #include "logging.h" #include "sgio.h" int verbose = 0; int prefer_ata12 = 0; #define F_PRESENT 0x0001 #define F_STANDBY 0x0002 struct disk_entry { struct list_head list; const char device[16]; unsigned int timeout; int flags; unsigned int sectors_rd; unsigned int sectors_wr; struct event_timeout *timeout_event; }; static LIST_HEAD(disk_list); static struct event_timeout *check_timeout; static int diskto_timeout_cb(void *privdata) { struct disk_entry *entry = (struct disk_entry *) privdata; entry->timeout_event = NULL; if (!(entry->flags & F_PRESENT)) return -1; int fd = open(entry->device, O_RDONLY | O_NONBLOCK); if (fd < 0) { log_print(LOG_WARN, "%s: failed to open %s", __FUNCTION__, entry->device); return -1; } log_print(LOG_INFO, "disktimeout: %s standby after %ds", entry->device, entry->timeout); unsigned char args[4] = { ATA_OP_STANDBYNOW1, 0, 0, 0}; if (do_drive_cmd(fd, args)) { args[0] = ATA_OP_STANDBYNOW2; if (do_drive_cmd(fd, args)) { log_print(LOG_WARN, "%s: do_drive_cmd(ATA_OP_STANDBYNOW) failed on %s", __FUNCTION__, entry->device); close(fd); return -1; } } entry->flags |= F_STANDBY; close(fd); return -1; } static int diskto_add_disk(struct strtoken *tokens, void *privdata) { if (tokens->count != 2) { log_print(LOG_WARN, "%s(): invalid config line '%s'", __FUNCTION__, tokens->input); return -1; } struct disk_entry *entry = malloc(sizeof(struct disk_entry)); if (entry == NULL) { log_print(LOG_WARN, "%s(): out of memory", __FUNCTION__); return -1; } strncpy((char *)entry->device, tokens->field[0], sizeof(entry->device)); char *tmp; entry->timeout = strtol(tokens->field[1], &tmp, 0); if (*tmp != '\0' && !isspace(*tmp)) { log_print(LOG_WARN, "%s(): invalid timeout value '%s'", __FUNCTION__, tokens->field[1]); free(entry); return -1; } entry->flags = 0; entry->sectors_rd = 0; entry->sectors_wr = 0; struct timeval tv = { .tv_sec = entry->timeout }; entry->timeout_event = event_add_timeout(&tv, diskto_timeout_cb, (void *)entry); log_print(LOG_INFO, "disktimeout: watching '%s' (standby timeout: %ds)", entry->device, entry->timeout); list_add_tail(&entry->list, &disk_list); return 0; } static int diskto_check_cb(void *privdata) { struct disk_entry *entry; list_for_each_entry(entry, &disk_list, list) { entry->flags &= ~(F_PRESENT); } FILE *fp = fopen("/proc/diskstats", "r"); if (fp == NULL) { log_print(LOG_WARN, "%s(): fopen()", __FUNCTION__); return -1; } char buffer[256]; while (fgets(buffer, sizeof(buffer), fp) != NULL) { int major, minor; char device[16]; unsigned int val[11]; /* field - description * 1 - major number * 2 - minor mumber * 3 - device name * 4 - reads completed successfully * 5 - reads merged * 6 - sectors read * 7 - time spent reading (ms) * 8 - writes completed * 9 - writes merged * 10 - sectors written * 11 - time spent writing (ms) * 12 - I/Os currently in progress * 13 - time spent doing I/Os (ms) * 14 - weighted time spent doing I/Os (ms) */ int cnt = sscanf(buffer, "%d %d %s %u %u %u %u %u %u %u %u %u %u %u", &major, &minor, device, &val[0], &val[1], &val[2], &val[3], &val[4], &val[5], &val[6], &val[7], &val[8], &val[9], &val[10]); if (cnt != 14) { log_print(LOG_WARN, "%s(): invalid diskstat line: '%s'", __FUNCTION__, buffer); continue; } struct disk_entry *entry; list_for_each_entry(entry, &disk_list, list) { char *dev = strrchr(entry->device, '/'); if (dev == NULL || *(dev +1) == '\0') continue; if (strncmp(dev +1, device, strlen(device)) != 0) continue; entry->flags |= F_PRESENT; if (entry->sectors_rd == val[2] && entry->sectors_wr == val[6]) continue; entry->flags &= ~(F_STANDBY); entry->sectors_rd = val[2]; entry->sectors_wr = val[6]; /* remove old timeout */ if (entry->timeout_event != NULL) event_remove_timeout(entry->timeout_event); /* set new one */ struct timeval tv = { .tv_sec = entry->timeout }; entry->timeout_event = event_add_timeout(&tv, diskto_timeout_cb, (void *)entry); } } fclose(fp); list_for_each_entry(entry, &disk_list, list) { if (!(entry->flags & F_PRESENT)) continue; int fd = open(entry->device, O_RDONLY | O_NONBLOCK); if (fd < 0) { log_print(LOG_ERROR, "%s(): failed to open %s", __FUNCTION__, entry->device); continue; } unsigned char args[4] = { ATA_OP_CHECKPOWERMODE1, 0, 0, 0 }; if (do_drive_cmd(fd, args)) { args[0] = ATA_OP_CHECKPOWERMODE2; if (do_drive_cmd(fd, args)) { log_print(LOG_WARN, "%s: do_drive_cmd(ATA_OP_CHECKPOWERMODE) failed on %s", __FUNCTION__, entry->device); close(fd); continue; } } /* args[2]: * 0x00 - standby * 0x40 - NVcache_spindown * 0x41 - NVcache_spinup * 0x80 - idle * 0xFF - active/idle */ if (args[2] != 0x00) { if (entry->flags & F_STANDBY) { log_print(LOG_INFO, "disktimeout: %s is awake, starting timer", entry->device, entry->timeout); /* device is not in standby mode, start timer */ if (entry->timeout_event == NULL) { struct timeval tv = { .tv_sec = entry->timeout }; entry->timeout_event = event_add_timeout(&tv, diskto_timeout_cb, (void *)entry); } entry->flags &= ~(F_STANDBY); } } else { if (!(entry->flags & F_STANDBY)) { log_print(LOG_INFO, "disktimeout: %s standby, stopping timer", entry->device, entry->timeout); /* device is in standby, stop timer */ if (entry->timeout_event != NULL) event_remove_timeout(entry->timeout_event); entry->flags |= F_STANDBY; } } } return 0; } int disktimeout_exit(void) { struct disk_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &disk_list, list) { if (entry->timeout_event != NULL) { event_remove_timeout(entry->timeout_event); entry->timeout_event = NULL; } list_del(&entry->list); free(entry); } if (check_timeout != NULL) { event_remove_timeout(check_timeout); check_timeout = NULL; } return 0; } int disktimeout_init(void) { int diskcount = config_get_strtokens("disktimeout", "disk", ",", 2, diskto_add_disk, NULL); if (diskcount <= 0) return -1; int check_interval = 0; config_get_int("disktimeout", "check_interval", &check_interval, 60); struct timeval tv = { .tv_sec = check_interval }; check_timeout = event_add_timeout(&tv, diskto_check_cb, NULL); return 0; }