QNAP-TS419p system daemon
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

340 lines
8.9 KiB

/***************************************************************************
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#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;
}