Improve message printed when signaling processes to stop. Patch from Matias A. Fonzo...
[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);
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;
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/*
309 * Open a sysfs file without getting a controlling tty
310 * and return FILE* pointer.
311 */
312static 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 */
333error:
334 return fp;
335}
336
337/*
338 * Check IDE/(S)ATA hard disk identity for
339 * the FLUSH CACHE EXT bit set.
340 */
341static 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;
399out:
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 */
412static 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 */
446static 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 */
463static 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 */
473static 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 */
487static 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 */
511static 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 */
526int 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
542int hdflush(void)
543{
544 return 0;
545}
546
a74aeac6
PR
547#endif /* ! USE_SYSFS */
548#else /* __linux__ */
549
550int hddown(void)
551{
552 return 0;
553}
554
46fd2e25
DWF
555int hdflush(void)
556{
557 return 0;
558}
559
a74aeac6
PR
560#endif /* __linux__ */
561
562#ifdef STANDALONE
563int main(int argc, char **argv)
564{
565 return (hddown() == 0);
566}
567#endif
568