Skip to content

Instantly share code, notes, and snippets.

@RogerGee
Created January 27, 2015 19:09
Show Gist options
  • Save RogerGee/cb68c56ec16db7f0a8fe to your computer and use it in GitHub Desktop.
Save RogerGee/cb68c56ec16db7f0a8fe to your computer and use it in GitHub Desktop.
Background process launching program
Launch by Roger Gee
/* launch.c */
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#define MAX_ARGV 100
static const char* PROGRAM_NAME;
static const int MAJOR_VERSION = 1;
static const int MINOR_VERSION = 1;
struct launch_options
{
int logFile; /* redirect stdout to this */
int errFile; /* redirect stderr to this */
int inFile; /* redirect stdin to this */
short doChdir; /* if non-zero then change directory to user home dir */
char const* executable;
char const* argv[MAX_ARGV];
int argc;
};
static int launch_options_load(struct launch_options*);
static int launch_options_unload(struct launch_options*);
static int process_option(const char* option,const char* argument,struct launch_options* options);
static int detach(struct launch_options* options);
static int start_process(struct launch_options* options);
static int open_log(int* pfd,const char* fileName);
static int option_help();
static int option_version();
int main(int argc,const char** argv)
{
int i;
struct launch_options options;
PROGRAM_NAME = argv[0];
i = 1;
launch_options_load(&options);
while (i < argc)
{
/* options may have the form:
-o [argument] OR --option[=argument]; they must
appear before the executable name */
if (options.executable == NULL)
{
if (argv[i][0]=='-' && argv[i][1])
{
int code;
const char* opt = argv[i]+1;
char option[256];
char argument[256];
option[0] = 0;
argument[0] = 0;
if (*opt == '-')
{
int j;
++opt;
j = 0;
while (opt[j] && opt[j]!='=' && j<256)
++j;
strncpy(option,opt,j);
option[j] = 0;
opt += j;
if (*opt)
++opt;
}
else
{
option[0] = *opt;
option[1] = 0;
++opt;
}
/* try the argument first to see if it needs an argument */
code = process_option(option,NULL,&options);
if (code == 2) {
if (*opt && *opt!='-')
strncpy(argument,opt,256);
else if (++i<argc && argv[i][0]!='-')
strncpy(argument,argv[i],256);
code = process_option(option,argument,&options);
}
if (code == 1) {
launch_options_unload(&options);
return 1;
}
}
else
options.executable = argv[i];
}
else
break;
++i;
}
if (options.executable == NULL) {
fprintf(stderr,"%s: error: expected executable name\n",argv[0]);
return 1;
}
options.argv[options.argc++] = options.executable;
for (;i<argc;i++,options.argc++)
{
if (options.argc+1 > MAX_ARGV) {
fprintf(stderr,"%s: warning: max argument count has been succeeded\n",argv[0]);
options.argc = MAX_ARGV-1;
break;
}
options.argv[options.argc] = argv[i];
}
options.argv[options.argc] = NULL; /* add null pointer to terminate argument array */
/* detach from terminal */
if (detach(&options) != 0) {
launch_options_unload(&options);
return 1;
}
/* invoke process; assume detach() unloaded options to close file descriptors */
return start_process(&options);
}
int launch_options_load(struct launch_options* options)
{
options->logFile = -1;
options->errFile = -1;
options->inFile = -1;
options->doChdir = 1;
options->executable = NULL;
options->argv[0] = NULL;
options->argc = 0;
return 0;
}
int launch_options_unload(struct launch_options* options)
{
if (options->logFile != -1) {
close(options->logFile);
options->logFile = -1;
}
if (options->errFile != -1) {
close(options->errFile);
options->errFile = -1;
}
if (options->inFile != -1) {
close(options->inFile);
options->inFile = -1;
}
return 0;
}
int process_option(const char* option,const char* argument,struct launch_options* options)
{
/* return 1 to mean quit program, 2 to mean argument needed */
if (strcmp(option,"o")==0 || strcmp(option,"redirect-output")==0) {
if (argument == NULL)
return 2;
if (!*argument) {
fprintf(stderr,"%s: option '%s' requires argument\n",PROGRAM_NAME,option);
return 1;
}
return open_log(&options->logFile,argument);
}
if (strcmp(option,"e")==0 || strcmp(option,"redirect-error")==0) {
if (argument == NULL)
return 2;
if (!*argument) {
fprintf(stderr,"%s: option '%s' requires argument\n",PROGRAM_NAME,option);
return 1;
}
return open_log(&options->errFile,argument);
}
if (strcmp(option,"i")==0 || strcmp(option,"redirect-input")==0) {
if (argument == NULL)
return 2;
if (!*argument) {
fprintf(stderr,"%s: option '%s' requires argument\n",PROGRAM_NAME,option);
return 1;
}
return open_log(&options->inFile,argument);
}
if (strcmp(option,"d")==0 || strcmp(option,"curdir")==0)
options->doChdir = 0; /* keep the current directory */
else if (strcmp(option,"?")==0 || strcmp(option,"help")==0) {
option_help();
return 1;
}
else if (strcmp(option,"version") == 0) {
option_version();
return 1;
}
else {
fprintf(stderr,"%s: option '%s' is not supported\n",PROGRAM_NAME,option);
return 1;
}
return 0;
}
int detach(struct launch_options* options)
{
/*int i;*/
struct passwd* pwd;
pid_t pid = fork();
int fdnull, fdout, fderr;
if (pid == -1)
{
fprintf(stderr,"%s: fail fork()\n",PROGRAM_NAME);
return 1;
}
if (pid != 0)
_exit(0);
if (setsid() == -1)
{
fprintf(stderr,"%s: fail setsid()\n",PROGRAM_NAME);
return 1;
}
pwd = getpwuid( getuid() );
if (pwd == NULL)
{
fprintf(stderr,"%s: fail getpwuid() for uid %d\n",PROGRAM_NAME,getuid());
return 1;
}
if (options->doChdir && chdir(pwd->pw_dir)==-1)
{
fprintf(stderr,"%s: fail chdir()\n",PROGRAM_NAME);
return 1;
}
/* open null device */
fdnull = open("/dev/null",O_RDWR);
if (fdnull == -1)
{
fprintf(stderr,"%s: fail open() on /dev/null\n",PROGRAM_NAME);
return 1;
}
fdout = options->logFile==-1 ? fdnull : options->logFile;
fderr = options->errFile==-1 ? fdnull : options->errFile;
/* redirect standard IO to null device or log file(s) */
assert(dup2(fdnull,STDIN_FILENO) == STDIN_FILENO);
assert(dup2(fdout,STDOUT_FILENO) == STDOUT_FILENO);
assert(dup2(fderr,STDERR_FILENO) == STDERR_FILENO);
close(fdnull);
launch_options_unload(options); /* close any log file descriptors */
return 0;
}
int start_process(struct launch_options* options)
{
if (execvp(options->executable,(char*const*)options->argv) == -1)
{
fprintf(stderr,"%s: could not start '%s' process\n",PROGRAM_NAME,options->executable);
return 1;
}
/* control no longer in this program */
return 0;
}
int open_log(int* pfd,const char* fileName)
{
*pfd = open(fileName,O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR);
if (*pfd == -1)
{
fprintf(stderr,"%s: error: could not open file '%s'",PROGRAM_NAME,fileName);
if (errno == ENOTDIR)
fprintf(stderr,": part of the path does not exist\n");
else if (errno == EACCES)
fprintf(stderr,": permission denied\n");
return 1;
}
return 0;
}
int option_help()
{
static const char* helpMessage =
"launch by Roger Gee\n\n\
launches a process detached from the terminal\n\
usage: %s [[-]-option[=] [argument]]\n\n\
options:\n\
--redirect-output=file-name\t- redirect process output to file\n\
-o file-name\n\
--redirect-error=file-name\t- redirect process error to file\n\
-e file-name\n\
--redirect-input=file-name\t- redirect process input to file\n\
-i file-name\n\
--curdir\t\t\t- keep current directory for process\n\
-d\n";
printf(helpMessage,PROGRAM_NAME);
return 0;
}
int option_version()
{
printf("launch by Roger Gee\nversion %d.%d\n",
MAJOR_VERSION,MINOR_VERSION);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment