diff options
| author | Oleg Nesterov <oleg@redhat.com> | 2013-03-22 18:04:41 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-03-22 19:41:20 -0400 |
| commit | 2ca067efd82939dfd87827d29d36a265823a4c2f (patch) | |
| tree | a6291128f65ef015ab36460a5e5f39a5bc25c4a1 /kernel | |
| parent | d00285884c0892bb1310df96bce6056e9ce9b9d9 (diff) | |
poweroff: change orderly_poweroff() to use schedule_work()
David said:
Commit 6c0c0d4d1080 ("poweroff: fix bug in orderly_poweroff()")
apparently fixes one bug in orderly_poweroff(), but introduces
another. The comments on orderly_poweroff() claim it can be called
from any context - and indeed we call it from interrupt context in
arch/powerpc/platforms/pseries/ras.c for example. But since that
commit this is no longer safe, since call_usermodehelper_fns() is not
safe in interrupt context without the UMH_NO_WAIT option.
orderly_poweroff() can be used from any context but UMH_WAIT_EXEC is
sleepable. Move the "force" logic into __orderly_poweroff() and change
orderly_poweroff() to use the global poweroff_work which simply calls
__orderly_poweroff().
While at it, remove the unneeded "int argc" and change argv_split() to
use GFP_KERNEL.
We use the global "bool poweroff_force" to pass the argument, this can
obviously affect the previous request if it is pending/running. So we
only allow the "false => true" transition assuming that the pending
"true" should succeed anyway. If schedule_work() fails after that we
know that work->func() was not called yet, it must see the new value.
This means that orderly_poweroff() becomes async even if we do not run
the command and always succeeds, schedule_work() can only fail if the
work is already pending. We can export __orderly_poweroff() and change
the non-atomic callers which want the old semantics.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Reported-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Reported-by: David Gibson <david@gibson.dropbear.id.au>
Cc: Lucas De Marchi <lucas.demarchi@profusion.mobi>
Cc: Feng Hong <hongfeng@marvell.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge Hallyn <serge.hallyn@canonical.com>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/sys.c | 57 |
1 files changed, 32 insertions, 25 deletions
diff --git a/kernel/sys.c b/kernel/sys.c index 81f56445fba9..39c9c4a2949f 100644 --- a/kernel/sys.c +++ b/kernel/sys.c | |||
| @@ -2185,9 +2185,8 @@ SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep, | |||
| 2185 | 2185 | ||
| 2186 | char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff"; | 2186 | char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff"; |
| 2187 | 2187 | ||
| 2188 | static int __orderly_poweroff(void) | 2188 | static int __orderly_poweroff(bool force) |
| 2189 | { | 2189 | { |
| 2190 | int argc; | ||
| 2191 | char **argv; | 2190 | char **argv; |
| 2192 | static char *envp[] = { | 2191 | static char *envp[] = { |
| 2193 | "HOME=/", | 2192 | "HOME=/", |
| @@ -2196,20 +2195,40 @@ static int __orderly_poweroff(void) | |||
| 2196 | }; | 2195 | }; |
| 2197 | int ret; | 2196 | int ret; |
| 2198 | 2197 | ||
| 2199 | argv = argv_split(GFP_ATOMIC, poweroff_cmd, &argc); | 2198 | argv = argv_split(GFP_KERNEL, poweroff_cmd, NULL); |
| 2200 | if (argv == NULL) { | 2199 | if (argv) { |
| 2200 | ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); | ||
| 2201 | argv_free(argv); | ||
| 2202 | } else { | ||
| 2201 | printk(KERN_WARNING "%s failed to allocate memory for \"%s\"\n", | 2203 | printk(KERN_WARNING "%s failed to allocate memory for \"%s\"\n", |
| 2202 | __func__, poweroff_cmd); | 2204 | __func__, poweroff_cmd); |
| 2203 | return -ENOMEM; | 2205 | ret = -ENOMEM; |
| 2204 | } | 2206 | } |
| 2205 | 2207 | ||
| 2206 | ret = call_usermodehelper_fns(argv[0], argv, envp, UMH_WAIT_EXEC, | 2208 | if (ret && force) { |
| 2207 | NULL, NULL, NULL); | 2209 | printk(KERN_WARNING "Failed to start orderly shutdown: " |
| 2208 | argv_free(argv); | 2210 | "forcing the issue\n"); |
| 2211 | /* | ||
| 2212 | * I guess this should try to kick off some daemon to sync and | ||
| 2213 | * poweroff asap. Or not even bother syncing if we're doing an | ||
| 2214 | * emergency shutdown? | ||
| 2215 | */ | ||
| 2216 | emergency_sync(); | ||
| 2217 | kernel_power_off(); | ||
| 2218 | } | ||
| 2209 | 2219 | ||
| 2210 | return ret; | 2220 | return ret; |
| 2211 | } | 2221 | } |
| 2212 | 2222 | ||
| 2223 | static bool poweroff_force; | ||
| 2224 | |||
| 2225 | static void poweroff_work_func(struct work_struct *work) | ||
| 2226 | { | ||
| 2227 | __orderly_poweroff(poweroff_force); | ||
| 2228 | } | ||
| 2229 | |||
| 2230 | static DECLARE_WORK(poweroff_work, poweroff_work_func); | ||
| 2231 | |||
| 2213 | /** | 2232 | /** |
| 2214 | * orderly_poweroff - Trigger an orderly system poweroff | 2233 | * orderly_poweroff - Trigger an orderly system poweroff |
| 2215 | * @force: force poweroff if command execution fails | 2234 | * @force: force poweroff if command execution fails |
| @@ -2219,21 +2238,9 @@ static int __orderly_poweroff(void) | |||
| 2219 | */ | 2238 | */ |
| 2220 | int orderly_poweroff(bool force) | 2239 | int orderly_poweroff(bool force) |
| 2221 | { | 2240 | { |
| 2222 | int ret = __orderly_poweroff(); | 2241 | if (force) /* do not override the pending "true" */ |
| 2223 | 2242 | poweroff_force = true; | |
| 2224 | if (ret && force) { | 2243 | schedule_work(&poweroff_work); |
| 2225 | printk(KERN_WARNING "Failed to start orderly shutdown: " | 2244 | return 0; |
| 2226 | "forcing the issue\n"); | ||
| 2227 | |||
| 2228 | /* | ||
| 2229 | * I guess this should try to kick off some daemon to sync and | ||
| 2230 | * poweroff asap. Or not even bother syncing if we're doing an | ||
| 2231 | * emergency shutdown? | ||
| 2232 | */ | ||
| 2233 | emergency_sync(); | ||
| 2234 | kernel_power_off(); | ||
| 2235 | } | ||
| 2236 | |||
| 2237 | return ret; | ||
| 2238 | } | 2245 | } |
| 2239 | EXPORT_SYMBOL_GPL(orderly_poweroff); | 2246 | EXPORT_SYMBOL_GPL(orderly_poweroff); |
