]>
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> | |
39 | ||
40 | #define USE_SYSFS | |
41 | #ifdef USE_SYSFS | |
42 | /* | |
43 | * sysfs part Find all disks on the system, list out IDE and unmanaged | |
44 | * SATA disks, flush the cache of those and shut them down. | |
45 | * Author: Werner Fink <werner@suse.de>, 2007/06/12 | |
46 | * | |
47 | */ | |
48 | #include <limits.h> | |
49 | #include <errno.h> | |
50 | #include <sys/stat.h> | |
51 | #include <sys/types.h> | |
52 | #ifdef WORDS_BIGENDIAN | |
53 | #include <byteswap.h> | |
54 | #endif | |
55 | ||
56 | #define SYS_BLK "/sys/block" | |
57 | #define SYS_CLASS "/sys/class/scsi_disk" | |
58 | #define DEV_BASE "/dev" | |
59 | #define ISSPACE(c) (((c)==' ')||((c)=='\n')||((c)=='\t')||((c)=='\v')||((c)=='\r')||((c)=='\f')) | |
60 | ||
61 | /* Used in flush_cache_ext(), compare with <linux/hdreg.h> */ | |
62 | #define IDBYTES 512 | |
63 | #define MASK_EXT 0xE000 /* Bit 15 shall be zero, bit 14 shall be one, bit 13 flush cache ext */ | |
64 | #define TEST_EXT 0x6000 | |
65 | ||
66 | /* Maybe set in list_disks() and used in do_standby_idedisk() */ | |
67 | #define DISK_IS_IDE 0x00000001 | |
68 | #define DISK_IS_SATA 0x00000002 | |
69 | #define DISK_EXTFLUSH 0x00000004 | |
70 | ||
71 | static char *strstrip(char *str); | |
72 | static FILE *hdopen(const char* const format, const char* const name); | |
73 | static int flush_cache_ext(const char *device); | |
74 | ||
75 | /* | |
76 | * Find all disks through /sys/block. | |
77 | */ | |
78 | static char *list_disks(DIR* blk, unsigned int* flags) | |
79 | { | |
80 | struct dirent *d; | |
81 | ||
82 | while ((d = readdir(blk))) { | |
83 | *flags = 0; | |
84 | if (d->d_name[1] == 'd' && (d->d_name[0] == 'h' || d->d_name[0] == 's')) { | |
85 | char buf[NAME_MAX+1], lnk[NAME_MAX+1], *ptr; | |
86 | struct stat st; | |
87 | FILE *fp; | |
88 | int ret; | |
89 | ||
90 | fp = hdopen(SYS_BLK "/%s/removable", d->d_name); | |
91 | if ((long)fp <= 0) { | |
92 | if ((long)fp < 0) | |
93 | goto empty; /* error */ | |
94 | continue; /* no entry `removable' */ | |
95 | } | |
96 | ||
97 | ret = getc(fp); | |
98 | fclose(fp); | |
99 | ||
100 | if (ret != '0') | |
101 | continue; /* not a hard disk */ | |
102 | ||
103 | if (d->d_name[0] == 'h') { | |
104 | (*flags) |= DISK_IS_IDE; | |
105 | if ((ret = flush_cache_ext(d->d_name))) { | |
106 | if (ret < 0) | |
107 | goto empty; | |
108 | (*flags) |= DISK_EXTFLUSH; | |
109 | } | |
110 | break; /* old IDE disk not managed by kernel, out here */ | |
111 | } | |
112 | ||
113 | ret = snprintf(buf, sizeof(buf), SYS_BLK "/%s/device", d->d_name); | |
192c4567 | 114 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
115 | goto empty; /* error */ |
116 | ||
117 | ret = readlink(buf, lnk, sizeof(lnk)); | |
192c4567 | 118 | if (ret >= (int)sizeof(lnk)) |
a74aeac6 PR |
119 | goto empty; /* error */ |
120 | if (ret < 0) { | |
121 | if (errno != ENOENT) | |
122 | goto empty; /* error */ | |
123 | continue; /* no entry `device' */ | |
124 | } | |
125 | lnk[ret] = '\0'; | |
126 | ||
127 | ptr = basename(lnk); | |
128 | if (!ptr || !*ptr) | |
129 | continue; /* should not happen */ | |
130 | ||
131 | ret = snprintf(buf, sizeof(buf), SYS_CLASS "/%s/manage_start_stop", ptr); | |
192c4567 | 132 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
133 | goto empty; /* error */ |
134 | ||
135 | ret = stat(buf, &st); | |
136 | if (ret == 0) | |
137 | continue; /* disk found but managed by kernel */ | |
138 | ||
139 | if (errno != ENOENT) | |
140 | goto empty; /* error */ | |
141 | ||
142 | fp = hdopen(SYS_BLK "/%s/device/vendor", d->d_name); | |
143 | if ((long)fp <= 0) { | |
144 | if ((long)fp < 0) | |
145 | goto empty; /* error */ | |
146 | continue; /* no entry `device/vendor' */ | |
147 | } | |
148 | ||
149 | ptr = fgets(buf, sizeof(buf), fp); | |
150 | fclose(fp); | |
151 | if (ptr == (char*)0) | |
152 | continue; /* should not happen */ | |
153 | ||
154 | ptr = strstrip(buf); | |
155 | if (*ptr == '\0') | |
156 | continue; /* should not happen */ | |
157 | ||
158 | if (strncmp(buf, "ATA", sizeof(buf))) | |
159 | continue; /* no SATA but a real SCSI disk */ | |
160 | ||
161 | (*flags) |= (DISK_IS_IDE|DISK_IS_SATA); | |
162 | if ((ret = flush_cache_ext(d->d_name))) { | |
163 | if (ret < 0) | |
164 | goto empty; | |
165 | (*flags) |= DISK_EXTFLUSH; | |
166 | } | |
167 | break; /* new SATA disk to shutdown, out here */ | |
168 | } | |
169 | } | |
170 | if (d == (struct dirent*)0) | |
171 | goto empty; | |
172 | return d->d_name; | |
173 | empty: | |
174 | return (char*)0; | |
175 | } | |
176 | ||
177 | /* | |
178 | * Put an disk in standby mode. | |
179 | * Code stolen from hdparm.c | |
180 | */ | |
181 | static int do_standby_idedisk(char *device, unsigned int flags) | |
182 | { | |
183 | #ifndef WIN_STANDBYNOW1 | |
184 | #define WIN_STANDBYNOW1 0xE0 | |
185 | #endif | |
186 | #ifndef WIN_STANDBYNOW2 | |
187 | #define WIN_STANDBYNOW2 0x94 | |
188 | #endif | |
189 | #ifndef WIN_FLUSH_CACHE_EXT | |
190 | #define WIN_FLUSH_CACHE_EXT 0xEA | |
191 | #endif | |
192 | #ifndef WIN_FLUSH_CACHE | |
193 | #define WIN_FLUSH_CACHE 0xE7 | |
194 | #endif | |
195 | unsigned char flush1[4] = {WIN_FLUSH_CACHE_EXT,0,0,0}; | |
196 | unsigned char flush2[4] = {WIN_FLUSH_CACHE,0,0,0}; | |
197 | unsigned char stdby1[4] = {WIN_STANDBYNOW1,0,0,0}; | |
198 | unsigned char stdby2[4] = {WIN_STANDBYNOW2,0,0,0}; | |
199 | char buf[NAME_MAX+1]; | |
200 | int fd, ret; | |
201 | ||
202 | ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device); | |
192c4567 | 203 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
204 | return -1; |
205 | ||
206 | if ((fd = open(buf, O_RDWR)) < 0) | |
207 | return -1; | |
208 | ||
209 | switch (flags & DISK_EXTFLUSH) { | |
210 | case DISK_EXTFLUSH: | |
211 | if (ioctl(fd, HDIO_DRIVE_CMD, &flush1) == 0) | |
212 | break; | |
213 | /* Extend flush rejected, try standard flush */ | |
214 | default: | |
215 | ioctl(fd, HDIO_DRIVE_CMD, &flush2); | |
216 | break; | |
217 | } | |
218 | ||
219 | ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) && | |
220 | ioctl(fd, HDIO_DRIVE_CMD, &stdby2); | |
221 | close(fd); | |
222 | ||
223 | if (ret) | |
224 | return -1; | |
225 | return 0; | |
226 | } | |
227 | ||
228 | /* | |
229 | * List all disks and put them in standby mode. | |
230 | * This has the side-effect of flushing the writecache, | |
231 | * which is exactly what we want on poweroff. | |
232 | */ | |
233 | int hddown(void) | |
234 | { | |
235 | unsigned int flags; | |
236 | char *disk; | |
237 | DIR *blk; | |
238 | ||
239 | if ((blk = opendir(SYS_BLK)) == (DIR*)0) | |
240 | return -1; | |
241 | ||
242 | while ((disk = list_disks(blk, &flags))) | |
243 | do_standby_idedisk(disk, flags); | |
244 | ||
245 | return closedir(blk); | |
246 | } | |
247 | ||
248 | /* | |
249 | * Strip off trailing white spaces | |
250 | */ | |
251 | static char *strstrip(char *str) | |
252 | { | |
253 | const size_t len = strlen(str); | |
254 | if (len) { | |
255 | char* end = str + len - 1; | |
256 | while ((end != str) && ISSPACE(*end)) | |
257 | end--; | |
258 | *(end + 1) = '\0'; /* remove trailing white spaces */ | |
259 | } | |
260 | return str; | |
261 | } | |
262 | ||
263 | /* | |
264 | * Open a sysfs file without getting a controlling tty | |
265 | * and return FILE* pointer. | |
266 | */ | |
267 | static FILE *hdopen(const char* const format, const char* const name) | |
268 | { | |
269 | char buf[NAME_MAX+1]; | |
270 | FILE *fp = (FILE*)-1; | |
271 | int fd, ret; | |
272 | ||
273 | ret = snprintf(buf, sizeof(buf), format, name); | |
192c4567 | 274 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
275 | goto error; /* error */ |
276 | ||
277 | fd = open(buf, O_RDONLY|O_NOCTTY); | |
278 | if (fd < 0) { | |
279 | if (errno != ENOENT) | |
280 | goto error; /* error */ | |
281 | fp = (FILE*)0; | |
282 | goto error; /* no entry `removable' */ | |
283 | } | |
284 | ||
285 | fp = fdopen(fd, "r"); | |
286 | if (fp == (FILE*)0) | |
287 | close(fd); /* should not happen */ | |
288 | error: | |
289 | return fp; | |
290 | } | |
291 | ||
292 | /* | |
293 | * Check IDE/(S)ATA hard disk identity for | |
294 | * the FLUSH CACHE EXT bit set. | |
295 | */ | |
296 | static int flush_cache_ext(const char *device) | |
297 | { | |
298 | #ifndef WIN_IDENTIFY | |
299 | #define WIN_IDENTIFY 0xEC | |
300 | #endif | |
301 | unsigned char args[4+IDBYTES]; | |
302 | unsigned short *id = (unsigned short*)(&args[4]); | |
303 | char buf[NAME_MAX+1], *ptr; | |
304 | int fd = -1, ret = 0; | |
305 | FILE *fp; | |
306 | ||
307 | fp = hdopen(SYS_BLK "/%s/size", device); | |
308 | if ((long)fp <= 0) { | |
309 | if ((long)fp < 0) | |
310 | return -1; /* error */ | |
311 | goto out; /* no entry `size' */ | |
312 | } | |
313 | ||
314 | ptr = fgets(buf, sizeof(buf), fp); | |
315 | fclose(fp); | |
316 | if (ptr == (char*)0) | |
317 | goto out; /* should not happen */ | |
318 | ||
319 | ptr = strstrip(buf); | |
320 | if (*ptr == '\0') | |
321 | goto out; /* should not happen */ | |
322 | ||
323 | if ((size_t)atoll(buf) < (1<<28)) | |
324 | goto out; /* small disk */ | |
325 | ||
326 | ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device); | |
192c4567 | 327 | if ((ret >= (int)sizeof(buf)) || (ret < 0)) |
a74aeac6 PR |
328 | return -1; /* error */ |
329 | ||
330 | if ((fd = open(buf, O_RDONLY|O_NONBLOCK)) < 0) | |
331 | goto out; | |
332 | ||
333 | memset(&args[0], 0, sizeof(args)); | |
334 | args[0] = WIN_IDENTIFY; | |
335 | args[3] = 1; | |
336 | if (ioctl(fd, HDIO_DRIVE_CMD, &args)) | |
337 | goto out; | |
338 | #ifdef WORDS_BIGENDIAN | |
339 | # if 0 | |
340 | { | |
341 | const unsigned short *end = id + IDBYTES/2; | |
342 | const unsigned short *from = id; | |
343 | unsigned short *to = id; | |
344 | ||
345 | while (from < end) | |
346 | *to++ = bswap_16(*from++); | |
347 | } | |
348 | # else | |
349 | id[83] = bswap_16(id[83]); | |
350 | # endif | |
351 | #endif | |
352 | if ((id[83] & MASK_EXT) == TEST_EXT) | |
353 | ret = 1; | |
354 | out: | |
355 | if (fd >= 0) | |
356 | close(fd); | |
357 | return ret; | |
358 | } | |
359 | #else /* ! USE_SYSFS */ | |
360 | #define MAX_DISKS 64 | |
361 | #define PROC_IDE "/proc/ide" | |
362 | #define DEV_BASE "/dev" | |
363 | ||
364 | /* | |
365 | * Find all IDE disks through /proc. | |
366 | */ | |
367 | static int find_idedisks(const char **dev, int maxdev, int *count) | |
368 | { | |
369 | DIR *dd; | |
370 | FILE *fp; | |
371 | struct dirent *d; | |
372 | char buf[256]; | |
373 | ||
374 | if ((dd = opendir(PROC_IDE)) == NULL) | |
375 | return -1; | |
376 | ||
377 | while (*count < maxdev && (d = readdir(dd)) != NULL) { | |
378 | if (strncmp(d->d_name, "hd", 2) != 0) | |
379 | continue; | |
380 | buf[0] = 0; | |
381 | snprintf(buf, sizeof(buf), PROC_IDE "/%s/media", d->d_name); | |
382 | if ((fp = fopen(buf, "r")) == NULL) | |
383 | continue; | |
384 | if (fgets(buf, sizeof(buf), fp) == 0 || | |
385 | strcmp(buf, "disk\n") != 0) { | |
386 | fclose(fp); | |
387 | continue; | |
388 | } | |
389 | fclose(fp); | |
390 | snprintf(buf, sizeof(buf), DEV_BASE "/%s", d->d_name); | |
391 | dev[(*count)++] = strdup(buf); | |
392 | } | |
393 | closedir(dd); | |
394 | ||
395 | return 0; | |
396 | } | |
397 | ||
398 | /* | |
399 | * Find all SCSI/SATA disks. | |
400 | */ | |
401 | static int find_scsidisks(const char **dev, int maxdev, int *count) | |
402 | { | |
403 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sda"; | |
404 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdb"; | |
405 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdc"; | |
406 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdd"; | |
407 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sde"; | |
408 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdf"; | |
409 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdg"; | |
410 | if (*count < maxdev) dev[(*count)++] = DEV_BASE "/sdh"; | |
411 | ||
412 | return 0; | |
413 | } | |
414 | ||
415 | /* | |
416 | * Open the device node of a disk. | |
417 | */ | |
418 | static int open_disk(const char *device) | |
419 | { | |
420 | return open(device, O_RDWR); | |
421 | } | |
422 | ||
423 | /* | |
424 | * Open device nodes of all disks, and store the file descriptors in fds. | |
425 | * This has to be done in advance because accessing the device nodes | |
426 | * might cause a disk to spin back up. | |
427 | */ | |
428 | static int open_disks(const char **disks, int *fds, int count) | |
429 | { | |
430 | int i; | |
431 | ||
432 | for (i = 0; i < count; i++) | |
433 | fds[i] = open_disk(disks[i]); | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | /* | |
439 | * Put an IDE/SCSI/SATA disk in standby mode. | |
440 | * Code stolen from hdparm.c | |
441 | */ | |
442 | static int do_standby_disk(int fd) | |
443 | { | |
444 | #ifndef WIN_STANDBYNOW1 | |
445 | #define WIN_STANDBYNOW1 0xE0 | |
446 | #endif | |
447 | #ifndef WIN_STANDBYNOW2 | |
448 | #define WIN_STANDBYNOW2 0x94 | |
449 | #endif | |
450 | unsigned char args1[4] = {WIN_STANDBYNOW1,0,0,0}; | |
451 | unsigned char args2[4] = {WIN_STANDBYNOW2,0,0,0}; | |
452 | ||
453 | if (fd < 0) | |
454 | return -1; | |
455 | ||
456 | if (ioctl(fd, HDIO_DRIVE_CMD, &args1) && | |
457 | ioctl(fd, HDIO_DRIVE_CMD, &args2)) | |
458 | return -1; | |
459 | ||
460 | return 0; | |
461 | } | |
462 | ||
463 | /* | |
464 | * Put all specified disks in standby mode. | |
465 | */ | |
466 | static int do_standby_disks(const int *fds, int count) | |
467 | { | |
468 | int i; | |
469 | ||
470 | for (i = 0; i < count; i++) | |
471 | do_standby_disk(fds[i]); | |
472 | ||
473 | return 0; | |
474 | } | |
475 | ||
476 | /* | |
477 | * First find all IDE/SCSI/SATA disks, then put them in standby mode. | |
478 | * This has the side-effect of flushing the writecache, | |
479 | * which is exactly what we want on poweroff. | |
480 | */ | |
481 | int hddown(void) | |
482 | { | |
483 | const char *disks[MAX_DISKS]; | |
484 | int fds[MAX_DISKS]; | |
485 | int count = 0; | |
486 | int result1, result2; | |
487 | ||
488 | result1 = find_idedisks(disks, MAX_DISKS, &count); | |
489 | result2 = find_scsidisks(disks, MAX_DISKS, &count); | |
490 | ||
491 | open_disks(disks, fds, count); | |
492 | do_standby_disks(fds, count); | |
493 | ||
494 | return (result1 ? result1 : result2); | |
495 | } | |
496 | #endif /* ! USE_SYSFS */ | |
497 | #else /* __linux__ */ | |
498 | ||
499 | int hddown(void) | |
500 | { | |
501 | return 0; | |
502 | } | |
503 | ||
504 | #endif /* __linux__ */ | |
505 | ||
506 | #ifdef STANDALONE | |
507 | int main(int argc, char **argv) | |
508 | { | |
509 | return (hddown() == 0); | |
510 | } | |
511 | #endif | |
512 |