aboutsummaryrefslogtreecommitdiff
path: root/stdlib/shell.c
blob: 7dcc9a0a7e49c8d4a29fe716d1e0e1dfeee190bb (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
146
// A lang for Shell Command Language
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistr.h>

#include "arrays.h"
#include "integers.h"
#include "paths.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)
{
    return Texts(Text("'"), Text$replace(text, Text("'"), Text("'\"'\"'"), Text(""), false), Text("'"));
}

public Shell_t Shell$escape_path(Path_t path)
{
    return Shell$escape_text(Path$as_text(&path, false, &Path$info));
}

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 NONE_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 NONE_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 NONE_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);
}

public OptionalInt32_t Shell$execute(Shell_t command)
{
    const char *cmd_str = Text$as_c_string(command);
    int status = system(cmd_str);
    if (WIFEXITED(status))
        return (OptionalInt32_t){.i=WEXITSTATUS(status)};
    else
        return (OptionalInt32_t){.is_none=true};
}

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 NONE_TEXT;

    char *line = NULL;
    size_t size = 0;
    ssize_t len = getline(&line, &size, *f);
    if (len <= 0) {
        _line_reader_cleanup(f);
        return NONE_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 NONE_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"},
    .metamethods=Text$metamethods,
};

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