/home/andy/git/oilshell/oil/cpp/core.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // core.cc |
2 | | |
3 | | #include "cpp/core.h" |
4 | | |
5 | | #include <ctype.h> // ispunct() |
6 | | #include <errno.h> |
7 | | #include <float.h> |
8 | | #include <math.h> // fmod() |
9 | | #include <pwd.h> // passwd |
10 | | #include <signal.h> |
11 | | #include <sys/resource.h> // getrusage |
12 | | #include <sys/select.h> // select(), FD_ISSET, FD_SET, FD_ZERO |
13 | | #include <sys/stat.h> // stat |
14 | | #include <sys/time.h> // gettimeofday |
15 | | #include <sys/times.h> // tms / times() |
16 | | #include <sys/utsname.h> // uname |
17 | | #include <sys/wait.h> // waitpid() |
18 | | #include <termios.h> // tcgetattr(), tcsetattr() |
19 | | #include <time.h> // time() |
20 | | #include <unistd.h> // getuid(), environ |
21 | | |
22 | | #include "_build/detected-cpp-config.h" // HAVE_PWENT |
23 | | #include "_gen/cpp/build_stamp.h" // gCommitHash |
24 | | #include "_gen/frontend/consts.h" // gVersion |
25 | | #include "cpp/embedded_file.h" |
26 | | |
27 | | extern char** environ; |
28 | | |
29 | | namespace pyos { |
30 | | |
31 | | SignalSafe* gSignalSafe = nullptr; |
32 | | |
33 | 1 | Tuple2<int, int> WaitPid(int waitpid_options) { |
34 | 1 | int status; |
35 | 1 | int result = ::waitpid(-1, &status, WUNTRACED | waitpid_options); |
36 | 1 | if (result < 0) { |
37 | 1 | if (errno == EINTR && gSignalSafe->PollUntrappedSigInt()) { |
38 | 0 | throw Alloc<KeyboardInterrupt>(); |
39 | 0 | } |
40 | 1 | return Tuple2<int, int>(-1, errno); |
41 | 1 | } |
42 | 0 | return Tuple2<int, int>(result, status); |
43 | 1 | } |
44 | | |
45 | 2 | Tuple2<int, int> Read(int fd, int n, List<BigStr*>* chunks) { |
46 | 2 | BigStr* s = OverAllocatedStr(n); // Allocate enough for the result |
47 | | |
48 | 2 | int length = ::read(fd, s->data(), n); |
49 | 2 | if (length < 0) { |
50 | 0 | if (errno == EINTR && gSignalSafe->PollUntrappedSigInt()) { |
51 | 0 | throw Alloc<KeyboardInterrupt>(); |
52 | 0 | } |
53 | 0 | return Tuple2<int, int>(-1, errno); |
54 | 0 | } |
55 | 2 | if (length == 0) { |
56 | 1 | return Tuple2<int, int>(length, 0); |
57 | 1 | } |
58 | | |
59 | | // Now we know how much data we got back |
60 | 1 | s->MaybeShrink(length); |
61 | 1 | chunks->append(s); |
62 | | |
63 | 1 | return Tuple2<int, int>(length, 0); |
64 | 2 | } |
65 | | |
66 | 3 | Tuple2<int, int> ReadByte(int fd) { |
67 | 3 | unsigned char buf[1]; |
68 | 3 | ssize_t n = read(fd, &buf, 1); |
69 | 3 | if (n < 0) { // read error |
70 | 0 | if (errno == EINTR && gSignalSafe->PollUntrappedSigInt()) { |
71 | 0 | throw Alloc<KeyboardInterrupt>(); |
72 | 0 | } |
73 | 0 | return Tuple2<int, int>(-1, errno); |
74 | 3 | } else if (n == 0) { // EOF |
75 | 1 | return Tuple2<int, int>(EOF_SENTINEL, 0); |
76 | 2 | } else { // return character |
77 | 2 | return Tuple2<int, int>(buf[0], 0); |
78 | 2 | } |
79 | 3 | } |
80 | | |
81 | 1 | Dict<BigStr*, BigStr*>* Environ() { |
82 | 1 | auto d = Alloc<Dict<BigStr*, BigStr*>>(); |
83 | | |
84 | 56 | for (char** env = environ; *env; ++env) { |
85 | 55 | char* pair = *env; |
86 | | |
87 | 55 | char* eq = strchr(pair, '='); |
88 | 55 | assert(eq != nullptr); // must look like KEY=value |
89 | | |
90 | 0 | int len = strlen(pair); |
91 | | |
92 | 55 | int key_len = eq - pair; |
93 | 55 | BigStr* key = StrFromC(pair, key_len); |
94 | | |
95 | 55 | int val_len = len - key_len - 1; |
96 | 55 | BigStr* val = StrFromC(eq + 1, val_len); |
97 | | |
98 | 55 | d->set(key, val); |
99 | 55 | } |
100 | | |
101 | 1 | return d; |
102 | 1 | } |
103 | | |
104 | 3 | int Chdir(BigStr* dest_dir) { |
105 | 3 | if (chdir(dest_dir->data_) == 0) { |
106 | 2 | return 0; // success |
107 | 2 | } else { |
108 | 1 | return errno; |
109 | 1 | } |
110 | 3 | } |
111 | | |
112 | 1 | BigStr* GetMyHomeDir() { |
113 | 1 | uid_t uid = getuid(); // always succeeds |
114 | | |
115 | | // Don't free this. (May return a pointer to a static area) |
116 | 1 | struct passwd* entry = getpwuid(uid); |
117 | 1 | if (entry == nullptr) { |
118 | 0 | return nullptr; |
119 | 0 | } |
120 | 1 | BigStr* s = StrFromC(entry->pw_dir); |
121 | 1 | return s; |
122 | 1 | } |
123 | | |
124 | 1 | BigStr* GetHomeDir(BigStr* user_name) { |
125 | | // Don't free this. (May return a pointer to a static area) |
126 | 1 | struct passwd* entry = getpwnam(user_name->data_); |
127 | 1 | if (entry == nullptr) { |
128 | 0 | return nullptr; |
129 | 0 | } |
130 | 1 | BigStr* s = StrFromC(entry->pw_dir); |
131 | 1 | return s; |
132 | 1 | } |
133 | | |
134 | 1 | List<PasswdEntry*>* GetAllUsers() { |
135 | 1 | #ifdef HAVE_PWENT |
136 | 1 | auto* ret = NewList<PasswdEntry*>(); |
137 | 1 | struct passwd* entry = nullptr; |
138 | | |
139 | 1 | setpwent(); |
140 | 37 | while (true) { |
141 | 37 | errno = 0; |
142 | 37 | entry = getpwent(); |
143 | 37 | if (entry == nullptr) { |
144 | 1 | if (errno == EINTR) { |
145 | 0 | continue; // try again |
146 | 1 | } else if (errno != 0) { |
147 | 0 | throw Alloc<OSError>(errno); |
148 | 0 | } |
149 | 1 | break; |
150 | 1 | } |
151 | 36 | ret->append(Alloc<PasswdEntry>(entry)); |
152 | 36 | } |
153 | 1 | endpwent(); |
154 | | |
155 | 1 | return ret; |
156 | | #else |
157 | | fprintf( |
158 | | stderr, |
159 | | "Oils compiled without libc *pwent() functions. Can't list users.\n"); |
160 | | return NewList<PasswdEntry*>(); |
161 | | #endif |
162 | 1 | } |
163 | | |
164 | 2 | BigStr* GetUserName(int uid) { |
165 | 2 | BigStr* result = kEmptyString; |
166 | | |
167 | 2 | if (passwd* pw = getpwuid(uid)) { |
168 | 2 | result = StrFromC(pw->pw_name); |
169 | 2 | } else { |
170 | 0 | throw Alloc<IOError>(errno); |
171 | 0 | } |
172 | | |
173 | 2 | return result; |
174 | 2 | } |
175 | | |
176 | 1 | BigStr* OsType() { |
177 | 1 | BigStr* result = kEmptyString; |
178 | | |
179 | 1 | utsname un = {}; |
180 | 1 | if (::uname(&un) == 0) { |
181 | 1 | result = StrFromC(un.sysname); |
182 | 1 | } else { |
183 | 0 | throw Alloc<IOError>(errno); |
184 | 0 | } |
185 | | |
186 | 1 | return result; |
187 | 1 | } |
188 | | |
189 | 0 | Tuple2<mops::BigInt, mops::BigInt> GetRLimit(int resource) { |
190 | 0 | struct rlimit lim; |
191 | 0 | if (::getrlimit(resource, &lim) < 0) { |
192 | 0 | throw Alloc<IOError>(errno); |
193 | 0 | } |
194 | 0 | return Tuple2<mops::BigInt, mops::BigInt>(lim.rlim_cur, lim.rlim_max); |
195 | 0 | } |
196 | | |
197 | 0 | void SetRLimit(int resource, mops::BigInt soft, mops::BigInt hard) { |
198 | 0 | struct rlimit lim; |
199 | 0 | lim.rlim_cur = soft; |
200 | 0 | lim.rlim_max = hard; |
201 | |
|
202 | 0 | if (::setrlimit(resource, &lim) < 0) { |
203 | 0 | throw Alloc<IOError>(errno); |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | 1 | Tuple3<double, double, double> Time() { |
208 | 1 | struct timeval now; |
209 | 1 | if (gettimeofday(&now, nullptr) < 0) { |
210 | 0 | throw Alloc<IOError>(errno); // could be a permission error |
211 | 0 | } |
212 | 1 | double real = now.tv_sec + static_cast<double>(now.tv_usec) / 1e6; |
213 | | |
214 | 1 | struct rusage ru; |
215 | 1 | if (::getrusage(RUSAGE_SELF, &ru) == -1) { |
216 | 0 | throw Alloc<IOError>(errno); |
217 | 0 | } |
218 | 1 | struct timeval* u = &(ru.ru_utime); |
219 | 1 | struct timeval* s = &(ru.ru_stime); |
220 | | |
221 | 1 | double user = u->tv_sec + static_cast<double>(u->tv_usec) / 1e6; |
222 | 1 | double sys = s->tv_sec + static_cast<double>(s->tv_usec) / 1e6; |
223 | | |
224 | 1 | return Tuple3<double, double, double>(real, user, sys); |
225 | 1 | } |
226 | | |
227 | 4 | static void PrintClock(clock_t ticks, long ticks_per_sec) { |
228 | 4 | double seconds = static_cast<double>(ticks) / ticks_per_sec; |
229 | 4 | printf("%ldm%.3fs", static_cast<long>(seconds) / 60, fmod(seconds, 60)); |
230 | 4 | } |
231 | | |
232 | | // bash source: builtins/times.def |
233 | 1 | void PrintTimes() { |
234 | 1 | struct tms t; |
235 | 1 | if (times(&t) == -1) { |
236 | 0 | throw Alloc<IOError>(errno); |
237 | 0 | } |
238 | 1 | long ticks_per_sec = sysconf(_SC_CLK_TCK); |
239 | | |
240 | 1 | PrintClock(t.tms_utime, ticks_per_sec); |
241 | 1 | putc(' ', stdout); |
242 | 1 | PrintClock(t.tms_stime, ticks_per_sec); |
243 | 1 | putc('\n', stdout); |
244 | 1 | PrintClock(t.tms_cutime, ticks_per_sec); |
245 | 1 | putc(' ', stdout); |
246 | 1 | PrintClock(t.tms_cstime, ticks_per_sec); |
247 | 1 | putc('\n', stdout); |
248 | 1 | } |
249 | | |
250 | 0 | bool InputAvailable(int fd) { |
251 | 0 | fd_set fds; |
252 | 0 | FD_ZERO(&fds); |
253 | 0 | struct timeval timeout = {0}; // return immediately |
254 | 0 | FD_SET(fd, &fds); |
255 | 0 | return select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0; |
256 | 0 | } |
257 | | |
258 | 1 | IOError_OSError* FlushStdout() { |
259 | | // Flush libc buffers |
260 | 1 | if (::fflush(stdout) != 0) { |
261 | 0 | return Alloc<IOError>(errno); |
262 | 0 | } |
263 | 1 | return nullptr; |
264 | 1 | } |
265 | | |
266 | 1 | SignalSafe* InitSignalSafe() { |
267 | 1 | gSignalSafe = Alloc<SignalSafe>(); |
268 | 1 | gHeap.RootGlobalVar(gSignalSafe); |
269 | | |
270 | 1 | RegisterSignalInterest(SIGINT); // for KeyboardInterrupt checks |
271 | | |
272 | 1 | return gSignalSafe; |
273 | 1 | } |
274 | | |
275 | | // Note that the Python implementation of pyos.sigaction() calls |
276 | | // signal.signal(), which calls PyOS_setsig(), which calls sigaction() #ifdef |
277 | | // HAVE_SIGACTION. |
278 | 2 | void sigaction(int sig_num, void (*handler)(int)) { |
279 | | // SIGINT and SIGWINCH must be registered through SignalSafe |
280 | 2 | DCHECK(sig_num != SIGINT); |
281 | 2 | DCHECK(sig_num != SIGWINCH); |
282 | | |
283 | 0 | struct sigaction act = {}; |
284 | 2 | act.sa_handler = handler; |
285 | 2 | if (sigaction(sig_num, &act, nullptr) != 0) { |
286 | 0 | throw Alloc<OSError>(errno); |
287 | 0 | } |
288 | 2 | } |
289 | | |
290 | 4 | static void OurSignalHandler(int sig_num) { |
291 | 4 | assert(gSignalSafe != nullptr); |
292 | 0 | gSignalSafe->UpdateFromSignalHandler(sig_num); |
293 | 4 | } |
294 | | |
295 | 4 | void RegisterSignalInterest(int sig_num) { |
296 | 4 | struct sigaction act = {}; |
297 | 4 | act.sa_handler = OurSignalHandler; |
298 | 4 | if (sigaction(sig_num, &act, nullptr) != 0) { |
299 | 0 | throw Alloc<OSError>(errno); |
300 | 0 | } |
301 | 4 | } |
302 | | |
303 | 2 | Tuple2<BigStr*, int>* MakeDirCacheKey(BigStr* path) { |
304 | 2 | struct stat st; |
305 | 2 | if (::stat(path->data(), &st) == -1) { |
306 | 1 | throw Alloc<OSError>(errno); |
307 | 1 | } |
308 | | |
309 | 1 | return Alloc<Tuple2<BigStr*, int>>(path, st.st_mtime); |
310 | 2 | } |
311 | | |
312 | 0 | Tuple2<int, void*> PushTermAttrs(int fd, int mask) { |
313 | 0 | struct termios* term_attrs = |
314 | 0 | static_cast<struct termios*>(malloc(sizeof(struct termios))); |
315 | |
|
316 | 0 | if (tcgetattr(fd, term_attrs) < 0) { |
317 | 0 | throw Alloc<OSError>(errno); |
318 | 0 | } |
319 | | // Flip the bits in one field |
320 | 0 | int orig_local_modes = term_attrs->c_lflag; |
321 | 0 | term_attrs->c_lflag = orig_local_modes & mask; |
322 | |
|
323 | 0 | if (tcsetattr(fd, TCSANOW, term_attrs) < 0) { |
324 | 0 | throw Alloc<OSError>(errno); |
325 | 0 | } |
326 | | |
327 | 0 | return Tuple2<int, void*>(orig_local_modes, term_attrs); |
328 | 0 | } |
329 | | |
330 | 0 | void PopTermAttrs(int fd, int orig_local_modes, void* term_attrs) { |
331 | 0 | struct termios* t = static_cast<struct termios*>(term_attrs); |
332 | 0 | t->c_lflag = orig_local_modes; |
333 | 0 | if (tcsetattr(fd, TCSANOW, t) < 0) { |
334 | 0 | ; // Like Python, ignore error because of issue #1001 |
335 | 0 | } |
336 | 0 | } |
337 | | |
338 | | } // namespace pyos |
339 | | |
340 | | namespace pyutil { |
341 | | |
342 | 0 | double infinity() { |
343 | 0 | return INFINITY; // float.h |
344 | 0 | } |
345 | | |
346 | 0 | double nan() { |
347 | 0 | return NAN; // float.h |
348 | 0 | } |
349 | | |
350 | | // TODO: SHARE with pyext |
351 | 2 | bool IsValidCharEscape(BigStr* c) { |
352 | 2 | DCHECK(len(c) == 1); |
353 | | |
354 | 0 | int ch = c->data_[0]; |
355 | | |
356 | 2 | if (ch == '/' || ch == '.' || ch == '-') { |
357 | 0 | return false; |
358 | 0 | } |
359 | 2 | if (ch == ' ') { // foo\ bar is idiomatic |
360 | 0 | return true; |
361 | 0 | } |
362 | 2 | return ispunct(ch); |
363 | 2 | } |
364 | | |
365 | 3 | BigStr* ChArrayToString(List<int>* ch_array) { |
366 | 3 | int n = len(ch_array); |
367 | 3 | BigStr* result = NewStr(n); |
368 | 11 | for (int i = 0; i < n; ++i) { |
369 | 8 | result->data_[i] = ch_array->at(i); |
370 | 8 | } |
371 | 3 | result->data_[n] = '\0'; |
372 | 3 | return result; |
373 | 3 | } |
374 | | |
375 | 3 | BigStr* _ResourceLoader::Get(BigStr* path) { |
376 | 3 | TextFile* t = gEmbeddedFiles; // start of generated data |
377 | 6 | while (t->rel_path != nullptr) { |
378 | 5 | if (strcmp(t->rel_path, path->data_) == 0) { |
379 | 2 | return t->contents; |
380 | 2 | } |
381 | 3 | t++; |
382 | 3 | } |
383 | | // Emulate Python |
384 | 1 | throw Alloc<IOError>(ENOENT); |
385 | 3 | } |
386 | | |
387 | 1 | _ResourceLoader* GetResourceLoader() { |
388 | 1 | return Alloc<_ResourceLoader>(); |
389 | 1 | } |
390 | | |
391 | 1 | BigStr* GetVersion(_ResourceLoader* loader) { |
392 | 1 | return consts::gVersion; |
393 | 1 | } |
394 | | |
395 | 1 | void PrintVersionDetails(_ResourceLoader* loader) { |
396 | | // Invoked by core/util.py VersionFlag() |
397 | 1 | printf("git commit = %s\n", gCommitHash); |
398 | | |
399 | | // TODO: I would like the CPU, OS, compiler |
400 | | // How do we get those? Look at CPython |
401 | 1 | } |
402 | | |
403 | 2 | BigStr* BackslashEscape(BigStr* s, BigStr* meta_chars) { |
404 | 2 | int upper_bound = len(s) * 2; |
405 | 2 | BigStr* buf = OverAllocatedStr(upper_bound); |
406 | 2 | char* p = buf->data_; |
407 | | |
408 | 11 | for (int i = 0; i < len(s); ++i) { |
409 | 9 | char c = s->data_[i]; |
410 | 9 | if (memchr(meta_chars->data_, c, len(meta_chars))) { |
411 | 3 | *p++ = '\\'; |
412 | 3 | } |
413 | 9 | *p++ = c; |
414 | 9 | } |
415 | 2 | buf->MaybeShrink(p - buf->data_); |
416 | 2 | return buf; |
417 | 2 | } |
418 | | |
419 | 1 | BigStr* strerror(IOError_OSError* e) { |
420 | 1 | BigStr* s = StrFromC(::strerror(e->errno_)); |
421 | 1 | return s; |
422 | 1 | } |
423 | | |
424 | | static grammar::Grammar* gOilGrammar = nullptr; |
425 | | |
426 | 0 | grammar::Grammar* LoadYshGrammar(_ResourceLoader*) { |
427 | 0 | if (gOilGrammar != nullptr) { |
428 | 0 | return gOilGrammar; |
429 | 0 | } |
430 | | |
431 | 0 | gOilGrammar = Alloc<grammar::Grammar>(); |
432 | 0 | gHeap.RootGlobalVar(gOilGrammar); |
433 | 0 | return gOilGrammar; |
434 | 0 | } |
435 | | |
436 | | } // namespace pyutil |