/*- * Copyright (C) 2005-2007 Nick Withers. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "downtime.h" #include #include #include #include #include #include #ifdef BSD #include #endif /* Launches a shutdown using the binary at shutdown_path and the user specifications specs and updates the shutdown struct accordingly */ shutdown_t * start_shutdown(user_specs_t *specs, shutdown_t *shutdown) { extern char **environ, *shutdown_path; unsigned long int max_timeout; size_t params_length; char **shutdown_param_array, *shutdown_type_string, *timeout_string; int shutdown_status; #ifndef WITH_NOFORK_SHUTDOWN_PARAMETER int shutdown_pipe[2]; char *shutdown_out, c; size_t shutdown_out_line_offset; struct pollfd shutdown_err_poll; #endif if (shutdown == NULL) { if ((shutdown = malloc(sizeof (*shutdown))) == NULL) show_error_dialog(EX_OSERR, "malloc() returned NULL in launch_shutdown(). Error: \"%s\" (%d)", strerror(errno), errno); } switch (specs->timeout_unit) { case MINUTES: shutdown->timeout = specs->timeout; break; case HOURS: shutdown->timeout = specs->timeout * 60; break; case DAYS: shutdown->timeout = specs->timeout * 60 * 24; break; default: show_stderr("Unrecognised timeout unit in launch_shutdown(), defaulting to MINUTES"); break; } shutdown->start_time = time(NULL); shutdown->shutdown_time = shutdown->start_time + (60 * shutdown->timeout); /* 60 * because shutdown->shutdown_time is specified in seconds, whereas shutdown->timeout is specified in minutes */ shutdown->type = specs->shutdown_type; max_timeout = (INT_MAX - time(NULL)) / 60; /* Due to current limitations in the way time is represented (i.e., as a signed int representing the number of seconds since the 1970-01-01), it isn't possible to specify a shutdown past 2038 */ /* There's a potential race condition here: It's theoretically possible that by the time shutdown(8) is actually fully called (after the vfork(), etc...) the time is no longer valid. This is a fairly unlikely scenario for what is definitely a boundary case (specifying a shutdown time decades away, at the time of writing) */ if (shutdown->timeout > max_timeout) /* At least on x86 FreeBSD 6.1-RELEASE's shutdown(8), the shutdown time is stored in an int (well, a time_t, which is ant __int32_t, which is an int), so make sure that the offset specified will fit. Note that we check for both new shutdowns and existing shutdowns. This is because a pause may have caused the timeout to become too large, though this really shouldn't come up in a screaming hurry... */ { free (shutdown); show_warning_dialog("Please specify a value between 0 and %d minutes (note that the upper bound is current-time dependent)", max_timeout); errno = EDOM; /* "Numerical argument out of domain" */ return (NULL); } if ((pasprintf(&timeout_string, "+%lu", shutdown->timeout)) == -1) show_error_dialog(EX_OSERR, "pasprintf() returned -1 in launch_shutdown(). Error: \"%s\" (%d)", strerror(errno), errno); params_length = 0; if (!add_to_string_array(&shutdown_param_array, ¶ms_length, get_filename_from_path(shutdown_path))) show_error_dialog(EX_OSERR, "Couldn't construct argument list for shutdown(8). Error: \"%s\" (%d)", strerror(errno), errno); shutdown_type_string = ""; switch (shutdown->type) { case POWEROFF: shutdown_type_string = "-p"; break; case HALT: shutdown_type_string = "-h"; break; case SINGLE_USER: break; /* Add no shutdown parameter as going to single user mode is shutdown(8)'s default action */ case RESTART: shutdown_type_string = "-r"; break; default: show_error_dialog(EX_SOFTWARE, "Unrecognised shutdown type encountered in launch_shutdown()"); break; } if (strlen(shutdown_type_string) && !add_to_string_array(&shutdown_param_array, ¶ms_length, shutdown_type_string)) show_error_dialog(EX_OSERR, "Couldn't construct argument list for shutdown(8). Error: \"%s\" (%d)", strerror(errno), errno); #ifdef WITH_NOFORK_SHUTDOWN_PARAMETER if (!add_to_string_array(&shutdown_param_array, ¶ms_length, WITH_NOFORK_SHUTDOWN_PARAMETER)) show_error_dialog(EX_OSERR, "Couldn't construct argument list for shutdown(8). Error: \"%s\" (%d)", strerror(errno), errno); #endif if (!add_to_string_array(&shutdown_param_array, ¶ms_length, timeout_string) || !add_to_string_array(&shutdown_param_array, ¶ms_length, (char *) NULL)) /* Append the NULL string to the end of the string array */ show_error_dialog(EX_OSERR, "Coulnd't construct argument list for shutdown(8). Error: \"%s\" (%d)", strerror(errno), errno); #ifndef WITH_NOFORK_SHUTDOWN_PARAMETER if (pipe(shutdown_pipe) != 0) /* Create a pipe for the soon-to-be-created child process to write to and the parent process to read from */ show_error_dialog(EX_OSERR, "Couldn't create a pipe to shutdown(8). Error: \"%s\" (%d)", strerror(errno), errno); #endif if ((shutdown->pid = vfork()) == 0) /* vfork() returns the PID of the child after returning 0 in the child's context, so we're able to assign the PID to pid without dramas */ { #ifndef WITH_NOFORK_SHUTDOWN_PARAMETER close(shutdown_pipe[0]); if (shutdown_pipe[1] != PID_OUTPUT_FILENO) { if (dup2(shutdown_pipe[1], PID_OUTPUT_FILENO) == -1) show_error_dialog(EX_OSERR, "Encountered an error piping stderr to parent. Error: \"%s\" (%d)", strerror(errno), errno); close(shutdown_pipe[1]); } #endif execve(shutdown_path, shutdown_param_array, environ); free(timeout_string); free(shutdown_param_array); show_stderr("Unable to launch \"%s\", error: \"%s\" (%d)", shutdown_path, strerror(errno), errno); /* execve() should overwrite the process containing this instruction, so if we get here, it means the execve failed! */ _exit(errno); } if (shutdown->pid == -1 || waitpid(shutdown->pid, &shutdown_status, 0) == -1 || (WIFEXITED (shutdown_status) == TRUE && WEXITSTATUS (shutdown_status) != EXIT_SUCCESS)) /* Make sure that (in this order): 1) The vfork() was successful; 2) shutdown(8) terminated successfully */ show_error_dialog(WEXITSTATUS (shutdown_status), "Failed to launch shutdown(8). Error: \"%s\" (%d)", strerror(WEXITSTATUS (shutdown_status)), WEXITSTATUS (shutdown_status)); #ifndef WITH_NOFORK_SHUTDOWN_PARAMETER close(shutdown_pipe[1]); shutdown_err_poll.fd = shutdown_pipe[0]; shutdown_err_poll.events = POLLIN; shutdown_err_poll.revents = POLLIN; shutdown_out = NULL; shutdown_out_line_offset = 0; shutdown->pid = 0; while (!shutdown->pid && poll(&shutdown_err_poll, 1, 0) > 0 && read(shutdown_pipe[0], &c, 1) == 1) { if (!cat_strchar(&shutdown_out, c)) show_error_dialog(EX_OSERR, "Couldn't store shutdown(8) output. Error: \"%s\" (%d)", strerror(errno), errno); if (c == '\n') /* At the end of each line, see if the line began with START_SHUTDOWN_PID_OUTPUT and if it did, extract the subsequent long int. Note that we could instead simply check the nast strlen(START_SHUTDOWN_PID_OUTPUT) characters of shutdown(s*)'s output (regardless of newlines) against START_SHUTDOWN_PID_OUTPUT. This would be useful if any platform's shutdown(8) inserts, for instance, the date / time in its output: We couldn't necessarily match the whole start of the line */ { if (strncasecmp(shutdown_out + shutdown_out_line_offset, START_SHUTDOWN_PID_OUTPUT, strlen(START_SHUTDOWN_PID_OUTPUT)) == 0) /* strncasecmp() doesn't mind if ether string is shorter than the requested comparison length */ { shutdown->pid = strtol(shutdown_out + shutdown_out_line_offset + strlen(START_SHUTDOWN_PID_OUTPUT), NULL, 10); /* Get the next long out of the string */ if ((shutdown->pid > 0) && (shutdown->pid <= INT_MAX)) /* Check that the long is appropriate (it needs to be positive, non-zero, and <= INT_MAX (really want to use PID_MAX, but in sys/proc.h it's only #defined ifdef(_KERNEL))) */ { if (kill(shutdown->pid, 0) != 0) { free(shutdown); return (NULL); } } else /* Impossible PID */ { free(shutdown); errno = EDOM; return (NULL); } } shutdown_out_line_offset = strlen(shutdown_out); /* shutdown_out + shutdown_out_line_offset points to the terminating NUL character in shutdown_out */ } } if (!shutdown->pid) show_warning_dialog("Couldn't read shutdown(8)'s PID from its output. It may or may not have been launched successfully. %s will continue to display shutdown progress, but please check that shutdown(8) is actually running", PRGNAME); free(shutdown_out); /* If nothing was actually written to shutdown_out, it will == NULL and this free() will be a no-op */ #endif shutdown->in_progress = TRUE; shutdown->paused = FALSE; return (shutdown); }