summaryrefslogblamecommitdiffstats
path: root/03_exercise/srv/shell.c
blob: b93030fd1491a34de81380f205dad2a1a9d092d7 (plain) (tree)
1
2
3
4
5



                   
                  








                         
















                                                                                                                    

                                 
                     


                   
                                                           


                         
                                                                            
                            
                         
                      







                       

                                                        
                 

                  



                                                                             
 




                       
 
                 
 

                             





                                                              
         
     
 

                                                                                              

                     
                                                                                      
                     
     
 
                   

 







                                                          
                     









                                                 
                     


               
                                              









                                                     


                                                                               

     
                
                      

                     





                        










                                                          

                                        

                              





                                  

                                  







                                         
                               
















                                            



                                     

                               






                                                                                                  
                                                                                          
      
                    
 
                                                           
                                                                   


                      
                                 
                         
 
                       



                                                         





                                                                 
                                                     
 



                                


                                   





                                                              
                             









                                                      

                                                        





                                     



                                                     


                                                               






                                                              









                                                                                                  


                                                         
             
                         




                                   
                               
                 
 
                               


             
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <wait.h>
#include <errno.h>

#include "array.h"
#include "process.h"
#include "prompt_utils.h"
#include "builtins.h"

/*
 * READ ME
 *  sorry für den unaufgeräumten Code hier :/
 *
 *  What works:
 *   - remote shell, weitesgehend die funktionalität der vorhergehenden aufgabe (damit auch pipes und background,
 *      auch wenn wir das nicht ausführlich getestet haben)
 *   - get und put für dateien
 *  What doesn't work:
 *   - mehrere clients an einem server
 *  Known bugs:
 *   - exit beendet nicht nur die client-server connection und setzt den server wieder in einen wartenden zustand,
 *      sondern terminiert ebenfalls den server
 *   - das erkennen, wann eine neue prompt geprintet werden muss ist aktuell sehr hacky client seitig implementiert,
 *      scheitert teilweise - die eingabe geht trzd ganz normal, es wird nur keine prompt angezeigt
 */

char *get_current_dir_name(void);

#define BUF_SIZE 2048

process *processes;

int write_all(int target_sock, char *buffer, size_t size) {
    int nwrite, sent = 0;

    while (sent < size) {
        if ((nwrite = write(target_sock, buffer + sent, size - sent)) < 0) {
            perror("Write");
            printf("\n");
            return -1;
        }

        sent += nwrite;
    }

    return sent;
}

void receive_file(int recv_sock, char *path, int size) {
    char buffer[BUF_SIZE];
    int  file_fd;
    int  received;

    if ((file_fd = open(path, (O_WRONLY | O_CREAT | O_TRUNC), 0644)) == -1) {
        perror("Open");
        printf("\n");
    }

    if (size == 0) {
        // just touch
        close(file_fd);
        return;
    }

    received = 0;

    /* Read all data */
    while (received < size) {
        int nread;
        if ((nread = read(recv_sock, buffer, BUF_SIZE)) > 0) {
            if (write_all(file_fd, buffer, nread) != nread) {
                break;
            }
            received += nread;
        }
    }

    if (received == size) {
        printf("successfully wrote to remote file \"%s/%s\"\n", get_current_dir_name(), path);
        printf("\n");
    } else {
        printf("error writing remote file \"%s/%s\"\n", get_current_dir_name(), path);
        printf("\n");
    }

    close(file_fd);
}

void send_file(int client_fd, char *path) {
    printf("Trying to get \"%s\" from remote...\n", path);
    char buffer[BUF_SIZE];
    memset(buffer, 0, BUF_SIZE);
    int tmp_fd, file_fd;

    if ((tmp_fd = open(path, O_RDONLY)) == -1) {
        perror("Open");
        printf("\n");
        return;
    }
    FILE *file = fdopen(tmp_fd, "r");
    fseek(file, 0L, SEEK_END);
    long int sz = ftell(file);
    fseek(file, 0L, SEEK_SET);
    fclose(file);
    close(tmp_fd);
    if ((file_fd = open(path, O_RDONLY)) == -1) {
        perror("Open");
        printf("\n");
        return;
    }

    int length = snprintf(NULL, 0, "%ld", sz);
    sprintf(buffer, "<<!%ld!", sz);
    sprintf(&(buffer[3 + length + 1]), "%s\n", path);

    write(client_fd, buffer, strlen(buffer));

    usleep(150);

    memset(buffer, 0, BUF_SIZE);

    errno = 0;
    size_t count;
    while ((count = read(file_fd, buffer, sizeof(buffer))) > 0) {
        /*printf("Sent %i bytes\n", */write_all(client_fd, buffer, count)/*)*/;
    }

    if (errno) {
        perror("wad");
        printf("\n");
    }

    close(file_fd);

    //printf("done.\n");
}

void signal_handler(int signal) {
    if (signal == SIGINT) {
        for (size_t i = 0; i < arrayLen(processes); ++i) {
            pid_t pid = processes[i].pid;
            if (pid != 0) {
                kill(pid, SIGINT);
            }
        }
    }
}

bool check_put(char *buffer, int sock) {
    // "<<!bytecount!name"
    char   cmd[4];
    char   rest[BUF_SIZE - 3];
    size_t file_size;

    memcpy(cmd, buffer, 3);
    cmd[3] = 0;

    if (strcmp(cmd, "<<!") == 0) {
        int offset       = 3;
        int i            = offset;
        for (; i < BUF_SIZE; ++i) {
            if (buffer[i] == '!')
                break;
            rest[i - offset] = buffer[i];
        }
        rest[i - offset] = 0;
        file_size = atoi(rest);

        offset           = ++i;
        for (; i < BUF_SIZE; ++i) {
            if (buffer[i] == '!')
                break;
            rest[i - offset] = buffer[i];
        }
        rest[i - offset] = 0;
        char path[strlen(rest) + 1];
        memcpy(path, rest, strlen(rest));
        path[strlen(rest)] = 0;

        receive_file(sock, path, file_size);

        return true;
    }
    return false;
}

int shell(int in_fd) {
    setvbuf(stderr, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    dup2(in_fd, STDOUT_FILENO);
    dup2(in_fd, STDERR_FILENO);

    printf("Welcome! Available built-ins are:\n"
           "cd: `cd <path>` - if no path is given, return to the current dir\n"
           "wait: `wait pid1 ... pidN` - wait on the processes and report their exit conditions\n"
           "fg: `fg pid` - pulls a process from the background back in the foreground\n"
           "\n"
           "You can put processes in the background using `&`\n"
           "And with `|` you can pipe the output from one process to the input of another"
    );
    printf("\n$> ");

    char const *const original_wd = get_current_dir_name();
    //char const *prompt = relative_path(original_wd, original_wd);

    bool done = false;
    while (!done) {
        char      line[BUF_SIZE];
        __ssize_t length;

        fflush(stdout);
        if ((length = read(in_fd, line, BUF_SIZE)) < 0) {
            fprintf(stderr, "Failed to read from STDIN");
            fflush(stderr);
            exit(-1);
        }

        if (strspn(line, " \n\t") == strlen(line)) {
            // skip empty lines - empty being just spaces or tabs
            continue;
        }
        line[length - 1] = '\0'; // cut the line feed

        if (strlen(line) == 0) {
            continue;
        }

        if (check_put(line, in_fd))
            continue;

        processes = NULL;
        parse_line(line, &processes);

        if (strcmp(processes[0].argv[0], "cd") == 0) {
            if (arrayLen(processes) != 1) {
                perror("Can't chain cd with other processes");
                printf("\n");
            }
            int ret;
            switch (arrayLen(processes[0].argv)) {
                case 3:
                    ret = chdir(processes[0].argv[1]);
                    break;
                case 2:
                    ret = chdir(original_wd);
                    break;
                default:
                    fprintf(stderr, "usage: cd <path>");
                    fprintf(stderr, "\n");
                    fflush(stderr);
                    ret = -1;
            }

            if (ret)
                printf("[%i] ", ret);
            else {
                printf("%s", get_current_dir_name());
                printf("\n");
            }

        } else if (strcmp(processes[0].argv[0], "exit") == 0) {
            done = true;
        } else if (strcmp(processes[0].argv[0], "get") == 0) {
            if (arrayLen(processes[0].argv) != 3) {
                printf("please provide a file name to get!");
                printf("\n");
            } else {
                send_file(in_fd, processes[0].argv[1]);
            }
        } else if (strcmp(processes[0].argv[0], "wait") == 0) {
            builtin_wait(processes[0], false);
        } else if (strcmp(processes[0].argv[0], "fg") == 0) {
            // same behaviour as wait, just bind to shell again (i.e. terminate process on ctrl-c)
            builtin_wait(processes[0], true);
        } else {
            if (arrayLen(processes) != 0 && processes[arrayLen(processes) - 1].blocking) {
                signal(SIGINT, signal_handler);
            }
            for (size_t i = 0; i < arrayLen(processes); ++i) {
                /*int ret = */exec_command(processes[i]);
                /*if (ret)
                    printf("[%i] ", ret);*/
            }
            printf("\n");
        }

        free_processes(&processes);
    }

    printf("Disconnecting...");
    printf("\n");

    free((void *) original_wd);

    return 0;
}