/*************************************************************************** * Copyright (C) 05/2011 by Olaf Rempel * * razzor@kopf-tisch.de * * * * 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 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "configfile.h" #include "diskwatch.h" #include "event.h" #include "lcd.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[32]; unsigned int timeout_sec; int flags; time_t standby_timeout; unsigned int sectors_rd; unsigned int sectors_wr; }; struct diskwatch { struct list_head disk_list; struct lcddev *lcd; struct event_timeout *check_timeout; }; static struct diskwatch diskwatch_glob; static int diskwatch_check_standby(struct disk_entry *entry); static int lcdpage_diskwatch(struct lcddev *lcd, int event, void *privdata) { static struct disk_entry *entry; struct diskwatch *dwatch = (struct diskwatch *)privdata; if (entry == NULL) { if (list_empty(&dwatch->disk_list)) { return LCDPAGE_COMMAND_NEXT; } entry = list_entry(dwatch->disk_list.next, struct disk_entry, list); } switch (event) { case LCDPAGE_EVENT_BUTTON1: if (entry->list.next == &dwatch->disk_list) { entry = NULL; return LCDPAGE_COMMAND_NEXT; } entry = list_entry(entry->list.next, struct disk_entry, list); lcd_set_backlight(lcd, 1); break; case LCDPAGE_EVENT_ENTER: entry = list_entry(dwatch->disk_list.next, struct disk_entry, list); lcd_set_backlight(lcd, 1); break; case LCDPAGE_EVENT_BACKLIGHT: case LCDPAGE_EVENT_EXIT: return 0; default: break; } char line1[32]; char line2[32]; snprintf(line1, sizeof(line1), "DISK(%s):", entry->device); if (entry->flags & F_PRESENT) { diskwatch_check_standby(entry); if (entry->flags & F_STANDBY) { snprintf(line2, sizeof(line2), "STANDBY"); } else { int timeout = entry->standby_timeout - time(NULL); int hours = timeout / 3600; timeout = timeout % 3600; int minutes = timeout / 60; timeout = timeout % 60; snprintf(line2, sizeof(line2), "RUN -%02d:%02d:%02d", hours, minutes, timeout); } } else { snprintf(line2, sizeof(line2), "not present"); } lcd_setlines(lcd, line1, line2); return 1000; } static int diskwatch_check_standby(struct disk_entry *entry) { if (!(entry->flags & F_PRESENT)) return 0; int fd = open(entry->device, O_RDONLY | O_NONBLOCK); if (fd < 0) { log_print(LOG_ERROR, "%s(): failed to open %s", __FUNCTION__, entry->device); return -1; } 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_CHECKPOWERMODEx) failed on %s", __FUNCTION__, entry->device); close(fd); return -1; } } time_t now = time(NULL); /* args[2]: * 0x00 - standby * 0x40 - NVcache_spindown * 0x41 - NVcache_spinup * 0x80 - idle * 0xFF - active/idle */ /* drive is in standby */ if (args[2] == 0x00) { entry->flags |= F_STANDBY; /* drive was in standby, and is now active */ } else if (entry->flags & F_STANDBY) { entry->flags &= ~(F_STANDBY); entry->standby_timeout = now + entry->timeout_sec; /* drive is active, and timeout is up */ } else if (now >= entry->standby_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_STANDBYNOWx) failed on %s", __FUNCTION__, entry->device); close(fd); return -1; } } entry->flags |= F_STANDBY; } close(fd); return 0; } static int diskwatch_check_stats(int timerid, void *privdata) { struct diskwatch *dwatch = (struct diskwatch *)privdata; struct disk_entry *entry; list_for_each_entry(entry, &dwatch->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; } time_t now = time(NULL); char buffer[256]; while (fgets(buffer, sizeof(buffer), fp) != NULL) { int major, minor; char device[32]; 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, &dwatch->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; /* sector counts changed, disk is not in standby */ entry->flags &= ~(F_STANDBY); entry->sectors_rd = val[2]; entry->sectors_wr = val[6]; entry->standby_timeout = now + entry->timeout_sec; } } fclose(fp); list_for_each_entry(entry, &dwatch->disk_list, list) { diskwatch_check_standby(entry); } return 0; } static int diskwatch_add_disk(struct strtoken *tokens, void *privdata) { struct diskwatch *dwatch = (struct diskwatch *)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; } memset(entry, 0x00, sizeof(struct disk_entry)); strncpy((char *)entry->device, tokens->field[0], sizeof(entry->device)); char *tmp; entry->timeout_sec = 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; } log_print(LOG_INFO, "diskwatch: watching '%s' (standby timeout: %ds)", entry->device, entry->timeout_sec); list_add_tail(&entry->list, &dwatch->disk_list); return 0; } void diskwatch_exit(void) { struct diskwatch *dwatch = &diskwatch_glob; struct disk_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &dwatch->disk_list, list) { list_del(&entry->list); free(entry); } if (dwatch->check_timeout != NULL) { event_remove_timeout(dwatch->check_timeout); dwatch->check_timeout = NULL; } } int diskwatch_init(struct lcddev *lcd) { struct diskwatch *dwatch = &diskwatch_glob; INIT_LIST_HEAD(&dwatch->disk_list); int diskcount = config_get_strtokens("diskwatch", "disk", ",", 2, diskwatch_add_disk, dwatch); if (diskcount <= 0) return 0; diskwatch_check_stats(0, dwatch); int check_interval = 0; config_get_int("diskwatch", "check_interval", &check_interval, 60); dwatch->check_timeout = event_add_timeout_ms(check_interval * 1000, diskwatch_check_stats, 0, dwatch); dwatch->lcd = lcd; if (dwatch->lcd != NULL) { lcd_addpage_cb(dwatch->lcd, 150, lcdpage_diskwatch, dwatch); } return 0; }