]>
Commit | Line | Data |
---|---|---|
a74aeac6 PR |
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> | |
46fd2e25 | 39 | #include <linux/fs.h> |
a74aeac6 | 40 | |
46fd2e25 DWF |
41 | #ifndef USE_SYSFS |
42 | # define USE_SYSFS 1 | |
43 | #endif | |
44 | #if defined(USE_SYSFS) && (USE_SYSFS == 1) | |
a74aeac6 PR |
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 | ||
46fd2e25 | 69 | /* Maybe set in list_disks() and used in do_standby_disk() */ |
a74aeac6 PR |
70 | #define DISK_IS_IDE 0x00000001 |
71 | #define DISK_IS_SATA 0x00000002 | |
72 | #define DISK_EXTFLUSH 0x00000004 | |
46fd2e25 DWF |
73 | #define DISK_REMOVABLE 0x00000008 |
74 | #define DISK_MANAGED 0x00000010 | |
75 | #define DISK_FLUSHONLY 0x00000020 | |
a74aeac6 PR |
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))) { | |
46fd2e25 | 89 | (*flags) = 0; |
a74aeac6 PR |
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; | |
a74aeac6 PR |
92 | FILE *fp; |
93 | int ret; | |
94 | ||
95 | fp = hdopen(SYS_BLK "/%s/removable", d->d_name); | |
96 | if ((long)fp <= 0) { | |
97 | if ((long)fp < 0) | |
98 | goto empty; /* error */ | |
99 | continue; /* no entry `removable' */ | |
100 | } | |
101 | ||
102 | ret = getc(fp); | |
103 | fclose(fp); | |
104 | ||
105 | if (ret != '0') | |
46fd2e25 | 106 | (*flags) |= DISK_REMOVABLE; |
a74aeac6 PR |
107 | |
108 | if (d->d_name[0] == 'h') { | |
46fd2e25 DWF |
109 | if ((*flags) & DISK_REMOVABLE) |
110 | continue; /* not a hard disk */ | |
111 | ||
a74aeac6 PR |
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); | |
192c4567 | 122 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
123 | goto empty; /* error */ |
124 | ||
125 | ret = readlink(buf, lnk, sizeof(lnk)); | |
192c4567 | 126 | if (ret >= (int)sizeof(lnk)) |
a74aeac6 PR |
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 | ||
46fd2e25 DWF |
139 | fp = hdopen(SYS_CLASS "/%s/manage_start_stop", ptr); |
140 | if ((long)fp <= 0) { | |
141 | if ((long)fp < 0) | |
142 | goto empty; /* error */ | |
143 | } else { | |
144 | ret = getc(fp); | |
145 | fclose(fp); | |
a74aeac6 | 146 | |
46fd2e25 DWF |
147 | if (ret != '0') { |
148 | (*flags) |= DISK_MANAGED; | |
149 | continue; | |
150 | } | |
151 | } | |
a74aeac6 PR |
152 | |
153 | fp = hdopen(SYS_BLK "/%s/device/vendor", d->d_name); | |
154 | if ((long)fp <= 0) { | |
155 | if ((long)fp < 0) | |
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 | ||
46fd2e25 DWF |
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 */ | |
a74aeac6 | 184 | |
a74aeac6 PR |
185 | if ((ret = flush_cache_ext(d->d_name))) { |
186 | if (ret < 0) | |
187 | goto empty; | |
188 | (*flags) |= DISK_EXTFLUSH; | |
189 | } | |
46fd2e25 | 190 | break; /* Removable disk like USB stick to shutdown */ |
a74aeac6 PR |
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 | /* | |
46fd2e25 | 201 | * Put an IDE/SCSI/SATA disk in standby mode. |
a74aeac6 PR |
202 | * Code stolen from hdparm.c |
203 | */ | |
46fd2e25 | 204 | static int do_standby_disk(char *device, unsigned int flags) |
a74aeac6 PR |
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); | |
192c4567 | 226 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
227 | return -1; |
228 | ||
46fd2e25 | 229 | if ((fd = open(buf, O_RDWR|O_NONBLOCK)) < 0) |
a74aeac6 PR |
230 | return -1; |
231 | ||
232 | switch (flags & DISK_EXTFLUSH) { | |
233 | case DISK_EXTFLUSH: | |
46fd2e25 | 234 | if ((ret = ioctl(fd, HDIO_DRIVE_CMD, &flush1)) == 0) |
a74aeac6 PR |
235 | break; |
236 | /* Extend flush rejected, try standard flush */ | |
237 | default: | |
46fd2e25 DWF |
238 | ret = ioctl(fd, HDIO_DRIVE_CMD, &flush2) && |
239 | ioctl(fd, BLKFLSBUF); | |
a74aeac6 PR |
240 | break; |
241 | } | |
242 | ||
46fd2e25 DWF |
243 | if ((flags & DISK_FLUSHONLY) == 0x0) { |
244 | ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) && | |
245 | ioctl(fd, HDIO_DRIVE_CMD, &stdby2); | |
246 | } | |
247 | ||
a74aeac6 PR |
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))) | |
46fd2e25 DWF |
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)); | |
a74aeac6 PR |
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 | |
310 | * and return FILE* pointer. | |
311 | */ | |
312 | static FILE *hdopen(const char* const format, const char* const name) | |
313 | { | |
314 | char buf[NAME_MAX+1]; | |
315 | FILE *fp = (FILE*)-1; | |
316 | int fd, ret; | |
317 | ||
318 | ret = snprintf(buf, sizeof(buf), format, name); | |
192c4567 | 319 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
320 | goto error; /* error */ |
321 | ||
322 | fd = open(buf, O_RDONLY|O_NOCTTY); | |
323 | if (fd < 0) { | |
324 | if (errno != ENOENT) | |
325 | goto error; /* error */ | |
326 | fp = (FILE*)0; | |
327 | goto error; /* no entry `removable' */ | |
328 | } | |
329 | ||
330 | fp = fdopen(fd, "r"); | |
331 | if (fp == (FILE*)0) | |
332 | close(fd); /* should not happen */ | |
333 | error: | |
334 | return fp; | |
335 | } | |
336 | ||
337 | /* | |
338 | * Check IDE/(S)ATA hard disk identity for | |
339 | * the FLUSH CACHE EXT bit set. | |
340 | */ | |
341 | static int flush_cache_ext(const char *device) | |
342 | { | |
343 | #ifndef WIN_IDENTIFY | |
344 | #define WIN_IDENTIFY 0xEC | |
345 | #endif | |
346 | unsigned char args[4+IDBYTES]; | |
347 | unsigned short *id = (unsigned short*)(&args[4]); | |
348 | char buf[NAME_MAX+1], *ptr; | |
349 | int fd = -1, ret = 0; | |
350 | FILE *fp; | |
351 | ||
352 | fp = hdopen(SYS_BLK "/%s/size", device); | |
353 | if ((long)fp <= 0) { | |
354 | if ((long)fp < 0) | |
355 | return -1; /* error */ | |
356 | goto out; /* no entry `size' */ | |
357 | } | |
358 | ||
359 | ptr = fgets(buf, sizeof(buf), fp); | |
360 | fclose(fp); | |
361 | if (ptr == (char*)0) | |
362 | goto out; /* should not happen */ | |
363 | ||
364 | ptr = strstrip(buf); | |
365 | if (*ptr == '\0') | |
366 | goto out; /* should not happen */ | |
367 | ||
368 | if ((size_t)atoll(buf) < (1<<28)) | |
369 | goto out; /* small disk */ | |
370 | ||
371 | ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device); | |
192c4567 | 372 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
373 | return -1; /* error */ |
374 | ||
375 | if ((fd = open(buf, O_RDONLY|O_NONBLOCK)) < 0) | |
376 | goto out; | |
377 | ||
378 | memset(&args[0], 0, sizeof(args)); | |
379 | args[0] = WIN_IDENTIFY; | |
380 | args[3] = 1; | |
381 | if (ioctl(fd, HDIO_DRIVE_CMD, &args)) | |
382 | goto out; | |
383 | #ifdef WORDS_BIGENDIAN | |
384 | # if 0 | |
385 | { | |
386 | const unsigned short *end = id + IDBYTES/2; | |
387 | const unsigned short *from = id; | |
388 | unsigned short *to = id; | |
389 | ||
390 | while (from < end) | |
391 | *to++ = bswap_16(*from++); | |
392 | } | |
393 | # else | |
394 | id[83] = bswap_16(id[83]); | |
395 | # endif | |
396 | #endif | |
397 | if ((id[83] & MASK_EXT) == TEST_EXT) | |
398 | ret = 1; | |
399 | out: | |
400 | if (fd >= 0) | |
401 | close(fd); | |
402 | return ret; | |
403 | } | |
404 | #else /* ! USE_SYSFS */ | |
405 | #define MAX_DISKS 64 | |
406 | #define PROC_IDE "/proc/ide" | |
407 | #define DEV_BASE "/dev" | |
408 | ||
409 | /* | |
410 | * Find all IDE disks through /proc. | |
411 | */ | |
412 | static int find_idedisks(const char **dev, int maxdev, int *count) | |
413 | { | |
414 | DIR *dd; | |
415 | FILE *fp; | |
416 | struct dirent *d; | |
417 | char buf[256]; | |
418 | ||
419 | if ((dd = opendir(PROC_IDE)) == NULL) | |
420 | return -1; | |
421 | ||
422 | while (*count < maxdev && (d = readdir(dd)) != NULL) { | |
423 | if (strncmp(d->d_name, "hd", 2) != 0) | |
424 | continue; | |
425 | buf[0] = 0; | |
426 | snprintf(buf, sizeof(buf), PROC_IDE "/%s/media", d->d_name); | |
427 | if ((fp = fopen(buf, "r")) == NULL) | |
428 | continue; | |
429 | if (fgets(buf, sizeof(buf), fp) == 0 || | |
430 | strcmp(buf, "disk\n") != 0) { | |
431 | fclose(fp); | |
432 | continue; | |
433 | } | |
434 | fclose(fp); | |
435 | snprintf(buf, sizeof(buf), DEV_BASE "/%s", d->d_name); | |
436 | dev[(*count)++] = strdup(buf); | |
437 | } | |
438 | closedir(dd); | |
439 | ||
440 | return 0; | |
441 | } | |
442 | ||
443 | /* | |
444 | * Find all SCSI/SATA disks. | |
445 | */ | |
446 | static int find_scsidisks(const char **dev, int maxdev, int *count) | |
447 | { | |
448 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sda"; | |
449 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdb"; | |
450 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdc"; | |
451 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdd"; | |
452 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sde"; | |
453 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdf"; | |
454 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdg"; | |
455 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdh"; | |
456 | ||
457 | return 0; | |
458 | } | |
459 | ||
460 | /* | |
461 | * Open the device node of a disk. | |
462 | */ | |
463 | static int open_disk(const char *device) | |
464 | { | |
465 | return open(device, O_RDWR); | |
466 | } | |
467 | ||
468 | /* | |
469 | * Open device nodes of all disks, and store the file descriptors in fds. | |
470 | * This has to be done in advance because accessing the device nodes | |
471 | * might cause a disk to spin back up. | |
472 | */ | |
473 | static int open_disks(const char **disks, int *fds, int count) | |
474 | { | |
475 | int i; | |
476 | ||
477 | for (i = 0; i < count; i++) | |
478 | fds[i] = open_disk(disks[i]); | |
479 | ||
480 | return 0; | |
481 | } | |
482 | ||
483 | /* | |
484 | * Put an IDE/SCSI/SATA disk in standby mode. | |
485 | * Code stolen from hdparm.c | |
486 | */ | |
487 | static int do_standby_disk(int fd) | |
488 | { | |
489 | #ifndef WIN_STANDBYNOW1 | |
490 | #define WIN_STANDBYNOW1 0xE0 | |
491 | #endif | |
492 | #ifndef WIN_STANDBYNOW2 | |
493 | #define WIN_STANDBYNOW2 0x94 | |
494 | #endif | |
495 | unsigned char args1[4] = {WIN_STANDBYNOW1,0,0,0}; | |
496 | unsigned char args2[4] = {WIN_STANDBYNOW2,0,0,0}; | |
497 | ||
498 | if (fd < 0) | |
499 | return -1; | |
500 | ||
501 | if (ioctl(fd, HDIO_DRIVE_CMD, &args1) && | |
502 | ioctl(fd, HDIO_DRIVE_CMD, &args2)) | |
503 | return -1; | |
504 | ||
505 | return 0; | |
506 | } | |
507 | ||
508 | /* | |
509 | * Put all specified disks in standby mode. | |
510 | */ | |
511 | static int do_standby_disks(const int *fds, int count) | |
512 | { | |
513 | int i; | |
514 | ||
515 | for (i = 0; i < count; i++) | |
516 | do_standby_disk(fds[i]); | |
517 | ||
518 | return 0; | |
519 | } | |
520 | ||
521 | /* | |
522 | * First find all IDE/SCSI/SATA disks, then put them in standby mode. | |
523 | * This has the side-effect of flushing the writecache, | |
524 | * which is exactly what we want on poweroff. | |
525 | */ | |
526 | int hddown(void) | |
527 | { | |
528 | const char *disks[MAX_DISKS]; | |
529 | int fds[MAX_DISKS]; | |
530 | int count = 0; | |
531 | int result1, result2; | |
532 | ||
533 | result1 = find_idedisks(disks, MAX_DISKS, &count); | |
534 | result2 = find_scsidisks(disks, MAX_DISKS, &count); | |
535 | ||
536 | open_disks(disks, fds, count); | |
537 | do_standby_disks(fds, count); | |
538 | ||
539 | return (result1 ? result1 : result2); | |
540 | } | |
46fd2e25 DWF |
541 | |
542 | int hdflush(void) | |
543 | { | |
544 | return 0; | |
545 | } | |
546 | ||
a74aeac6 PR |
547 | #endif /* ! USE_SYSFS */ |
548 | #else /* __linux__ */ | |
549 | ||
550 | int hddown(void) | |
551 | { | |
552 | return 0; | |
553 | } | |
554 | ||
46fd2e25 DWF |
555 | int hdflush(void) |
556 | { | |
557 | return 0; | |
558 | } | |
559 | ||
a74aeac6 PR |
560 | #endif /* __linux__ */ |
561 | ||
562 | #ifdef STANDALONE | |
563 | int main(int argc, char **argv) | |
564 | { | |
565 | return (hddown() == 0); | |
566 | } | |
567 | #endif | |
568 |