Add casts to get rid of compiler warning about signed/unsigned issues.
[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
40 #define USE_SYSFS
41 #ifdef USE_SYSFS
42 /*
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
46 *
47 */
48 #include <limits.h>
49 #include <errno.h>
50 #include <sys/stat.h>
51 #include <sys/types.h>
52 #ifdef WORDS_BIGENDIAN
53 #include <byteswap.h>
54 #endif
55
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'))
60
61 /* Used in flush_cache_ext(), compare with <linux/hdreg.h> */
62 #define IDBYTES 512
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
65
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
70
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);
74
75 /*
76 * Find all disks through /sys/block.
77 */
78 static char *list_disks(DIR* blk, unsigned int* flags)
79 {
80 struct dirent *d;
81
82 while ((d = readdir(blk))) {
83 *flags = 0;
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;
86 struct stat st;
87 FILE *fp;
88 int ret;
89
90 fp = hdopen(SYS_BLK "/%s/removable", d->d_name);
91 if ((long)fp <= 0) {
92 if ((long)fp < 0)
93 goto empty; /* error */
94 continue; /* no entry `removable' */
95 }
96
97 ret = getc(fp);
98 fclose(fp);
99
100 if (ret != '0')
101 continue; /* not a hard disk */
102
103 if (d->d_name[0] == 'h') {
104 (*flags) |= DISK_IS_IDE;
105 if ((ret = flush_cache_ext(d->d_name))) {
106 if (ret < 0)
107 goto empty;
108 (*flags) |= DISK_EXTFLUSH;
109 }
110 break; /* old IDE disk not managed by kernel, out here */
111 }
112
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 */
116
117 ret = readlink(buf, lnk, sizeof(lnk));
118 if (ret >= (int)sizeof(lnk))
119 goto empty; /* error */
120 if (ret < 0) {
121 if (errno != ENOENT)
122 goto empty; /* error */
123 continue; /* no entry `device' */
124 }
125 lnk[ret] = '\0';
126
127 ptr = basename(lnk);
128 if (!ptr || !*ptr)
129 continue; /* should not happen */
130
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 */
134
135 ret = stat(buf, &st);
136 if (ret == 0)
137 continue; /* disk found but managed by kernel */
138
139 if (errno != ENOENT)
140 goto empty; /* error */
141
142 fp = hdopen(SYS_BLK "/%s/device/vendor", d->d_name);
143 if ((long)fp <= 0) {
144 if ((long)fp < 0)
145 goto empty; /* error */
146 continue; /* no entry `device/vendor' */
147 }
148
149 ptr = fgets(buf, sizeof(buf), fp);
150 fclose(fp);
151 if (ptr == (char*)0)
152 continue; /* should not happen */
153
154 ptr = strstrip(buf);
155 if (*ptr == '\0')
156 continue; /* should not happen */
157
158 if (strncmp(buf, "ATA", sizeof(buf)))
159 continue; /* no SATA but a real SCSI disk */
160
161 (*flags) |= (DISK_IS_IDE|DISK_IS_SATA);
162 if ((ret = flush_cache_ext(d->d_name))) {
163 if (ret < 0)
164 goto empty;
165 (*flags) |= DISK_EXTFLUSH;
166 }
167 break; /* new SATA disk to shutdown, out here */
168 }
169 }
170 if (d == (struct dirent*)0)
171 goto empty;
172 return d->d_name;
173 empty:
174 return (char*)0;
175 }
176
177 /*
178 * Put an disk in standby mode.
179 * Code stolen from hdparm.c
180 */
181 static int do_standby_idedisk(char *device, unsigned int flags)
182 {
183 #ifndef WIN_STANDBYNOW1
184 #define WIN_STANDBYNOW1 0xE0
185 #endif
186 #ifndef WIN_STANDBYNOW2
187 #define WIN_STANDBYNOW2 0x94
188 #endif
189 #ifndef WIN_FLUSH_CACHE_EXT
190 #define WIN_FLUSH_CACHE_EXT 0xEA
191 #endif
192 #ifndef WIN_FLUSH_CACHE
193 #define WIN_FLUSH_CACHE 0xE7
194 #endif
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];
200 int fd, ret;
201
202 ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device);
203 if ((ret >= (int)sizeof(buf)) || (ret < 0))
204 return -1;
205
206 if ((fd = open(buf, O_RDWR)) < 0)
207 return -1;
208
209 switch (flags & DISK_EXTFLUSH) {
210 case DISK_EXTFLUSH:
211 if (ioctl(fd, HDIO_DRIVE_CMD, &flush1) == 0)
212 break;
213 /* Extend flush rejected, try standard flush */
214 default:
215 ioctl(fd, HDIO_DRIVE_CMD, &flush2);
216 break;
217 }
218
219 ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) &&
220 ioctl(fd, HDIO_DRIVE_CMD, &stdby2);
221 close(fd);
222
223 if (ret)
224 return -1;
225 return 0;
226 }
227
228 /*
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.
232 */
233 int hddown(void)
234 {
235 unsigned int flags;
236 char *disk;
237 DIR *blk;
238
239 if ((blk = opendir(SYS_BLK)) == (DIR*)0)
240 return -1;
241
242 while ((disk = list_disks(blk, &flags)))
243 do_standby_idedisk(disk, flags);
244
245 return closedir(blk);
246 }
247
248 /*
249 * Strip off trailing white spaces
250 */
251 static char *strstrip(char *str)
252 {
253 const size_t len = strlen(str);
254 if (len) {
255 char* end = str + len - 1;
256 while ((end != str) && ISSPACE(*end))
257 end--;
258 *(end + 1) = '\0'; /* remove trailing white spaces */
259 }
260 return str;
261 }
262
263 /*
264 * Open a sysfs file without getting a controlling tty
265 * and return FILE* pointer.
266 */
267 static FILE *hdopen(const char* const format, const char* const name)
268 {
269 char buf[NAME_MAX+1];
270 FILE *fp = (FILE*)-1;
271 int fd, ret;
272
273 ret = snprintf(buf, sizeof(buf), format, name);
274 if ((ret >= (int)sizeof(buf)) || (ret < 0))
275 goto error; /* error */
276
277 fd = open(buf, O_RDONLY|O_NOCTTY);
278 if (fd < 0) {
279 if (errno != ENOENT)
280 goto error; /* error */
281 fp = (FILE*)0;
282 goto error; /* no entry `removable' */
283 }
284
285 fp = fdopen(fd, "r");
286 if (fp == (FILE*)0)
287 close(fd); /* should not happen */
288 error:
289 return fp;
290 }
291
292 /*
293 * Check IDE/(S)ATA hard disk identity for
294 * the FLUSH CACHE EXT bit set.
295 */
296 static int flush_cache_ext(const char *device)
297 {
298 #ifndef WIN_IDENTIFY
299 #define WIN_IDENTIFY 0xEC
300 #endif
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;
305 FILE *fp;
306
307 fp = hdopen(SYS_BLK "/%s/size", device);
308 if ((long)fp <= 0) {
309 if ((long)fp < 0)
310 return -1; /* error */
311 goto out; /* no entry `size' */
312 }
313
314 ptr = fgets(buf, sizeof(buf), fp);
315 fclose(fp);
316 if (ptr == (char*)0)
317 goto out; /* should not happen */
318
319 ptr = strstrip(buf);
320 if (*ptr == '\0')
321 goto out; /* should not happen */
322
323 if ((size_t)atoll(buf) < (1<<28))
324 goto out; /* small disk */
325
326 ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device);
327 if ((ret >= (int)sizeof(buf)) || (ret < 0))
328 return -1; /* error */
329
330 if ((fd = open(buf, O_RDONLY|O_NONBLOCK)) < 0)
331 goto out;
332
333 memset(&args[0], 0, sizeof(args));
334 args[0] = WIN_IDENTIFY;
335 args[3] = 1;
336 if (ioctl(fd, HDIO_DRIVE_CMD, &args))
337 goto out;
338 #ifdef WORDS_BIGENDIAN
339 # if 0
340 {
341 const unsigned short *end = id + IDBYTES/2;
342 const unsigned short *from = id;
343 unsigned short *to = id;
344
345 while (from < end)
346 *to++ = bswap_16(*from++);
347 }
348 # else
349 id[83] = bswap_16(id[83]);
350 # endif
351 #endif
352 if ((id[83] & MASK_EXT) == TEST_EXT)
353 ret = 1;
354 out:
355 if (fd >= 0)
356 close(fd);
357 return ret;
358 }
359 #else /* ! USE_SYSFS */
360 #define MAX_DISKS 64
361 #define PROC_IDE "/proc/ide"
362 #define DEV_BASE "/dev"
363
364 /*
365 * Find all IDE disks through /proc.
366 */
367 static int find_idedisks(const char **dev, int maxdev, int *count)
368 {
369 DIR *dd;
370 FILE *fp;
371 struct dirent *d;
372 char buf[256];
373
374 if ((dd = opendir(PROC_IDE)) == NULL)
375 return -1;
376
377 while (*count < maxdev && (d = readdir(dd)) != NULL) {
378 if (strncmp(d->d_name, "hd", 2) != 0)
379 continue;
380 buf[0] = 0;
381 snprintf(buf, sizeof(buf), PROC_IDE "/%s/media", d->d_name);
382 if ((fp = fopen(buf, "r")) == NULL)
383 continue;
384 if (fgets(buf, sizeof(buf), fp) == 0 ||
385 strcmp(buf, "disk\n") != 0) {
386 fclose(fp);
387 continue;
388 }
389 fclose(fp);
390 snprintf(buf, sizeof(buf), DEV_BASE "/%s", d->d_name);
391 dev[(*count)++] = strdup(buf);
392 }
393 closedir(dd);
394
395 return 0;
396 }
397
398 /*
399 * Find all SCSI/SATA disks.
400 */
401 static int find_scsidisks(const char **dev, int maxdev, int *count)
402 {
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";
411
412 return 0;
413 }
414
415 /*
416 * Open the device node of a disk.
417 */
418 static int open_disk(const char *device)
419 {
420 return open(device, O_RDWR);
421 }
422
423 /*
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.
427 */
428 static int open_disks(const char **disks, int *fds, int count)
429 {
430 int i;
431
432 for (i = 0; i < count; i++)
433 fds[i] = open_disk(disks[i]);
434
435 return 0;
436 }
437
438 /*
439 * Put an IDE/SCSI/SATA disk in standby mode.
440 * Code stolen from hdparm.c
441 */
442 static int do_standby_disk(int fd)
443 {
444 #ifndef WIN_STANDBYNOW1
445 #define WIN_STANDBYNOW1 0xE0
446 #endif
447 #ifndef WIN_STANDBYNOW2
448 #define WIN_STANDBYNOW2 0x94
449 #endif
450 unsigned char args1[4] = {WIN_STANDBYNOW1,0,0,0};
451 unsigned char args2[4] = {WIN_STANDBYNOW2,0,0,0};
452
453 if (fd < 0)
454 return -1;
455
456 if (ioctl(fd, HDIO_DRIVE_CMD, &args1) &&
457 ioctl(fd, HDIO_DRIVE_CMD, &args2))
458 return -1;
459
460 return 0;
461 }
462
463 /*
464 * Put all specified disks in standby mode.
465 */
466 static int do_standby_disks(const int *fds, int count)
467 {
468 int i;
469
470 for (i = 0; i < count; i++)
471 do_standby_disk(fds[i]);
472
473 return 0;
474 }
475
476 /*
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.
480 */
481 int hddown(void)
482 {
483 const char *disks[MAX_DISKS];
484 int fds[MAX_DISKS];
485 int count = 0;
486 int result1, result2;
487
488 result1 = find_idedisks(disks, MAX_DISKS, &count);
489 result2 = find_scsidisks(disks, MAX_DISKS, &count);
490
491 open_disks(disks, fds, count);
492 do_standby_disks(fds, count);
493
494 return (result1 ? result1 : result2);
495 }
496 #endif /* ! USE_SYSFS */
497 #else /* __linux__ */
498
499 int hddown(void)
500 {
501 return 0;
502 }
503
504 #endif /* __linux__ */
505
506 #ifdef STANDALONE
507 int main(int argc, char **argv)
508 {
509 return (hddown() == 0);
510 }
511 #endif
512