Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/fs_ops.c
Line
Count
Source
1
#include "fs_ops.h"
2
#include <stdlib.h>
3
#include <string.h>
4
#include <stdio.h>
5
#include <stdint.h>
6
7
#ifndef __EMSCRIPTEN__
8
9
#include <sys/stat.h>
10
#include <unistd.h>
11
#include <dirent.h>
12
#include <errno.h>
13
#include <glob.h>
14
#include <limits.h>
15
16
21
bool fs_file_exists(const char *path) {
17
21
    struct stat st;
18
21
    return stat(path, &st) == 0;
19
21
}
20
21
36
bool fs_delete_file(const char *path, char **err) {
22
36
    if (unlink(path) != 0) {
23
3
        char buf[512];
24
3
        snprintf(buf, sizeof(buf), "delete_file: %s: %s", path, strerror(errno));
25
3
        *err = strdup(buf);
26
3
        return false;
27
3
    }
28
33
    return true;
29
36
}
30
31
6
char **fs_list_dir(const char *path, size_t *count, char **err) {
32
6
    DIR *dir = opendir(path);
33
6
    if (!dir) {
34
3
        char buf[512];
35
3
        snprintf(buf, sizeof(buf), "list_dir: %s: %s", path, strerror(errno));
36
3
        *err = strdup(buf);
37
3
        *count = 0;
38
3
        return NULL;
39
3
    }
40
41
3
    size_t cap = 16;
42
3
    size_t len = 0;
43
3
    char **entries = malloc(cap * sizeof(char *));
44
3
    if (!entries) {
45
0
        closedir(dir);
46
0
        *err = strdup("list_dir: out of memory");
47
0
        *count = 0;
48
0
        return NULL;
49
0
    }
50
51
3
    struct dirent *ent;
52
4.88k
    while ((ent = readdir(dir)) != NULL) {
53
4.87k
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
54
6
            continue;
55
4.87k
        if (len >= cap) {
56
21
            cap *= 2;
57
21
            char **tmp = realloc(entries, cap * sizeof(char *));
58
21
            if (!tmp) {
59
0
                for (size_t i = 0; i < len; i++) free(entries[i]);
60
0
                free(entries);
61
0
                closedir(dir);
62
0
                *err = strdup("list_dir: out of memory");
63
0
                *count = 0;
64
0
                return NULL;
65
0
            }
66
21
            entries = tmp;
67
21
        }
68
4.87k
        entries[len++] = strdup(ent->d_name);
69
4.87k
    }
70
3
    closedir(dir);
71
72
3
    *count = len;
73
3
    return entries;
74
3
}
75
76
6
bool fs_append_file(const char *path, const char *data, char **err) {
77
6
    FILE *f = fopen(path, "a");
78
6
    if (!f) {
79
0
        char buf[512];
80
0
        snprintf(buf, sizeof(buf), "append_file: %s: %s", path, strerror(errno));
81
0
        *err = strdup(buf);
82
0
        return false;
83
0
    }
84
6
    if (fputs(data, f) == EOF) {
85
0
        char buf[512];
86
0
        snprintf(buf, sizeof(buf), "append_file: write failed: %s", strerror(errno));
87
0
        *err = strdup(buf);
88
0
        fclose(f);
89
0
        return false;
90
0
    }
91
6
    fclose(f);
92
6
    return true;
93
6
}
94
95
9
bool fs_mkdir(const char *path, char **err) {
96
9
    if (mkdir(path, 0755) != 0) {
97
0
        char buf[512];
98
0
        snprintf(buf, sizeof(buf), "mkdir: %s: %s", path, strerror(errno));
99
0
        *err = strdup(buf);
100
0
        return false;
101
0
    }
102
9
    return true;
103
9
}
104
105
3
bool fs_rename(const char *oldpath, const char *newpath, char **err) {
106
3
    if (rename(oldpath, newpath) != 0) {
107
0
        char buf[512];
108
0
        snprintf(buf, sizeof(buf), "rename: %s -> %s: %s", oldpath, newpath, strerror(errno));
109
0
        *err = strdup(buf);
110
0
        return false;
111
0
    }
112
3
    return true;
113
3
}
114
115
15
bool fs_is_dir(const char *path) {
116
15
    struct stat st;
117
15
    if (stat(path, &st) != 0) return false;
118
12
    return S_ISDIR(st.st_mode);
119
15
}
120
121
9
bool fs_is_file(const char *path) {
122
9
    struct stat st;
123
9
    if (stat(path, &st) != 0) return false;
124
6
    return S_ISREG(st.st_mode);
125
9
}
126
127
12
bool fs_rmdir(const char *path, char **err) {
128
12
    if (rmdir(path) != 0) {
129
3
        char buf[512];
130
3
        snprintf(buf, sizeof(buf), "rmdir: %s: %s", path, strerror(errno));
131
3
        *err = strdup(buf);
132
3
        return false;
133
3
    }
134
9
    return true;
135
12
}
136
137
6
char **fs_glob(const char *pattern, size_t *count, char **err) {
138
6
    glob_t gl;
139
6
    int flags = GLOB_TILDE;
140
6
#ifdef GLOB_BRACE
141
6
    flags |= GLOB_BRACE;
142
6
#endif
143
6
    int rc = glob(pattern, flags, NULL, &gl);
144
6
    if (rc == GLOB_NOMATCH) {
145
3
        globfree(&gl);
146
3
        *count = 0;
147
3
        return NULL; /* no error, just empty */
148
3
    }
149
3
    if (rc != 0) {
150
0
        char buf[512];
151
0
        snprintf(buf, sizeof(buf), "glob: pattern error: %s", pattern);
152
0
        *err = strdup(buf);
153
0
        *count = 0;
154
0
        return NULL;
155
0
    }
156
157
3
    size_t n = gl.gl_pathc;
158
3
    char **results = malloc(n * sizeof(char *));
159
3
    if (!results) {
160
0
        globfree(&gl);
161
0
        *err = strdup("glob: out of memory");
162
0
        *count = 0;
163
0
        return NULL;
164
0
    }
165
9
    for (size_t i = 0; i < n; i++) {
166
6
        results[i] = strdup(gl.gl_pathv[i]);
167
6
    }
168
3
    globfree(&gl);
169
3
    *count = n;
170
3
    return results;
171
3
}
172
173
bool fs_stat(const char *path, int64_t *size_out, int64_t *mtime_out,
174
9
             int64_t *mode_out, const char **type_out, char **err) {
175
9
    struct stat st;
176
9
    if (lstat(path, &st) != 0) {
177
3
        char buf[512];
178
3
        snprintf(buf, sizeof(buf), "stat: %s: %s", path, strerror(errno));
179
3
        *err = strdup(buf);
180
3
        return false;
181
3
    }
182
6
    *size_out = (int64_t)st.st_size;
183
6
    *mtime_out = (int64_t)st.st_mtime * 1000;
184
6
    *mode_out = (int64_t)(st.st_mode & 07777);
185
6
    if (S_ISREG(st.st_mode))       *type_out = "file";
186
3
    else if (S_ISDIR(st.st_mode))  *type_out = "dir";
187
0
    else if (S_ISLNK(st.st_mode))  *type_out = "symlink";
188
0
    else                            *type_out = "other";
189
6
    return true;
190
9
}
191
192
6
bool fs_copy_file(const char *src, const char *dst, char **err) {
193
6
    FILE *fin = fopen(src, "rb");
194
6
    if (!fin) {
195
3
        char buf[512];
196
3
        snprintf(buf, sizeof(buf), "copy_file: cannot open source: %s: %s", src, strerror(errno));
197
3
        *err = strdup(buf);
198
3
        return false;
199
3
    }
200
3
    FILE *fout = fopen(dst, "wb");
201
3
    if (!fout) {
202
0
        char buf[512];
203
0
        snprintf(buf, sizeof(buf), "copy_file: cannot open destination: %s: %s", dst, strerror(errno));
204
0
        *err = strdup(buf);
205
0
        fclose(fin);
206
0
        return false;
207
0
    }
208
209
3
    char buffer[8192];
210
3
    size_t n;
211
6
    while ((n = fread(buffer, 1, sizeof(buffer), fin)) > 0) {
212
3
        if (fwrite(buffer, 1, n, fout) != n) {
213
0
            char buf[512];
214
0
            snprintf(buf, sizeof(buf), "copy_file: write failed: %s", strerror(errno));
215
0
            *err = strdup(buf);
216
0
            fclose(fin);
217
0
            fclose(fout);
218
0
            return false;
219
0
        }
220
3
    }
221
3
    if (ferror(fin)) {
222
0
        char buf[512];
223
0
        snprintf(buf, sizeof(buf), "copy_file: read failed: %s", strerror(errno));
224
0
        *err = strdup(buf);
225
0
        fclose(fin);
226
0
        fclose(fout);
227
0
        return false;
228
0
    }
229
3
    fclose(fin);
230
3
    fclose(fout);
231
3
    return true;
232
3
}
233
234
6
char *fs_realpath(const char *path, char **err) {
235
6
    char *resolved = realpath(path, NULL);
236
6
    if (!resolved) {
237
3
        char buf[512];
238
3
        snprintf(buf, sizeof(buf), "realpath: %s: %s", path, strerror(errno));
239
3
        *err = strdup(buf);
240
3
        return NULL;
241
3
    }
242
3
    return resolved;
243
6
}
244
245
3
char *fs_tempdir(char **err) {
246
3
    char tmpl[] = "/tmp/lattice_XXXXXX";
247
3
    char *result = mkdtemp(tmpl);
248
3
    if (!result) {
249
0
        char buf[512];
250
0
        snprintf(buf, sizeof(buf), "tempdir: %s", strerror(errno));
251
0
        *err = strdup(buf);
252
0
        return NULL;
253
0
    }
254
3
    return strdup(result);
255
3
}
256
257
6
char *fs_tempfile(char **err) {
258
6
    char tmpl[] = "/tmp/lattice_XXXXXX";
259
6
    int fd = mkstemp(tmpl);
260
6
    if (fd < 0) {
261
0
        char buf[512];
262
0
        snprintf(buf, sizeof(buf), "tempfile: %s", strerror(errno));
263
0
        *err = strdup(buf);
264
0
        return NULL;
265
0
    }
266
6
    close(fd);
267
6
    return strdup(tmpl);
268
6
}
269
270
3
bool fs_chmod(const char *path, int mode, char **err) {
271
3
    if (chmod(path, (mode_t)mode) != 0) {
272
0
        char buf[512];
273
0
        snprintf(buf, sizeof(buf), "chmod: %s: %s", path, strerror(errno));
274
0
        *err = strdup(buf);
275
0
        return false;
276
0
    }
277
3
    return true;
278
3
}
279
280
6
int64_t fs_file_size(const char *path, char **err) {
281
6
    struct stat st;
282
6
    if (stat(path, &st) != 0) {
283
3
        char buf[512];
284
3
        snprintf(buf, sizeof(buf), "file_size: %s: %s", path, strerror(errno));
285
3
        *err = strdup(buf);
286
3
        return -1;
287
3
    }
288
3
    return (int64_t)st.st_size;
289
6
}
290
291
#else /* __EMSCRIPTEN__ */
292
293
bool fs_file_exists(const char *path) {
294
    (void)path;
295
    return false;
296
}
297
298
bool fs_delete_file(const char *path, char **err) {
299
    (void)path;
300
    *err = strdup("delete_file: not available in browser");
301
    return false;
302
}
303
304
char **fs_list_dir(const char *path, size_t *count, char **err) {
305
    (void)path;
306
    *count = 0;
307
    *err = strdup("list_dir: not available in browser");
308
    return NULL;
309
}
310
311
bool fs_append_file(const char *path, const char *data, char **err) {
312
    (void)path; (void)data;
313
    *err = strdup("append_file: not available in browser");
314
    return false;
315
}
316
317
bool fs_mkdir(const char *path, char **err) {
318
    (void)path;
319
    *err = strdup("mkdir: not available in browser");
320
    return false;
321
}
322
323
bool fs_rename(const char *oldpath, const char *newpath, char **err) {
324
    (void)oldpath; (void)newpath;
325
    *err = strdup("rename: not available in browser");
326
    return false;
327
}
328
329
bool fs_is_dir(const char *path) {
330
    (void)path;
331
    return false;
332
}
333
334
bool fs_is_file(const char *path) {
335
    (void)path;
336
    return false;
337
}
338
339
bool fs_rmdir(const char *path, char **err) {
340
    (void)path;
341
    *err = strdup("rmdir: not available in browser");
342
    return false;
343
}
344
345
char **fs_glob(const char *pattern, size_t *count, char **err) {
346
    (void)pattern;
347
    *count = 0;
348
    *err = strdup("glob: not available in browser");
349
    return NULL;
350
}
351
352
bool fs_stat(const char *path, int64_t *size_out, int64_t *mtime_out,
353
             int64_t *mode_out, const char **type_out, char **err) {
354
    (void)path; (void)size_out; (void)mtime_out; (void)mode_out; (void)type_out;
355
    *err = strdup("stat: not available in browser");
356
    return false;
357
}
358
359
bool fs_copy_file(const char *src, const char *dst, char **err) {
360
    (void)src; (void)dst;
361
    *err = strdup("copy_file: not available in browser");
362
    return false;
363
}
364
365
char *fs_realpath(const char *path, char **err) {
366
    (void)path;
367
    *err = strdup("realpath: not available in browser");
368
    return NULL;
369
}
370
371
char *fs_tempdir(char **err) {
372
    *err = strdup("tempdir: not available in browser");
373
    return NULL;
374
}
375
376
char *fs_tempfile(char **err) {
377
    *err = strdup("tempfile: not available in browser");
378
    return NULL;
379
}
380
381
bool fs_chmod(const char *path, int mode, char **err) {
382
    (void)path; (void)mode;
383
    *err = strdup("chmod: not available in browser");
384
    return false;
385
}
386
387
int64_t fs_file_size(const char *path, char **err) {
388
    (void)path;
389
    *err = strdup("file_size: not available in browser");
390
    return -1;
391
}
392
393
#endif /* __EMSCRIPTEN__ */