Skip to content

Instantly share code, notes, and snippets.

@ObjectBoxPC
Created November 28, 2020 06:12
Show Gist options
  • Save ObjectBoxPC/97c1836431ea6917f2e7d1802464adbf to your computer and use it in GitHub Desktop.
Save ObjectBoxPC/97c1836431ea6917f2e7d1802464adbf to your computer and use it in GitHub Desktop.
Simple program start a shell based on certain inferences (mainly to learn some POSIX C programming)
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
/**
* @file
* Utility function to start a shell based on reasonable inferences
*/
static pid_t shell_start_env(char* name, size_t len);
static pid_t shell_start_pwd(char* name, size_t len);
static pid_t shell_start_familiar(char* name, size_t len);
static pid_t shell_start_run(
const char* name, char* store_name, size_t store_len
);
/**
* Delegate function for starting a shell based on a specific inference
* @param name Buffer to store the name of the shell that was started.
* If this is a null pointer, the name is not stored.
* @param len Length of buffer, including room for the null terminator
* @return Process ID of the shell started, or -1 on failure
*/
typedef pid_t (*shell_start_delegate)(char* name, size_t len);
/**
* Delegate functions, in order of preference
*/
static const shell_start_delegate SHELL_START_DELEGATES[] = {
&shell_start_env,
&shell_start_pwd,
&shell_start_familiar,
};
/**
* "Familiar" shells to fall back on when other inferences fail
* @see shell_start_familiar
*/
static const char* SHELL_START_FAMILIARS[] = {
"sh",
"csh",
"/bin/sh",
"/bin/csh",
};
/**
* Get the size of an array.
* @param arr Array
*/
#define SHELL_START_ARR_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/**
* Start a shell based on reasonable inferences.
* @param name Buffer to store the name of the shell that was started.
* If this is a null pointer, the name is not stored.
* @param len Length of buffer, including room for the null terminator
* @return Process ID of the shell started, or -1 on failure
*/
pid_t shell_start(char* name, size_t len) {
pid_t shell_pid;
size_t i;
for (i = 0; i < SHELL_START_ARR_SIZE(SHELL_START_DELEGATES); ++i) {
if ((shell_pid = SHELL_START_DELEGATES[i](name, len)) > 0) {
return shell_pid;
}
}
return -1;
}
/**
* Start the shell indicated in the `SHELL` environment variable. If this
* variable is blank or not set, failure occurs.
* @param name Buffer to store the name of the shell that was started.
* If this is a null pointer, the name is not stored.
* @param len Length of buffer, including room for the null terminator
* @return Process ID of the shell started, or -1 on failure
*/
static pid_t shell_start_env(char* name, size_t len) {
const char* env_shell = getenv("SHELL");
if (!env_shell || strlen(env_shell) == 0) {
return -1;
}
return shell_start_run(env_shell, name, len);
}
/**
* Start the effective user's login shell (from the user's `passwd` entry).
* @param name Buffer to store the name of the shell that was started.
* If this is a null pointer, the name is not stored.
* @param len Length of buffer, including room for the null terminator
* @return Process ID of the shell started, or -1 on failure
*/
static pid_t shell_start_pwd(char* name, size_t len) {
struct passwd* pw = getpwuid(geteuid());
if (!pw) {
return -1;
}
return shell_start_run(pw->pw_shell, name, len);
}
/**
* Start a "familiar" shell, such as `sh`. Multiple shells are attempted
* in turn until a suitable one is found.
* @param name Buffer to store the name of the shell that was started.
* If this is a null pointer, the name is not stored.
* @param len Length of buffer, including room for the null terminator
* @return Process ID of the shell started, or -1 on failure
* @warning Failure is not properly reported. If the shell fails to start,
* the child process will simply exit with a status of 1.
*/
static pid_t shell_start_familiar(char* name, size_t len) {
size_t i;
pid_t child_pid;
for (i = 0; i < SHELL_START_ARR_SIZE(SHELL_START_FAMILIARS); ++i) {
if (
(child_pid = shell_start_run(SHELL_START_FAMILIARS[i], name, len))
> 0
) {
return child_pid;
}
}
return -1;
}
/**
* Run a shell in a child process.
* @param name Name of shell to run
* @param store_name Buffer to store the name of the shell upon success.
* If this is a null pointer, the name is not stored.
* @param store_len Length of buffer, including room for the null terminator
* @return Process ID of the shell started, or -1 on failure
*/
static pid_t shell_start_run(
const char* name, char* store_name, size_t store_len
) {
pid_t shell_pid;
shell_pid = fork();
if (shell_pid > 0) {
/* Parent */
if (store_name) {
store_name[0] = '\0';
strncat(store_name, name, store_len - 1);
}
return shell_pid;
} else if (shell_pid == 0) {
/* Child */
execlp(name, name, (const char*) 0);
/* Could not exec */
_exit(1);
} else {
/* Could not fork */
return -1;
}
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stddef.h>
pid_t shell_start(char* name, size_t len);
int main(void) {
pid_t shell_pid;
int shell_status;
char shell_name[100];
shell_pid = shell_start(shell_name, sizeof(shell_name));
printf("Started %s with PID %ld\n", shell_name, (long) shell_pid);
waitpid(shell_pid, &shell_status, 0);
if (WIFEXITED(shell_status)) {
printf("Shell exited with status %d\n", WEXITSTATUS(shell_status));
} else if (WIFSIGNALED(shell_status)) {
printf("Shell terminated by signal %d\n", WTERMSIG(shell_status));
} else {
printf("Shell terminated in an unknown way\n");
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment