/home/uke/oil/cpp/stdlib.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // stdlib.cc: Replacement for standard library modules |
2 | | // and native/posixmodule.c |
3 | | |
4 | | #include "stdlib.h" |
5 | | |
6 | | #include <dirent.h> // closedir(), opendir(), readdir() |
7 | | #include <errno.h> |
8 | | #include <fcntl.h> // open |
9 | | #include <math.h> // isinf, isnan |
10 | | #include <signal.h> // kill |
11 | | #include <sys/stat.h> // umask |
12 | | #include <sys/types.h> // umask |
13 | | #include <sys/wait.h> // WUNTRACED |
14 | | #include <time.h> |
15 | | #include <unistd.h> |
16 | | |
17 | | #include "mycpp/runtime.h" |
18 | | // To avoid circular dependency with e_die() |
19 | | #include "prebuilt/core/error.mycpp.h" |
20 | | |
21 | | using error::e_die; |
22 | | |
23 | | namespace math { |
24 | | |
25 | 0 | bool isinf(float f) { |
26 | 0 | return ::isinf(f); |
27 | 0 | } |
28 | | |
29 | 0 | bool isnan(float f) { |
30 | 0 | return ::isnan(f); |
31 | 0 | } |
32 | | |
33 | | } // namespace math |
34 | | |
35 | | namespace fcntl_ { |
36 | | |
37 | 0 | int fcntl(int fd, int cmd) { |
38 | 0 | int result = ::fcntl(fd, cmd); |
39 | 0 | if (result < 0) { |
40 | 0 | throw Alloc<IOError>(errno); |
41 | 0 | } |
42 | 0 | return result; |
43 | 0 | } |
44 | | |
45 | 0 | int fcntl(int fd, int cmd, int arg) { |
46 | 0 | int result = ::fcntl(fd, cmd, arg); |
47 | 0 | if (result < 0) { |
48 | 0 | throw Alloc<IOError>(errno); |
49 | 0 | } |
50 | 0 | return result; |
51 | 0 | } |
52 | | |
53 | | } // namespace fcntl_ |
54 | | |
55 | | namespace posix { |
56 | | |
57 | 0 | mode_t umask(mode_t mask) { |
58 | | // No error case: always succeeds |
59 | 0 | return ::umask(mask); |
60 | 0 | } |
61 | | |
62 | 2 | int open(BigStr* path, int flags, int perms) { |
63 | 2 | int result = ::open(path->data_, flags, perms); |
64 | 2 | if (result < 0) { |
65 | 1 | throw Alloc<OSError>(errno); |
66 | 1 | } |
67 | 1 | return result; |
68 | 2 | } |
69 | | |
70 | 0 | void dup2(int oldfd, int newfd) { |
71 | 0 | if (::dup2(oldfd, newfd) < 0) { |
72 | 0 | throw Alloc<OSError>(errno); |
73 | 0 | } |
74 | 0 | } |
75 | 1 | void putenv(BigStr* name, BigStr* value) { |
76 | 1 | int overwrite = 1; |
77 | 1 | int ret = ::setenv(name->data_, value->data_, overwrite); |
78 | 1 | if (ret < 0) { |
79 | 0 | throw Alloc<IOError>(errno); |
80 | 0 | } |
81 | 1 | } |
82 | | |
83 | 0 | mylib::File* fdopen(int fd, BigStr* c_mode) { |
84 | | // CPython checks if it's a directory first |
85 | 0 | struct stat buf; |
86 | 0 | if (fstat(fd, &buf) == 0 && S_ISDIR(buf.st_mode)) { |
87 | 0 | throw Alloc<OSError>(EISDIR); |
88 | 0 | } |
89 | | |
90 | | // CPython does some fcntl() stuff with mode == 'a', which we don't support |
91 | 0 | DCHECK(c_mode->data_[0] != 'a'); |
92 | | |
93 | 0 | FILE* f = ::fdopen(fd, c_mode->data_); |
94 | 0 | if (f == nullptr) { |
95 | 0 | throw Alloc<OSError>(errno); |
96 | 0 | } |
97 | | |
98 | 0 | return Alloc<mylib::CFile>(f); |
99 | 0 | } |
100 | | |
101 | | void execve(BigStr* argv0, List<BigStr*>* argv, |
102 | 0 | Dict<BigStr*, BigStr*>* environ) { |
103 | 0 | int n_args = len(argv); |
104 | 0 | int n_env = len(environ); |
105 | 0 | int combined_size = 0; |
106 | 0 | for (DictIter<BigStr*, BigStr*> it(environ); !it.Done(); it.Next()) { |
107 | 0 | BigStr* k = it.Key(); |
108 | 0 | BigStr* v = it.Value(); |
109 | |
|
110 | 0 | int joined_len = len(k) + len(v) + 2; // = and NUL terminator |
111 | 0 | combined_size += joined_len; |
112 | 0 | } |
113 | 0 | const int argv_size = (n_args + 1) * sizeof(char*); |
114 | 0 | const int env_size = (n_env + 1) * sizeof(char*); |
115 | 0 | combined_size += argv_size; |
116 | 0 | combined_size += env_size; |
117 | 0 | char* combined_buf = static_cast<char*>(malloc(combined_size)); |
118 | | |
119 | | // never deallocated |
120 | 0 | char** _argv = reinterpret_cast<char**>(combined_buf); |
121 | 0 | combined_buf += argv_size; |
122 | | |
123 | | // Annoying const_cast |
124 | | // https://stackoverflow.com/questions/190184/execv-and-const-ness |
125 | 0 | for (int i = 0; i < n_args; ++i) { |
126 | 0 | _argv[i] = const_cast<char*>(argv->at(i)->data_); |
127 | 0 | } |
128 | 0 | _argv[n_args] = nullptr; |
129 | | |
130 | | // Convert environ into an array of pointers to strings of the form: "k=v". |
131 | 0 | char** envp = reinterpret_cast<char**>(combined_buf); |
132 | 0 | combined_buf += env_size; |
133 | 0 | int env_index = 0; |
134 | 0 | for (DictIter<BigStr*, BigStr*> it(environ); !it.Done(); it.Next()) { |
135 | 0 | BigStr* k = it.Key(); |
136 | 0 | BigStr* v = it.Value(); |
137 | |
|
138 | 0 | char* buf = combined_buf; |
139 | 0 | int joined_len = len(k) + len(v) + 1; |
140 | 0 | combined_buf += joined_len + 1; |
141 | 0 | memcpy(buf, k->data_, len(k)); |
142 | 0 | buf[len(k)] = '='; |
143 | 0 | memcpy(buf + len(k) + 1, v->data_, len(v)); |
144 | 0 | buf[joined_len] = '\0'; |
145 | |
|
146 | 0 | envp[env_index++] = buf; |
147 | 0 | } |
148 | 0 | envp[n_env] = nullptr; |
149 | |
|
150 | 0 | int ret = ::execve(argv0->data_, _argv, envp); |
151 | 0 | if (ret == -1) { |
152 | 0 | throw Alloc<OSError>(errno); |
153 | 0 | } |
154 | | |
155 | | // ::execve() never returns on success |
156 | 0 | FAIL(kShouldNotGetHere); |
157 | 0 | } |
158 | | |
159 | 0 | void kill(int pid, int sig) { |
160 | 0 | if (::kill(pid, sig) != 0) { |
161 | 0 | throw Alloc<OSError>(errno); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | 0 | void killpg(int pgid, int sig) { |
166 | 0 | if (::killpg(pgid, sig) != 0) { |
167 | 0 | throw Alloc<OSError>(errno); |
168 | 0 | } |
169 | 0 | } |
170 | | |
171 | 2 | List<BigStr*>* listdir(BigStr* path) { |
172 | 2 | DIR* dirp = opendir(path->data()); |
173 | 2 | if (dirp == NULL) { |
174 | 1 | throw Alloc<OSError>(errno); |
175 | 1 | } |
176 | | |
177 | 1 | auto* ret = Alloc<List<BigStr*>>(); |
178 | 24 | while (true) { |
179 | 24 | errno = 0; |
180 | 24 | struct dirent* ep = readdir(dirp); |
181 | 24 | if (ep == NULL) { |
182 | 1 | if (errno != 0) { |
183 | 0 | closedir(dirp); |
184 | 0 | throw Alloc<OSError>(errno); |
185 | 0 | } |
186 | 1 | break; // no more files |
187 | 1 | } |
188 | | // Skip . and .. |
189 | 23 | int name_len = strlen(ep->d_name); |
190 | 23 | if (ep->d_name[0] == '.' && |
191 | 23 | (name_len == 1 || (ep->d_name[1] == '.' && name_len == 2))) { |
192 | 2 | continue; |
193 | 2 | } |
194 | 21 | ret->append(StrFromC(ep->d_name, name_len)); |
195 | 21 | } |
196 | | |
197 | 1 | closedir(dirp); |
198 | | |
199 | 1 | return ret; |
200 | 1 | } |
201 | | |
202 | | } // namespace posix |
203 | | |
204 | | namespace time_ { |
205 | | |
206 | 1 | void tzset() { |
207 | | // No error case: no return value |
208 | 1 | ::tzset(); |
209 | 1 | } |
210 | | |
211 | 1 | time_t time() { |
212 | 1 | time_t result = ::time(nullptr); |
213 | 1 | if (result < 0) { |
214 | 0 | throw Alloc<IOError>(errno); |
215 | 0 | } |
216 | 1 | return result; |
217 | 1 | } |
218 | | |
219 | | // NOTE(Jesse): time_t is specified to be an arithmetic type by C++. On most |
220 | | // systems it's a 64-bit integer. 64 bits is used because 32 will overflow in |
221 | | // 2038. Someone on a committee somewhere thought of that when moving to |
222 | | // 64-bit architectures to prevent breaking ABI again; on 32-bit systems it's |
223 | | // usually 32 bits. Point being, using anything but the time_t typedef here |
224 | | // could (unlikely, but possible) produce weird behavior. |
225 | 0 | time_t localtime(time_t ts) { |
226 | | // localtime returns a pointer to a static buffer |
227 | 0 | tm* loc_time = ::localtime(&ts); |
228 | |
|
229 | 0 | time_t result = mktime(loc_time); |
230 | 0 | if (result < 0) { |
231 | 0 | throw Alloc<IOError>(errno); |
232 | 0 | } |
233 | 0 | return result; |
234 | 0 | } |
235 | | |
236 | 3 | BigStr* strftime(BigStr* s, time_t ts) { |
237 | 3 | tm* loc_time = ::localtime(&ts); |
238 | | |
239 | 3 | const int max_len = 1024; |
240 | 3 | BigStr* result = OverAllocatedStr(max_len); |
241 | 3 | int n = strftime(result->data(), max_len, s->data_, loc_time); |
242 | 3 | if (n == 0) { |
243 | | // bash silently truncates on large format string like |
244 | | // printf '%(%Y)T' |
245 | | // Oil doesn't mask errors |
246 | | // Leaving out location info points to 'printf' builtin |
247 | |
|
248 | 0 | e_die(StrFromC("strftime() result exceeds 1024 bytes")); |
249 | 0 | } |
250 | 3 | result->MaybeShrink(n); |
251 | 3 | return result; |
252 | 3 | } |
253 | | |
254 | 1 | void sleep(int seconds) { |
255 | 1 | ::sleep(seconds); |
256 | 1 | } |
257 | | |
258 | | } // namespace time_ |