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

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