| /* shutdown.c - shutdown a Linux system |
| * Initially written by poe@daimi.aau.dk |
| * Currently maintained at ftp://ftp.daimi.aau.dk/pub/Software/Linux/ |
| */ |
| |
| /* |
| * Modified by jrs@world.std.com to try to exec "umount -a" and if |
| * that doesn't work, then umount filesystems ourselves in reverse |
| * order. The old-way was in forward order. Also if the device |
| * field of the mtab does not start with a "/" then give umount |
| * the mount point instead. This is needed for the nfs and proc |
| * filesystems and yet is compatible with older systems. |
| * |
| * We also use the mntent library interface to read the mtab file |
| * instead of trying to parse it directly and no longer give a |
| * warning about not being able to umount the root. |
| * |
| * The reason "umount -a" should be tried first is because it may do |
| * special processing for some filesystems (such as informing an |
| * nfs server about nfs umounts) that we don't want to cope with here. |
| */ |
| |
| /* |
| * Various changes and additions to resemble SunOS 4 shutdown/reboot/halt(8) |
| * more closely by Scott Telford (s.telford@ed.ac.uk) 93/05/18. |
| * (I butchered Scotts patches somewhat. - poe) |
| * |
| * Changes by Richard Gooch <rgooch@atnf.csiro.au> (butchered by aeb) |
| * introducing shutdown.conf. |
| * |
| * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> |
| * - added Native Language Support |
| * |
| * 2000-03-02 Richard Gooch <rgooch@atnf.csiro.au> |
| * - pause forever if (pid == 1) and send SIGQUIT to pid = 1 |
| * |
| * 2000-11-04 Richard Gooch <rgooch@atnf.csiro.au> |
| * - continue reaping if (pid == 1) |
| * |
| * 2000-11-06 Richard Gooch <rgooch@atnf.csiro.au> |
| * - shut down "finalprog" from /etc/inittab |
| * - kill normal user (non-root and non-daemon) processes first with SIGTERM |
| * |
| * 2000-11-08 Richard Gooch <rgooch@atnf.csiro.au> |
| * - rollback services |
| * - do not unmount devfs (otherwise get harmless but annoying messages) |
| * - created syncwait() for faster shutting down |
| * - kill getty processes |
| * 2001-05-12 Richard Gooch <rgooch@atnf.csiro.au> |
| * - unblock all signals (sigmask from simpleinit(8) stopped sleep(3)) |
| * - close all files |
| */ |
| |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <utmp.h> |
| #include <time.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <sys/param.h> |
| #include <termios.h> |
| #include <mntent.h> |
| #include <sys/mount.h> |
| #include <sys/wait.h> |
| #include <syslog.h> |
| #include <sys/resource.h> |
| #include <sys/types.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| #include <sys/utsname.h> |
| #include "linux_reboot.h" |
| #include "pathnames.h" |
| #include "xstrncpy.h" |
| #include "nls.h" |
| #include "usleep.h" |
| |
| static void usage(void), int_handler(int), write_user(struct utmp *); |
| static void wall(void), write_wtmp(void), unmount_disks(void); |
| static void unmount_disks_ourselves(void); |
| static void swap_off(void), do_halt(char *); |
| static void kill_mortals (int sig); |
| static void stop_finalprog (void); |
| static void syncwait (int timeval); |
| |
| |
| char *prog; /* name of the program */ |
| int opt_reboot; /* true if -r option or reboot command */ |
| int timeout; /* number of seconds to shutdown */ |
| int opt_quiet; /* true if no message is wanted */ |
| int opt_fast; /* true if fast boot */ |
| char message[90]; /* reason for shutdown if any... */ |
| int opt_single = 0; /* true is we want to boot singleuser */ |
| char *whom; /* who is shutting the system down */ |
| int opt_msgset = 0; /* message set on command line */ |
| /* change 1 to 0 if no file is to be used by default */ |
| int opt_use_config_file = 1; /* read _PATH_SHUTDOWN_CONF */ |
| char halt_action[256]; /* to find out what to do upon halt */ |
| |
| /* #define DEBUGGING */ |
| |
| #define WR(s) write(fd, s, strlen(s)) |
| #define WRCRLF write(fd, "\r\n", 2) |
| #define ERRSTRING strerror(errno) |
| |
| #define UMOUNT_ARGS "umount", "-a", "-t", "nodevfs" |
| #define SWAPOFF_ARGS "swapoff", "-a" |
| |
| void |
| usage(void) |
| { |
| fprintf(stderr, |
| _("Usage: shutdown [-h|-r] [-fqs] [now|hh:ss|+mins]\n")); |
| exit(1); |
| } |
| |
| static void |
| my_puts(char *s) |
| { |
| /* Use a fresh stdout after forking */ |
| freopen(_PATH_CONSOLE, "w", stdout); |
| puts(s); |
| fflush(stdout); |
| } |
| |
| void |
| int_handler(int sig) |
| { |
| unlink(_PATH_NOLOGIN); |
| signal(SIGINT, SIG_DFL); |
| my_puts(_("Shutdown process aborted")); |
| exit(1); |
| } |
| |
| static int |
| iswhitespace(int a) { |
| return (a == ' ' || a == '\t'); |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| int c, i, fd; |
| char *ptr; |
| |
| i = getdtablesize (); |
| for (fd = 3; fd < i; fd++) close (fd); |
| if (getpid () == 1) |
| { |
| for (fd = 0; fd < 3; fd++) close (fd); |
| while (1) wait (NULL); /* Grim reaper never stops */ |
| } |
| sigsetmask (0); /* simpleinit(8) blocks all signals: undo for ALRM */ |
| for (i = 1; i < NSIG; i++) signal (i, SIG_DFL); |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| |
| #ifndef DEBUGGING |
| if(setreuid (0, 0)) { |
| fprintf(stderr, _("%s: Only root can shut a system down.\n"), |
| argv[0]); |
| exit(1); |
| } |
| #endif |
| |
| if(*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */ |
| prog = argv[0]; |
| if((ptr = strrchr(argv[0], '/'))) prog = ++ptr; |
| |
| /* All names (halt, reboot, fasthalt, fastboot, shutdown) |
| refer to the same program with the same options, |
| only the defaults differ. */ |
| if(!strcmp("halt", prog)) { |
| opt_reboot = 0; |
| opt_quiet = 1; |
| opt_fast = 0; |
| timeout = 0; |
| } else if(!strcmp("fasthalt", prog)) { |
| opt_reboot = 0; |
| opt_quiet = 1; |
| opt_fast = 1; |
| timeout = 0; |
| } else if(!strcmp("reboot", prog)) { |
| opt_reboot = 1; |
| opt_quiet = 1; |
| opt_fast = 0; |
| timeout = 0; |
| } else if(!strcmp("fastboot", prog)) { |
| opt_reboot = 1; |
| opt_quiet = 1; |
| opt_fast = 1; |
| timeout = 0; |
| } else { |
| /* defaults */ |
| opt_reboot = 0; |
| opt_quiet = 0; |
| opt_fast = 0; |
| timeout = 2*60; |
| } |
| |
| c = 0; |
| while(++c < argc) { |
| if(argv[c][0] == '-') { |
| for(i = 1; argv[c][i]; i++) { |
| switch(argv[c][i]) { |
| case 'C': |
| opt_use_config_file = 1; |
| break; |
| case 'h': |
| opt_reboot = 0; |
| break; |
| case 'r': |
| opt_reboot = 1; |
| break; |
| case 'f': |
| opt_fast = 1; |
| break; |
| case 'q': |
| opt_quiet = 1; |
| break; |
| case 's': |
| opt_single = 1; |
| break; |
| |
| default: |
| usage(); |
| } |
| } |
| } else if(!strcmp("now", argv[c])) { |
| timeout = 0; |
| } else if(argv[c][0] == '+') { |
| timeout = 60 * atoi(&argv[c][1]); |
| } else if (isdigit(argv[c][0])) { |
| char *colon; |
| int hour = 0; |
| int minute = 0; |
| time_t tics; |
| struct tm *tt; |
| int now, then; |
| |
| if((colon = strchr(argv[c], ':'))) { |
| *colon = '\0'; |
| hour = atoi(argv[c]); |
| minute = atoi(++colon); |
| } else usage(); |
| |
| (void) time(&tics); |
| tt = localtime(&tics); |
| |
| now = 3600 * tt->tm_hour + 60 * tt->tm_min; |
| then = 3600 * hour + 60 * minute; |
| timeout = then - now; |
| if(timeout < 0) { |
| fprintf(stderr, _("That must be tomorrow, " |
| "can't you wait till then?\n")); |
| exit(1); |
| } |
| } else { |
| xstrncpy(message, argv[c], sizeof(message)); |
| opt_msgset = 1; |
| } |
| } |
| |
| halt_action[0] = 0; |
| |
| /* No doubt we shall want to extend this some day |
| and register a series of commands to be executed |
| at various points during the shutdown sequence, |
| and to define the number of milliseconds to sleep, etc. */ |
| if (opt_use_config_file) { |
| char line[256], *p; |
| FILE *fp; |
| |
| /* Read and parse the config file */ |
| halt_action[0] = '\0'; |
| if ((fp = fopen (_PATH_SHUTDOWN_CONF, "r")) != NULL) { |
| if (fgets (line, sizeof(line), fp) != NULL && |
| strncasecmp (line, "HALT_ACTION", 11) == 0 && |
| iswhitespace(line[11])) { |
| p = strchr(line, '\n'); |
| if (p) |
| *p = 0; /* strip final '\n' */ |
| p = line+11; |
| while(iswhitespace(*p)) |
| p++; |
| strcpy(halt_action, p); |
| } |
| fclose (fp); |
| } |
| } |
| |
| if(!opt_quiet && !opt_msgset) { |
| /* now ask for message, gets() is insecure */ |
| int cnt = sizeof(message)-1; |
| char *ptr; |
| |
| printf("Why? "); fflush(stdout); |
| |
| ptr = message; |
| while(--cnt >= 0 && (*ptr = getchar()) && *ptr != '\n') { |
| ptr++; |
| } |
| *ptr = '\0'; |
| } else if (!opt_msgset) { |
| strcpy(message, _("for maintenance; bounce, bounce")); |
| } |
| |
| #ifdef DEBUGGING |
| printf("timeout = %d, quiet = %d, reboot = %d\n", |
| timeout, opt_quiet, opt_reboot); |
| #endif |
| |
| /* so much for option-processing, now begin termination... */ |
| if(!(whom = getlogin()) || !*whom) whom = "ghost"; |
| if(strlen(whom) > 40) whom[40] = 0; /* see write_user() */ |
| |
| setpriority(PRIO_PROCESS, 0, PRIO_MIN); |
| signal(SIGINT, int_handler); |
| signal(SIGHUP, int_handler); |
| signal(SIGQUIT, int_handler); |
| signal(SIGTERM, int_handler); |
| |
| chdir("/"); |
| |
| if(timeout > 5*60) { |
| sleep(timeout - 5*60); |
| timeout = 5*60; |
| } |
| |
| |
| if((fd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT, 0644)) >= 0) { |
| /* keep xgettext happy and leave \r\n outside strings */ |
| WRCRLF; |
| WR(_("The system is being shut down within 5 minutes")); |
| WRCRLF; |
| write(fd, message, strlen(message)); |
| WRCRLF; |
| WR(_("Login is therefore prohibited.")); |
| WRCRLF; |
| close(fd); |
| } |
| |
| signal(SIGPIPE, SIG_IGN); |
| |
| if(timeout > 0) { |
| wall(); |
| sleep(timeout); |
| } |
| |
| timeout = 0; |
| wall(); |
| sleep(3); |
| |
| /* now there's no turning back... */ |
| signal(SIGINT, SIG_IGN); |
| |
| /* do syslog message... */ |
| openlog(prog, LOG_CONS, LOG_AUTH); |
| if (opt_reboot) |
| syslog(LOG_NOTICE, _("rebooted by %s: %s"), |
| whom, message); |
| else |
| syslog(LOG_NOTICE, _("halted by %s: %s"), |
| whom, message); |
| closelog(); |
| |
| if(opt_fast) |
| if((fd = open("/fastboot", O_WRONLY|O_CREAT, 0644)) >= 0) |
| close(fd); |
| |
| kill(1, SIGTSTP); /* tell init not to spawn more getty's */ |
| write_wtmp(); |
| if(opt_single) |
| if((fd = open(_PATH_SINGLE, O_CREAT|O_WRONLY, 0644)) >= 0) |
| close(fd); |
| |
| sync(); |
| |
| signal(SIGTERM, SIG_IGN); |
| if(fork() > 0) sleep(1000); /* the parent will die soon... */ |
| setpgrp(); /* so the shell wont kill us in the fall */ |
| |
| #ifndef DEBUGGING |
| /* a gentle kill of all other processes except init */ |
| kill_mortals (SIGTERM); |
| for (fd = 0; fd < 3; fd++) close (fd); |
| stop_finalprog (); |
| sleep (1); /* Time for saves to start */ |
| kill (1, SIGTERM); /* Tell init to kill spawned gettys */ |
| usleep (100000); /* Wait for gettys to die */ |
| my_puts (""); /* Get past the login prompt */ |
| system ("/sbin/initctl -r"); /* Roll back services */ |
| syncwait (1); |
| my_puts ("Sending SIGTERM to all remaining processes..."); |
| kill (-1, SIGTERM); |
| sleep (2); /* Default 2, some people need 5 */ |
| |
| kill (-1, SIGKILL); /* Now use brute force... */ |
| |
| /* turn off accounting */ |
| acct(NULL); |
| #endif |
| /* RedHat and SuSE like to remove /etc/nologin. |
| Perhaps the usual sequence is |
| touch nologin; shutdown -h; fiddle with hardware; |
| boot; fiddle with software; rm nologin |
| and removing it here will be counterproductive. |
| Let us see whether people complain. */ |
| unlink(_PATH_NOLOGIN); |
| |
| /* Tell init(8) to exec so that the old inode may be freed cleanly if |
| required. Need to sleep before remounting root read-only */ |
| kill (1, SIGQUIT); |
| |
| sleep (1); /* Time for processes to die and close files */ |
| syncwait (2); |
| |
| /* remove swap files and partitions using swapoff */ |
| swap_off(); |
| |
| /* unmount disks... */ |
| unmount_disks(); |
| syncwait (1); |
| |
| if(opt_reboot) { |
| my_reboot(LINUX_REBOOT_CMD_RESTART); /* RB_AUTOBOOT */ |
| my_puts(_("\nWhy am I still alive after reboot?")); |
| } else { |
| my_puts(_("\nNow you can turn off the power...")); |
| |
| /* allow C-A-D now, faith@cs.unc.edu, re-fixed 8-Jul-96 */ |
| my_reboot(LINUX_REBOOT_CMD_CAD_ON); /* RB_ENABLE_CAD */ |
| sleep (1); /* Wait for devices to finish writing to media */ |
| do_halt(halt_action); |
| } |
| /* NOTREACHED */ |
| exit(0); /* to quiet gcc */ |
| } |
| |
| /*** end of main() ***/ |
| |
| void |
| do_halt(char *action) { |
| if (strcasecmp (action, "power_off") == 0) { |
| printf(_("Calling kernel power-off facility...\n")); |
| fflush(stdout); |
| my_reboot(LINUX_REBOOT_CMD_POWER_OFF); |
| printf(_("Error powering off\t%s\n"), ERRSTRING); |
| fflush(stdout); |
| sleep (2); |
| } else |
| |
| /* This should be improved; e.g. Mike Jagdis wants "/sbin/mdstop -a" */ |
| /* Maybe we should also fork and wait */ |
| if (action[0] == '/') { |
| printf(_("Executing the program \"%s\" ...\n"), action); |
| fflush(stdout); |
| execl(action, action, NULL); |
| printf(_("Error executing\t%s\n"), ERRSTRING); |
| fflush(stdout); |
| sleep (2); |
| } |
| |
| my_reboot(LINUX_REBOOT_CMD_HALT); /* RB_HALT_SYSTEM */ |
| } |
| |
| void |
| write_user(struct utmp *ut) |
| { |
| int fd; |
| int minutes, hours; |
| char term[40] = {'/','d','e','v','/',0}; |
| char msg[100]; |
| |
| minutes = timeout / 60; |
| hours = minutes / 60; |
| minutes %= 60; |
| |
| (void) strncat(term, ut->ut_line, sizeof(ut->ut_line)); |
| |
| /* try not to get stuck on a mangled ut_line entry... */ |
| if((fd = open(term, O_WRONLY|O_NONBLOCK)) < 0) |
| return; |
| |
| msg[0] = '\007'; /* gettext crashes on \a */ |
| sprintf(msg+1, _("URGENT: broadcast message from %s:"), whom); |
| WRCRLF; |
| WR(msg); |
| WRCRLF; |
| |
| if (hours > 1) |
| sprintf(msg, _("System going down in %d hours %d minutes"), |
| hours, minutes); |
| else if (hours == 1) |
| sprintf(msg, _("System going down in 1 hour %d minutes"), |
| minutes); |
| else if (minutes > 1) |
| sprintf(msg, _("System going down in %d minutes\n"), |
| minutes); |
| else if (minutes == 1) |
| sprintf(msg, _("System going down in 1 minute\n")); |
| else |
| sprintf(msg, _("System going down IMMEDIATELY!\n")); |
| |
| WR(msg); |
| WRCRLF; |
| |
| sprintf(msg, _("\t... %s ...\n"), message); |
| WR(msg); |
| WRCRLF; |
| |
| close(fd); |
| } |
| |
| void |
| wall(void) |
| { |
| /* write to all users, that the system is going down. */ |
| struct utmp *ut; |
| |
| utmpname(_PATH_UTMP); |
| setutent(); |
| |
| while((ut = getutent())) { |
| if(ut->ut_type == USER_PROCESS) |
| write_user(ut); |
| } |
| endutent(); |
| } |
| |
| void |
| write_wtmp(void) |
| { |
| /* write in wtmp that we are dying */ |
| int fd; |
| struct utmp ut; |
| |
| memset((char *)&ut, 0, sizeof(ut)); |
| strcpy(ut.ut_line, "~"); |
| memcpy(ut.ut_name, "shutdown", sizeof(ut.ut_name)); |
| |
| time(&ut.ut_time); |
| ut.ut_type = BOOT_TIME; |
| |
| if((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0644)) >= 0) { |
| write(fd, (char *)&ut, sizeof(ut)); |
| close(fd); |
| } |
| } |
| |
| void |
| swap_off(void) |
| { |
| /* swapoff esp. swap FILES so the underlying partition can be |
| unmounted. It you don't have swapoff(1) or use mount to |
| add swapspace, this may not be necessary, but I guess it |
| won't hurt */ |
| |
| int pid; |
| int result; |
| int status; |
| |
| sync(); |
| if ((pid = fork()) < 0) { |
| my_puts(_("Cannot fork for swapoff. Shrug!")); |
| return; |
| } |
| if (!pid) { |
| execl("/sbin/swapoff", SWAPOFF_ARGS, NULL); |
| execl("/etc/swapoff", SWAPOFF_ARGS, NULL); |
| execl("/bin/swapoff", SWAPOFF_ARGS, NULL); |
| execlp("swapoff", SWAPOFF_ARGS, NULL); |
| my_puts(_("Cannot exec swapoff, " |
| "hoping umount will do the trick.")); |
| exit(0); |
| } |
| while ((result = wait(&status)) != -1 && result != pid) |
| ; |
| } |
| |
| void |
| unmount_disks(void) |
| { |
| /* better to use umount directly because it may be smarter than us */ |
| |
| int pid; |
| int result; |
| int status; |
| |
| sync(); |
| if ((pid = fork()) < 0) { |
| my_puts(_("Cannot fork for umount, trying manually.")); |
| unmount_disks_ourselves(); |
| return; |
| } |
| if (!pid) { |
| execl(_PATH_UMOUNT, UMOUNT_ARGS, NULL); |
| |
| /* need my_printf instead of my_puts here */ |
| freopen(_PATH_CONSOLE, "w", stdout); |
| printf(_("Cannot exec %s, trying umount.\n"), _PATH_UMOUNT); |
| fflush(stdout); |
| |
| execlp("umount", UMOUNT_ARGS, NULL); |
| my_puts(_("Cannot exec umount, giving up on umount.")); |
| exit(0); |
| } |
| while ((result = wait(&status)) != -1 && result != pid) |
| ; |
| my_puts(_("Unmounting any remaining filesystems...")); |
| unmount_disks_ourselves(); |
| } |
| |
| void |
| unmount_disks_ourselves(void) |
| { |
| /* unmount all disks */ |
| |
| FILE *mtab; |
| struct mntent *mnt; |
| char *mntlist[128]; |
| int i; |
| int n; |
| char *filesys; |
| |
| sync(); |
| if (!(mtab = setmntent(_PATH_MOUNTED, "r"))) { |
| my_puts("shutdown: Cannot open " _PATH_MOUNTED "."); |
| return; |
| } |
| n = 0; |
| while (n < 100 && (mnt = getmntent(mtab))) { |
| /* |
| * Neil Phillips: trying to unmount temporary / kernel |
| * filesystems is pointless and may cause error messages; |
| * /dev can be a ramfs managed by udev. |
| */ |
| if (strcmp(mnt->mnt_type, "devfs") == 0 || |
| strcmp(mnt->mnt_type, "proc") == 0 || |
| strcmp(mnt->mnt_type, "sysfs") == 0 || |
| strcmp(mnt->mnt_type, "ramfs") == 0 || |
| strcmp(mnt->mnt_type, "tmpfs") == 0 || |
| strcmp(mnt->mnt_type, "devpts") == 0) |
| continue; |
| mntlist[n++] = strdup(mnt->mnt_dir); |
| } |
| endmntent(mtab); |
| |
| /* we are careful to do this in reverse order of the mtab file */ |
| |
| for (i = n - 1; i >= 0; i--) { |
| filesys = mntlist[i]; |
| #ifdef DEBUGGING |
| printf("umount %s\n", filesys); |
| #else |
| if (umount(mntlist[i]) < 0) |
| printf(_("shutdown: Couldn't umount %s: %s\n"), |
| filesys, ERRSTRING); |
| #endif |
| } |
| } |
| |
| static void kill_mortals (int sig) |
| { |
| int npids = 0; |
| int index = 0; |
| int pid; |
| struct stat statbuf; |
| DIR *dp; |
| struct dirent *de; |
| pid_t *pids = NULL; |
| char path[256]; |
| |
| if ( ( dp = opendir ("/proc") ) == NULL ) return; |
| while ( ( de = readdir (dp) ) != NULL ) |
| { |
| if ( !isdigit (de->d_name[0]) ) continue; |
| pid = atoi (de->d_name); |
| sprintf (path, "/proc/%d", pid); |
| if (stat (path, &statbuf) != 0) continue; |
| if (statbuf.st_uid < 100) continue; |
| if (index <= npids) |
| { |
| pids = realloc (pids, npids + 16384); |
| if (pids == NULL) return; |
| npids += 16384; |
| } |
| pids[index++] = pid; |
| } |
| fputs ("Sending SIGTERM to mortals...", stderr); |
| for (--index; index >= 0; --index) kill (pids[index], sig); |
| free (pids); |
| closedir (dp); |
| } /* End Function kill_mortals */ |
| |
| static void stop_finalprog (void) |
| { |
| char *p1, *p2; |
| FILE *fp; |
| char line[256]; |
| |
| if ( ( fp = fopen (_PATH_INITTAB, "r") ) == NULL ) return; |
| while (fgets (line, 256, fp) != NULL) |
| { |
| pid_t pid; |
| |
| line[strlen (line) - 1] = '\0'; |
| p1 = line; |
| while ( isspace (*p1) ) ++p1; |
| if (strncmp (p1, "finalprog", 9) != 0) continue; |
| if ( ( p1 = strchr (p1 + 9, '=') ) == NULL ) continue; |
| for (++p1; isspace (*p1); ++p1); |
| if (*p1 == '\0') continue; |
| for (p2 = p1; !isspace (*p2); ++p2); |
| *p2 = '\0'; |
| switch ( pid = fork () ) |
| { |
| case 0: /* Child */ |
| execl (p1, p1, "stop", NULL); |
| break; |
| case -1: /* Error */ |
| break; |
| default: /* Parent */ |
| waitpid (pid, NULL, 0); |
| break; |
| } |
| fclose (fp); |
| return; |
| } |
| fclose (fp); |
| } /* End Function stop_finalprog */ |
| |
| static void syncwait (int timeval) |
| { |
| static int do_wait = 0; |
| static int first_time = 1; |
| |
| sync (); |
| /* Kernel version 1.3.20 and after are supposed to wait automatically */ |
| if (first_time) |
| { |
| struct utsname uts; |
| |
| first_time = 0; |
| uname (&uts); |
| if (uts.release[0] < '2') do_wait = 1; |
| } |
| if (do_wait) sleep (timeval); |
| } /* End Function syncwait */ |