]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * hddown.c Find all disks on the system and | |
3 | * shut them down. | |
4 | * | |
5 | * Copyright (C) 2003 Miquel van Smoorenburg. | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | * | |
21 | */ | |
22 | char *v_hddown = "@(#)hddown.c 1.02 22-Apr-2003 miquels@cistron.nl"; | |
23 | ||
24 | #ifndef _GNU_SOURCE | |
25 | #define _GNU_SOURCE | |
26 | #endif | |
27 | #include <stdio.h> | |
28 | #include <stdlib.h> | |
29 | #include <unistd.h> | |
30 | #include <time.h> | |
31 | #include <string.h> | |
32 | #include <fcntl.h> | |
33 | #include <dirent.h> | |
34 | ||
35 | #ifdef __linux__ | |
36 | ||
37 | #include <sys/ioctl.h> | |
38 | #include <linux/hdreg.h> | |
39 | #include <linux/fs.h> | |
40 | ||
41 | #ifndef USE_SYSFS | |
42 | # define USE_SYSFS 1 | |
43 | #endif | |
44 | #if defined(USE_SYSFS) && (USE_SYSFS == 1) | |
45 | /* | |
46 | * sysfs part Find all disks on the system, list out IDE and unmanaged | |
47 | * SATA disks, flush the cache of those and shut them down. | |
48 | * Author: Werner Fink <werner@suse.de>, 2007/06/12 | |
49 | * | |
50 | */ | |
51 | #include <limits.h> | |
52 | #include <errno.h> | |
53 | #include <sys/stat.h> | |
54 | #include <sys/types.h> | |
55 | #ifdef WORDS_BIGENDIAN | |
56 | #include <byteswap.h> | |
57 | #endif | |
58 | ||
59 | #define SYS_BLK "/sys/block" | |
60 | #define SYS_CLASS "/sys/class/scsi_disk" | |
61 | #define DEV_BASE "/dev" | |
62 | #define ISSPACE(c) (((c)==' ')||((c)=='\n')||((c)=='\t')||((c)=='\v')||((c)=='\r')||((c)=='\f')) | |
63 | ||
64 | /* Used in flush_cache_ext(), compare with <linux/hdreg.h> */ | |
65 | #define IDBYTES 512 | |
66 | #define MASK_EXT 0xE000 /* Bit 15 shall be zero, bit 14 shall be one, bit 13 flush cache ext */ | |
67 | #define TEST_EXT 0x6000 | |
68 | ||
69 | /* Maybe set in list_disks() and used in do_standby_disk() */ | |
70 | #define DISK_IS_IDE 0x00000001 | |
71 | #define DISK_IS_SATA 0x00000002 | |
72 | #define DISK_EXTFLUSH 0x00000004 | |
73 | #define DISK_REMOVABLE 0x00000008 | |
74 | #define DISK_MANAGED 0x00000010 | |
75 | #define DISK_FLUSHONLY 0x00000020 | |
76 | ||
77 | static char *strstrip(char *str); | |
78 | static FILE *hdopen(const char* const format, const char* const name); | |
79 | static int flush_cache_ext(const char *device); | |
80 | ||
81 | /* | |
82 | * Find all disks through /sys/block. | |
83 | */ | |
84 | static char *list_disks(DIR* blk, unsigned int* flags) | |
85 | { | |
86 | struct dirent *d; | |
87 | ||
88 | while ((d = readdir(blk))) { | |
89 | (*flags) = 0; | |
90 | if (d->d_name[1] == 'd' && (d->d_name[0] == 'h' || d->d_name[0] == 's')) { | |
91 | char buf[NAME_MAX+1], lnk[NAME_MAX+1], *ptr; | |
92 | FILE *fp; | |
93 | int ret; | |
94 | ||
95 | fp = hdopen(SYS_BLK "/%s/removable", d->d_name); | |
96 | if (0 == (long)fp || -1 == (long)fp) { | |
97 | if (-1 == (long)fp) | |
98 | goto empty; /* error */ | |
99 | continue; /* no entry `removable' */ | |
100 | } | |
101 | ||
102 | ret = getc(fp); | |
103 | fclose(fp); | |
104 | ||
105 | if (ret != '0') | |
106 | (*flags) |= DISK_REMOVABLE; | |
107 | ||
108 | if (d->d_name[0] == 'h') { | |
109 | if ((*flags) & DISK_REMOVABLE) | |
110 | continue; /* not a hard disk */ | |
111 | ||
112 | (*flags) |= DISK_IS_IDE; | |
113 | if ((ret = flush_cache_ext(d->d_name))) { | |
114 | if (ret < 0) | |
115 | goto empty; | |
116 | (*flags) |= DISK_EXTFLUSH; | |
117 | } | |
118 | break; /* old IDE disk not managed by kernel, out here */ | |
119 | } | |
120 | ||
121 | ret = snprintf(buf, sizeof(buf), SYS_BLK "/%s/device", d->d_name); | |
122 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) | |
123 | goto empty; /* error */ | |
124 | ||
125 | ret = readlink(buf, lnk, sizeof(lnk)); | |
126 | if (ret >= (int)sizeof(lnk)) | |
127 | goto empty; /* error */ | |
128 | if (ret < 0) { | |
129 | if (errno != ENOENT) | |
130 | goto empty; /* error */ | |
131 | continue; /* no entry `device' */ | |
132 | } | |
133 | lnk[ret] = '\0'; | |
134 | ||
135 | ptr = basename(lnk); | |
136 | if (!ptr || !*ptr) | |
137 | continue; /* should not happen */ | |
138 | ||
139 | fp = hdopen(SYS_CLASS "/%s/manage_start_stop", ptr); | |
140 | if (0 == (long)fp || -1 == (long)fp) { | |
141 | if (-1 == (long)fp) | |
142 | goto empty; /* error */ | |
143 | } else { | |
144 | ret = getc(fp); | |
145 | fclose(fp); | |
146 | ||
147 | if (ret != '0') { | |
148 | (*flags) |= DISK_MANAGED; | |
149 | continue; | |
150 | } | |
151 | } | |
152 | ||
153 | fp = hdopen(SYS_BLK "/%s/device/vendor", d->d_name); | |
154 | if (0 == (long)fp || -1 == (long)fp) { | |
155 | if (-1 == (long)fp) | |
156 | goto empty; /* error */ | |
157 | continue; /* no entry `device/vendor' */ | |
158 | } | |
159 | ||
160 | ptr = fgets(buf, sizeof(buf), fp); | |
161 | fclose(fp); | |
162 | if (ptr == (char*)0) | |
163 | continue; /* should not happen */ | |
164 | ||
165 | ptr = strstrip(buf); | |
166 | if (*ptr == '\0') | |
167 | continue; /* should not happen */ | |
168 | ||
169 | if (strncmp(buf, "ATA", sizeof(buf)) == 0) { | |
170 | if ((*flags) & DISK_REMOVABLE) | |
171 | continue; /* not a hard disk */ | |
172 | ||
173 | (*flags) |= (DISK_IS_IDE|DISK_IS_SATA); | |
174 | if ((ret = flush_cache_ext(d->d_name))) { | |
175 | if (ret < 0) | |
176 | goto empty; | |
177 | (*flags) |= DISK_EXTFLUSH; | |
178 | } | |
179 | break; /* new SATA disk to shutdown, out here */ | |
180 | } | |
181 | ||
182 | if (((*flags) & DISK_REMOVABLE) == 0) | |
183 | continue; /* Seems to be a real SCSI disk */ | |
184 | ||
185 | if ((ret = flush_cache_ext(d->d_name))) { | |
186 | if (ret < 0) | |
187 | goto empty; | |
188 | (*flags) |= DISK_EXTFLUSH; | |
189 | } | |
190 | break; /* Removable disk like USB stick to shutdown */ | |
191 | } | |
192 | } | |
193 | if (d == (struct dirent*)0) | |
194 | goto empty; | |
195 | return d->d_name; | |
196 | empty: | |
197 | return (char*)0; | |
198 | } | |
199 | ||
200 | /* | |
201 | * Put an IDE/SCSI/SATA disk in standby mode. | |
202 | * Code stolen from hdparm.c | |
203 | */ | |
204 | static int do_standby_disk(char *device, unsigned int flags) | |
205 | { | |
206 | #ifndef WIN_STANDBYNOW1 | |
207 | #define WIN_STANDBYNOW1 0xE0 | |
208 | #endif | |
209 | #ifndef WIN_STANDBYNOW2 | |
210 | #define WIN_STANDBYNOW2 0x94 | |
211 | #endif | |
212 | #ifndef WIN_FLUSH_CACHE_EXT | |
213 | #define WIN_FLUSH_CACHE_EXT 0xEA | |
214 | #endif | |
215 | #ifndef WIN_FLUSH_CACHE | |
216 | #define WIN_FLUSH_CACHE 0xE7 | |
217 | #endif | |
218 | unsigned char flush1[4] = {WIN_FLUSH_CACHE_EXT,0,0,0}; | |
219 | unsigned char flush2[4] = {WIN_FLUSH_CACHE,0,0,0}; | |
220 | unsigned char stdby1[4] = {WIN_STANDBYNOW1,0,0,0}; | |
221 | unsigned char stdby2[4] = {WIN_STANDBYNOW2,0,0,0}; | |
222 | char buf[NAME_MAX+1]; | |
223 | int fd, ret; | |
224 | ||
225 | ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device); | |
226 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) | |
227 | return -1; | |
228 | ||
229 | if ((fd = open(buf, O_RDWR|O_NONBLOCK)) < 0) | |
230 | return -1; | |
231 | ||
232 | switch (flags & DISK_EXTFLUSH) { | |
233 | case DISK_EXTFLUSH: | |
234 | if ((ret = ioctl(fd, HDIO_DRIVE_CMD, &flush1)) == 0) | |
235 | break; | |
236 | /* Extend flush rejected, try standard flush */ | |
237 | default: | |
238 | ret = ioctl(fd, HDIO_DRIVE_CMD, &flush2) && | |
239 | ioctl(fd, BLKFLSBUF); | |
240 | break; | |
241 | } | |
242 | ||
243 | if ((flags & DISK_FLUSHONLY) == 0x0) { | |
244 | ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) && | |
245 | ioctl(fd, HDIO_DRIVE_CMD, &stdby2); | |
246 | } | |
247 | ||
248 | close(fd); | |
249 | ||
250 | if (ret) | |
251 | return -1; | |
252 | return 0; | |
253 | } | |
254 | ||
255 | /* | |
256 | * List all disks and put them in standby mode. | |
257 | * This has the side-effect of flushing the writecache, | |
258 | * which is exactly what we want on poweroff. | |
259 | */ | |
260 | int hddown(void) | |
261 | { | |
262 | unsigned int flags; | |
263 | char *disk; | |
264 | DIR *blk; | |
265 | ||
266 | if ((blk = opendir(SYS_BLK)) == (DIR*)0) | |
267 | return -1; | |
268 | ||
269 | while ((disk = list_disks(blk, &flags))) | |
270 | do_standby_disk(disk, flags); | |
271 | ||
272 | return closedir(blk); | |
273 | } | |
274 | ||
275 | /* | |
276 | * List all disks and cause them to flush their buffers. | |
277 | */ | |
278 | int hdflush(void) | |
279 | { | |
280 | unsigned int flags; | |
281 | char *disk; | |
282 | DIR *blk; | |
283 | ||
284 | if ((blk = opendir(SYS_BLK)) == (DIR*)0) | |
285 | return -1; | |
286 | ||
287 | while ((disk = list_disks(blk, &flags))) | |
288 | do_standby_disk(disk, (flags|DISK_FLUSHONLY)); | |
289 | ||
290 | return closedir(blk); | |
291 | } | |
292 | ||
293 | /* | |
294 | * Strip off trailing white spaces | |
295 | */ | |
296 | static char *strstrip(char *str) | |
297 | { | |
298 | const size_t len = strlen(str); | |
299 | if (len) { | |
300 | char* end = str + len - 1; | |
301 | while ((end != str) && ISSPACE(*end)) | |
302 | end--; | |
303 | *(end + 1) = '\0'; /* remove trailing white spaces */ | |
304 | } | |
305 | return str; | |
306 | } | |
307 | ||
308 | /* | |
309 | * Open a sysfs file without getting a controlling tty and return | |
310 | * FILE* pointer. Return 0 if the file didn't exist, or (FILE*)-1 if | |
311 | * something else went wrong. | |
312 | */ | |
313 | static FILE *hdopen(const char* const format, const char* const name) | |
314 | { | |
315 | char buf[NAME_MAX+1]; | |
316 | FILE *fp = (FILE*)-1; | |
317 | int fd, ret; | |
318 | ||
319 | ret = snprintf(buf, sizeof(buf), format, name); | |
320 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) | |
321 | goto error; /* error */ | |
322 | ||
323 | fd = open(buf, O_RDONLY|O_NOCTTY); | |
324 | if (fd < 0) { | |
325 | if (errno != ENOENT) | |
326 | goto error; /* error */ | |
327 | fp = (FILE*)0; | |
328 | goto error; /* no entry `removable' */ | |
329 | } | |
330 | ||
331 | fp = fdopen(fd, "r"); | |
332 | if (fp == (FILE*)0) | |
333 | close(fd); /* should not happen */ | |
334 | error: | |
335 | return fp; | |
336 | } | |
337 | ||
338 | /* | |
339 | * Check IDE/(S)ATA hard disk identity for | |
340 | * the FLUSH CACHE EXT bit set. | |
341 | */ | |
342 | static int flush_cache_ext(const char *device) | |
343 | { | |
344 | #ifndef WIN_IDENTIFY | |
345 | #define WIN_IDENTIFY 0xEC | |
346 | #endif | |
347 | unsigned char args[4+IDBYTES]; | |
348 | unsigned short *id = (unsigned short*)(&args[4]); | |
349 | char buf[NAME_MAX+1], *ptr; | |
350 | int fd = -1, ret = 0; | |
351 | FILE *fp; | |
352 | ||
353 | fp = hdopen(SYS_BLK "/%s/size", device); | |
354 | if (0 == (long)fp || -1 == (long)fp) { | |
355 | if (-1 == (long)fp) | |
356 | return -1; /* error */ | |
357 | goto out; /* no entry `size' */ | |
358 | } | |
359 | ||
360 | ptr = fgets(buf, sizeof(buf), fp); | |
361 | fclose(fp); | |
362 | if (ptr == (char*)0) | |
363 | goto out; /* should not happen */ | |
364 | ||
365 | ptr = strstrip(buf); | |
366 | if (*ptr == '\0') | |
367 | goto out; /* should not happen */ | |
368 | ||
369 | if ((size_t)atoll(buf) < (1<<28)) | |
370 | goto out; /* small disk */ | |
371 | ||
372 | ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device); | |
373 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) | |
374 | return -1; /* error */ | |
375 | ||
376 | if ((fd = open(buf, O_RDONLY|O_NONBLOCK)) < 0) | |
377 | goto out; | |
378 | ||
379 | memset(&args[0], 0, sizeof(args)); | |
380 | args[0] = WIN_IDENTIFY; | |
381 | args[3] = 1; | |
382 | if (ioctl(fd, HDIO_DRIVE_CMD, &args)) | |
383 | goto out; | |
384 | #ifdef WORDS_BIGENDIAN | |
385 | # if 0 | |
386 | { | |
387 | const unsigned short *end = id + IDBYTES/2; | |
388 | const unsigned short *from = id; | |
389 | unsigned short *to = id; | |
390 | ||
391 | while (from < end) | |
392 | *to++ = bswap_16(*from++); | |
393 | } | |
394 | # else | |
395 | id[83] = bswap_16(id[83]); | |
396 | # endif | |
397 | #endif | |
398 | if ((id[83] & MASK_EXT) == TEST_EXT) | |
399 | ret = 1; | |
400 | out: | |
401 | if (fd >= 0) | |
402 | close(fd); | |
403 | return ret; | |
404 | } | |
405 | #else /* ! USE_SYSFS */ | |
406 | #define MAX_DISKS 64 | |
407 | #define PROC_IDE "/proc/ide" | |
408 | #define DEV_BASE "/dev" | |
409 | ||
410 | /* | |
411 | * Find all IDE disks through /proc. | |
412 | */ | |
413 | static int find_idedisks(const char **dev, int maxdev, int *count) | |
414 | { | |
415 | DIR *dd; | |
416 | FILE *fp; | |
417 | struct dirent *d; | |
418 | char buf[256]; | |
419 | ||
420 | if ((dd = opendir(PROC_IDE)) == NULL) | |
421 | return -1; | |
422 | ||
423 | while (*count < maxdev && (d = readdir(dd)) != NULL) { | |
424 | if (strncmp(d->d_name, "hd", 2) != 0) | |
425 | continue; | |
426 | buf[0] = 0; | |
427 | snprintf(buf, sizeof(buf), PROC_IDE "/%s/media", d->d_name); | |
428 | if ((fp = fopen(buf, "r")) == NULL) | |
429 | continue; | |
430 | if (fgets(buf, sizeof(buf), fp) == 0 || | |
431 | strcmp(buf, "disk\n") != 0) { | |
432 | fclose(fp); | |
433 | continue; | |
434 | } | |
435 | fclose(fp); | |
436 | snprintf(buf, sizeof(buf), DEV_BASE "/%s", d->d_name); | |
437 | dev[(*count)++] = strdup(buf); | |
438 | } | |
439 | closedir(dd); | |
440 | ||
441 | return 0; | |
442 | } | |
443 | ||
444 | /* | |
445 | * Find all SCSI/SATA disks. | |
446 | */ | |
447 | static int find_scsidisks(const char **dev, int maxdev, int *count) | |
448 | { | |
449 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sda"; | |
450 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdb"; | |
451 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdc"; | |
452 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdd"; | |
453 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sde"; | |
454 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdf"; | |
455 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdg"; | |
456 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdh"; | |
457 | ||
458 | return 0; | |
459 | } | |
460 | ||
461 | /* | |
462 | * Open the device node of a disk. | |
463 | */ | |
464 | static int open_disk(const char *device) | |
465 | { | |
466 | return open(device, O_RDWR); | |
467 | } | |
468 | ||
469 | /* | |
470 | * Open device nodes of all disks, and store the file descriptors in fds. | |
471 | * This has to be done in advance because accessing the device nodes | |
472 | * might cause a disk to spin back up. | |
473 | */ | |
474 | static int open_disks(const char **disks, int *fds, int count) | |
475 | { | |
476 | int i; | |
477 | ||
478 | for (i = 0; i < count; i++) | |
479 | fds[i] = open_disk(disks[i]); | |
480 | ||
481 | return 0; | |
482 | } | |
483 | ||
484 | /* | |
485 | * Put an IDE/SCSI/SATA disk in standby mode. | |
486 | * Code stolen from hdparm.c | |
487 | */ | |
488 | static int do_standby_disk(int fd) | |
489 | { | |
490 | #ifndef WIN_STANDBYNOW1 | |
491 | #define WIN_STANDBYNOW1 0xE0 | |
492 | #endif | |
493 | #ifndef WIN_STANDBYNOW2 | |
494 | #define WIN_STANDBYNOW2 0x94 | |
495 | #endif | |
496 | unsigned char args1[4] = {WIN_STANDBYNOW1,0,0,0}; | |
497 | unsigned char args2[4] = {WIN_STANDBYNOW2,0,0,0}; | |
498 | ||
499 | if (fd < 0) | |
500 | return -1; | |
501 | ||
502 | if (ioctl(fd, HDIO_DRIVE_CMD, &args1) && | |
503 | ioctl(fd, HDIO_DRIVE_CMD, &args2)) | |
504 | return -1; | |
505 | ||
506 | return 0; | |
507 | } | |
508 | ||
509 | /* | |
510 | * Put all specified disks in standby mode. | |
511 | */ | |
512 | static int do_standby_disks(const int *fds, int count) | |
513 | { | |
514 | int i; | |
515 | ||
516 | for (i = 0; i < count; i++) | |
517 | do_standby_disk(fds[i]); | |
518 | ||
519 | return 0; | |
520 | } | |
521 | ||
522 | /* | |
523 | * First find all IDE/SCSI/SATA disks, then put them in standby mode. | |
524 | * This has the side-effect of flushing the writecache, | |
525 | * which is exactly what we want on poweroff. | |
526 | */ | |
527 | int hddown(void) | |
528 | { | |
529 | const char *disks[MAX_DISKS]; | |
530 | int fds[MAX_DISKS]; | |
531 | int count = 0; | |
532 | int result1, result2; | |
533 | ||
534 | result1 = find_idedisks(disks, MAX_DISKS, &count); | |
535 | result2 = find_scsidisks(disks, MAX_DISKS, &count); | |
536 | ||
537 | open_disks(disks, fds, count); | |
538 | do_standby_disks(fds, count); | |
539 | ||
540 | return (result1 ? result1 : result2); | |
541 | } | |
542 | ||
543 | int hdflush(void) | |
544 | { | |
545 | return 0; | |
546 | } | |
547 | ||
548 | #endif /* ! USE_SYSFS */ | |
549 | #else /* __linux__ */ | |
550 | ||
551 | int hddown(void) | |
552 | { | |
553 | return 0; | |
554 | } | |
555 | ||
556 | int hdflush(void) | |
557 | { | |
558 | return 0; | |
559 | } | |
560 | ||
561 | #endif /* __linux__ */ | |
562 | ||
563 | #ifdef STANDALONE | |
564 | int main(int argc, char **argv) | |
565 | { | |
566 | return (hddown() == 0); | |
567 | } | |
568 | #endif | |
569 |