summaryrefslogtreecommitdiffstats
path: root/libc-bottom-half/sources/chdir.c
blob: 37c95a4e56e1c8da7fa511f090a9d6e94dca0113 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wasi/libc-find-relpath.h>
#include <wasi/libc.h>

#ifdef _REENTRANT
void __wasilibc_cwd_lock(void);
void __wasilibc_cwd_unlock(void);
#else
#define __wasilibc_cwd_lock() (void)0
#define __wasilibc_cwd_unlock() (void)0
#endif
extern char *__wasilibc_cwd;
static int __wasilibc_cwd_mallocd = 0;

int chdir(const char *path)
{
    static char *relative_buf = NULL;
    static size_t relative_buf_len = 0;

    // Find a preopen'd directory as well as a relative path we're anchored
    // from which we're changing directories to.
    const char *abs;
    int parent_fd = __wasilibc_find_relpath_alloc(path, &abs, &relative_buf, &relative_buf_len, 1);
    if (parent_fd == -1)
        return -1;

    // Make sure that this directory we're accessing is indeed a directory.
    struct stat dirinfo;
    int ret = fstatat(parent_fd, relative_buf, &dirinfo, 0);
    if (ret == -1)
        return -1;
    if (!S_ISDIR(dirinfo.st_mode)) {
        errno = ENOTDIR;
        return -1;
    }

    // Create a string that looks like:
    //
    //    __wasilibc_cwd = "/" + abs + "/" + relative_buf
    //
    // If `relative_buf` is equal to "." or `abs` is equal to the empty string,
    // however, we skip that part and the middle slash.
    size_t abs_len = strlen(abs);
    int copy_relative = strcmp(relative_buf, ".") != 0;
    int mid = copy_relative && abs[0] != 0;
    char *new_cwd = malloc(1 + abs_len + mid + (copy_relative ? strlen(relative_buf) : 0) + 1);
    if (new_cwd == NULL) {
        errno = ENOMEM;
        return -1;
    }
    new_cwd[0] = '/';
    strcpy(new_cwd + 1, abs);
    if (mid)
        new_cwd[1 + abs_len] = '/';
    if (copy_relative)
        strcpy(new_cwd + 1 + abs_len + mid, relative_buf);

    // And set our new malloc'd buffer into the global cwd, freeing the
    // previous one if necessary.
    __wasilibc_cwd_lock();
    char *prev_cwd = __wasilibc_cwd;
    __wasilibc_cwd = new_cwd;
    __wasilibc_cwd_unlock();
    if (__wasilibc_cwd_mallocd)
        free(prev_cwd);
    __wasilibc_cwd_mallocd = 1;
    return 0;
}

static const char *make_absolute(const char *path) {
    static char *make_absolute_buf = NULL;
    static size_t make_absolute_len = 0;

    // If this path is absolute, then we return it as-is.
    if (path[0] == '/') {
        return path;
    }

#ifndef _REENTRANT
    // If the path is empty, or points to the current directory, then return
    // the current directory.
    if (path[0] == 0 || !strcmp(path, ".") || !strcmp(path, "./")) {
        return __wasilibc_cwd;
    }
#endif

    // If the path starts with `./` then we won't be appending that to the cwd.
    if (path[0] == '.' && path[1] == '/')
        path += 2;

    // Otherwise we'll take the current directory, add a `/`, and then add the
    // input `path`. Note that this doesn't do any normalization (like removing
    // `/./`).
    __wasilibc_cwd_lock();
    size_t cwd_len = strlen(__wasilibc_cwd);
    size_t path_len = path ? strlen(path) : 0;
    __wasilibc_cwd_unlock();
    int need_slash = __wasilibc_cwd[cwd_len - 1] == '/' ? 0 : 1;
    size_t alloc_len = cwd_len + path_len + 1 + need_slash;
    if (alloc_len > make_absolute_len) {
        char *tmp = realloc(make_absolute_buf, alloc_len);
        if (tmp == NULL) {
            __wasilibc_cwd_unlock();
            return NULL;
        }
        make_absolute_buf = tmp;
        make_absolute_len = alloc_len;
    }
    strcpy(make_absolute_buf, __wasilibc_cwd);
    __wasilibc_cwd_unlock();

#ifdef _REENTRANT
    if (path[0] == 0 || !strcmp(path, ".") || !strcmp(path, "./")) {
        return make_absolute_buf;
    }
#endif

    if (need_slash)
        strcpy(make_absolute_buf + cwd_len, "/");
    strcpy(make_absolute_buf + cwd_len + need_slash, path);
    return make_absolute_buf;
}

// Helper function defined only in this object file and weakly referenced from
// `preopens.c` and `posix.c` This function isn't necessary unless `chdir` is
// pulled in because all paths are otherwise absolute or relative to the root.
int __wasilibc_find_relpath_alloc(
    const char *path,
    const char **abs_prefix,
    char **relative_buf,
    size_t *relative_buf_len,
    int can_realloc
) {
    // First, make our path absolute taking the cwd into account.
    const char *abspath = make_absolute(path);
    if (abspath == NULL) {
        errno = ENOMEM;
        return -1;
    }

    // Next use our absolute path and split it. Find the preopened `fd` parent
    // directory and set `abs_prefix`. Next up we'll be trying to fit `rel`
    // into `relative_buf`.
    const char *rel;
    int fd = __wasilibc_find_abspath(abspath, abs_prefix, &rel);
    if (fd == -1)
        return -1;

    size_t rel_len = strlen(rel);
    if (*relative_buf_len < rel_len + 1) {
        if (!can_realloc) {
            errno = ERANGE;
            return -1;
        }
        char *tmp = realloc(*relative_buf, rel_len + 1);
        if (tmp == NULL) {
            errno = ENOMEM;
            return -1;
        }
        *relative_buf = tmp;
        *relative_buf_len = rel_len + 1;
    }
    strcpy(*relative_buf, rel);
    return fd;
}