mirror of
https://github.com/EnderIce2/Fennix.git
synced 2025-05-27 15:04:31 +00:00
feat(userspace/coreutils): improve fennix shell implementation
Signed-off-by: EnderIce2 <enderice2@protonmail.com>
This commit is contained in:
parent
76b3d30db9
commit
c4225f7bdf
@ -57,3 +57,11 @@ if(UNIX AND TARGET test)
|
||||
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/[\"
|
||||
)")
|
||||
endif()
|
||||
|
||||
if(UNIX AND TARGET sh)
|
||||
install(CODE "execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
sh
|
||||
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/fsh\"
|
||||
)")
|
||||
endif()
|
||||
|
@ -15,6 +15,7 @@
|
||||
along with Fennix C Library. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -27,16 +28,22 @@
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <pwd.h>
|
||||
#include <coreutils.h>
|
||||
|
||||
#define MAX_LINE_LEN 1024
|
||||
#define MAX_HISTORY 128
|
||||
#define CTRL_KEY(k) ((k) & 0x1f)
|
||||
#define CTRL_KEY(k) ((k) & 0x1F)
|
||||
#define MAX_COMPLETIONS 128
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *items[MAX_HISTORY];
|
||||
int count;
|
||||
int index;
|
||||
int Index;
|
||||
} History;
|
||||
|
||||
typedef enum
|
||||
@ -49,43 +56,85 @@ typedef struct
|
||||
{
|
||||
char line[MAX_LINE_LEN];
|
||||
int cursor;
|
||||
int len;
|
||||
int length;
|
||||
InputMode mode;
|
||||
History history;
|
||||
struct termios orig_termios;
|
||||
struct termios origTermios;
|
||||
char prompt[PATH_MAX + 128];
|
||||
int promptLength;
|
||||
} ShellState;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *items[MAX_COMPLETIONS];
|
||||
int count;
|
||||
} CompletionList;
|
||||
|
||||
static ShellState GlobalShellState;
|
||||
static int progIsFsh = 0;
|
||||
|
||||
void DisableRawMode(ShellState *state);
|
||||
void SaveHistory(ShellState *state);
|
||||
void InitializeShell(ShellState *state);
|
||||
void FreeCompletionList(CompletionList *list);
|
||||
CompletionList GetCompletions(const char *partial);
|
||||
void DisplayCompletions(ShellState *state, CompletionList *list);
|
||||
void UpdatePrompt(ShellState *state);
|
||||
int GetVisibleLength(const char *str);
|
||||
|
||||
void CleanupAndExit(int code)
|
||||
{
|
||||
SaveHistory(&GlobalShellState);
|
||||
DisableRawMode(&GlobalShellState);
|
||||
exit(code);
|
||||
}
|
||||
|
||||
void HandleSignalInterrupt(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
write(STDOUT_FILENO, "\n\r", 2);
|
||||
CleanupAndExit(130);
|
||||
}
|
||||
|
||||
void EnableRawMode(ShellState *state)
|
||||
{
|
||||
tcgetattr(STDIN_FILENO, &state->orig_termios);
|
||||
struct termios raw = state->orig_termios;
|
||||
raw.c_lflag &= ~(ECHO | ICANON);
|
||||
tcgetattr(STDIN_FILENO, &state->origTermios);
|
||||
struct termios raw = state->origTermios;
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
raw.c_cflag |= (CS8);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
}
|
||||
|
||||
void DisableRawMode(ShellState *state)
|
||||
{
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->orig_termios);
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->origTermios);
|
||||
}
|
||||
|
||||
void AddHistory(ShellState *state, const char *line)
|
||||
{
|
||||
if (!line || strlen(line) == 0)
|
||||
return;
|
||||
|
||||
char *newEntry = strdup(line);
|
||||
if (!newEntry)
|
||||
return;
|
||||
|
||||
if (state->history.count < MAX_HISTORY)
|
||||
state->history.items[state->history.count++] = strdup(line);
|
||||
state->history.items[state->history.count++] = newEntry;
|
||||
else
|
||||
{
|
||||
free(state->history.items[0]);
|
||||
memmove(state->history.items, state->history.items + 1,
|
||||
(MAX_HISTORY - 1) * sizeof(char *));
|
||||
state->history.items[MAX_HISTORY - 1] = strdup(line);
|
||||
state->history.items[MAX_HISTORY - 1] = newEntry;
|
||||
}
|
||||
state->history.index = state->history.count;
|
||||
state->history.Index = state->history.count;
|
||||
|
||||
SaveHistory(state);
|
||||
}
|
||||
|
||||
void LoadHistory(ShellState *state)
|
||||
@ -94,20 +143,42 @@ void LoadHistory(ShellState *state)
|
||||
if (!home)
|
||||
return;
|
||||
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/.sh_history", home);
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s/.fsh_history", home);
|
||||
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
char **tmpHistory = malloc(MAX_HISTORY * sizeof(char *));
|
||||
int tmpCount = 0;
|
||||
|
||||
char line[MAX_LINE_LEN];
|
||||
while (fgets(line, sizeof(line), fp))
|
||||
{
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
AddHistory(state, line);
|
||||
size_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
line[len - 1] = '\0';
|
||||
|
||||
if (strlen(line) > 0)
|
||||
{
|
||||
if (tmpCount < MAX_HISTORY)
|
||||
tmpHistory[tmpCount++] = strdup(line);
|
||||
else
|
||||
{
|
||||
free(tmpHistory[0]);
|
||||
memmove(tmpHistory, tmpHistory + 1, (MAX_HISTORY - 1) * sizeof(char *));
|
||||
tmpHistory[MAX_HISTORY - 1] = strdup(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
for (int i = 0; i < tmpCount; i++)
|
||||
state->history.items[i] = tmpHistory[i];
|
||||
state->history.count = tmpCount;
|
||||
state->history.Index = tmpCount;
|
||||
free(tmpHistory);
|
||||
}
|
||||
|
||||
void SaveHistory(ShellState *state)
|
||||
@ -116,18 +187,39 @@ void SaveHistory(ShellState *state)
|
||||
if (!home)
|
||||
return;
|
||||
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/.sh_history", home);
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s/.fsh_history", home);
|
||||
|
||||
FILE *fp = fopen(path, "w");
|
||||
if (!fp)
|
||||
int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600);
|
||||
if (fd < 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < state->history.count; i++)
|
||||
struct flock fl = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
.l_len = 0};
|
||||
|
||||
if (fcntl(fd, F_SETLKW, &fl) == -1)
|
||||
{
|
||||
fprintf(fp, "%s\n", state->history.items[i]);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (state->history.count > 0)
|
||||
{
|
||||
int last = state->history.count - 1;
|
||||
if (state->history.items[last])
|
||||
{
|
||||
char *cmd = state->history.items[last];
|
||||
write(fd, cmd, strlen(cmd));
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
fl.l_type = F_UNLCK;
|
||||
fcntl(fd, F_SETLK, &fl);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void ProcessViCommand(ShellState *state, char c)
|
||||
@ -139,31 +231,27 @@ void ProcessViCommand(ShellState *state, char c)
|
||||
state->cursor--;
|
||||
break;
|
||||
case 'l':
|
||||
if (state->cursor < state->len)
|
||||
if (state->cursor < state->length)
|
||||
state->cursor++;
|
||||
break;
|
||||
case 'k':
|
||||
{
|
||||
if (state->history.index > 0)
|
||||
if (state->history.Index > 0)
|
||||
{
|
||||
state->history.index--;
|
||||
strcpy(state->line, state->history.items[state->history.index]);
|
||||
state->len = strlen(state->line);
|
||||
state->cursor = state->len;
|
||||
state->history.Index--;
|
||||
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN);
|
||||
state->length = strlen(state->line);
|
||||
state->cursor = state->length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'j':
|
||||
{
|
||||
if (state->history.index < state->history.count - 1)
|
||||
if (state->history.Index < state->history.count - 1)
|
||||
{
|
||||
state->history.index++;
|
||||
strcpy(state->line, state->history.items[state->history.index]);
|
||||
state->len = strlen(state->line);
|
||||
state->cursor = state->len;
|
||||
state->history.Index++;
|
||||
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN);
|
||||
state->length = strlen(state->line);
|
||||
state->cursor = state->length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'i':
|
||||
state->mode = MODE_INSERT;
|
||||
break;
|
||||
@ -173,94 +261,471 @@ void ProcessViCommand(ShellState *state, char c)
|
||||
}
|
||||
}
|
||||
|
||||
void FreeCompletionList(CompletionList *list)
|
||||
{
|
||||
for (int i = 0; i < list->count; i++)
|
||||
free(list->items[i]);
|
||||
list->count = 0;
|
||||
}
|
||||
|
||||
CompletionList GetCompletions(const char *partial)
|
||||
{
|
||||
CompletionList list = {0};
|
||||
char *path = getenv("PATH");
|
||||
if (!path)
|
||||
return list;
|
||||
|
||||
char *pathCopy = strdup(path);
|
||||
char *dir = strtok(pathCopy, ":");
|
||||
|
||||
while (dir && list.count < MAX_COMPLETIONS)
|
||||
{
|
||||
DIR *d = opendir(dir);
|
||||
if (!d)
|
||||
{
|
||||
dir = strtok(NULL, ":");
|
||||
continue;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(d)) && list.count < MAX_COMPLETIONS)
|
||||
{
|
||||
if (strncmp(entry->d_name, partial, strlen(partial)) == 0)
|
||||
{
|
||||
char fullPath[PATH_MAX];
|
||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", dir, entry->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(fullPath, &st) == 0 && (st.st_mode & S_IXUSR))
|
||||
list.items[list.count++] = strdup(entry->d_name);
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
dir = strtok(NULL, ":");
|
||||
}
|
||||
|
||||
free(pathCopy);
|
||||
return list;
|
||||
}
|
||||
|
||||
void DisplayCompletions(ShellState *state, CompletionList *list)
|
||||
{
|
||||
if (list->count == 0)
|
||||
return;
|
||||
|
||||
write(STDOUT_FILENO, "\n\r", 2);
|
||||
|
||||
for (int i = 0; i < list->count; i++)
|
||||
{
|
||||
write(STDOUT_FILENO, list->items[i], strlen(list->items[i]));
|
||||
write(STDOUT_FILENO, " ", 2);
|
||||
}
|
||||
write(STDOUT_FILENO, "\n\r", 2);
|
||||
|
||||
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||
write(STDOUT_FILENO, state->line, state->length);
|
||||
}
|
||||
|
||||
void ReadLine(ShellState *state)
|
||||
{
|
||||
state->len = 0;
|
||||
state->length = 0;
|
||||
state->cursor = 0;
|
||||
state->line[0] = '\0';
|
||||
state->mode = MODE_INSERT;
|
||||
|
||||
printf("$ ");
|
||||
fflush(stdout);
|
||||
UpdatePrompt(state);
|
||||
|
||||
write(STDOUT_FILENO, "\r", 1);
|
||||
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||
|
||||
EnableRawMode(state);
|
||||
|
||||
while (1)
|
||||
{
|
||||
char c = '\0';
|
||||
if (read(STDIN_FILENO, &c, 1) != 1)
|
||||
break;
|
||||
char c;
|
||||
ssize_t nread = read(STDIN_FILENO, &c, 1);
|
||||
if (nread <= 0)
|
||||
continue;
|
||||
|
||||
if (state->mode == MODE_COMMAND)
|
||||
ProcessViCommand(state, c);
|
||||
else
|
||||
{
|
||||
if (c == CTRL_KEY('c'))
|
||||
if (c == '\t')
|
||||
{
|
||||
printf("\n");
|
||||
|
||||
char *wordStart = state->line;
|
||||
for (int i = state->cursor - 1; i >= 0; i--)
|
||||
{
|
||||
if (state->line[i] == ' ')
|
||||
{
|
||||
wordStart = &state->line[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char partial[MAX_LINE_LEN];
|
||||
int len = state->cursor - (wordStart - state->line);
|
||||
strncpy(partial, wordStart, len);
|
||||
partial[len] = '\0';
|
||||
|
||||
CompletionList completions = GetCompletions(partial);
|
||||
|
||||
if (completions.count == 1)
|
||||
{
|
||||
|
||||
int restLength = strlen(completions.items[0]) - len;
|
||||
if (restLength > 0)
|
||||
{
|
||||
memmove(&state->line[state->cursor + restLength],
|
||||
&state->line[state->cursor],
|
||||
state->length - state->cursor + 1);
|
||||
memcpy(&state->line[state->cursor],
|
||||
&completions.items[0][len],
|
||||
restLength);
|
||||
state->cursor += restLength;
|
||||
state->length += restLength;
|
||||
}
|
||||
}
|
||||
else if (completions.count > 1)
|
||||
{
|
||||
|
||||
int prefixLength = len;
|
||||
int canExtend = 1;
|
||||
|
||||
while (canExtend)
|
||||
{
|
||||
char nextChar = completions.items[0][prefixLength];
|
||||
if (nextChar == '\0')
|
||||
break;
|
||||
|
||||
for (int i = 1; i < completions.count; i++)
|
||||
{
|
||||
if (completions.items[i][prefixLength] != nextChar)
|
||||
{
|
||||
canExtend = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canExtend)
|
||||
prefixLength++;
|
||||
}
|
||||
|
||||
if (prefixLength > len)
|
||||
{
|
||||
|
||||
int restLength = prefixLength - len;
|
||||
memmove(&state->line[state->cursor + restLength],
|
||||
&state->line[state->cursor],
|
||||
state->length - state->cursor + 1);
|
||||
memcpy(&state->line[state->cursor],
|
||||
&completions.items[0][len],
|
||||
restLength);
|
||||
state->cursor += restLength;
|
||||
state->length += restLength;
|
||||
}
|
||||
|
||||
DisplayCompletions(state, &completions);
|
||||
}
|
||||
|
||||
FreeCompletionList(&completions);
|
||||
continue;
|
||||
}
|
||||
else if (c == 0x1B)
|
||||
{
|
||||
char seq[3];
|
||||
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
||||
continue;
|
||||
|
||||
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||
continue;
|
||||
|
||||
if (seq[0] == '[')
|
||||
{
|
||||
switch (seq[1])
|
||||
{
|
||||
case 'A':
|
||||
if (state->history.Index > 0)
|
||||
{
|
||||
state->history.Index--;
|
||||
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN - 1);
|
||||
state->length = strlen(state->line);
|
||||
state->cursor = state->length;
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
if (state->history.Index < state->history.count)
|
||||
{
|
||||
state->history.Index++;
|
||||
if (state->history.Index == state->history.count)
|
||||
{
|
||||
state->line[0] = '\0';
|
||||
state->length = 0;
|
||||
state->cursor = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN - 1);
|
||||
state->length = strlen(state->line);
|
||||
state->cursor = state->length;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
if (state->cursor < state->length)
|
||||
state->cursor++;
|
||||
break;
|
||||
case 'D':
|
||||
if (state->cursor > 0)
|
||||
state->cursor--;
|
||||
break;
|
||||
case 'H':
|
||||
state->cursor = 0;
|
||||
break;
|
||||
case 'F':
|
||||
state->cursor = state->length;
|
||||
break;
|
||||
case '3':
|
||||
{
|
||||
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||
continue;
|
||||
|
||||
if (seq[2] == '~' && state->cursor < state->length)
|
||||
{
|
||||
memmove(&state->line[state->cursor], &state->line[state->cursor + 1],
|
||||
state->length - state->cursor);
|
||||
state->length--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (seq[0] == 'O')
|
||||
{
|
||||
switch (seq[1])
|
||||
{
|
||||
case 'H':
|
||||
state->cursor = 0;
|
||||
break;
|
||||
case 'F':
|
||||
state->cursor = state->length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == CTRL_KEY('c'))
|
||||
{
|
||||
write(STDOUT_FILENO, "\n\r", 2);
|
||||
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||
state->length = 0;
|
||||
state->cursor = 0;
|
||||
state->line[0] = '\0';
|
||||
}
|
||||
else if (c == '\r' || c == '\n')
|
||||
{
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
state->line[state->length] = '\0';
|
||||
DisableRawMode(state);
|
||||
if (state->length > 0)
|
||||
{
|
||||
AddHistory(state, state->line);
|
||||
state->history.Index = state->history.count;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
else if (c == 127)
|
||||
else if (c == 0x7F)
|
||||
{
|
||||
if (state->cursor > 0)
|
||||
{
|
||||
memmove(&state->line[state->cursor - 1],
|
||||
&state->line[state->cursor],
|
||||
state->len - state->cursor + 1);
|
||||
memmove(&state->line[state->cursor - 1], &state->line[state->cursor],
|
||||
state->length - state->cursor + 1);
|
||||
state->cursor--;
|
||||
state->len--;
|
||||
state->length--;
|
||||
}
|
||||
}
|
||||
else if (state->len < MAX_LINE_LEN - 1)
|
||||
else if (c == CTRL_KEY('d') && state->length == 0)
|
||||
{
|
||||
memmove(&state->line[state->cursor + 1],
|
||||
&state->line[state->cursor],
|
||||
state->len - state->cursor + 1);
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
DisableRawMode(state);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (isprint(c) && state->length < MAX_LINE_LEN - 1)
|
||||
{
|
||||
memmove(&state->line[state->cursor + 1], &state->line[state->cursor],
|
||||
state->length - state->cursor + 1);
|
||||
state->line[state->cursor] = c;
|
||||
state->cursor++;
|
||||
state->len++;
|
||||
state->length++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\r\x1B[2K$ %s", state->line);
|
||||
fflush(stdout);
|
||||
printf("\r\x1B[%dC", state->cursor + 3);
|
||||
write(STDOUT_FILENO, "\r", 1);
|
||||
write(STDOUT_FILENO, "\x1b[K", 3);
|
||||
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||
write(STDOUT_FILENO, state->line, state->length);
|
||||
|
||||
int promptVisibleLength = GetVisibleLength(state->prompt);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "\r\x1b[%dC", promptVisibleLength + state->cursor);
|
||||
write(STDOUT_FILENO, buf, strlen(buf));
|
||||
}
|
||||
}
|
||||
|
||||
int GetVisibleLength(const char *str)
|
||||
{
|
||||
int len = 0;
|
||||
int inEscape = 0;
|
||||
|
||||
while (*str)
|
||||
{
|
||||
if (*str == '\x1b')
|
||||
inEscape = 1;
|
||||
else if (inEscape)
|
||||
{
|
||||
if ((*str >= 'A' && *str <= 'Z') || (*str >= 'a' && *str <= 'z'))
|
||||
inEscape = 0;
|
||||
}
|
||||
else
|
||||
len++;
|
||||
str++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void UpdatePrompt(ShellState *state)
|
||||
{
|
||||
char hostname[256] = {0};
|
||||
gethostname(hostname, sizeof(hostname));
|
||||
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
char *username = pw ? pw->pw_name : "user";
|
||||
|
||||
char cwd[PATH_MAX];
|
||||
if (!getcwd(cwd, sizeof(cwd)))
|
||||
strcpy(cwd, "~");
|
||||
|
||||
if (pw && pw->pw_dir && strncmp(cwd, pw->pw_dir, strlen(pw->pw_dir)) == 0)
|
||||
{
|
||||
size_t home_len = strlen(pw->pw_dir);
|
||||
if (strlen(cwd) == home_len)
|
||||
strcpy(cwd, "~");
|
||||
else if (cwd[home_len] == '/')
|
||||
{
|
||||
memmove(cwd + 1, cwd + home_len, strlen(cwd) - home_len + 1);
|
||||
cwd[0] = '~';
|
||||
}
|
||||
}
|
||||
|
||||
DisableRawMode(state);
|
||||
AddHistory(state, state->line);
|
||||
char *customPrompt = getenv("SHELL_PROMPT");
|
||||
if (customPrompt)
|
||||
{
|
||||
snprintf(state->prompt, sizeof(state->prompt), "%s", customPrompt);
|
||||
}
|
||||
else
|
||||
{
|
||||
// snprintf(state->prompt, sizeof(state->prompt),
|
||||
// "\x1b[;32m┌──(%s@%s)-[\x1b[0;1m%s\x1b[;32m]\n\r└─\x1b[;32m$\x1b[00m ",
|
||||
// username, hostname, cwd);
|
||||
|
||||
// snprintf(state->prompt, sizeof(state->prompt),
|
||||
// "\x1b[;32m%s@%s\x1b[0;1m:\x1b[01;34m%s\x1b[0;1m$\x1b[00m ",
|
||||
// username, hostname, cwd);
|
||||
|
||||
if (progIsFsh)
|
||||
{
|
||||
snprintf(state->prompt, sizeof(state->prompt),
|
||||
"\x1b[1;34m%s\x1b[0;1m:\x1b[01;35m%s\x1b[0;1m$\x1b[00m ",
|
||||
username, cwd);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(state->prompt, sizeof(state->prompt),
|
||||
"$ ");
|
||||
}
|
||||
}
|
||||
|
||||
state->promptLength = GetVisibleLength(state->prompt);
|
||||
}
|
||||
|
||||
void ExecuteCommand(char **args)
|
||||
{
|
||||
if (args[0] == NULL)
|
||||
if (!args[0])
|
||||
return;
|
||||
|
||||
for (int i = 0; args[i] != NULL; i++)
|
||||
{
|
||||
char *arg = args[i];
|
||||
size_t len = strlen(arg);
|
||||
if (len >= 2 && arg[0] == '"' && arg[len - 1] == '"')
|
||||
{
|
||||
arg[len - 1] = '\0';
|
||||
memmove(arg, arg + 1, len - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(args[0], "exit") == 0)
|
||||
exit(0);
|
||||
if (strcmp(args[0], "cd") == 0)
|
||||
else if (strcmp(args[0], "cd") == 0)
|
||||
{
|
||||
if (args[1] == NULL)
|
||||
fprintf(stderr, "cd: missing argument\n");
|
||||
else if (chdir(args[1]))
|
||||
char *targetDirectory = args[1];
|
||||
if (!targetDirectory || strcmp(targetDirectory, "~") == 0)
|
||||
{
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir)
|
||||
targetDirectory = pw->pw_dir;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "cd: HOME not set and no password directory available\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (targetDirectory[0] == '~')
|
||||
{
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir)
|
||||
{
|
||||
char newPath[PATH_MAX];
|
||||
snprintf(newPath, sizeof(newPath), "%s%s", pw->pw_dir, targetDirectory + 1);
|
||||
targetDirectory = newPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (chdir(targetDirectory))
|
||||
perror("cd");
|
||||
else
|
||||
UpdatePrompt(&GlobalShellState);
|
||||
return;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0)
|
||||
{
|
||||
struct termios term;
|
||||
tcgetattr(STDIN_FILENO, &term);
|
||||
term.c_lflag |= (ECHO | ICANON | IEXTEN | ISIG);
|
||||
term.c_oflag |= (OPOST | ONLCR);
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
|
||||
write(STDOUT_FILENO, "\r", 1);
|
||||
|
||||
execvp(args[0], args);
|
||||
perror("execvp");
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
write(STDERR_FILENO, "\r", 1);
|
||||
fprintf(stderr, "%s: command not found\n", args[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
write(STDERR_FILENO, "\r", 1);
|
||||
perror(args[0]);
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else if (pid > 0)
|
||||
wait(NULL);
|
||||
{
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
write(STDOUT_FILENO, "\r\n", 2);
|
||||
}
|
||||
else
|
||||
perror("fork");
|
||||
}
|
||||
@ -273,36 +738,113 @@ void ShellLoop(ShellState *state)
|
||||
while (1)
|
||||
{
|
||||
ReadLine(state);
|
||||
if (state->len == 0)
|
||||
if (state->length == 0)
|
||||
continue;
|
||||
|
||||
char *trimmed = state->line;
|
||||
while (*trimmed && isspace(*trimmed))
|
||||
trimmed++;
|
||||
|
||||
char *end = trimmed + strlen(trimmed) - 1;
|
||||
while (end > trimmed && isspace(*end))
|
||||
*end-- = '\0';
|
||||
|
||||
if (*trimmed == '\0')
|
||||
continue;
|
||||
|
||||
memmove(state->line, trimmed, strlen(trimmed) + 1);
|
||||
state->length = strlen(state->line);
|
||||
|
||||
char *args[MAX_LINE_LEN / 2 + 1];
|
||||
char *token = strtok(state->line, " ");
|
||||
int i = 0;
|
||||
while (token != NULL)
|
||||
char *p = state->line;
|
||||
int inQuotes = 0;
|
||||
char *start = p;
|
||||
|
||||
while (*p)
|
||||
{
|
||||
args[i++] = token;
|
||||
token = strtok(NULL, " ");
|
||||
if (*p == '"')
|
||||
inQuotes = !inQuotes;
|
||||
else if ((*p == ' ' || *p == '\t') && !inQuotes)
|
||||
{
|
||||
if (p > start)
|
||||
{
|
||||
*p = '\0';
|
||||
args[i++] = start;
|
||||
}
|
||||
start = p + 1;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p > start)
|
||||
args[i++] = start;
|
||||
args[i] = NULL;
|
||||
|
||||
ExecuteCommand(args);
|
||||
if (i > 0)
|
||||
ExecuteCommand(args);
|
||||
}
|
||||
|
||||
SaveHistory(state);
|
||||
}
|
||||
|
||||
void InitializeShell(ShellState *state)
|
||||
{
|
||||
memset(state, 0, sizeof(ShellState));
|
||||
UpdatePrompt(state);
|
||||
LoadHistory(state);
|
||||
signal(SIGINT, HandleSignalInterrupt);
|
||||
|
||||
printf("\x1b[01;35mFennix Shell v%s\n\r", PROGRAM_VERSION);
|
||||
printf("\x1b[;31mEarly development version!\x1b[0m\n\r");
|
||||
}
|
||||
|
||||
void DisableRawModeAtExit(void)
|
||||
{
|
||||
DisableRawMode(&GlobalShellState);
|
||||
}
|
||||
|
||||
void PrintHelp()
|
||||
{
|
||||
printf("Usage: sh [OPTION]... [SCRIPT]\n");
|
||||
printf("A simple shell implementation.\n\n");
|
||||
printf("Options:\n");
|
||||
printf(" -c COMMAND execute COMMAND and exit\n");
|
||||
printf(" --help display this help and exit\n");
|
||||
printf(" --version output version information and exit\n\n");
|
||||
printf("If SCRIPT is provided, execute commands from the script file.\n");
|
||||
printf("Otherwise, run in interactive mode.\n\n");
|
||||
printf("Environment variables:\n");
|
||||
printf(" SHELL_PROMPT custom prompt format (default: user@host:path$ )\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
ShellState state = {0};
|
||||
InitializeShell(&state);
|
||||
if (argc > 1)
|
||||
{
|
||||
if (strcmp(argv[1], "--help") == 0)
|
||||
{
|
||||
PrintHelp();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (strcmp(argv[1], "--version") == 0)
|
||||
{
|
||||
PRINTF_VERSION;
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
char *basename = strrchr(argv[0], '/');
|
||||
if (basename == NULL)
|
||||
basename = argv[0];
|
||||
else
|
||||
basename++;
|
||||
if (strcmp(basename, "fsh") == 0)
|
||||
progIsFsh = 1;
|
||||
|
||||
memset(&GlobalShellState, 0, sizeof(ShellState));
|
||||
InitializeShell(&GlobalShellState);
|
||||
|
||||
signal(SIGINT, HandleSignalInterrupt);
|
||||
atexit(DisableRawModeAtExit);
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
@ -314,8 +856,9 @@ int main(int argc, char *argv[])
|
||||
if (!fp)
|
||||
{
|
||||
perror("fopen");
|
||||
exit(EXIT_FAILURE);
|
||||
CleanupAndExit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LEN];
|
||||
while (fgets(line, sizeof(line), fp))
|
||||
{
|
||||
@ -327,7 +870,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
else
|
||||
ShellLoop(&state);
|
||||
ShellLoop(&GlobalShellState);
|
||||
|
||||
CleanupAndExit(EXIT_SUCCESS);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user