Get more help from gcc, add -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2...
[sysvinit.git] / src / hddown.c
CommitLineData
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 */
22char *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
77static char *strstrip(char *str);
78static FILE *hdopen(const char* const format, const char* const name);
79static int flush_cache_ext(const char *device);
80
81/*
82 * Find all disks through /sys/block.
83 */
84static 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);
381af6a4
PR
96 if (0 == (long)fp || -1 == (long)fp) {
97 if (-1 == (long)fp)
a74aeac6
PR
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 139 fp = hdopen(SYS_CLASS "/%s/manage_start_stop", ptr);
381af6a4
PR
140 if (0 == (long)fp || -1 == (long)fp) {
141 if (-1 == (long)fp)
46fd2e25
DWF
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);
381af6a4
PR
154 if (0 == (long)fp || -1 == (long)fp) {
155 if (-1 == (long)fp)
a74aeac6
PR
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;
196empty:
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 204static 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 */
260int 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 */
278int 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 */
296static 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/*
381af6a4
PR
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.
a74aeac6
PR
312 */
313static 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);
192c4567 320 if ((ret >= (int)sizeof(buf)) || (ret < 0))
a74aeac6
PR
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 */
334error:
335 return fp;
336}
337
338/*
339 * Check IDE/(S)ATA hard disk identity for
340 * the FLUSH CACHE EXT bit set.
341 */
342static 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);
381af6a4
PR
354 if (0 == (long)fp || -1 == (long)fp) {
355 if (-1 == (long)fp)
a74aeac6
PR
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);
192c4567 373 if ((ret >= (int)sizeof(buf)) || (ret < 0))
a74aeac6
PR
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;
400out:
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 */
413static 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 */
447static 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 */
464static 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 */
474static 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 */
488static 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 */
512static 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 */
527int 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}
46fd2e25
DWF
542
543int hdflush(void)
544{
545 return 0;
546}
547
a74aeac6
PR
548#endif /* ! USE_SYSFS */
549#else /* __linux__ */
550
551int hddown(void)
552{
553 return 0;
554}
555
46fd2e25
DWF
556int hdflush(void)
557{
558 return 0;
559}
560
a74aeac6
PR
561#endif /* __linux__ */
562
563#ifdef STANDALONE
564int main(int argc, char **argv)
565{
566 return (hddown() == 0);
567}
568#endif
569