// // aegis - project change supervisor // Copyright (C) 1991-1997, 1999, 2002-2008, 2012, 2014 Peter Miller // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see // . // #include #include #include #include #include #include #include // for umask prototype! #include #include #include #include #include #include #include // for lock_release_child #include #include #include #include #include #include quit_action_log log_quitter; static int pid; static void tee_stdout(user_ty::pointer up, char *filename, int also_to_tty, int append_to_file) { int fd[2]; int uid; int gid; int um; const char *argv[4]; int argc; trace(("tee_stdout(up = %p, filename = \"%s\", also_to_tty = %d, " "append_to_file = %d)\n{\n", up.get(), filename, also_to_tty, append_to_file)); // // We are about to mangle stdout, which is one of the file descriptors // that programs typically use to determine the width of the // terminal. So we need to set LINES and COLS before we do, so // that our sub-commands will think the terminal is the same width // that Aegis does. // env_set_page(); // // list both to a file and to the terminal // uid = up->get_uid(); gid = up->get_gid(); um = up->umask_get(); if (pipe(fd)) nfatal("pipe()"); switch (pid = fork()) { case 0: // // child // undo_cancel(); lock_release_child(); while (os_become_active()) os_become_undo(); close(fd[1]); close(0); if (dup(fd[0]) != 0) fatal_raw("dup was wrong"); close(fd[0]); signal(SIGINT, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGTERM, SIG_IGN); os_setgid(gid); os_setuid(uid); umask(um); if (!also_to_tty) { // // Some systems can't write to a user's file // when euid=0 over NFS. The permissions are // supposed to only apply when the file is // opened, and subsequent writes are not // affected. Sigh. Ever seen "Permission // denied" from a write() call? Eek! // // For systems with no functioning seteuid // call, this is essential, even if NFS writes // behave as they should. // // So: we always open a pipe, and simply run it // through "tee" with the output redirected to // the bit bucket. // close(1); if (1 != open("/dev/null", O_WRONLY, 0666)) nfatal("open /dev/null"); } // // build the tee command // argc = 0; argv[argc++] = "tee"; if (append_to_file) argv[argc++] = "-a"; argv[argc++] = filename; argv[argc] = 0; // // try to exec it // // (The cast is because many operating systems have a stupid // prototype, in turn probably because gcc whines incorrectly when // you have the correct prototype.) // execvp(argv[0], (char **)argv); nfatal("exec \"%s\"", argv[0]); case -1: nfatal("fork()"); default: // // parent: // redirect stdout to go through the pipe // close(fd[0]); close(1); if (dup(fd[1]) != 1) fatal_raw("dup was wrong"); close(fd[1]); break; } trace(("}\n")); } static log_style_ty pref_to_style(uconf_log_file_preference_ty dflt) { user_ty::pointer up = user_ty::create(); switch (up->log_file_preference(dflt)) { case uconf_log_file_preference_never: return log_style_none; case uconf_log_file_preference_append: return log_style_append; case uconf_log_file_preference_snuggle: return log_style_snuggle; case uconf_log_file_preference_replace: return log_style_create; } assert(0); return log_style_append; } // // NAME // log_open - start logging // // SYNOPSIS // void log_open(string_ty *logfile, user_ty::pointer up); // // DESCRIPTION // The log_open function is used to start sending stdout // and stderr to a longfile. If necessary it creates the log // file before returning. // void log_open(string_ty *filename, user_ty::pointer up, log_style_ty style) { static int already_done; int bg; int append_to_file; int exists; if (already_done) return; assert(filename); trace(("log_open(s = \"%s\")\n{\n", filename->str_text)); already_done = 1; // // check the user defaults // switch (style) { case log_style_none_default: style = pref_to_style(uconf_log_file_preference_never); break; case log_style_create_default: style = pref_to_style(uconf_log_file_preference_replace); break; case log_style_append_default: style = pref_to_style(uconf_log_file_preference_append); break; case log_style_snuggle_default: style = pref_to_style(uconf_log_file_preference_snuggle); break; default: break; } if (style == log_style_none) { trace(("}\n")); return; } // // if the logfile exists, unlink it first // (so that baseline linked to int dir works correctly) // append_to_file = (style == log_style_append); up->become_begin(); exists = os_exists(filename); if (style == log_style_snuggle && exists) { time_t log_old; time_t log_new; os_mtime_range(filename, &log_old, &log_new); if (now() - log_new < 30) append_to_file = 1; } if (!append_to_file && exists) os_unlink(filename); if (append_to_file && !exists) append_to_file = 0; up->become_end(); // // If we are in the background, // don't send the output to the terminal. // bg = os_background(); tee_stdout(up, filename->str_text, !bg, append_to_file); // // tell the user we are logging // (without putting it into the logfile) // if (!bg) { sub_context_ty *scp; scp = sub_context_new(); sub_var_set_string(scp, "File_Name", filename); if (append_to_file) verbose_intl(scp, i18n("appending log to $filename")); else verbose_intl(scp, i18n("logging to $filename")); sub_context_delete(scp); } // // make stderr go to the same place as stdout // [will this work if stdout is already closed?] // close(2); switch (dup(1)) { case 0: // oops, stdin is was closed if (dup(1) != 2) fatal_raw("dup was wrong"); close(0); break; case 2: break; default: nfatal("dup"); } trace(("}\n")); } void log_open(const nstring &filename, user_ty::pointer up, log_style_ty style) { log_open(filename.get_ref(), up, style); } void log_close(void) { if (pid > 0) { int status; int old_pid; old_pid = pid; pid = 0; // // The tee has been created to ignore the common interrupts. // The only reason it will dies is if its stdin goes away. // Closing stdout and stderr does this. // // If we get a ^C during the waitpid (and we could, if // some process's child is still talking to the tee and the // user gets impatient) the quitter will skip this close // (and hence this waitpid) in the subsequent cleanup. // fclose(stdout); fclose(stderr); os_waitpid(old_pid, &status); } } // vim: set ts=8 sw=4 et :