#include "utils.h"
#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "sish.h"
/* for PATH_MAX */
#ifdef __NetBSD__
#include <sys/syslimits.h>
#endif
#ifdef __linux__
#include <linux/limits.h>
#endif
#if defined(__sun) && defined(__illumos__)
#include <limits.h>
#endif
static void (*default_sigint_handler)(int) = NULL;
static void (*default_sigquit_handler)(int) = NULL;
static void (*default_sigtstop_handler)(int) = NULL;
/* 20 by getting the max number of digits of an 64 bit integer, i.e.,
* ceil(log_10(2^64)), even though pid_t is usually 4 bytes and exit
* statuses, 1. */
static char last_pid_bg_STRING[20] = {0};
static char last_fg_exit_status_STRING[20] = {0};
static char sish_pid_STRING[20] = {0};
/* there is likely a much better / safer way to do this by performing lookups in
* /proc or something but as far as I know this is heavily operating system
* specific and below I have done my best to make an intuitive mostly
* cross-platform implementation */
void
set_shell_env(char* arg)
{
char sish_path[PATH_MAX] = {0};
char cwd[PATH_MAX] = {0};
if (getcwd(cwd, sizeof(cwd)) == NULL)
{
fprintf(stderr, "%s: set_shell_env: getcwd: %s\n", GET_PROGNAME_ERR,
strerror(errno));
exit(EXIT_FAILURE);
}
char progname[BUFSIZ] = {0};
#ifdef __linux__
(void)strncpy(progname, basename(arg), sizeof(progname));
#else
/* very surprised the compiler didn't catch this, leaving me to explicitly
* cast to void -- it makes sense as to why knowing the stages of
* compilation, but still */
(void)arg;
(void)strncpy(progname, getprogname(), sizeof(progname) - 1);
#endif
int n = snprintf(sish_path, sizeof(sish_path), "%s/%s", cwd, progname);
if (n < 0 || (size_t)n >= sizeof(sish_path))
{
fprintf(stderr, "%s: set_shell_env: path too long\n", GET_PROGNAME_ERR);
exit(EXIT_FAILURE);
}
if (setenv("SHELL", sish_path, true) == -1)
{
fprintf(stderr, "%s: set_shell_env: setenv: %s\n", GET_PROGNAME_ERR,
strerror(errno));
exit(EXIT_FAILURE);
}
}
void
try_expand_env_vars(char** argv)
{
int i = 0;
/* args is (should be) null terminated */
while (argv[i] != NULL)
{
char* arg = argv[i];
int len = strlen(arg);
if (arg[0] != '$' || len < 2 || arg[1] == '\0')
{
i++;
continue;
}
char* env_val = NULL;
/* in instances like $$$*, the entire string will turn into say, the
* shell's PID, whereas in sh(1), it would turn into <pid>$*, where *
* represents >= 0 more $ symbols */
switch (arg[1])
{
case '!':
snprintf(last_pid_bg_STRING, sizeof(last_pid_bg_STRING), "%d",
last_pid_bg);
env_val = last_pid_bg_STRING;
break;
case '?':
snprintf(last_fg_exit_status_STRING,
sizeof(last_fg_exit_status_STRING), "%d",
WEXITSTATUS(last_fg_exit_status));
env_val = last_fg_exit_status_STRING;
break;
case '$':
snprintf(sish_pid_STRING, sizeof(sish_pid_STRING), "%d", getpid());
env_val = sish_pid_STRING;
break;
default:
/* skip the leading $*/
env_val = getenv(argv[i] + 1);
break;
}
void* old = argv[i];
char* tmp = NULL;
if (env_val == NULL)
{
tmp = malloc(1);
if (tmp == NULL)
{
fprintf(stderr, "%s: try_expand_env_vars: malloc failed: %s\n",
GET_PROGNAME_ERR, strerror(errno));
return;
}
tmp[0] = '\0';
}
else
{
tmp = malloc(strlen(env_val) + 1);
if (tmp == NULL)
{
fprintf(stderr, "%s: try_expand_env_vars: malloc failed: %s\n",
GET_PROGNAME_ERR, strerror(errno));
return;
}
/* surely strcpy is acceptable here, surely */
strcpy(tmp, env_val);
}
/* free only after success */
free(old);
argv[i] = tmp;
i++;
}
return;
}
/* a monstrosity of an attempt at adapting strtok/a previous call made to it to
* ignore whitespaces while parsing user input */
int
tokenize(char* input, char** token_out)
{
int token_args_count = 0;
char* p = input;
while (*p != '\0')
{
while (*p != '\0' && isspace((unsigned char)*p))
{
p++;
}
if (IS_SHELL_SPECIAL_C(*p))
{
int len = 1;
if ((*p == '>') && *(p + 1) == '>')
{
len = 2;
}
char* tok = malloc(len + 1);
if (tok == NULL)
{
fprintf(stderr, "%s: tokenize: malloc: %s\n", GET_PROGNAME_ERR,
strerror(errno));
goto try_free_tokens;
}
memcpy(tok, p, len);
tok[len] = '\0';
token_out[token_args_count] = tok;
token_args_count++;
p += len;
continue;
}
char* start = p;
while (*p != '\0' && !IS_SHELL_SPECIAL_C(*p))
{
if (isspace((unsigned char)*p))
{
if (p > start)
{
int len = (int)(p - start);
char* tok = malloc(len + 1);
if (tok == NULL)
{
fprintf(stderr, "%s: tokenize: malloc: %s\n",
GET_PROGNAME_ERR, strerror(errno));
goto try_free_tokens;
}
memcpy(tok, start, len);
tok[len] = '\0';
token_out[token_args_count] = tok;
token_args_count++;
}
while (*p != '\0' && isspace((unsigned char)*p))
{
p++;
}
start = p;
}
else
{
p++;
}
}
if (p > start)
{
int len = (int)(p - start);
char* tok = malloc(len + 1);
if (tok == NULL)
{
fprintf(stderr, "%s: tokenize: malloc: %s\n", GET_PROGNAME_ERR,
strerror(errno));
goto try_free_tokens;
}
memcpy(tok, start, len);
tok[len] = '\0';
token_out[token_args_count] = tok;
token_args_count++;
}
}
token_out[token_args_count] = NULL;
return token_args_count;
/* malloc may fail after successfully allocated any number of previous tokens */
try_free_tokens:
for (int i = 0; i < token_args_count; i++)
{
free(token_out[i]);
}
return -1;
}
static void
signal_handler(int sig)
{
(void)sig;
/* taught in CS 392 for that class' minishell project */
interrupted = true;
/* async signal safe! */
write(STDOUT_FILENO, "\n", 1);
}
void
ignore_signals()
{
const char* err_msg = NULL;
/* apparently using the library signal(3)
* defaults to IO and such not being interuptted / restarting (like fgets),
* whereas using sigaction apparently does default to interuptting. Learned
* this while replacing my old CS 392 code for the shell with the calls as
* mentioned, hence bringing back the old code from there as it just so
* happens to work! */
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
/* disrupt IO!!! */
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1)
{
err_msg = "SIGINT";
goto sig_fail;
}
if (sigaction(SIGQUIT, &sa, NULL) == -1)
{
err_msg = "SIGQUIT";
goto sig_fail;
}
if (sigaction(SIGTSTP, &sa, NULL) == -1)
{
err_msg = "SIGTSTP";
goto sig_fail;
}
/* success */
return;
sig_fail:
(void)fprintf(stderr, "%s: Unable to ignore %s. Cannot continue.\n",
GET_PROGNAME_ERR, err_msg);
exit(EXIT_FAILURE);
/* not reached */
}
void
restore_default_signals()
{
const char* err_msg = NULL;
if (signal(SIGINT, default_sigint_handler) == SIG_ERR)
{
err_msg = "SIGINT";
goto sig_restore_fail;
}
if (signal(SIGQUIT, default_sigquit_handler) == SIG_ERR)
{
err_msg = "SIGQUIT";
goto sig_restore_fail;
}
if (signal(SIGTSTP, default_sigtstop_handler) == SIG_ERR)
{
err_msg = "SIGTSTP";
goto sig_restore_fail;
}
/* success */
return;
sig_restore_fail:
(void)fprintf(stderr,
"%s: Unable to restore %s default handler. Cannot "
"continue.\n",
GET_PROGNAME_ERR, err_msg);
exit(EXIT_FAILURE);
/* not reached */
}