qnapd/disktimeout.c

292 lines
8.0 KiB
C

/***************************************************************************
* 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 "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;
}
}
close(fd);
/* 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 diskcount;
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;
}