OILS / cpp / stdlib.cc View on Github | oils.pub

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