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

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