OILS / cpp / stdlib.cc View on Github | oilshell.org

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