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