]>
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>
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
51 #include <sys/types.h>
52 #ifdef WORDS_BIGENDIAN
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'))
61 /* Used in flush_cache_ext(), compare with <linux/hdreg.h> */
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
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
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
);
76 * Find all disks through /sys/block.
78 static char *list_disks(DIR* blk
, unsigned int* flags
)
82 while ((d
= readdir(blk
))) {
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
;
90 fp
= hdopen(SYS_BLK
"/%s/removable", d
->d_name
);
93 goto empty
; /* error */
94 continue; /* no entry `removable' */
101 continue; /* not a hard disk */
103 if (d
->d_name
[0] == 'h') {
104 (*flags
) |= DISK_IS_IDE
;
105 if ((ret
= flush_cache_ext(d
->d_name
))) {
108 (*flags
) |= DISK_EXTFLUSH
;
110 break; /* old IDE disk not managed by kernel, out here */
113 ret
= snprintf(buf
, sizeof(buf
), SYS_BLK
"/%s/device", d
->d_name
);
114 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
115 goto empty
; /* error */
117 ret
= readlink(buf
, lnk
, sizeof(lnk
));
118 if (ret
>= (int)sizeof(lnk
))
119 goto empty
; /* error */
122 goto empty
; /* error */
123 continue; /* no entry `device' */
129 continue; /* should not happen */
131 ret
= snprintf(buf
, sizeof(buf
), SYS_CLASS
"/%s/manage_start_stop", ptr
);
132 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
133 goto empty
; /* error */
135 ret
= stat(buf
, &st
);
137 continue; /* disk found but managed by kernel */
140 goto empty
; /* error */
142 fp
= hdopen(SYS_BLK
"/%s/device/vendor", d
->d_name
);
145 goto empty
; /* error */
146 continue; /* no entry `device/vendor' */
149 ptr
= fgets(buf
, sizeof(buf
), fp
);
152 continue; /* should not happen */
156 continue; /* should not happen */
158 if (strncmp(buf
, "ATA", sizeof(buf
)))
159 continue; /* no SATA but a real SCSI disk */
161 (*flags
) |= (DISK_IS_IDE
|DISK_IS_SATA
);
162 if ((ret
= flush_cache_ext(d
->d_name
))) {
165 (*flags
) |= DISK_EXTFLUSH
;
167 break; /* new SATA disk to shutdown, out here */
170 if (d
== (struct dirent
*)0)
178 * Put an disk in standby mode.
179 * Code stolen from hdparm.c
181 static int do_standby_idedisk(char *device
, unsigned int flags
)
183 #ifndef WIN_STANDBYNOW1
184 #define WIN_STANDBYNOW1 0xE0
186 #ifndef WIN_STANDBYNOW2
187 #define WIN_STANDBYNOW2 0x94
189 #ifndef WIN_FLUSH_CACHE_EXT
190 #define WIN_FLUSH_CACHE_EXT 0xEA
192 #ifndef WIN_FLUSH_CACHE
193 #define WIN_FLUSH_CACHE 0xE7
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];
202 ret
= snprintf(buf
, sizeof(buf
), DEV_BASE
"/%s", device
);
203 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
206 if ((fd
= open(buf
, O_RDWR
)) < 0)
209 switch (flags
& DISK_EXTFLUSH
) {
211 if (ioctl(fd
, HDIO_DRIVE_CMD
, &flush1
) == 0)
213 /* Extend flush rejected, try standard flush */
215 ioctl(fd
, HDIO_DRIVE_CMD
, &flush2
);
219 ret
= ioctl(fd
, HDIO_DRIVE_CMD
, &stdby1
) &&
220 ioctl(fd
, HDIO_DRIVE_CMD
, &stdby2
);
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.
239 if ((blk
= opendir(SYS_BLK
)) == (DIR*)0)
242 while ((disk
= list_disks(blk
, &flags
)))
243 do_standby_idedisk(disk
, flags
);
245 return closedir(blk
);
249 * Strip off trailing white spaces
251 static char *strstrip(char *str
)
253 const size_t len
= strlen(str
);
255 char* end
= str
+ len
- 1;
256 while ((end
!= str
) && ISSPACE(*end
))
258 *(end
+ 1) = '\0'; /* remove trailing white spaces */
264 * Open a sysfs file without getting a controlling tty
265 * and return FILE* pointer.
267 static FILE *hdopen(const char* const format
, const char* const name
)
269 char buf
[NAME_MAX
+1];
270 FILE *fp
= (FILE*)-1;
273 ret
= snprintf(buf
, sizeof(buf
), format
, name
);
274 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
275 goto error
; /* error */
277 fd
= open(buf
, O_RDONLY
|O_NOCTTY
);
280 goto error
; /* error */
282 goto error
; /* no entry `removable' */
285 fp
= fdopen(fd
, "r");
287 close(fd
); /* should not happen */
293 * Check IDE/(S)ATA hard disk identity for
294 * the FLUSH CACHE EXT bit set.
296 static int flush_cache_ext(const char *device
)
299 #define WIN_IDENTIFY 0xEC
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;
307 fp
= hdopen(SYS_BLK
"/%s/size", device
);
310 return -1; /* error */
311 goto out
; /* no entry `size' */
314 ptr
= fgets(buf
, sizeof(buf
), fp
);
317 goto out
; /* should not happen */
321 goto out
; /* should not happen */
323 if ((size_t)atoll(buf
) < (1<<28))
324 goto out
; /* small disk */
326 ret
= snprintf(buf
, sizeof(buf
), DEV_BASE
"/%s", device
);
327 if ((ret
>= (int)sizeof(buf
)) || (ret
< 0))
328 return -1; /* error */
330 if ((fd
= open(buf
, O_RDONLY
|O_NONBLOCK
)) < 0)
333 memset(&args
[0], 0, sizeof(args
));
334 args
[0] = WIN_IDENTIFY
;
336 if (ioctl(fd
, HDIO_DRIVE_CMD
, &args
))
338 #ifdef WORDS_BIGENDIAN
341 const unsigned short *end
= id
+ IDBYTES
/2;
342 const unsigned short *from
= id
;
343 unsigned short *to
= id
;
346 *to
++ = bswap_16(*from
++);
349 id
[83] = bswap_16(id
[83]);
352 if ((id
[83] & MASK_EXT
) == TEST_EXT
)
359 #else /* ! USE_SYSFS */
361 #define PROC_IDE "/proc/ide"
362 #define DEV_BASE "/dev"
365 * Find all IDE disks through /proc.
367 static int find_idedisks(const char **dev
, int maxdev
, int *count
)
374 if ((dd
= opendir(PROC_IDE
)) == NULL
)
377 while (*count
< maxdev
&& (d
= readdir(dd
)) != NULL
) {
378 if (strncmp(d
->d_name
, "hd", 2) != 0)
381 snprintf(buf
, sizeof(buf
), PROC_IDE
"/%s/media", d
->d_name
);
382 if ((fp
= fopen(buf
, "r")) == NULL
)
384 if (fgets(buf
, sizeof(buf
), fp
) == 0 ||
385 strcmp(buf
, "disk\n") != 0) {
390 snprintf(buf
, sizeof(buf
), DEV_BASE
"/%s", d
->d_name
);
391 dev
[(*count
)++] = strdup(buf
);
399 * Find all SCSI/SATA disks.
401 static int find_scsidisks(const char **dev
, int maxdev
, int *count
)
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";
416 * Open the device node of a disk.
418 static int open_disk(const char *device
)
420 return open(device
, O_RDWR
);
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.
428 static int open_disks(const char **disks
, int *fds
, int count
)
432 for (i
= 0; i
< count
; i
++)
433 fds
[i
] = open_disk(disks
[i
]);
439 * Put an IDE/SCSI/SATA disk in standby mode.
440 * Code stolen from hdparm.c
442 static int do_standby_disk(int fd
)
444 #ifndef WIN_STANDBYNOW1
445 #define WIN_STANDBYNOW1 0xE0
447 #ifndef WIN_STANDBYNOW2
448 #define WIN_STANDBYNOW2 0x94
450 unsigned char args1
[4] = {WIN_STANDBYNOW1
,0,0,0};
451 unsigned char args2
[4] = {WIN_STANDBYNOW2
,0,0,0};
456 if (ioctl(fd
, HDIO_DRIVE_CMD
, &args1
) &&
457 ioctl(fd
, HDIO_DRIVE_CMD
, &args2
))
464 * Put all specified disks in standby mode.
466 static int do_standby_disks(const int *fds
, int count
)
470 for (i
= 0; i
< count
; i
++)
471 do_standby_disk(fds
[i
]);
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.
483 const char *disks
[MAX_DISKS
];
486 int result1
, result2
;
488 result1
= find_idedisks(disks
, MAX_DISKS
, &count
);
489 result2
= find_scsidisks(disks
, MAX_DISKS
, &count
);
491 open_disks(disks
, fds
, count
);
492 do_standby_disks(fds
, count
);
494 return (result1
? result1
: result2
);
496 #endif /* ! USE_SYSFS */
497 #else /* __linux__ */
504 #endif /* __linux__ */
507 int main(int argc
, char **argv
)
509 return (hddown() == 0);