1 #! /bin/sh /usr/share/dpatch/dpatch-run
2 ## 92_sata-hddown.dpatch by Werner Fink and SuSe
4 ## All lines beginning with `## DP:' are a description of the patch.
5 ## DP: Make sure to shut down SATA disks properly, and handle each
6 ## DP: disk individually. See also http://linux-ata.org/shutdown.html
9 diff -urNad trunk~/src/hddown.c trunk/src/hddown.c
10 --- trunk~/src/hddown.c 2008-03-26 09:32:51.000000000 +0100
11 +++ trunk/src/hddown.c 2008-03-26 09:32:51.000000000 +0100
14 char *v_hddown = "@(#)hddown.c 1.02 22-Apr-2003 miquels@cistron.nl";
23 #include <sys/ioctl.h>
24 #include <linux/hdreg.h>
29 + * sysfs part Find all disks on the system, list out IDE and unmanaged
30 + * SATA disks, flush the cache of those and shut them down.
31 + * Author: Werner Fink <werner@suse.de>, 2007/06/12
36 +#include <sys/stat.h>
37 +#include <sys/types.h>
38 +#ifdef WORDS_BIGENDIAN
39 +#include <byteswap.h>
42 +#define SYS_BLK "/sys/block"
43 +#define SYS_CLASS "/sys/class/scsi_disk"
44 +#define DEV_BASE "/dev"
45 +#define ISSPACE(c) (((c)==' ')||((c)=='\n')||((c)=='\t')||((c)=='\v')||((c)=='\r')||((c)=='\f'))
47 +/* Used in flush_cache_ext(), compare with <linux/hdreg.h> */
49 +#define MASK_EXT 0xE000 /* Bit 15 shall be zero, bit 14 shall be one, bit 13 flush cache ext */
50 +#define TEST_EXT 0x6000
52 +/* Maybe set in list_disks() and used in do_standby_idedisk() */
53 +#define DISK_IS_IDE 0x00000001
54 +#define DISK_IS_SATA 0x00000002
55 +#define DISK_EXTFLUSH 0x00000004
57 +static char *strstrip(char *str);
58 +static FILE *hdopen(const char* const format, const char* const name);
59 +static int flush_cache_ext(const char *device);
62 + * Find all disks through /sys/block.
64 +static char *list_disks(DIR* blk, unsigned int* flags)
68 + while ((d = readdir(blk))) {
70 + if (d->d_name[1] == 'd' && (d->d_name[0] == 'h' || d->d_name[0] == 's')) {
71 + char buf[NAME_MAX+1], lnk[NAME_MAX+1], *ptr;
76 + fp = hdopen(SYS_BLK "/%s/removable", d->d_name);
77 + if ((long)fp <= 0) {
79 + goto empty; /* error */
80 + continue; /* no entry `removable' */
87 + continue; /* not a hard disk */
89 + if (d->d_name[0] == 'h') {
90 + (*flags) |= DISK_IS_IDE;
91 + if ((ret = flush_cache_ext(d->d_name))) {
94 + (*flags) |= DISK_EXTFLUSH;
96 + break; /* old IDE disk not managed by kernel, out here */
99 + ret = snprintf(buf, sizeof(buf), SYS_BLK "/%s/device", d->d_name);
100 + if ((ret >= sizeof(buf)) || (ret < 0))
101 + goto empty; /* error */
103 + ret = readlink(buf, lnk, sizeof(lnk));
104 + if (ret >= sizeof(lnk))
105 + goto empty; /* error */
107 + if (errno != ENOENT)
108 + goto empty; /* error */
109 + continue; /* no entry `device' */
113 + ptr = basename(lnk);
115 + continue; /* should not happen */
117 + ret = snprintf(buf, sizeof(buf), SYS_CLASS "/%s/manage_start_stop", ptr);
118 + if ((ret >= sizeof(buf)) || (ret < 0))
119 + goto empty; /* error */
121 + ret = stat(buf, &st);
123 + continue; /* disk found but managed by kernel */
125 + if (errno != ENOENT)
126 + goto empty; /* error */
128 + fp = hdopen(SYS_BLK "/%s/device/vendor", d->d_name);
129 + if ((long)fp <= 0) {
131 + goto empty; /* error */
132 + continue; /* no entry `device/vendor' */
135 + ptr = fgets(buf, sizeof(buf), fp);
137 + if (ptr == (char*)0)
138 + continue; /* should not happen */
140 + ptr = strstrip(buf);
142 + continue; /* should not happen */
144 + if (strncmp(buf, "ATA", sizeof(buf)))
145 + continue; /* no SATA but a real SCSI disk */
147 + (*flags) |= (DISK_IS_IDE|DISK_IS_SATA);
148 + if ((ret = flush_cache_ext(d->d_name))) {
151 + (*flags) |= DISK_EXTFLUSH;
153 + break; /* new SATA disk to shutdown, out here */
156 + if (d == (struct dirent*)0)
164 + * Put an disk in standby mode.
165 + * Code stolen from hdparm.c
167 +static int do_standby_idedisk(char *device, unsigned int flags)
169 +#ifndef WIN_STANDBYNOW1
170 +#define WIN_STANDBYNOW1 0xE0
172 +#ifndef WIN_STANDBYNOW2
173 +#define WIN_STANDBYNOW2 0x94
175 +#ifndef WIN_FLUSH_CACHE_EXT
176 +#define WIN_FLUSH_CACHE_EXT 0xEA
178 +#ifndef WIN_FLUSH_CACHE
179 +#define WIN_FLUSH_CACHE 0xE7
181 + unsigned char flush1[4] = {WIN_FLUSH_CACHE_EXT,0,0,0};
182 + unsigned char flush2[4] = {WIN_FLUSH_CACHE,0,0,0};
183 + unsigned char stdby1[4] = {WIN_STANDBYNOW1,0,0,0};
184 + unsigned char stdby2[4] = {WIN_STANDBYNOW2,0,0,0};
185 + char buf[NAME_MAX+1];
188 + ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device);
189 + if ((ret >= sizeof(buf)) || (ret < 0))
192 + if ((fd = open(buf, O_RDWR)) < 0)
195 + switch (flags & DISK_EXTFLUSH) {
196 + case DISK_EXTFLUSH:
197 + if (ioctl(fd, HDIO_DRIVE_CMD, &flush1) == 0)
199 + /* Extend flush rejected, try standard flush */
201 + ioctl(fd, HDIO_DRIVE_CMD, &flush2);
205 + ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) &&
206 + ioctl(fd, HDIO_DRIVE_CMD, &stdby2);
215 + * List all disks and put them in standby mode.
216 + * This has the side-effect of flushing the writecache,
217 + * which is exactly what we want on poweroff.
221 + unsigned int flags;
225 + if ((blk = opendir(SYS_BLK)) == (DIR*)0)
228 + while ((disk = list_disks(blk, &flags)))
229 + do_standby_idedisk(disk, flags);
231 + return closedir(blk);
235 + * Strip off trailing white spaces
237 +static char *strstrip(char *str)
239 + const size_t len = strlen(str);
241 + char* end = str + len - 1;
242 + while ((end != str) && ISSPACE(*end))
244 + *(end + 1) = '\0'; /* remove trailing white spaces */
250 + * Open a sysfs file without getting a controlling tty
251 + * and return FILE* pointer.
253 +static FILE *hdopen(const char* const format, const char* const name)
255 + char buf[NAME_MAX+1];
256 + FILE *fp = (FILE*)-1;
259 + ret = snprintf(buf, sizeof(buf), format, name);
260 + if ((ret >= sizeof(buf)) || (ret < 0))
261 + goto error; /* error */
263 + fd = open(buf, O_RDONLY|O_NOCTTY);
265 + if (errno != ENOENT)
266 + goto error; /* error */
268 + goto error; /* no entry `removable' */
271 + fp = fdopen(fd, "r");
272 + if (fp == (FILE*)0)
273 + close(fd); /* should not happen */
279 + * Check IDE/(S)ATA hard disk identity for
280 + * the FLUSH CACHE EXT bit set.
282 +static int flush_cache_ext(const char *device)
284 +#ifndef WIN_IDENTIFY
285 +#define WIN_IDENTIFY 0xEC
287 + unsigned char args[4+IDBYTES];
288 + unsigned short *id = (unsigned short*)(&args[4]);
289 + char buf[NAME_MAX+1], *ptr;
290 + int fd = -1, ret = 0;
293 + fp = hdopen(SYS_BLK "/%s/size", device);
294 + if ((long)fp <= 0) {
296 + return -1; /* error */
297 + goto out; /* no entry `size' */
300 + ptr = fgets(buf, sizeof(buf), fp);
302 + if (ptr == (char*)0)
303 + goto out; /* should not happen */
305 + ptr = strstrip(buf);
307 + goto out; /* should not happen */
309 + if ((size_t)atoll(buf) < (1<<28))
310 + goto out; /* small disk */
312 + ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device);
313 + if ((ret >= sizeof(buf)) || (ret < 0))
314 + return -1; /* error */
316 + if ((fd = open(buf, O_RDONLY|O_NONBLOCK)) < 0)
319 + memset(&args[0], 0, sizeof(args));
320 + args[0] = WIN_IDENTIFY;
322 + if (ioctl(fd, HDIO_DRIVE_CMD, &args))
324 +#ifdef WORDS_BIGENDIAN
327 + const unsigned short *end = id + IDBYTES/2;
328 + const unsigned short *from = id;
329 + unsigned short *to = id;
332 + *to++ = bswap_16(*from++);
335 + id[83] = bswap_16(id[83]);
338 + if ((id[83] & MASK_EXT) == TEST_EXT)
345 +#else /* ! USE_SYSFS */
347 #define PROC_IDE "/proc/ide"
348 #define DEV_BASE "/dev"
351 return (result1 ? result1 : result2);
354 +#endif /* ! USE_SYSFS */
355 #else /* __linux__ */