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.

341 lines
8.9 KiB

  1. /***************************************************************************
  2. * Copyright (C) 05/2011 by Olaf Rempel *
  3. * razzor@kopf-tisch.de *
  4. * *
  5. * This program is free software; you can redistribute it and/or modify *
  6. * it under the terms of the GNU General Public License as published by *
  7. * the Free Software Foundation; either version 2 of the License, or *
  8. * (at your option) any later version. *
  9. * *
  10. * This program is distributed in the hope that it will be useful, *
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  13. * GNU General Public License for more details. *
  14. * *
  15. * You should have received a copy of the GNU General Public License *
  16. * along with this program; if not, write to the *
  17. * Free Software Foundation, Inc., *
  18. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
  19. ***************************************************************************/
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <unistd.h>
  23. #include <string.h>
  24. #include <sys/types.h>
  25. #include <sys/stat.h>
  26. #include <fcntl.h>
  27. #include <ctype.h>
  28. #include <time.h>
  29. #include "configfile.h"
  30. #include "diskwatch.h"
  31. #include "event.h"
  32. #include "lcd.h"
  33. #include "list.h"
  34. #include "logging.h"
  35. #include "sgio.h"
  36. int verbose = 0;
  37. int prefer_ata12 = 0;
  38. #define F_PRESENT 0x0001
  39. #define F_STANDBY 0x0002
  40. struct disk_entry {
  41. struct list_head list;
  42. const char device[32];
  43. unsigned int timeout_sec;
  44. int flags;
  45. time_t standby_timeout;
  46. unsigned int sectors_rd;
  47. unsigned int sectors_wr;
  48. };
  49. struct diskwatch {
  50. struct list_head disk_list;
  51. struct lcddev *lcd;
  52. struct event_timeout *check_timeout;
  53. };
  54. static struct diskwatch diskwatch_glob;
  55. static int diskwatch_check_standby(struct disk_entry *entry);
  56. static int lcdpage_diskwatch(struct lcddev *lcd, int event, void *privdata)
  57. {
  58. static struct disk_entry *entry;
  59. struct diskwatch *dwatch = (struct diskwatch *)privdata;
  60. if (entry == NULL) {
  61. if (list_empty(&dwatch->disk_list)) {
  62. return LCDPAGE_COMMAND_NEXT;
  63. }
  64. entry = list_entry(dwatch->disk_list.next, struct disk_entry, list);
  65. }
  66. switch (event) {
  67. case LCDPAGE_EVENT_BUTTON1:
  68. if (entry->list.next == &dwatch->disk_list) {
  69. entry = NULL;
  70. return LCDPAGE_COMMAND_NEXT;
  71. }
  72. entry = list_entry(entry->list.next, struct disk_entry, list);
  73. lcd_set_backlight(lcd, 1);
  74. break;
  75. case LCDPAGE_EVENT_ENTER:
  76. entry = list_entry(dwatch->disk_list.next, struct disk_entry, list);
  77. lcd_set_backlight(lcd, 1);
  78. break;
  79. case LCDPAGE_EVENT_BACKLIGHT:
  80. case LCDPAGE_EVENT_EXIT:
  81. return 0;
  82. default:
  83. break;
  84. }
  85. char line1[32];
  86. char line2[32];
  87. snprintf(line1, sizeof(line1), "DISK(%s):", entry->device);
  88. if (entry->flags & F_PRESENT) {
  89. diskwatch_check_standby(entry);
  90. if (entry->flags & F_STANDBY) {
  91. snprintf(line2, sizeof(line2), "STANDBY");
  92. } else {
  93. int timeout = entry->standby_timeout - time(NULL);
  94. int hours = timeout / 3600;
  95. timeout = timeout % 3600;
  96. int minutes = timeout / 60;
  97. timeout = timeout % 60;
  98. snprintf(line2, sizeof(line2), "RUN -%02d:%02d:%02d", hours, minutes, timeout);
  99. }
  100. } else {
  101. snprintf(line2, sizeof(line2), "not present");
  102. }
  103. lcd_setlines(lcd, line1, line2);
  104. return 1000;
  105. }
  106. static int diskwatch_check_standby(struct disk_entry *entry)
  107. {
  108. if (!(entry->flags & F_PRESENT))
  109. return 0;
  110. int fd = open(entry->device, O_RDONLY | O_NONBLOCK);
  111. if (fd < 0) {
  112. log_print(LOG_ERROR, "%s(): failed to open %s", __FUNCTION__, entry->device);
  113. return -1;
  114. }
  115. unsigned char args[4] = { ATA_OP_CHECKPOWERMODE1, 0, 0, 0 };
  116. if (do_drive_cmd(fd, args)) {
  117. args[0] = ATA_OP_CHECKPOWERMODE2;
  118. if (do_drive_cmd(fd, args)) {
  119. log_print(LOG_WARN, "%s: do_drive_cmd(ATA_OP_CHECKPOWERMODEx) failed on %s", __FUNCTION__, entry->device);
  120. close(fd);
  121. return -1;
  122. }
  123. }
  124. time_t now = time(NULL);
  125. /* args[2]:
  126. * 0x00 - standby
  127. * 0x40 - NVcache_spindown
  128. * 0x41 - NVcache_spinup
  129. * 0x80 - idle
  130. * 0xFF - active/idle
  131. */
  132. /* drive is in standby */
  133. if (args[2] == 0x00) {
  134. entry->flags |= F_STANDBY;
  135. /* drive was in standby, and is now active */
  136. } else if (entry->flags & F_STANDBY) {
  137. entry->flags &= ~(F_STANDBY);
  138. entry->standby_timeout = now + entry->timeout_sec;
  139. /* drive is active, and timeout is up */
  140. } else if (now >= entry->standby_timeout) {
  141. unsigned char args[4] = { ATA_OP_STANDBYNOW1, 0, 0, 0};
  142. if (do_drive_cmd(fd, args)) {
  143. args[0] = ATA_OP_STANDBYNOW2;
  144. if (do_drive_cmd(fd, args)) {
  145. log_print(LOG_WARN, "%s: do_drive_cmd(ATA_OP_STANDBYNOWx) failed on %s", __FUNCTION__, entry->device);
  146. close(fd);
  147. return -1;
  148. }
  149. }
  150. entry->flags |= F_STANDBY;
  151. }
  152. close(fd);
  153. return 0;
  154. }
  155. static int diskwatch_check_stats(int timerid, void *privdata)
  156. {
  157. struct diskwatch *dwatch = (struct diskwatch *)privdata;
  158. struct disk_entry *entry;
  159. list_for_each_entry(entry, &dwatch->disk_list, list) {
  160. entry->flags &= ~(F_PRESENT);
  161. }
  162. FILE *fp = fopen("/proc/diskstats", "r");
  163. if (fp == NULL) {
  164. log_print(LOG_WARN, "%s(): fopen()", __FUNCTION__);
  165. return -1;
  166. }
  167. time_t now = time(NULL);
  168. char buffer[256];
  169. while (fgets(buffer, sizeof(buffer), fp) != NULL) {
  170. int major, minor;
  171. char device[32];
  172. unsigned int val[11];
  173. /* field - description
  174. * 1 - major number
  175. * 2 - minor mumber
  176. * 3 - device name
  177. * 4 - reads completed successfully
  178. * 5 - reads merged
  179. * 6 - sectors read
  180. * 7 - time spent reading (ms)
  181. * 8 - writes completed
  182. * 9 - writes merged
  183. * 10 - sectors written
  184. * 11 - time spent writing (ms)
  185. * 12 - I/Os currently in progress
  186. * 13 - time spent doing I/Os (ms)
  187. * 14 - weighted time spent doing I/Os (ms)
  188. */
  189. int cnt = sscanf(buffer, "%d %d %s %u %u %u %u %u %u %u %u %u %u %u",
  190. &major, &minor, device, &val[0],
  191. &val[1], &val[2], &val[3], &val[4],
  192. &val[5], &val[6], &val[7], &val[8],
  193. &val[9], &val[10]);
  194. if (cnt != 14) {
  195. log_print(LOG_WARN, "%s(): invalid diskstat line: '%s'", __FUNCTION__, buffer);
  196. continue;
  197. }
  198. struct disk_entry *entry;
  199. list_for_each_entry(entry, &dwatch->disk_list, list) {
  200. char *dev = strrchr(entry->device, '/');
  201. if (dev == NULL || *(dev +1) == '\0')
  202. continue;
  203. if (strncmp(dev +1, device, strlen(device)) != 0)
  204. continue;
  205. entry->flags |= F_PRESENT;
  206. if (entry->sectors_rd == val[2] && entry->sectors_wr == val[6])
  207. continue;
  208. /* sector counts changed, disk is not in standby */
  209. entry->flags &= ~(F_STANDBY);
  210. entry->sectors_rd = val[2];
  211. entry->sectors_wr = val[6];
  212. entry->standby_timeout = now + entry->timeout_sec;
  213. }
  214. }
  215. fclose(fp);
  216. list_for_each_entry(entry, &dwatch->disk_list, list) {
  217. diskwatch_check_standby(entry);
  218. }
  219. return 0;
  220. }
  221. static int diskwatch_add_disk(struct strtoken *tokens, void *privdata)
  222. {
  223. struct diskwatch *dwatch = (struct diskwatch *)privdata;
  224. if (tokens->count != 2) {
  225. log_print(LOG_WARN, "%s(): invalid config line '%s'", __FUNCTION__, tokens->input);
  226. return -1;
  227. }
  228. struct disk_entry *entry = malloc(sizeof(struct disk_entry));
  229. if (entry == NULL) {
  230. log_print(LOG_WARN, "%s(): out of memory", __FUNCTION__);
  231. return -1;
  232. }
  233. memset(entry, 0x00, sizeof(struct disk_entry));
  234. strncpy((char *)entry->device, tokens->field[0], sizeof(entry->device));
  235. char *tmp;
  236. entry->timeout_sec = strtol(tokens->field[1], &tmp, 0);
  237. if (*tmp != '\0' && !isspace(*tmp)) {
  238. log_print(LOG_WARN, "%s(): invalid timeout value '%s'", __FUNCTION__, tokens->field[1]);
  239. free(entry);
  240. return -1;
  241. }
  242. log_print(LOG_INFO, "diskwatch: watching '%s' (standby timeout: %ds)", entry->device, entry->timeout_sec);
  243. list_add_tail(&entry->list, &dwatch->disk_list);
  244. return 0;
  245. }
  246. void diskwatch_exit(void)
  247. {
  248. struct diskwatch *dwatch = &diskwatch_glob;
  249. struct disk_entry *entry, *tmp;
  250. list_for_each_entry_safe(entry, tmp, &dwatch->disk_list, list) {
  251. list_del(&entry->list);
  252. free(entry);
  253. }
  254. if (dwatch->check_timeout != NULL) {
  255. event_remove_timeout(dwatch->check_timeout);
  256. dwatch->check_timeout = NULL;
  257. }
  258. }
  259. int diskwatch_init(struct lcddev *lcd)
  260. {
  261. struct diskwatch *dwatch = &diskwatch_glob;
  262. INIT_LIST_HEAD(&dwatch->disk_list);
  263. int diskcount = config_get_strtokens("diskwatch", "disk", ",", 2, diskwatch_add_disk, dwatch);
  264. if (diskcount <= 0)
  265. return 0;
  266. diskwatch_check_stats(0, dwatch);
  267. int check_interval = 0;
  268. config_get_int("diskwatch", "check_interval", &check_interval, 60);
  269. dwatch->check_timeout = event_add_timeout_ms(check_interval * 1000, diskwatch_check_stats, 0, dwatch);
  270. dwatch->lcd = lcd;
  271. if (dwatch->lcd != NULL) {
  272. lcd_addpage_cb(dwatch->lcd, 150, lcdpage_diskwatch, dwatch);
  273. }
  274. return 0;
  275. }