Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/kernel/src/wasm/posix/unistd.ts
1068 views
1
import { notImplemented } from "./util";
2
import constants from "./constants";
3
import debug from "debug";
4
import { constants as wasi_constants } from "wasi-js";
5
6
const log = debug("posix:unistd");
7
8
export default function unistd(context) {
9
const { fs, os, process, recv, send, wasi, posix, memory, callWithString } =
10
context;
11
// TODO: this doesn't throw an error yet if the target filesystem isn't native.
12
function toNativeFd(fd: number): number {
13
// OBVIOUSLY -- these functions won't work if the target
14
// is in a wasi memfs, since the host posix libc knows nothing about that.
15
// We do translate file descriptors at least.
16
// Do we need to check and throw an error if target path isn't native?
17
// Of course, that will happen anyways since the syscall will immediately
18
// reject the invalid file descriptor anyways.
19
const x = wasi.FD_MAP.get(fd);
20
if (x == null) {
21
throw Error("invalid file descriptor");
22
}
23
return x.real;
24
}
25
26
// We use the rights from stdin and stdout when making
27
// a pipe. These can get closed after startup (e.g., in
28
// the test_subprocess.py cpython tests), so we have to
29
// make a copy here. This also avoids having to keep a data
30
// structure in sync with wasi-js.
31
const STDIN = wasi.FD_MAP.get(0);
32
const STDOUT = wasi.FD_MAP.get(1);
33
34
const unistd = {
35
chown: (pathPtr: number, uid: number, gid: number): -1 | 0 => {
36
const path = recv.string(pathPtr);
37
fs.chownSync(path, uid, gid);
38
return 0;
39
},
40
lchown: (pathPtr: number, uid: number, gid: number): -1 | 0 => {
41
const path = recv.string(pathPtr);
42
fs.lchownSync(path, uid, gid);
43
return 0;
44
},
45
46
// int fchown(int fd, uid_t owner, gid_t group);
47
_fchown: (fd: number, uid: number, gid: number): number => {
48
if (uid == 0 || gid == 0) {
49
// if either is 0 = root, we just do nothing.
50
// TODO: We really need to get rid of anything that involves uid/gid, which
51
// just doesn't make sense for the model of WASM.
52
return 0;
53
}
54
fs.fchownSync(toNativeFd(fd), uid, gid);
55
return 0;
56
},
57
58
getuid: () => process.getuid?.() ?? 0,
59
getgid: () => process.getgid?.() ?? 0,
60
_geteuid: () => process.geteuid?.() ?? 0,
61
getegid: () => process.getegid?.() ?? 0,
62
63
// int getgroups(int gidsetsize, gid_t grouplist[]);
64
// in WASI, "typedef unsigned gid_t"
65
getgroups: (gidsetsize, grouplistPtr): number => {
66
const groups = process.getgroups?.();
67
if (groups == null) {
68
return 0; // no groups
69
}
70
if (gidsetsize == 0) {
71
// yep, we end up computing getgroups twice, since the
72
// posix api is a bit awkward...
73
return groups.length;
74
}
75
const count = Math.min(groups.length, gidsetsize);
76
if (count == 0) {
77
return 0;
78
}
79
const view = new DataView(memory.buffer);
80
for (let i = 0; i < count; i++) {
81
view.setUint32(grouplistPtr + 4 * i, groups[i], true);
82
}
83
return count;
84
},
85
86
getpid: () => process.pid ?? 1,
87
88
getpgid: (pid: number): number => {
89
return posix.getpgid?.(pid) ?? 1;
90
},
91
92
// int setpgid(pid_t pid, pid_t pgid);
93
setpgid: (pid: number, pgid: number): number => {
94
if (posix.setpgid == null) {
95
notImplemented("setpgid");
96
}
97
posix.setpgid(pid, pgid);
98
return 0; // success
99
},
100
101
getpgrp: (): number => {
102
return posix.getpgrp?.() ?? 1;
103
},
104
105
nice: (incr: number) => {
106
const p = os.getPriority?.();
107
if (p != null) {
108
os.setPriority?.(p + incr);
109
}
110
},
111
// int getpriority(int which, id_t who);
112
getpriority: (which: number, who: number): number => {
113
if (os.getPriority == null) {
114
// environ with no info about processes (e.g., browser).
115
return 0;
116
}
117
if (which != 0) {
118
console.warn(
119
"getpriority can only be implemented in node.js for *process id*"
120
);
121
return 0; // minimal info.
122
}
123
return os.getPriority?.(who);
124
},
125
126
// int setpriority(int which, id_t who, int value);
127
setpriority: (which: number, who: number, value: number): number => {
128
if (os.setPriority == null) {
129
// environ with no info about processes (e.g., browser).
130
return 0;
131
}
132
if (which != 0) {
133
console.warn(
134
"setpriority can only be implemented in node.js for *process id*"
135
);
136
return -1;
137
}
138
return os.setPriority?.(who, value);
139
},
140
141
// int dup(int oldfd);
142
dup: (oldfd: number): number => {
143
if (posix.dup == null) {
144
notImplemented("dup");
145
}
146
// Considered in 2022, but closed by node developers: https://github.com/libuv/libuv/issues/3448#issuecomment-1174786218
147
148
const x = wasi.FD_MAP.get(oldfd);
149
const newfd_real = posix.dup(x.real);
150
const newfd = wasi.getUnusedFileDescriptor();
151
wasi.FD_MAP.set(newfd, { ...x, real: newfd_real });
152
return newfd;
153
},
154
155
// int dup2(int oldfd, int newfd);
156
dup2: (oldfd: number, newfd: number): number => {
157
if (posix.dup2 == null) {
158
notImplemented("dup2");
159
}
160
const x_old = wasi.FD_MAP.get(oldfd);
161
let x_new;
162
// I'm not 100% happy with this.
163
if (wasi.FD_MAP.has(newfd)) {
164
x_new = wasi.FD_MAP.get(newfd).real ?? newfd;
165
} else {
166
x_new = newfd;
167
}
168
169
const newfd_real = posix.dup2(x_old.real, x_new);
170
wasi.FD_MAP.set(newfd, { ...x_old, real: newfd_real });
171
return newfd;
172
},
173
174
sync: () => {
175
// nodejs doesn't expose sync, but it does expose fsync for a file descriptor, so we call it on
176
// all the open file descriptors
177
if (fs.fsyncSync == null) return;
178
for (const [_, { real }] of wasi.FD_MAP) {
179
fs.fsyncSync(real);
180
}
181
},
182
183
// In nodejs these set*id function can't be done in a worker thread:
184
// https://nodejs.org/api/process.html#processsetgidid
185
// TODO: maybe we should implement these by sending a message to
186
// the main thread requesting to do them? For now, you'll get
187
// an error unless you run in a mode without a worker thread.
188
setuid: () => {
189
throw Error("setuid is not supported");
190
},
191
seteuid: (uid: number) => {
192
if (posix.seteuid == null) {
193
notImplemented("seteuid");
194
}
195
posix.seteuid(uid);
196
return 0;
197
},
198
setegid: (gid: number) => {
199
if (posix.setegid == null) {
200
notImplemented("setegid");
201
}
202
posix.setegid(gid);
203
return 0;
204
},
205
setgid: (gid: number) => {
206
if (process.setgid == null) {
207
notImplemented("setgid");
208
}
209
process.setgid(gid);
210
return 0;
211
},
212
setsid: (sid) => {
213
if (posix.setsid == null) {
214
notImplemented("setsid");
215
}
216
return posix.setsid(sid);
217
},
218
// TODO!
219
getsid: () => {
220
notImplemented("getsid");
221
},
222
223
setreuid: (uid) => {
224
if (posix.setreuid == null) {
225
notImplemented("setreuid");
226
}
227
posix.setreuid(uid);
228
return 0;
229
},
230
setregid: (gid) => {
231
if (posix.setregid == null) {
232
notImplemented("setregid");
233
}
234
posix.setregid(gid);
235
return 0;
236
},
237
getppid: () => {
238
if (posix.getppid == null) {
239
// in browser -- only one process id:
240
return unistd.getpid();
241
}
242
return posix.getppid();
243
},
244
setgroups: () => {
245
notImplemented("setgroups");
246
},
247
248
setpgrp: () => {
249
notImplemented("setpgrp");
250
},
251
252
tcgetpgrp: () => {
253
notImplemented("tcgetpgrp");
254
},
255
256
tcsetpgrp: () => {
257
notImplemented("tcsetpgrp");
258
},
259
260
fork: () => {
261
if (posix.fork == null) {
262
notImplemented("fork");
263
}
264
const pid = posix.fork();
265
if (pid == 0) {
266
// we end the event loop in the child, because hopefully usually anything
267
// that is using fork is about to exec* anyways. It seems that trying
268
// to actually use the Node.js event loop after forking tends to randomly
269
// hang, so isn't really viable.
270
posix.close_event_loop?.();
271
}
272
return pid;
273
},
274
275
fork1: () => {
276
notImplemented("fork1");
277
},
278
279
vfork: () => {
280
// "this system call behaves identically to the fork(2) system call, except without
281
// calling any handlers registered with pthread_atfork(2)."
282
return unistd.fork();
283
},
284
285
forkpty: () => {
286
notImplemented("forkpty");
287
},
288
289
getlogin: (): number => {
290
if (context.state.getlogin_ptr != null) return context.state.getlogin_ptr;
291
// returns the username of the signed in user; if not available, e.g.,
292
// in a browser, returns "user".
293
const username = os.userInfo?.()?.username ?? "user";
294
return (context.state.getlogin_ptr = send.string(username));
295
},
296
297
// int gethostname(char *name, size_t len);
298
gethostname: (namePtr: number, len: number): number => {
299
if (os.hostname == null) {
300
throw Error("gethostname not supported on this platform");
301
}
302
const name = os.hostname();
303
send.string(name, { ptr: namePtr, len });
304
return 0;
305
},
306
307
// int sethostname(const char *name, size_t len);
308
sethostname: (namePtr: number, len: number): number => {
309
if (posix.sethostname == null) {
310
throw Error("sethostname not supported on this platform");
311
}
312
const name = recv.string(namePtr, len);
313
posix.sethostname(name);
314
return 0;
315
},
316
317
// char *ttyname(int fd);
318
ttyname: (fd: number): number => {
319
if (posix.ttyname == null) {
320
throw Error("ttyname_r is not supported on this platform");
321
}
322
if (context.state.ttyname_ptr != null) return context.state.ttyname_ptr;
323
const len = 128;
324
context.state.ttyname_ptr = send.malloc(len);
325
send.string(posix.ttyname(fd), { ptr: context.state.ttyname_ptr, len });
326
return context.state.ttyname_ptr;
327
},
328
// int ttyname_r(int fd, char *buf, size_t buflen);
329
ttyname_r: (fd: number, ptr: number, len: number): number => {
330
if (posix.ttyname == null) {
331
throw Error("ttyname_r is not supported on this platform");
332
}
333
send.string(posix.ttyname(fd), { ptr, len });
334
return 0;
335
},
336
337
alarm: (seconds: number): number => {
338
if (posix.alarm == null) {
339
throw Error("alarm is not supported on this platform");
340
}
341
return posix.alarm(seconds);
342
},
343
344
// The following 4 are actually only available on a Linux host,
345
// though wasi-musl defines them,
346
// so cpython-wasm thinks they exist.
347
// For CoWasm, let's just make these no-ops when not available,
348
// since they are about multiple users, which we shouldn't
349
// support in WASM.
350
getresuid: (ruidPtr: number, euidPtr: number, suidPtr: number): number => {
351
let ruid, euid, suid;
352
if (posix.getresuid == null) {
353
ruid = euid = suid = 0;
354
} else {
355
({ ruid, euid, suid } = posix.getresuid());
356
}
357
const view = new DataView(memory.buffer);
358
view.setUint32(ruidPtr, ruid, true);
359
view.setUint32(euidPtr, euid, true);
360
view.setUint32(suidPtr, suid, true);
361
return 0;
362
},
363
364
getresgid: (rgidPtr: number, egidPtr: number, sgidPtr: number): number => {
365
let rgid, egid, sgid;
366
if (posix.getresgid == null) {
367
rgid = egid = sgid = 0;
368
} else {
369
({ rgid, egid, sgid } = posix.getresgid());
370
}
371
const view = new DataView(memory.buffer);
372
view.setUint32(rgidPtr, rgid, true);
373
view.setUint32(egidPtr, egid, true);
374
view.setUint32(sgidPtr, sgid, true);
375
return 0;
376
},
377
378
setresuid: (ruid: number, euid: number, suid: number): number => {
379
if (posix.setresuid != null) {
380
posix.setresuid(ruid, euid, suid);
381
}
382
return 0;
383
},
384
385
setresgid: (rgid: number, egid: number, sgid: number): number => {
386
if (posix.setresgid != null) {
387
posix.setresgid(rgid, egid, sgid);
388
}
389
return 0;
390
},
391
392
// int execve(const char *pathname, char *const argv[], char *const envp[]);
393
execve: (pathnamePtr: number, argvPtr: number, envpPtr: number): number => {
394
if (posix._execve == null) {
395
notImplemented("execve");
396
}
397
const pathname = recv.string(pathnamePtr);
398
const argv = recv.arrayOfStrings(argvPtr);
399
const envp = recv.arrayOfStrings(envpPtr);
400
log("execve", pathname, argv, envp);
401
posix._execve(pathname, argv, envp);
402
return 0; // this won't happen because execve takes over, or there's an error
403
},
404
405
execv: (pathnamePtr: number, argvPtr: number): number => {
406
if (posix.execv == null) {
407
notImplemented("execv");
408
}
409
const pathname = recv.string(pathnamePtr);
410
const argv = recv.arrayOfStrings(argvPtr);
411
log("execv", pathname, argv);
412
posix.execv(pathname, argv);
413
return 0; // this won't happen because execv takes over
414
},
415
416
// execvp is like execv but takes the filename rather than the path.
417
// int execvp(const char *file, char *const argv[]);
418
execvp: (filePtr: number, argvPtr: number): number => {
419
if (posix.execvp == null) {
420
notImplemented("execvp");
421
}
422
const file = recv.string(filePtr);
423
const argv = recv.arrayOfStrings(argvPtr);
424
log("execvp", file, argv);
425
posix.execvp(file, argv);
426
return 0; // this won't happen because execvp takes over
427
},
428
429
// execlp is so far only by libedit to launch vim to edit
430
// the history. So it's safe to just disable. Python doesn't
431
// use this at all.
432
execlp: () => {
433
notImplemented("execlp");
434
},
435
436
/*
437
I don't have automated testing for this, since it quits node.
438
However, here is what works on Linux. There is no fexecve on macos.
439
>>> import os; a = os.open("/bin/ls",os.O_RDONLY | os.O_CREAT)
440
>>> os.execve(a,['-l','/'],{})
441
bin dev home media opt root sbin sys usr
442
boot etc lib mnt proc run srv tmp var
443
*/
444
fexecve: (fd: number, argvPtr: number, envpPtr: number): number => {
445
if (posix._fexecve == null) {
446
notImplemented("fexecve");
447
}
448
const argv = recv.arrayOfStrings(argvPtr);
449
const envp = recv.arrayOfStrings(envpPtr);
450
451
posix._fexecve(toNativeFd(fd), argv, envp);
452
return 0; // this won't happen because execve takes over
453
},
454
455
// int pipe(int pipefd[2]);
456
pipe: (pipefdPtr: number): number => {
457
if (posix.pipe == null) {
458
notImplemented("pipe");
459
}
460
const { readfd, writefd } = posix.pipe();
461
// readfd and writefd are genuine native file descriptors that we just created.
462
const wasi_readfd = wasi.getUnusedFileDescriptor();
463
wasi.FD_MAP.set(wasi_readfd, {
464
real: readfd,
465
rights: STDIN.rights, // just use rights for stdin
466
filetype: wasi_constants.WASI_FILETYPE_SOCKET_STREAM,
467
});
468
const wasi_writefd = wasi.getUnusedFileDescriptor();
469
wasi.FD_MAP.set(wasi_writefd, {
470
real: writefd,
471
rights: STDOUT.rights, // just use rights for stdout
472
filetype: wasi_constants.WASI_FILETYPE_SOCKET_STREAM,
473
});
474
475
send.i32(pipefdPtr, wasi_readfd);
476
send.i32(pipefdPtr + 4, wasi_writefd);
477
return 0;
478
},
479
480
pipe2: (pipefdPtr: number, flags: number): number => {
481
if (posix.pipe2 == null) {
482
notImplemented("pipe2");
483
}
484
let nativeFlags = 0;
485
if (flags & constants.O_NONBLOCK) {
486
nativeFlags += posix.constants?.O_NONBLOCK ?? 0;
487
}
488
// NOTE: wasi defined O_CLOEXEC to be 0, which is super annoying.
489
// We thus never set it, since otherwise it would always get set.
490
/* if (flags & constants.O_CLOEXEC) {
491
nativeFlags += posix.constants?.O_CLOEXEC ?? 0;
492
}*/
493
const { readfd, writefd } = posix.pipe2(nativeFlags);
494
console.warn(
495
"pipe2 -- TODO: we almost certainly need to abstract these through our WASI fd object!"
496
);
497
send.i32(pipefdPtr, readfd);
498
send.i32(pipefdPtr + 4, writefd);
499
return 0;
500
},
501
502
lockf: (fd: number, cmd: number, size: number): number => {
503
const { lockf } = posix;
504
if (lockf == null) {
505
notImplemented("lockf");
506
}
507
508
let cmdNative: number | undefined = undefined;
509
for (const x of ["F_ULOCK", "F_LOCK", "F_TLOCK", "F_TEST"]) {
510
if (cmd == constants[x]) {
511
cmdNative = posix.constants[x];
512
break;
513
}
514
}
515
if (cmdNative == null) {
516
throw Error(`invalid cmd ${cmd}`);
517
}
518
lockf(toNativeFd(fd), cmdNative, BigInt(size));
519
return 0;
520
},
521
522
pause: (): number => {
523
const { pause } = posix;
524
if (pause == null) {
525
// this could be implemented in case of worker
526
notImplemented("pause");
527
}
528
return pause();
529
},
530
531
// initgroups in node, so easier...
532
// int initgroups(const char *user, gid_t group);
533
initgroups: (userPtr: number, group: number): number => {
534
const { initgroups } = process;
535
if (initgroups == null) {
536
notImplemented("initgroups");
537
}
538
const user = recv.string(userPtr);
539
initgroups(user, group);
540
return 0;
541
},
542
543
// int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups);
544
getgrouplist: (
545
userPtr: number,
546
group: number,
547
groupPtr: number,
548
ngroupsPtr: number
549
): number => {
550
const { getgrouplist } = posix;
551
const user = recv.string(userPtr);
552
const ngroups = recv.i32(ngroupsPtr);
553
let v;
554
if (getgrouplist == null) {
555
v = [group];
556
} else {
557
v = getgrouplist(user, group);
558
}
559
const k = Math.min(v.length, ngroups);
560
for (let i = 0; i < k; i++) {
561
send.u32(groupPtr + 4 * i, v[i]);
562
}
563
send.i32(ngroupsPtr, v.length);
564
if (k < v.length) {
565
return -1;
566
}
567
return 0;
568
},
569
570
// just like chdir, but uses a file descriptor. WASI doesn't have it, so we
571
// add it.
572
fchdir: (fd: number): number => {
573
const dir = wasi.FD_MAP.get(fd)?.path;
574
if (!dir) {
575
console.error(`fchdir: invalid file descriptor: ${fd}`);
576
return -1;
577
}
578
return callWithString("chdir", dir);
579
},
580
581
// This is not a system call exactly. It's used by WASI.
582
// It is supposed to "Adjust the flags associated with a file descriptor."
583
// and it doesn't acctually just set them because WASI doesn't
584
// have a way to get. So what we do is change the three things
585
// that can be changed and leave everything else alone!
586
fcntlSetFlags: (fd: number, flags: number): number => {
587
if (posix.fcntlSetFlags == null || posix.fcntlGetFlags == null) {
588
notImplemented("fcntlSetFlags");
589
return 0;
590
}
591
const real_fd = wasi.FD_MAP.get(fd)?.real;
592
if (real_fd == null) {
593
throw Error("invalid file descriptor");
594
}
595
596
let current_native_flags = posix.fcntlGetFlags(real_fd);
597
let new_native_flags = current_native_flags;
598
for (const name of ["O_NONBLOCK", "O_APPEND"]) {
599
if (flags & constants[name]) {
600
// do want name
601
new_native_flags |= posix.constants[name];
602
} else {
603
// do not want name
604
new_native_flags &= ~posix.constants[name];
605
}
606
}
607
608
if (current_native_flags == new_native_flags) {
609
log("fcntlSetFlags - unchanged");
610
} else {
611
log("fcntlSetFlags ", current_native_flags, " to", new_native_flags);
612
posix.fcntlSetFlags(real_fd, new_native_flags);
613
}
614
return 0;
615
},
616
};
617
618
return unistd;
619
}
620
621