--- src/hddown.c +++ src/hddown.c 2007-06-06 15:42:04.799454093 +0200 @@ -5,6 +5,9 @@ */ char *v_hddown = "@(#)hddown.c 1.02 22-Apr-2003 miquels@cistron.nl"; +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif #include #include #include @@ -18,6 +21,198 @@ char *v_hddown = "@(#)hddown.c 1.02 22 #include #include +#define USE_SYSFS +#ifdef USE_SYSFS + +#include +#include +#include +#include + +#define SYS_BLK "/sys/block" +#define SYS_CLASS "/sys/class/scsi_disk" +#define DEV_BASE "/dev" +#define ISSPACE(c) (((c)==' ')||((c)=='\n')||((c)=='\t')||((c)=='\v')||((c)=='\r')||((c)=='\f')) + +/* + * Find all disks through /sys/block. + */ +static char *list_disks(DIR* blk) +{ + struct dirent *d; + + while ((d = readdir(blk))) { + if (d->d_name[1] == 'd' && (d->d_name[0] == 'h' || d->d_name[0] == 's')) { + char buf[NAME_MAX+1], lnk[NAME_MAX+1], *ptr; + struct stat st; + int fd, ret; + size_t len; + FILE *fp; + + ret = snprintf(buf, sizeof(buf), SYS_BLK "/%s/removable", d->d_name); + if ((ret >= sizeof(buf)) || (ret < 0)) + goto empty; /* error */ + + fd = open(buf, O_RDONLY|O_NOCTTY); + if (fd < 0) { + if (errno != ENOENT) + goto empty; /* error */ + continue; /* no entry `removable' */ + } + + fp = fdopen(fd, "r"); + if (fp == (FILE*)0) { + close(fd); + goto empty; /* error */ + } + + ret = getc(fp); + fclose(fp); + + if (ret != '0') + continue; /* not a hard disk */ + + if (d->d_name[0] == 'h') + break; /* old IDE disk not managed by kernel, out here */ + + ret = snprintf(buf, sizeof(buf), SYS_BLK "/%s/device", d->d_name); + if ((ret >= sizeof(buf)) || (ret < 0)) + goto empty; /* error */ + + ret = readlink(buf, lnk, sizeof(lnk)); + if (ret >= sizeof(lnk)) + goto empty; /* error */ + if (ret < 0) { + if (errno != ENOENT) + goto empty; /* error */ + continue; /* no entry `device' */ + } + lnk[ret] = '\0'; + + ptr = basename(lnk); + if (!ptr || !*ptr) + continue; /* should not happen */ + + ret = snprintf(buf, sizeof(buf), SYS_CLASS "/%s/manage_start_stop", ptr); + if ((ret >= sizeof(buf)) || (ret < 0)) + goto empty; /* error */ + + ret = stat(buf, &st); + if (ret == 0) + continue; /* disk found but managed by kernel */ + + if (errno != ENOENT) + goto empty; /* error */ + + + ret = snprintf(buf, sizeof(buf), SYS_BLK "/%s/device/vendor", d->d_name); + if ((ret >= sizeof(buf)) || (ret < 0)) + goto empty; /* error */ + + fd = open(buf, O_RDONLY|O_NOCTTY); + if (fd < 0) { + if (errno != ENOENT) + goto empty; /* error */ + continue; /* no entry `device/vendor' */ + } + + fp = fdopen(fd, "r"); + if (fp == (FILE*)0) { + close(fd); + goto empty; /* error */ + } + + ptr = fgets(buf, sizeof(buf), fp); + fclose(fp); + if (ptr == (char*)0) + continue; /* should not happen */ + + if ((len = strlen(buf)) == 0) + continue; /* should not happen */ + + ptr = &buf[0] + len - 1; + while ((ptr != &buf[0]) && ISSPACE(*ptr)) + ptr--; + *(ptr + 1) = '\0'; /* remove trailing white spaces */ + + if (strncmp(buf, "ATA", sizeof(buf))) + continue; /* no SATA but a real SCSI disk */ + + break; /* new SATA disk to shutdown, out here */ + } + } + if (d == (struct dirent*)0) + goto empty; + return d->d_name; +empty: + return (char*)0; +} + +/* + * Put an disk in standby mode. + * Code stolen from hdparm.c + */ +static int do_standby_idedisk(char *device) +{ +#ifndef WIN_STANDBYNOW1 +#define WIN_STANDBYNOW1 0xE0 +#endif +#ifndef WIN_STANDBYNOW2 +#define WIN_STANDBYNOW2 0x94 +#endif +#ifndef WIN_FLUSH_CACHE_EXT +#define WIN_FLUSH_CACHE_EXT 0xEA +#endif +#ifndef WIN_FLUSH_CACHE +#define WIN_FLUSH_CACHE 0xE7 +#endif + unsigned char flush1[4] = {WIN_FLUSH_CACHE_EXT,0,0,0}; + unsigned char flush2[4] = {WIN_FLUSH_CACHE,0,0,0}; + unsigned char stdby1[4] = {WIN_STANDBYNOW1,0,0,0}; + unsigned char stdby2[4] = {WIN_STANDBYNOW2,0,0,0}; + char buf[NAME_MAX+1]; + int fd, ret; + + ret = snprintf(buf, sizeof(buf), DEV_BASE "/%s", device); + if ((ret >= sizeof(buf)) || (ret < 0)) + return -1; + + if ((fd = open(buf, O_RDWR)) < 0) + return -1; + + if (ioctl(fd, HDIO_DRIVE_CMD, &flush1)) { + /* Extend flush rejected, try standard flush */ + ioctl(fd, HDIO_DRIVE_CMD, &flush2); + } + + ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) && + ioctl(fd, HDIO_DRIVE_CMD, &stdby2); + close(fd); + + if (ret) + return -1; + return 0; +} + +/* + * List all disks and put them in standby mode. + * This has the side-effect of flushing the writecache, + * which is exactly what we want on poweroff. + */ +int hddown(void) +{ + DIR *blk; + char *disk; + + if ((blk = opendir(SYS_BLK)) == (DIR*)0) + return -1; + + while ((disk = list_disks(blk))) + do_standby_idedisk(disk); + + return closedir(blk); +} +#else /* ! USE_SYSFS */ #define MAX_DISKS 64 #define PROC_IDE "/proc/ide" #define DEV_BASE "/dev" @@ -104,7 +299,7 @@ int hddown(void) return 0; } - +#endif /* ! USE_SYSFS */ #else /* __linux__ */ int hddown(void)