]>
git.wh0rd.org - sysvinit.git/blob - src/hddown.c
2 * hddown.c Find all disks on the system and
5 * Copyright (C) 2003 Miquel van Smoorenburg.
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.
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.
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
22 char *v_hddown
= "@(#)hddown.c 1.02 22-Apr-2003 miquels@cistron.nl";
37 #include <sys/ioctl.h>
38 #include <linux/hdreg.h>
44 #if defined(USE_SYSFS) && (USE_SYSFS == 1)
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
54 #include <sys/types.h>
55 #ifdef WORDS_BIGENDIAN
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'))
64 /* Used in flush_cache_ext(), compare with <linux/hdreg.h> */
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
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
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
);
82 * Find all disks through /sys/block.
84 static char *list_disks(DIR* blk
, unsigned int* flags
)
88 while ((d
= readdir(blk
))) {
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
;
95 fp
= hdopen(SYS_BLK
"/%s/removable", d
->d_name
);
96 if (0 == (long)fp
|| -1 == (long)fp
) {
98 goto empty
; /* error */
99 continue; /* no entry `removable' */
106 (*flags
) |= DISK_REMOVABLE
;
108 if (d
->d_name
[0] == 'h') {
109 if ((*flags
) & DISK_REMOVABLE
)
110 continue; /* not a hard disk */
112 (*flags
) |= DISK_IS_IDE
;
113 if ((ret
= flush_cache_ext(d
->d_name
))) {
116 (*flags
) |= DISK_EXTFLUSH
;
118 break; /* old IDE disk not managed by kernel, out here */
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 */
125 ret
= readlink(buf
, lnk
, sizeof(lnk
));
126 if (ret
>= (int)sizeof(lnk
))
127 goto empty
; /* error */
130 goto empty
; /* error */
131 continue; /* no entry `device' */
137 continue; /* should not happen */
139 fp
= hdopen(SYS_CLASS
"/%s/manage_start_stop", ptr
);
140 if (0 == (long)fp
|| -1 == (long)fp
) {
142 goto empty
; /* error */
148 (*flags
) |= DISK_MANAGED
;
153 fp
= hdopen(SYS_BLK
"/%s/device/vendor", d
->d_name
);
154 if (0 == (long)fp
|| -1 == (long)fp
) {
156 goto empty
; /* error */
157 continue; /* no entry `device/vendor' */
160 ptr
= fgets(buf
, sizeof(buf
), fp
);
163 continue; /* should not happen */
167 continue; /* should not happen */
169 if (strncmp(buf
, "ATA", sizeof(buf
)) == 0) {
170 if ((*flags
) & DISK_REMOVABLE
)
171 continue; /* not a hard disk */
173 (*flags
) |= (DISK_IS_IDE
|DISK_IS_SATA
);
174 if ((ret
= flush_cache_ext(d
->d_name
))) {
177 (*flags
) |= DISK_EXTFLUSH
;
179 break; /* new SATA disk to shutdown, out here */
182 if (((*flags
) & DISK_REMOVABLE
) == 0)
183 continue; /* Seems to be a real SCSI disk */
185 if ((ret
= flush_cache_ext(d
->d_name
))) {
188 (*flags
) |= DISK_EXTFLUSH
;
190 break; /* Removable disk like USB stick to shutdown */
193 if (d
== (struct dirent
*)0)
201 * Put an IDE/SCSI/SATA disk in standby mode.
202 * Code stolen from hdparm.c
204 static int do_standby_disk(char *device
, unsigned int flags
)
206 #ifndef WIN_STANDBYNOW1
207 #define WIN_STANDBYNOW1 0xE0
209 #ifndef WIN_STANDBYNOW2
210 #define WIN_STANDBYNOW2 0x94
212 #ifndef WIN_FLUSH_CACHE_EXT
213 #define WIN_FLUSH_CACHE_EXT 0xEA
215 #ifndef WIN_FLUSH_CACHE
216 #define WIN_FLUSH_CACHE 0xE7
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];
225 ret
= snprintf(buf
, sizeof(buf
), DEV_BASE
"/%s", device
);
226 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
229 if ((fd
= open(buf
, O_RDWR
|O_NONBLOCK
)) < 0)
232 switch (flags
& DISK_EXTFLUSH
) {
234 if ((ret
= ioctl(fd
, HDIO_DRIVE_CMD
, &flush1
)) == 0)
236 /* Extend flush rejected, try standard flush */
238 ret
= ioctl(fd
, HDIO_DRIVE_CMD
, &flush2
) &&
239 ioctl(fd
, BLKFLSBUF
);
243 if ((flags
& DISK_FLUSHONLY
) == 0x0) {
244 ret
= ioctl(fd
, HDIO_DRIVE_CMD
, &stdby1
) &&
245 ioctl(fd
, HDIO_DRIVE_CMD
, &stdby2
);
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.
266 if ((blk
= opendir(SYS_BLK
)) == (DIR*)0)
269 while ((disk
= list_disks(blk
, &flags
)))
270 do_standby_disk(disk
, flags
);
272 return closedir(blk
);
276 * List all disks and cause them to flush their buffers.
284 if ((blk
= opendir(SYS_BLK
)) == (DIR*)0)
287 while ((disk
= list_disks(blk
, &flags
)))
288 do_standby_disk(disk
, (flags
|DISK_FLUSHONLY
));
290 return closedir(blk
);
294 * Strip off trailing white spaces
296 static char *strstrip(char *str
)
298 const size_t len
= strlen(str
);
300 char* end
= str
+ len
- 1;
301 while ((end
!= str
) && ISSPACE(*end
))
303 *(end
+ 1) = '\0'; /* remove trailing white spaces */
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.
313 static FILE *hdopen(const char* const format
, const char* const name
)
315 char buf
[NAME_MAX
+1];
316 FILE *fp
= (FILE*)-1;
319 ret
= snprintf(buf
, sizeof(buf
), format
, name
);
320 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
321 goto error
; /* error */
323 fd
= open(buf
, O_RDONLY
|O_NOCTTY
);
326 goto error
; /* error */
328 goto error
; /* no entry `removable' */
331 fp
= fdopen(fd
, "r");
333 close(fd
); /* should not happen */
339 * Check IDE/(S)ATA hard disk identity for
340 * the FLUSH CACHE EXT bit set.
342 static int flush_cache_ext(const char *device
)
345 #define WIN_IDENTIFY 0xEC
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;
353 fp
= hdopen(SYS_BLK
"/%s/size", device
);
354 if (0 == (long)fp
|| -1 == (long)fp
) {
356 return -1; /* error */
357 goto out
; /* no entry `size' */
360 ptr
= fgets(buf
, sizeof(buf
), fp
);
363 goto out
; /* should not happen */
367 goto out
; /* should not happen */
369 if ((size_t)atoll(buf
) < (1<<28))
370 goto out
; /* small disk */
372 ret
= snprintf(buf
, sizeof(buf
), DEV_BASE
"/%s", device
);
373 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
374 return -1; /* error */
376 if ((fd
= open(buf
, O_RDONLY
|O_NONBLOCK
)) < 0)
379 memset(&args
[0], 0, sizeof(args
));
380 args
[0] = WIN_IDENTIFY
;
382 if (ioctl(fd
, HDIO_DRIVE_CMD
, &args
))
384 #ifdef WORDS_BIGENDIAN
387 const unsigned short *end
= id
+ IDBYTES
/2;
388 const unsigned short *from
= id
;
389 unsigned short *to
= id
;
392 *to
++ = bswap_16(*from
++);
395 id
[83] = bswap_16(id
[83]);
398 if ((id
[83] & MASK_EXT
) == TEST_EXT
)
405 #else /* ! USE_SYSFS */
407 #define PROC_IDE "/proc/ide"
408 #define DEV_BASE "/dev"
411 * Find all IDE disks through /proc.
413 static int find_idedisks(const char **dev
, int maxdev
, int *count
)
420 if ((dd
= opendir(PROC_IDE
)) == NULL
)
423 while (*count
< maxdev
&& (d
= readdir(dd
)) != NULL
) {
424 if (strncmp(d
->d_name
, "hd", 2) != 0)
427 snprintf(buf
, sizeof(buf
), PROC_IDE
"/%s/media", d
->d_name
);
428 if ((fp
= fopen(buf
, "r")) == NULL
)
430 if (fgets(buf
, sizeof(buf
), fp
) == 0 ||
431 strcmp(buf
, "disk\n") != 0) {
436 snprintf(buf
, sizeof(buf
), DEV_BASE
"/%s", d
->d_name
);
437 dev
[(*count
)++] = strdup(buf
);
445 * Find all SCSI/SATA disks.
447 static int find_scsidisks(const char **dev
, int maxdev
, int *count
)
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";
462 * Open the device node of a disk.
464 static int open_disk(const char *device
)
466 return open(device
, O_RDWR
);
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.
474 static int open_disks(const char **disks
, int *fds
, int count
)
478 for (i
= 0; i
< count
; i
++)
479 fds
[i
] = open_disk(disks
[i
]);
485 * Put an IDE/SCSI/SATA disk in standby mode.
486 * Code stolen from hdparm.c
488 static int do_standby_disk(int fd
)
490 #ifndef WIN_STANDBYNOW1
491 #define WIN_STANDBYNOW1 0xE0
493 #ifndef WIN_STANDBYNOW2
494 #define WIN_STANDBYNOW2 0x94
496 unsigned char args1
[4] = {WIN_STANDBYNOW1
,0,0,0};
497 unsigned char args2
[4] = {WIN_STANDBYNOW2
,0,0,0};
502 if (ioctl(fd
, HDIO_DRIVE_CMD
, &args1
) &&
503 ioctl(fd
, HDIO_DRIVE_CMD
, &args2
))
510 * Put all specified disks in standby mode.
512 static int do_standby_disks(const int *fds
, int count
)
516 for (i
= 0; i
< count
; i
++)
517 do_standby_disk(fds
[i
]);
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.
529 const char *disks
[MAX_DISKS
];
532 int result1
, result2
;
534 result1
= find_idedisks(disks
, MAX_DISKS
, &count
);
535 result2
= find_scsidisks(disks
, MAX_DISKS
, &count
);
537 open_disks(disks
, fds
, count
);
538 do_standby_disks(fds
, count
);
540 return (result1
? result1
: result2
);
548 #endif /* ! USE_SYSFS */
549 #else /* __linux__ */
561 #endif /* __linux__ */
564 int main(int argc
, char **argv
)
566 return (hddown() == 0);