/* vim:set ts=4 sw=4 tw=80 et cindent ai si cino=(0,ml,\:0: * ( settings from: http://datapax.com.au/code_conventions/ ) */ /********************************************************************** fork Copyright (C) 2016-2017 Todd Harbour This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 ONLY, as published by the Free Software Foundation. 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, in the file COPYING or COPYING.txt; if not, see http://www.gnu.org/licenses/ , or write to: The Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **********************************************************************/ /* * BASED ON THE ORIGINAL CONCEPT BY * Storlek of irc://irc.freenode.net/#ArchLinux */ /* Spawns a process that's COMPLETELY disconnected from the original parent * process * * TO BUILD: * gcc -o fork fork.c */ /* * v0.01 2011-07-30 * - Original by Storlek * v0.02 2016-10-27 * - (Near) complete rewrite * - Made more portable (Solaris etc) * v0.03 2016-11-17 * - Added help * v0.04 2017-02-28 * - Added check for daemon return value (thanks to nimaje of * irc://irc.freenode.net/#i3 for pointing it out) */ #include /* errno */ #include /* fprintf, perror */ #include /* strcmp */ #include /* _exit, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO */ #include /* sigaction, SIGHUP, SIG_IGN */ #define APP_NAME "fork" #define APP_VERSION "0.04" #define APP_SUMMARY "Spawns a process that's COMPLETELY disconnected from the original parent process" #define BIN_NAME "fork" #define APP_AUTHOR "Todd Harbour" #define APP_COPYRIGHT "Copyright (C) 2016-2017 "APP_AUTHOR #define APP_URL "https://gitlab.com/krayon/qdnxtools/" // TODO: i18n #define _(STRING) STRING static void help_version(void) { printf("%s v%s\n", (APP_NAME), (APP_VERSION)); printf("%s\n", (APP_COPYRIGHT)); printf("%s\n", (APP_URL)); } static void help_usage(void) { printf("\ \n\ %s\n\ \n\ %s: %s -h|--help\n\ %s -V|--version\n\ %s [--] [ [...]]\n\ \n\ -h|--help - %s\n\ -V|--version - %s %s %s\n\ \n\ [--] - %s\n\ - %s\n\ [...] - %s\n\ \n\ %s: %s youtube-dl https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\ ", _(APP_SUMMARY) ,_("Usage"), BIN_NAME /* -h|--h[elp] */ , BIN_NAME /* -V|--v[ersion] */ , BIN_NAME /* -s|--s[elect] ... */ ,_("Displays this help") ,_("Displays"), APP_NAME, _("version") ,_("Indicates all remaining parameters are for ") ,_("Program to run") ,_("Any arguments to pass to ") ,_("Example"), BIN_NAME ); } /* Not all systems implement daemon (Solaris, I'm looking at you) */ int daemon(int nochdir, int noclose) { /* 1. Fork process: * * Unblocks grandparent process that may be waiting for parent to * terminate; * * Ensures child process is not a process group leader. */ pid_t pid = fork(); struct sigaction sa; /* fork failed */ if (pid == -1) return -1; /* parent process */ if (pid != 0) _exit(0); /* 2. Create a new session: * * Starts a new session with us as leader; * * Starts a new process group, with us as process group leader. */ /* FIXME: * According to setsid(2): * EPERM - The process group ID of any process equals the PID of * the calling process. Thus, in particular, setsid() fails if the * calling process is already a process group leader. * * Is it therefore reasonable to ignore the setsid return value as it * seems it would only fail when our objective is ALREADY true? */ if (setsid() == -1) return -2; /* 3. Ignore parent SIGHUP: * When we fork again, the parent (who will be the session leader) * will generate a SIGHUP when it dies. We therefore must ignore * SIGHUP's. * * NOTE: If this was a daemon that wanted to handle SIGHUP, we would * need the handler to ignore the first SIGHUP as this would be the one * from the parent. */ sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sigaction(SIGHUP, &sa, 0); /* 4. Fork again: * * Ensures child process is not a session leader * * We currently don't have a controlling terminal. In the event we open * a terminal device, it becomes the controlling terminal automatically. * Not being the session leader prevents this). */ pid = fork(); /* fork failed */ if (pid == -1) return -1; /* parent process */ if (pid != 0) _exit(0); /* Change directory? */ if (nochdir == 0) chdir("/"); /* Close standard file descriptors? */ if (noclose == 0) { close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); } return 0; } int main(int argc, char **argv) { int argoff = 1; int ret; if (argc < 2) { fprintf(stderr, "ERROR: No program specified\n"); help_version(); help_usage(); _exit(1); } if ( strcmp("--help", argv[argoff]) == 0 || strcmp("-h", argv[argoff]) == 0 ) { help_version(); help_usage(); _exit(0); } if ( strcmp("--version", argv[argoff]) == 0 || strcmp("-V", argv[argoff]) == 0 ) { help_version(); _exit(0); } if (strcmp("--", argv[argoff]) == 0) { ++argoff; } /* daemon(nochdir, noclose) * If nochdir != 0, don't 'chdir /' * If noclose != 0, don't redirect stdin/out/err to '/dev/null' */ if ((ret = daemon(1, 0)) < 0) { if (ret == -1) perror("fork"); if (ret == -2) perror("setsid"); _exit(255); } /* Run the program (this exec's overwrites this process, which * therefore no longer exists. */ execvp(argv[argoff], argv + argoff); /* If we get here the exec failed so we need to print out the * error info and return 255. */ perror("execvp"); _exit(255); }