292 lines
8.0 KiB
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;
|
|
}
|