This seems to be a SUSE specific patch. Here we add the check for unmaintained disk like devices to be able to flush and maybe shut them down. Also we add the missing sync() system call for the direct halt/reboot systemctl command. Then we use the system halt as gfallback if poweroff fails for both the direct poweroff systemctl command as well as for the systemd-shutdown utility. --- Makefile.am | 2 Makefile.in | 7 src/core/shutdown.c | 8 - src/shared/hdflush.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/hdflush.h | 25 +++ src/systemctl/systemctl.c | 17 +- 6 files changed, 416 insertions(+), 8 deletions(-) --- systemd-208/Makefile.am +++ systemd-208/Makefile.am 2014-01-28 11:06:55.638238060 +0000 @@ -680,6 +680,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/strbuf.h \ src/shared/strxcpyx.c \ src/shared/strxcpyx.h \ + src/shared/hdflush.c \ + src/shared/hdflush.h \ src/shared/conf-parser.c \ src/shared/conf-parser.h \ src/shared/log.c \ --- systemd-208/Makefile.in +++ systemd-208/Makefile.in 2014-01-28 11:06:33.942246196 +0000 @@ -1509,7 +1509,7 @@ am_libsystemd_shared_la_OBJECTS = src/sh src/shared/hashmap.lo src/shared/set.lo src/shared/fdset.lo \ src/shared/prioq.lo src/shared/sleep-config.lo \ src/shared/strv.lo src/shared/env-util.lo src/shared/strbuf.lo \ - src/shared/strxcpyx.lo src/shared/conf-parser.lo \ + src/shared/strxcpyx.lo src/shared/hdflush.lo src/shared/conf-parser.lo \ src/shared/log.lo src/shared/ratelimit.lo \ src/shared/exit-status.lo src/shared/utf8.lo \ src/shared/pager.lo src/shared/socket-util.lo \ @@ -4137,6 +4137,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/strbuf.h \ src/shared/strxcpyx.c \ src/shared/strxcpyx.h \ + src/shared/hdflush.c \ + src/shared/hdflush.h \ src/shared/conf-parser.c \ src/shared/conf-parser.h \ src/shared/log.c \ @@ -7073,6 +7075,8 @@ src/shared/strbuf.lo: src/shared/$(am__d src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/strxcpyx.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/hdflush.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/conf-parser.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/log.lo: src/shared/$(am__dirstamp) \ @@ -9236,6 +9240,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/strbuf.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/strv.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/strxcpyx.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/hdflush.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/time-dst.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/time-util.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/unit-name.Plo@am__quote@ --- systemd-208/src/shared/hdflush.c +++ systemd-208/src/shared/hdflush.c 2014-01-28 10:58:56.490735704 +0000 @@ -0,0 +1,365 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Werner Fink + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +/* + * Find all disks on the system, list out IDE, unmanaged ATA disks, and + * USB sticks flush the cache of those and optional shut them down. + */ + +#include +#include +#ifdef LIST_DEBUG +# include +#endif +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#ifdef WORDS_BIGENDIAN +# include +#endif + +/* Used in flush_cache_ext(), compare with */ +#define IDBYTES 512 +#define MASK_EXT 0xE000 /* Bit 15 shall be zero, bit 14 shall be one, bit 13 flush cache ext */ +#define TEST_EXT 0x6000 + +/* Maybe set in list_disks() and used in do_standby_disk() */ +#define DISK_IS_IDE 0x00000001 +#define DISK_IS_SATA 0x00000002 +#define DISK_EXTFLUSH 0x00000004 +#define DISK_REMOVABLE 0x00000008 +#define DISK_MANAGED 0x00000010 +#define DISK_FLUSHONLY 0x00000020 + +struct sysfs { + struct udev *udev; + struct udev_enumerate *num; + struct udev_list_entry *item; + char *devnode; + size_t size; +}; + +static int flush_cache_ext(const struct sysfs *sysfs); + +static struct sysfs * open_sysfs(void) +{ + static struct sysfs sysfs; + sysfs.udev = udev_new(); + if (!sysfs.udev) + goto err; + sysfs.num = udev_enumerate_new(sysfs.udev); + if (!sysfs.num) + goto err; + if (udev_enumerate_add_match_subsystem(sysfs.num, "block") < 0) + goto err; + if (udev_enumerate_add_match_sysname(sysfs.num, "sd?") < 0) + goto err; + if (udev_enumerate_add_match_sysname(sysfs.num, "hd?") < 0) + goto err; + if (udev_enumerate_scan_devices(sysfs.num) < 0) + goto err; + sysfs.item = udev_enumerate_get_list_entry(sysfs.num); + sysfs.devnode = NULL; + sysfs.size = 0; + return &sysfs; +err: + if (sysfs.num) + udev_unref(sysfs.udev); + if (sysfs.udev) + udev_unref(sysfs.udev); + return NULL; +} + +static void close_sysfs(struct sysfs *sysfs) +{ + if (sysfs->num) + udev_enumerate_unref(sysfs->num); + if (sysfs->udev) + udev_unref(sysfs->udev); + if (sysfs->devnode) + free(sysfs->devnode); + sysfs->devnode = NULL; +} + + +static char *list_disks(struct sysfs *sysfs, unsigned int* flags) +{ + struct udev_device *device, *parent; + struct udev_list_entry *item; + const char *devnode; + char path[PATH_MAX]; + + device = NULL; +next: + if (device) + udev_device_unref(device); + if (sysfs->devnode) + free(sysfs->devnode); + sysfs->devnode = NULL; + sysfs->size = 0; + *flags = 0; + + if (!sysfs->item) + goto empty; + item = sysfs->item; + sysfs->item = udev_list_entry_get_next(sysfs->item); + + if (!(device = udev_device_new_from_syspath(sysfs->udev, udev_list_entry_get_name(item)))) + goto out; + if (!(devnode = udev_device_get_devnode(device))) + goto out; + if (!(sysfs->devnode = strdup(devnode))) + goto out; + + path[0] = '\0'; + parent = udev_device_get_parent(device); + if (parent) { + const char *sysname, *devpath; + struct udev_device *disk; + const char *value; + int ret; + + sysname = udev_device_get_sysname(parent); + devpath = udev_device_get_devpath(parent); + + strcpy(path, "/sys"); + strcat(path, devpath); + strcat(path, "/scsi_disk/"); + strcat(path, sysname); + + disk = udev_device_new_from_syspath(sysfs->udev, path); + if (disk) { + value = udev_device_get_sysattr_value(disk, "manage_start_stop"); + udev_device_unref(disk); + + if (value && *value != '0') { + *flags = DISK_MANAGED; +#ifdef LIST_DEBUG + goto next; /* Device managed by the kernel */ +#endif + } + } + + value = udev_device_get_sysattr_value(device, "size"); + if (value && *value) + sysfs->size = (size_t)atoll(value); + + value = udev_device_get_sysattr_value(device, "removable"); + if (value && *value != '0') { + *flags |= DISK_REMOVABLE; + + if ((ret = flush_cache_ext(sysfs))) { + if (ret < 0) + goto next; + *flags |= DISK_EXTFLUSH; + } + goto out; /* Removable disk like USB stick */ + } + + value = udev_device_get_sysname(device); + if (value && *value == 'h') { + *flags |= DISK_IS_IDE; + + if ((ret = flush_cache_ext(sysfs))) { + if (ret < 0) + goto next; + *flags |= DISK_EXTFLUSH; + } + goto out; /* IDE disk found */ + } + + value = udev_device_get_sysattr_value(parent, "vendor"); + if (value && strncmp(value, "ATA", 3) == 0) { + *flags |= (DISK_IS_IDE|DISK_IS_SATA); + + if ((ret = flush_cache_ext(sysfs))) { + if (ret < 0) + goto next; + *flags |= DISK_EXTFLUSH; + } + goto out; /* SATA disk to shutdown */ + } + goto next; + } +out: + udev_device_unref(device); +empty: + return sysfs->devnode; +} + +/* + * Check IDE/(S)ATA hard disk identity for + * the FLUSH CACHE EXT bit set. + */ +static int flush_cache_ext(const struct sysfs *sysfs) +{ +#ifndef WIN_IDENTIFY +#define WIN_IDENTIFY 0xEC +#endif + unsigned char args[4+IDBYTES]; + unsigned short *id = (unsigned short*)(&args[4]); + int fd = -1, ret = 0; + + if (sysfs->size < (1<<28)) + goto out; /* small disk */ + + if ((fd = open(sysfs->devnode, O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0) + goto out; + + memset(&args[0], 0, sizeof(args)); + args[0] = WIN_IDENTIFY; + args[3] = 1; + if (ioctl(fd, HDIO_DRIVE_CMD, &args)) + goto out; +#ifdef WORDS_BIGENDIAN +# if 0 + { + const unsigned short *end = id + IDBYTES/2; + const unsigned short *from = id; + unsigned short *to = id; + + while (from < end) + *to++ = bswap_16(*from++); + } +# else + id[83] = bswap_16(id[83]); +# endif +#endif + if ((id[83] & MASK_EXT) == TEST_EXT) + ret = 1; +out: + if (fd >= 0) + close(fd); + return ret; +} + +/* + * Put an IDE/SCSI/SATA disk in standby mode. + * Code stolen from hdparm.c + */ +static int do_standby_disk(struct sysfs *sysfs, unsigned int flags) +{ +#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}; + int fd, ret; + + if ((fd = open(sysfs->devnode, O_RDWR|O_NONBLOCK|O_CLOEXEC)) < 0) + return -1; + + switch (flags & DISK_EXTFLUSH) { + case DISK_EXTFLUSH: + if ((ret = ioctl(fd, HDIO_DRIVE_CMD, &flush1)) == 0) + break; + /* Extend flush rejected, try standard flush */ + default: + ret = ioctl(fd, HDIO_DRIVE_CMD, &flush2) && + ioctl(fd, BLKFLSBUF); + break; + } + + if ((flags & DISK_FLUSHONLY) == 0x0) { + ret = ioctl(fd, HDIO_DRIVE_CMD, &stdby1) && + ioctl(fd, HDIO_DRIVE_CMD, &stdby2); + } + + close(fd); + + if (ret) + return -1; + return 0; +} + +#ifdef LIST_DEBUG +int main() +{ + char *disk; + unsigned int flags; + struct sysfs *sysfs = open_sysfs(); + if (!sysfs) + goto err; + while ((disk = list_disks(sysfs, &flags))) + fprintf(stdout, "%s\n", sysfs->devnode); + close_sysfs(sysfs); +err: + return 0; +} +#else +/* + * 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. + */ +void hddown(void) +{ + struct sysfs *sysfs; + unsigned int flags; + char *disk; + + if (!(sysfs = open_sysfs())) + return; + + while ((disk = list_disks(sysfs, &flags))) + do_standby_disk(sysfs, flags); + + close_sysfs(sysfs); +} + +/* + * List all disks and cause them to flush their buffers. + */ +void hdflush(void) +{ + struct sysfs *sysfs; + unsigned int flags; + char *disk; + + if (!(sysfs = open_sysfs())) + return; + + while ((disk = list_disks(sysfs, &flags))) + do_standby_disk(sysfs, (flags|DISK_FLUSHONLY)); + + close_sysfs(sysfs); +} +#endif --- systemd-208/src/shared/hdflush.h +++ systemd-208/src/shared/hdflush.h 2014-01-28 11:00:08.286235696 +0000 @@ -0,0 +1,25 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Werner Fink + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +void hdflush(void); +void hddown(void); --- systemd-208/src/core/shutdown.c +++ systemd-208/src/core/shutdown.c 2014-01-28 11:14:15.722235591 +0000 @@ -40,6 +40,7 @@ #include "missing.h" #include "log.h" #include "fileio.h" +#include "hdflush.h" #include "umount.h" #include "util.h" #include "mkdir.h" @@ -302,8 +303,13 @@ int main(int argc, char *argv[]) { * on reboot(), but the file systems need to be synce'd * explicitly in advance. So let's do this here, but not * needlessly slow down containers. */ - if (!in_container) + if (!in_container) { sync(); + if (cmd == RB_POWER_OFF || cmd == RB_HALT_SYSTEM) + hddown(); + else + hdflush(); + } if (cmd == LINUX_REBOOT_CMD_KEXEC) { --- systemd-208/src/systemctl/systemctl.c +++ systemd-208/src/systemctl/systemctl.c 2014-01-28 11:31:27.150735613 +0000 @@ -87,6 +87,7 @@ static bool arg_no_pager = false; static bool arg_no_wtmp = false; static bool arg_no_wall = false; static bool arg_no_reload = false; +static bool arg_no_sync = false; static bool arg_show_types = false; static bool arg_ignore_inhibitors = false; static bool arg_dry = false; @@ -5272,6 +5273,7 @@ static int halt_parse_argv(int argc, cha { "reboot", no_argument, NULL, ARG_REBOOT }, { "force", no_argument, NULL, 'f' }, { "wtmp-only", no_argument, NULL, 'w' }, + { "no-sync", no_argument, NULL, 'n' }, { "no-wtmp", no_argument, NULL, 'd' }, { "no-wall", no_argument, NULL, ARG_NO_WALL }, { NULL, 0, NULL, 0 } @@ -5324,10 +5326,13 @@ static int halt_parse_argv(int argc, cha case 'i': case 'h': - case 'n': /* Compatibility nops */ break; + case 'n': + arg_no_sync = true; + break; + case '?': return -EINVAL; @@ -5981,14 +5986,14 @@ static int halt_now(enum action a) { switch (a) { - case ACTION_HALT: - log_info("Halting."); - reboot(RB_HALT_SYSTEM); - return -errno; - case ACTION_POWEROFF: log_info("Powering off."); reboot(RB_POWER_OFF); + /* Fall through */ + + case ACTION_HALT: + log_info("Halting."); + reboot(RB_HALT_SYSTEM); return -errno; case ACTION_REBOOT: