aboutsummaryrefslogtreecommitdiff
path: root/stdlib/shell.c
blob: d2d0f78af740ed642c3d7f4371ed13e4c532bafe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// A lang for Shell Command Language
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>

#include "arrays.h"
#include "integers.h"
#include "patterns.h"
#include "shell.h"
#include "text.h"
#include "types.h"
#include "util.h"

public Shell_t Shell$escape_text(Text_t text)
{
    // TODO: optimize for ASCII and short strings
    Array_t shell_graphemes = {.atomic=1};
#define add_char(c) Array$insert(&shell_graphemes, (uint32_t[1]){c}, I_small(0), sizeof(uint32_t))
    add_char('\'');
    const char *text_utf8 = Text$as_c_string(text);
    for (const char *p = text_utf8; *p; p++) {
        if (*p == '\'') {
            add_char('\'');
            add_char('"');
            add_char('\'');
            add_char('"');
            add_char('\'');
        } else
            add_char((uint8_t)*p);
    }
    add_char('\'');
#undef add_char
    return (Text_t){.length=shell_graphemes.length, .tag=TEXT_GRAPHEMES, .graphemes=shell_graphemes.data};
}

public Shell_t Shell$escape_text_array(Array_t texts)
{
    Array_t all_escaped = {};
    for (int64_t i = 0; i < texts.length; i++) {
        Text_t raw = *(Text_t*)(texts.data + i*texts.stride);
        Text_t escaped = Shell$escape_text(raw);
        Array$insert(&all_escaped, &escaped, I(0), sizeof(Text_t));
    }
    return Text$join(Text(" "), all_escaped);
}

public OptionalArray_t Shell$run_bytes(Shell_t command)
{
    const char *cmd_str = Text$as_c_string(command);
    FILE *prog = popen(cmd_str, "r");
    if (!prog)
        return NULL_ARRAY;

    size_t capacity = 256, len = 0;
    char *content = GC_MALLOC_ATOMIC(capacity);
    char chunk[256];
    size_t just_read;
    do {
        just_read = fread(chunk, 1, sizeof(chunk), prog);
        if (just_read == 0) {
            if (errno == EAGAIN || errno == EINTR)
                continue;
            break;
        }

        if (len + (size_t)just_read >= capacity)
            content = GC_REALLOC(content, (capacity *= 2));

        memcpy(&content[len], chunk, (size_t)just_read);
        len += (size_t)just_read;
    } while (just_read == sizeof(chunk));

    int status = pclose(prog);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
        return NULL_ARRAY;

    return (Array_t){.data=content, .atomic=1, .stride=1, .length=len};
}

public OptionalText_t Shell$run(Shell_t command)
{
    Array_t bytes = Shell$run_bytes(command);
    if (bytes.length < 0)
        return NULL_TEXT;

    if (bytes.length > 0 && *(char*)(bytes.data + (bytes.length-1)*bytes.stride) == '\n') {
        --bytes.length;
        if (bytes.length > 0 && *(char*)(bytes.data + (bytes.length-1)*bytes.stride) == '\r')
            --bytes.length;
    }
    return Text$from_bytes(bytes);
}

static void _line_reader_cleanup(FILE **f)
{
    if (f && *f) {
        pclose(*f);
        *f = NULL;
    }
}

static Text_t _next_line(FILE **f)
{
    if (!f || !*f) return NULL_TEXT;

    char *line = NULL;
    size_t size = 0;
    ssize_t len = getline(&line, &size, *f);
    if (len <= 0) {
        _line_reader_cleanup(f);
        return NULL_TEXT;
    }

    while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n'))
        --len;

    if (u8_check((uint8_t*)line, (size_t)len) != NULL)
        fail("Invalid UTF8!");

    Text_t line_text = Text$format("%.*s", len, line);
    free(line);
    return line_text;
}

public OptionalClosure_t Shell$by_line(Shell_t command)
{
    const char *cmd_str = Text$as_c_string(command);
    FILE *prog = popen(cmd_str, "r");
    if (!prog)
        return NULL_CLOSURE;

    FILE **wrapper = GC_MALLOC(sizeof(FILE*));
    *wrapper = prog;
    GC_register_finalizer(wrapper, (void*)_line_reader_cleanup, NULL, NULL, NULL);
    return (Closure_t){.fn=(void*)_next_line, .userdata=wrapper};
}

public const TypeInfo_t Shell$info = {
    .size=sizeof(Shell_t),
    .align=__alignof__(Shell_t),
    .tag=TextInfo,
    .TextInfo={.lang="Shell"},
};

// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0