Get more help from gcc, add -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2...
[sysvinit.git] / src / hddown.c
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