RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / syscall_ext.rs
47814 bytes · raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
//! Stage 17: the "coreutils / busybox" batch of Linux system calls.
//!
//! syscall.rs grew big, so the calls added in stage 17 live here. The
//! dispatcher there tries its own table first and then asks `dispatch`
//! below; `name` gives `--trace` their names.
//!
//! | nr  | call                 | what it does here                                  |
//! |-----|----------------------|----------------------------------------------------|
//! | 86  | link                 | a second name for a file (ramfs, ext2)             |
//! | 265 | linkat               | same, AT_SYMLINK_FOLLOW / AT_EMPTY_PATH            |
//! | 88  | symlink              | a symbolic link (ramfs, ext2)                      |
//! | 266 | symlinkat            | same, relative to a directory fd                   |
//! | 92  | chown                | change owner/group (stored, not enforced)          |
//! | 93  | fchown               | same, by fd                                        |
//! | 94  | lchown               | same, the symlink itself                           |
//! | 260 | fchownat             | same, relative to a directory fd                   |
//! | 137 | statfs               | sizes and free space of a file system              |
//! | 138 | fstatfs              | same, by fd                                        |
//! | 332 | statx                | the newer, extensible stat                         |
//! | 99  | sysinfo              | Linux's: uptime, memory, process count             |
//! | 98  | getrusage            | CPU time used (self, children, thread)             |
//! | 100 | times                | CPU time in clock ticks (100 per second)           |
//! | 229 | clock_getres         | how fine each clock is                             |
//! | 96  | gettimeofday         | stage 19: wall clock in µs (vDSO fallback)         |
//! | 201 | time                 | stage 19: wall clock in seconds (vDSO fallback)    |
//! | 309 | getcpu               | stage 19: which CPU am I on? (vDSO fallback)       |
//! | 25  | mremap               | stage 19: grow/shrink/move an mmap region          |
//! | 54  | setsockopt           | SO_RCVTIMEO; SO_REUSEADDR & co. accepted          |
//! | 55  | getsockopt           | SO_ERROR, SO_TYPE, SO_RCVBUF/SNDBUF, ...           |
//! | 51  | getsockname          | our own address and port                           |
//! | 52  | getpeername          | the other end's address and port                   |
//! | 48  | shutdown             | close one direction of a connection                |
//! | 53  | socketpair           | AF_INET: EOPNOTSUPP (AF_UNIX: net/unix.rs, st. 18) |
//! | 37  | alarm                | SIGALRM in N seconds                               |
//! | 36  | getitimer            | the ITIMER_REAL timer                              |
//! | 38  | setitimer            | same, set (periodic too)                           |
//! | 222 | timer_create         | POSIX timers (timer.rs)                            |
//! | 223-226 | timer_settime/gettime/getoverrun/delete |                         |
//! | 102/104/107/108 | getuid/getgid/geteuid/getegid | the stored ids           |
//! | 105/106 | setuid/setgid    | change ids (root: any; others: their own)          |
//! | 113/114 | setreuid/setregid |                                                   |
//! | 117/119 | setresuid/setresgid |                                                 |
//! | 118/120 | getresuid/getresgid |                                                 |
//! | 122/123 | setfsuid/setfsgid |                                                   |
//! | 115/116 | getgroups/setgroups | supplementary groups                            |
//! | 140/141 | getpriority/setpriority | the nice value (stored only)                |
//! | 170/171 | sethostname/setdomainname | what uname reports                        |
//! | 73  | flock                | whole-file advisory locks                          |
//! | 295/296 | preadv/pwritev   | readv/writev at an offset                          |
//! | 40  | sendfile             | copy from one fd to another inside the kernel      |

use crate::errno::Errno;
use crate::fs::{self, Inode};
use crate::process::{self, Creds};
use crate::syscall::{
    at_base, copy_to_user, current, path_at, read_iovecs, task, user_cstr, user_path, user_path_at, user_slice,
    user_slice_mut, with_socket, write_sockaddr, AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, PATH_MAX,
    SOL_SOCKET, SO_RCVTIMEO,
};
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::sync::atomic::Ordering;
use spin::Mutex;

pub const SYS_SHUTDOWN: u64 = 48;
pub const SYS_GETSOCKNAME: u64 = 51;
pub const SYS_GETPEERNAME: u64 = 52;
pub const SYS_SOCKETPAIR: u64 = 53;
pub const SYS_SETSOCKOPT: u64 = 54;
pub const SYS_GETSOCKOPT: u64 = 55;
pub const SYS_GETITIMER: u64 = 36;
pub const SYS_ALARM: u64 = 37;
pub const SYS_SETITIMER: u64 = 38;
pub const SYS_SENDFILE: u64 = 40;
pub const SYS_FLOCK: u64 = 73;
pub const SYS_LINK: u64 = 86;
pub const SYS_SYMLINK: u64 = 88;
pub const SYS_CHOWN: u64 = 92;
pub const SYS_FCHOWN: u64 = 93;
pub const SYS_LCHOWN: u64 = 94;
pub const SYS_GETRUSAGE: u64 = 98;
pub const SYS_SYSINFO: u64 = 99;
pub const SYS_TIMES: u64 = 100;
pub const SYS_GETUID: u64 = 102;
pub const SYS_GETGID: u64 = 104;
pub const SYS_SETUID: u64 = 105;
pub const SYS_SETGID: u64 = 106;
pub const SYS_GETEUID: u64 = 107;
pub const SYS_GETEGID: u64 = 108;
pub const SYS_SETREUID: u64 = 113;
pub const SYS_SETREGID: u64 = 114;
pub const SYS_GETGROUPS: u64 = 115;
pub const SYS_SETGROUPS: u64 = 116;
pub const SYS_SETRESUID: u64 = 117;
pub const SYS_GETRESUID: u64 = 118;
pub const SYS_SETRESGID: u64 = 119;
pub const SYS_GETRESGID: u64 = 120;
pub const SYS_SETFSUID: u64 = 122;
pub const SYS_SETFSGID: u64 = 123;
pub const SYS_STATFS: u64 = 137;
pub const SYS_FSTATFS: u64 = 138;
pub const SYS_GETPRIORITY: u64 = 140;
pub const SYS_SETPRIORITY: u64 = 141;
pub const SYS_SETHOSTNAME: u64 = 170;
pub const SYS_SETDOMAINNAME: u64 = 171;
pub const SYS_TIMER_CREATE: u64 = 222;
pub const SYS_TIMER_SETTIME: u64 = 223;
pub const SYS_TIMER_GETTIME: u64 = 224;
pub const SYS_TIMER_GETOVERRUN: u64 = 225;
pub const SYS_TIMER_DELETE: u64 = 226;
pub const SYS_CLOCK_GETRES: u64 = 229;
pub const SYS_FCHOWNAT: u64 = 260;
pub const SYS_LINKAT: u64 = 265;
pub const SYS_SYMLINKAT: u64 = 266;
pub const SYS_PREADV: u64 = 295;
pub const SYS_PWRITEV: u64 = 296;
pub const SYS_STATX: u64 = 332;
// Stage 19: what the vDSO falls back on.
pub const SYS_GETTIMEOFDAY: u64 = 96;
pub const SYS_TIME: u64 = 201;
pub const SYS_GETCPU: u64 = 309;
pub const SYS_MREMAP: u64 = 25;

/// linkat: link what a symlink points to, not the symlink.
pub const AT_SYMLINK_FOLLOW: u64 = 0x400;
const AT_NO_AUTOMOUNT: u64 = 0x800;
const AT_STATX_SYNC_TYPE: u64 = 0x6000;

/// The stage 17 calls; None = not one of ours (the caller says ENOSYS).
pub fn dispatch(nr: u64, a: [u64; 6]) -> Option<Result<u64, Errno>> {
    let fdcwd = AT_FDCWD as u64;
    Some(match nr {
        // 1. Links.
        SYS_LINK => sys_linkat(fdcwd, a[0], fdcwd, a[1], 0),
        SYS_LINKAT => sys_linkat(a[0], a[1], a[2], a[3], a[4]),
        SYS_SYMLINK => sys_symlinkat(a[0], fdcwd, a[1]),
        SYS_SYMLINKAT => sys_symlinkat(a[0], a[1], a[2]),
        // 2. Sockets options, CPU times.
        SYS_SETSOCKOPT => sys_setsockopt(a[0], a[1], a[2], a[3], a[4]),
        SYS_TIMES => sys_times(a[0]),
        // 3. Owners.
        SYS_CHOWN => sys_fchownat(fdcwd, a[0], a[1], a[2], 0),
        SYS_LCHOWN => sys_fchownat(fdcwd, a[0], a[1], a[2], AT_SYMLINK_NOFOLLOW),
        SYS_FCHOWN => sys_fchown(a[0], a[1], a[2]),
        SYS_FCHOWNAT => sys_fchownat(a[0], a[1], a[2], a[3], a[4]),
        // 4. File systems, statx.
        SYS_STATFS => sys_statfs(a[0], a[1]),
        SYS_FSTATFS => sys_fstatfs(a[0], a[1]),
        SYS_STATX => sys_statx(a[0], a[1], a[2], a[3], a[4]),
        // 5. System information, usage, clocks.
        SYS_SYSINFO => sys_sysinfo(a[0]),
        SYS_GETRUSAGE => sys_getrusage(a[0] as i64, a[1]),
        SYS_CLOCK_GETRES => sys_clock_getres(a[0], a[1]),
        SYS_GETTIMEOFDAY => sys_gettimeofday(a[0], a[1]),
        SYS_TIME => sys_time(a[0]),
        SYS_GETCPU => sys_getcpu(a[0], a[1]),
        SYS_MREMAP => sys_mremap(a[0], a[1], a[2], a[3], a[4]),
        // 6. Sockets.
        SYS_GETSOCKOPT => sys_getsockopt(a[0], a[1], a[2], a[3], a[4]),
        SYS_GETSOCKNAME => with_socket(a[0], |s| Ok(s.local())).and_then(|addr| write_sockaddr(a[1], a[2], addr).map(|_| 0)),
        SYS_GETPEERNAME => with_socket(a[0], |s| s.peer().ok_or(Errno::ENOTCONN)).and_then(|addr| write_sockaddr(a[1], a[2], addr).map(|_| 0)),
        SYS_SHUTDOWN => with_socket(a[0], |s| s.shutdown(a[1])).map(|_| 0),
        SYS_SOCKETPAIR => sys_socketpair(a[0]),
        // 7. Timers.
        SYS_ALARM => sys_alarm(a[0]),
        SYS_GETITIMER => sys_getitimer(a[0], a[1]),
        SYS_SETITIMER => sys_setitimer(a[0], a[1], a[2]),
        SYS_TIMER_CREATE => sys_timer_create(a[0], a[1], a[2]),
        SYS_TIMER_SETTIME => sys_timer_settime(a[0], a[1], a[2], a[3]),
        SYS_TIMER_GETTIME => sys_timer_gettime(a[0], a[1]),
        SYS_TIMER_GETOVERRUN => current().and_then(|p| crate::timer::getoverrun(p.pid, a[0] as i32)),
        SYS_TIMER_DELETE => current().and_then(|p| crate::timer::delete(p.pid, a[0] as i32)).map(|_| 0),
        // 8. Ids, priority, host name.
        SYS_GETUID => creds().map(|c| c.ruid as u64),
        SYS_GETEUID => creds().map(|c| c.euid as u64),
        SYS_GETGID => creds().map(|c| c.rgid as u64),
        SYS_GETEGID => creds().map(|c| c.egid as u64),
        SYS_SETUID => set_id(false, a[0] as u32),
        SYS_SETGID => set_id(true, a[0] as u32),
        SYS_SETREUID => set_re(false, a[0] as u32, a[1] as u32),
        SYS_SETREGID => set_re(true, a[0] as u32, a[1] as u32),
        SYS_SETRESUID => set_res(false, [a[0] as u32, a[1] as u32, a[2] as u32]),
        SYS_SETRESGID => set_res(true, [a[0] as u32, a[1] as u32, a[2] as u32]),
        SYS_GETRESUID => get_res(false, [a[0], a[1], a[2]]),
        SYS_GETRESGID => get_res(true, [a[0], a[1], a[2]]),
        SYS_SETFSUID => set_fs(false, a[0] as u32),
        SYS_SETFSGID => set_fs(true, a[0] as u32),
        SYS_GETGROUPS => sys_getgroups(a[0] as i32 as i64, a[1]),
        SYS_SETGROUPS => sys_setgroups(a[0], a[1]),
        SYS_GETPRIORITY => sys_getpriority(a[0], a[1]),
        SYS_SETPRIORITY => sys_setpriority(a[0], a[1], a[2] as i32),
        SYS_SETHOSTNAME => set_name(&HOSTNAME, a[0], a[1]),
        SYS_SETDOMAINNAME => set_name(&DOMAINNAME, a[0], a[1]),
        // 9. The rest.
        SYS_FLOCK => sys_flock(a[0], a[1]),
        SYS_PREADV => sys_preadv(a[0], a[1], a[2], a[3], false),
        SYS_PWRITEV => sys_preadv(a[0], a[1], a[2], a[3], true),
        SYS_SENDFILE => sys_sendfile(a[0], a[1], a[2], a[3]),
        _ => return None,
    })
}

/// Names for `--trace`.
pub fn name(nr: u64) -> Option<&'static str> {
    Some(match nr {
        SYS_SHUTDOWN => "shutdown",
        SYS_GETSOCKNAME => "getsockname",
        SYS_GETPEERNAME => "getpeername",
        SYS_SOCKETPAIR => "socketpair",
        SYS_GETSOCKOPT => "getsockopt",
        SYS_GETITIMER => "getitimer",
        SYS_ALARM => "alarm",
        SYS_SETITIMER => "setitimer",
        SYS_SENDFILE => "sendfile",
        SYS_FLOCK => "flock",
        SYS_LINK => "link",
        SYS_SYMLINK => "symlink",
        SYS_CHOWN => "chown",
        SYS_FCHOWN => "fchown",
        SYS_LCHOWN => "lchown",
        SYS_GETRUSAGE => "getrusage",
        SYS_SYSINFO => "sysinfo",
        SYS_TIMES => "times",
        SYS_SETUID => "setuid",
        SYS_SETGID => "setgid",
        SYS_SETREUID => "setreuid",
        SYS_SETREGID => "setregid",
        SYS_GETGROUPS => "getgroups",
        SYS_SETGROUPS => "setgroups",
        SYS_SETRESUID => "setresuid",
        SYS_GETRESUID => "getresuid",
        SYS_SETRESGID => "setresgid",
        SYS_GETRESGID => "getresgid",
        SYS_SETFSUID => "setfsuid",
        SYS_SETFSGID => "setfsgid",
        SYS_STATFS => "statfs",
        SYS_FSTATFS => "fstatfs",
        SYS_GETPRIORITY => "getpriority",
        SYS_SETPRIORITY => "setpriority",
        SYS_SETHOSTNAME => "sethostname",
        SYS_SETDOMAINNAME => "setdomainname",
        SYS_TIMER_CREATE => "timer_create",
        SYS_TIMER_SETTIME => "timer_settime",
        SYS_TIMER_GETTIME => "timer_gettime",
        SYS_TIMER_GETOVERRUN => "timer_getoverrun",
        SYS_TIMER_DELETE => "timer_delete",
        SYS_CLOCK_GETRES => "clock_getres",
        SYS_GETTIMEOFDAY => "gettimeofday",
        SYS_TIME => "time",
        SYS_GETCPU => "getcpu",
        SYS_MREMAP => "mremap",
        SYS_FCHOWNAT => "fchownat",
        SYS_LINKAT => "linkat",
        SYS_SYMLINKAT => "symlinkat",
        SYS_PREADV => "preadv",
        SYS_PWRITEV => "pwritev",
        SYS_STATX => "statx",
        _ => return None,
    })
}

/// Write `bytes` (a C struct we built) to user memory.
fn put(ptr: u64, bytes: &[u8]) -> Result<(), Errno> {
    user_slice_mut(ptr, bytes.len() as u64)?.copy_from_slice(bytes);
    Ok(())
}

/// A little-endian struct builder: `at(offset, value)`.
struct Buf(Vec<u8>);
impl Buf {
    fn new(len: usize) -> Buf {
        Buf(alloc::vec![0; len])
    }
    fn u64(&mut self, off: usize, v: u64) -> &mut Buf {
        self.0[off..off + 8].copy_from_slice(&v.to_le_bytes());
        self
    }
    fn u32(&mut self, off: usize, v: u32) -> &mut Buf {
        self.0[off..off + 4].copy_from_slice(&v.to_le_bytes());
        self
    }
    fn u16(&mut self, off: usize, v: u16) -> &mut Buf {
        self.0[off..off + 2].copy_from_slice(&v.to_le_bytes());
        self
    }
}

fn read_u64(ptr: u64, i: u64) -> Result<u64, Errno> {
    Ok(u64::from_le_bytes(user_slice(ptr + 8 * i, 8)?.try_into().unwrap()))
}

// --- 1. link / symlink ------------------------------------------------------

/// link / linkat(olddirfd, old, newdirfd, new, flags). Like Linux, a
/// symlink is linked itself unless AT_SYMLINK_FOLLOW; AT_EMPTY_PATH with
/// "" links the file open on `olddirfd`. Directories: EPERM. FAT, exFAT
/// (no hard links on disk): EPERM; btrfs (read-only): EROFS.
fn sys_linkat(olddirfd: u64, old_ptr: u64, newdirfd: u64, new_ptr: u64, flags: u64) -> Result<u64, Errno> {
    if flags & !(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH) != 0 {
        return Err(Errno::EINVAL);
    }
    let raw = user_cstr(old_ptr, PATH_MAX - 1)?;
    let new = user_path_at(newdirfd, new_ptr)?;
    if raw.is_empty() && flags & AT_EMPTY_PATH != 0 {
        let file = current()?.file(olddirfd)?;
        let (inode, path) = file.vfs_inode().zip(file.path()).ok_or(Errno::ENOENT)?;
        fs::link_inode(&inode, &path, &new)?;
        return Ok(0);
    }
    let old = path_at(olddirfd, &raw)?;
    fs::link(&old, &new, flags & AT_SYMLINK_FOLLOW != 0)?;
    Ok(0)
}

/// symlink / symlinkat(target, newdirfd, linkpath). The target is stored
/// exactly as given -- it may be relative, or point nowhere yet.
fn sys_symlinkat(target_ptr: u64, newdirfd: u64, path_ptr: u64) -> Result<u64, Errno> {
    let raw = user_cstr(target_ptr, PATH_MAX - 1)?;
    let target = core::str::from_utf8(&raw).map_err(|_| Errno::EINVAL)?;
    let path = user_path_at(newdirfd, path_ptr)?;
    let link = fs::symlink(target, &path)?;
    set_creator(&*link);
    Ok(0)
}

/// A new file, directory or symlink belongs to whoever made it (its
/// fsuid/fsgid). Root's files are owned by 0:0 already, so only other
/// users cost a write; where owners can't be stored (FAT), nothing happens.
pub fn set_creator(inode: &dyn Inode) {
    if let Ok(c) = creds() {
        if c.fsuid != 0 || c.fsgid != 0 {
            let _ = inode.set_owner(Some(c.fsuid), Some(c.fsgid));
        }
    }
}

// --- 2. setsockopt, times(), CPU clocks -----------------------------------

const SO_REUSEADDR: u64 = 2;
const SO_TYPE: u64 = 3;
const SO_ERROR: u64 = 4;
const SO_SNDBUF: u64 = 7;
const SO_RCVBUF: u64 = 8;
const SO_KEEPALIVE: u64 = 9;
const SO_REUSEPORT: u64 = 15;
const SO_SNDTIMEO: u64 = 21;
const SO_ACCEPTCONN: u64 = 30;
const SO_PROTOCOL: u64 = 38;
const SO_DOMAIN: u64 = 39;
const IPPROTO_TCP: u64 = 6;
const TCP_NODELAY: u64 = 1;
/// Where TCP_NODELAY is remembered among the socket's flag bits.
const NODELAY_BIT: u32 = 31;

/// setsockopt. SO_RCVTIMEO really works (recv/accept time out). The rest
/// that servers routinely set are accepted and remembered, but change
/// nothing: SO_REUSEADDR/SO_REUSEPORT (smoltcp lets a port be reused at
/// once anyway), SO_KEEPALIVE (no keepalive probes), SO_SNDBUF/SO_RCVBUF
/// (fixed buffers), SO_SNDTIMEO (sends don't block for long), TCP_NODELAY.
/// Anything else: ENOPROTOOPT, like Linux for an unknown option.
fn sys_setsockopt(fd: u64, level: u64, opt: u64, value: u64, len: u64) -> Result<u64, Errno> {
    let int = || -> Result<u32, Errno> {
        if len < 4 {
            return Err(Errno::EINVAL);
        }
        Ok(u32::from_le_bytes(user_slice(value, 4)?.try_into().unwrap()))
    };
    match (level, opt) {
        (SOL_SOCKET, SO_RCVTIMEO) => {
            if len < 16 {
                return Err(Errno::EINVAL);
            }
            let (secs, usecs) = (read_u64(value, 0)?, read_u64(value, 1)?);
            let total = secs.saturating_mul(1_000_000).saturating_add(usecs);
            with_socket(fd, |s| Ok(s.set_recv_timeout(if total == 0 { None } else { Some(total) })))?;
        }
        (SOL_SOCKET, SO_SNDTIMEO) => {
            if len < 16 {
                return Err(Errno::EINVAL);
            }
            with_socket(fd, |_| Ok(()))?;
        }
        (SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT | SO_KEEPALIVE) => {
            let on = int()? != 0;
            with_socket(fd, |s| Ok(s.set_flag(opt as u32, on)))?;
        }
        (SOL_SOCKET, SO_SNDBUF | SO_RCVBUF) => {
            int()?;
            with_socket(fd, |_| Ok(()))?;
        }
        (IPPROTO_TCP, TCP_NODELAY) => {
            let on = int()? != 0;
            with_socket(fd, |s| {
                if s.kind != crate::net::socket::Kind::Tcp {
                    return Err(Errno::EOPNOTSUPP);
                }
                Ok(s.set_flag(NODELAY_BIT, on))
            })?;
        }
        _ => {
            with_socket(fd, |_| Ok(()))?; // ENOTSOCK / EBADF first, like Linux
            return Err(Errno::ENOPROTOOPT);
        }
    }
    Ok(0)
}

/// getsockopt(fd, level, opt, *value, *len).
fn sys_getsockopt(fd: u64, level: u64, opt: u64, value: u64, len_ptr: u64) -> Result<u64, Errno> {
    use crate::net::socket::Kind;
    let room = u32::from_le_bytes(user_slice(len_ptr, 4)?.try_into().unwrap()) as i32;
    if room < 0 {
        return Err(Errno::EINVAL);
    }
    let answer: Vec<u8> = with_socket(fd, |s| {
        let int = |v: u32| v.to_le_bytes().to_vec();
        Ok(match (level, opt) {
            (SOL_SOCKET, SO_TYPE) => int(if s.kind == Kind::Tcp { 1 } else { 2 }), // SOCK_STREAM / SOCK_DGRAM
            (SOL_SOCKET, SO_ERROR) => int(s.take_error() as u32),
            (SOL_SOCKET, SO_SNDBUF | SO_RCVBUF) => int(s.buffer_size() as u32),
            (SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT | SO_KEEPALIVE) => int(s.flag(opt as u32) as u32),
            (SOL_SOCKET, SO_ACCEPTCONN) => int(s.listening() as u32),
            (SOL_SOCKET, SO_DOMAIN) => int(2), // AF_INET
            (SOL_SOCKET, SO_PROTOCOL) => int(match s.kind {
                Kind::Tcp => 6,
                Kind::Udp => 17,
                Kind::Icmp => 1,
            }),
            (SOL_SOCKET, SO_RCVTIMEO | SO_SNDTIMEO) => {
                let us = if opt == SO_RCVTIMEO { s.recv_timeout_us().unwrap_or(0) } else { 0 };
                let mut b = (us / 1_000_000).to_le_bytes().to_vec();
                b.extend_from_slice(&(us % 1_000_000).to_le_bytes());
                b
            }
            (IPPROTO_TCP, TCP_NODELAY) if s.kind == Kind::Tcp => int(s.flag(NODELAY_BIT) as u32),
            _ => return Err(Errno::ENOPROTOOPT),
        })
    })?;
    let n = answer.len().min(room as usize);
    put(value, &answer[..n])?;
    copy_to_user(len_ptr, &(n as u32))?;
    Ok(0)
}

/// socketpair: Linux makes these with AF_UNIX sockets, which Rustbelt
/// doesn't have yet (deferred). We say so honestly: EAFNOSUPPORT for
/// AF_UNIX, and EOPNOTSUPP for AF_INET -- as Linux does.
fn sys_socketpair(domain: u64) -> Result<u64, Errno> {
    match domain {
        2 => Err(Errno::EOPNOTSUPP),
        _ => Err(Errno::EAFNOSUPPORT),
    }
}

/// The CPU clocks of clock_gettime (2 = this process, 3 = this thread),
/// in microseconds. Counted in 10 ms timer ticks (see process::CpuTimes),
/// so a program that ran for less than a tick reads 0.
pub fn cpu_clock_us(clock: u64) -> Result<u64, Errno> {
    let ticks = match clock {
        2 => {
            let p = current()?;
            p.cpu.user.load(Ordering::Relaxed) + p.cpu.system.load(Ordering::Relaxed)
        }
        3 => task()?.cpu_ticks.load(Ordering::Relaxed),
        _ => return Err(Errno::EINVAL),
    };
    Ok(ticks * process::CPU_TICK_US)
}

/// times(*tms): CPU time in clock ticks (sysconf(_SC_CLK_TCK) = 100 per
/// second, which is exactly our 10 ms timer tick): user, system, and the
/// same for reaped children. Returns ticks since boot.
fn sys_times(buf: u64) -> Result<u64, Errno> {
    let p = current()?;
    if buf != 0 {
        let c = &p.cpu;
        let t = [c.user.load(Ordering::Relaxed), c.system.load(Ordering::Relaxed), c.child_user.load(Ordering::Relaxed), c.child_system.load(Ordering::Relaxed)];
        copy_to_user(buf, &t)?;
    }
    Ok(crate::time::now_us() / process::CPU_TICK_US)
}

/// clock_getres(clock, *res): our clocks count microseconds; the CPU
/// clocks move in 10 ms ticks.
fn sys_clock_getres(clock: u64, res: u64) -> Result<u64, Errno> {
    let ns = match clock {
        0 | 1 | 4 | 5 | 6 | 7 => 1, // stage 19: nanoseconds (was microseconds)
        2 | 3 => process::CPU_TICK_US * 1000,
        _ => return Err(Errno::EINVAL),
    };
    if res != 0 {
        copy_to_user(res, &[0u64, ns])?;
    }
    Ok(0)
}

/// gettimeofday(*tv, *tz) (stage 19; the vDSO normally answers it): the
/// wall clock in seconds + microseconds. The time zone is always UTC.
fn sys_gettimeofday(tv: u64, tz: u64) -> Result<u64, Errno> {
    let us = crate::time::realtime_us();
    if tv != 0 {
        copy_to_user(tv, &[us / 1_000_000, us % 1_000_000])?;
    }
    if tz != 0 {
        copy_to_user(tz, &[0u32, 0u32])?;
    }
    Ok(0)
}

/// time(*t) (stage 19): Unix time in whole seconds, also stored at `t`.
fn sys_time(t: u64) -> Result<u64, Errno> {
    let secs = crate::time::realtime_ns() / 1_000_000_000;
    if t != 0 {
        copy_to_user(t, &[secs])?;
    }
    Ok(secs)
}

/// getcpu(*cpu, *node, unused) (stage 19): which CPU we're running on
/// (sched_getcpu() in C). One NUMA node, 0.
fn sys_getcpu(cpu: u64, node: u64) -> Result<u64, Errno> {
    let id = crate::smp::cpu_id() as u32;
    if cpu != 0 {
        copy_to_user(cpu, &[id])?;
    }
    if node != 0 {
        copy_to_user(node, &[0u32])?;
    }
    Ok(0)
}

/// mremap(old, old_len, new_len, flags, new_addr) (stage 19): make an
/// mmap region bigger or smaller. realloc() of a big block uses it, so the
/// bytes don't have to be copied. Flags: MREMAP_MAYMOVE (1) lets the kernel
/// move it if it can't grow where it is; MREMAP_FIXED (2, needs MAYMOVE)
/// moves it to `new_addr`. MREMAP_DONTUNMAP (4) and old_len 0 (a second
/// copy of a shared mapping) aren't supported: EINVAL. The real work is
/// Mm::remap in mm.rs.
fn sys_mremap(old: u64, old_len: u64, new_len: u64, flags: u64, new_addr: u64) -> Result<u64, Errno> {
    const MAYMOVE: u64 = 1;
    const FIXED: u64 = 2;
    let fixed = flags & FIXED != 0;
    if old % 4096 != 0 || flags & !(MAYMOVE | FIXED) != 0 || (fixed && flags & MAYMOVE == 0) {
        return Err(Errno::EINVAL);
    }
    let old_len = old_len.next_multiple_of(4096);
    if old_len == 0 || new_len == 0 || new_len > crate::memory::USER_END {
        return Err(Errno::EINVAL);
    }
    old.checked_add(old_len).filter(|&e| e <= crate::memory::USER_END).ok_or(Errno::EFAULT)?;
    current()?.mm().remap(old, old_len, new_len, flags & MAYMOVE != 0, fixed.then_some(new_addr))
}

/// getrusage(who, *rusage): 0 = RUSAGE_SELF, -1 = RUSAGE_CHILDREN (reaped
/// ones), 1 = RUSAGE_THREAD. Fills the two CPU times and ru_maxrss (our
/// resident size now, in KiB); the other 14 counters stay 0.
fn sys_getrusage(who: i64, buf: u64) -> Result<u64, Errno> {
    let p = current()?;
    let c = &p.cpu;
    let (user, system) = match who {
        0 => (c.user.load(Ordering::Relaxed), c.system.load(Ordering::Relaxed)),
        -1 => (c.child_user.load(Ordering::Relaxed), c.child_system.load(Ordering::Relaxed)),
        1 => {
            // Per thread we only count ticks, not user/system: say "user".
            (task()?.cpu_ticks.load(Ordering::Relaxed), 0)
        }
        _ => return Err(Errno::EINVAL),
    };
    let tv = |ticks: u64| {
        let us = ticks * process::CPU_TICK_US;
        (us / 1_000_000, us % 1_000_000)
    };
    let mut b = Buf::new(144);
    let (us, uu) = tv(user);
    let (ss, su) = tv(system);
    b.u64(0, us).u64(8, uu).u64(16, ss).u64(24, su);
    if who != -1 {
        let resident = p.mm().usage().0;
        b.u64(32, resident * 4);
    }
    put(buf, &b.0)?;
    Ok(0)
}

// --- 3. chown family -------------------------------------------------------

/// -1 (as a 32-bit id) = "leave this one alone".
fn id_arg(v: u64) -> Option<u32> {
    let v = v as u32;
    (v != u32::MAX).then_some(v)
}

/// Change an inode's owner. Rustbelt stores owners (ramfs, ext2) but
/// doesn't check permissions, so anyone may chown anything. FAT and exFAT
/// can't store an owner: asking for the owner they already show (root) is
/// fine, anything else is EPERM, like Linux's vfat. btrfs (read-only): EROFS.
fn chown_inode(inode: &dyn Inode, uid: u64, gid: u64) -> Result<u64, Errno> {
    let (uid, gid) = (id_arg(uid), id_arg(gid));
    match inode.set_owner(uid, gid) {
        Err(Errno::EPERM) => {
            let (u, g) = inode.owner();
            if uid.unwrap_or(u) == u && gid.unwrap_or(g) == g {
                Ok(0)
            } else {
                Err(Errno::EPERM)
            }
        }
        other => other.map(|_| 0),
    }
}

fn sys_fchownat(dirfd: u64, path_ptr: u64, uid: u64, gid: u64, flags: u64) -> Result<u64, Errno> {
    if flags & !(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH) != 0 {
        return Err(Errno::EINVAL);
    }
    let raw = user_cstr(path_ptr, PATH_MAX - 1)?;
    if raw.is_empty() && flags & AT_EMPTY_PATH != 0 {
        if dirfd as i32 as i64 == AT_FDCWD {
            let cwd = at_base(dirfd)?;
            return chown_inode(&*fs::lookup(&cwd)?, uid, gid);
        }
        return sys_fchown(dirfd, uid, gid);
    }
    let path = path_at(dirfd, &raw)?;
    let inode = if flags & AT_SYMLINK_NOFOLLOW != 0 { fs::lookup_nofollow(&path)? } else { fs::lookup(&path)? };
    chown_inode(&*inode, uid, gid)
}

fn sys_fchown(fd: u64, uid: u64, gid: u64) -> Result<u64, Errno> {
    let file = current()?.file(fd)?;
    match file.vfs_inode() {
        Some(inode) => chown_inode(&*inode, uid, gid),
        // Pipes, sockets, terminals: nothing on disk to own. Linux keeps
        // an owner in their in-memory inode; we accept and forget it.
        None => Ok(0),
    }
}

// --- 4. statfs / fstatfs / statx ----------------------------------------

const PIPEFS_MAGIC: u64 = 0x5049_5045;
const SOCKFS_MAGIC: u64 = 0x534F_434B;
const ST_RDONLY: u64 = 1;
const ST_VALID: u64 = 0x20;

/// Linux's `struct statfs` (120 bytes).
fn write_statfs(buf: u64, st: &fs::StatFs, dev: u64) -> Result<u64, Errno> {
    let mut b = Buf::new(120);
    b.u64(0, st.magic)
        .u64(8, st.block_size)
        .u64(16, st.blocks)
        .u64(24, st.free_blocks)
        .u64(32, st.avail_blocks)
        .u64(40, st.files)
        .u64(48, st.free_files)
        .u32(56, dev as u32) // f_fsid: which mount, like st_dev
        .u64(64, st.name_max)
        .u64(72, st.block_size) // f_frsize
        .u64(80, ST_VALID | if st.read_only { ST_RDONLY } else { 0 });
    put(buf, &b.0)?;
    Ok(0)
}

fn statfs_path(path: &str, buf: u64) -> Result<u64, Errno> {
    let (index, fs) = fs::fs_of(path).ok_or(Errno::ENOENT)?;
    write_statfs(buf, &fs.statfs(), index as u64 + 1)
}

fn sys_statfs(path_ptr: u64, buf: u64) -> Result<u64, Errno> {
    let path = user_path(path_ptr)?;
    fs::lookup(&path)?; // ENOENT for a missing path
    statfs_path(&path, buf)
}

fn sys_fstatfs(fd: u64, buf: u64) -> Result<u64, Errno> {
    let file = current()?.file(fd)?;
    if let Some(path) = file.path() {
        return statfs_path(&path, buf);
    }
    let magic = match file.stat()?.kind {
        fs::file::KIND_PIPE => PIPEFS_MAGIC,
        fs::file::KIND_SOCKET => SOCKFS_MAGIC,
        _ => fs::TMPFS_MAGIC, // terminals, epoll: in-memory things
    };
    let st = fs::StatFs { magic, block_size: 4096, name_max: 255, ..Default::default() };
    write_statfs(buf, &st, 0)
}

/// statx(dirfd, path, flags, mask, *statx): the same facts as stat, in
/// the newer layout (256 bytes). We fill all the basic fields (mask
/// STATX_BASIC_STATS = 0x7ff) whatever `mask` asks; no birth time.
fn sys_statx(dirfd: u64, path_ptr: u64, flags: u64, mask: u64, buf: u64) -> Result<u64, Errno> {
    if flags & !(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE) != 0
        || flags & AT_STATX_SYNC_TYPE == AT_STATX_SYNC_TYPE
        || mask & 0x8000_0000 != 0
    {
        return Err(Errno::EINVAL);
    }
    let st = crate::syscall::stat_at(dirfd, path_ptr, flags & (AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))?;
    let mut b = Buf::new(256);
    b.u32(0, 0x7ff)
        .u32(4, st.st_blksize as u32)
        .u32(16, st.st_nlink as u32)
        .u32(20, st.st_uid)
        .u32(24, st.st_gid)
        .u16(28, st.st_mode as u16)
        .u64(32, st.st_ino)
        .u64(40, st.st_size as u64)
        .u64(48, st.st_blocks as u64);
    for (off, t) in [(64, st.st_atime), (96, st.st_ctime), (112, st.st_mtime)] {
        b.u64(off, t[0] as u64).u32(off + 8, t[1] as u32);
    }
    b.u32(128, fs::file::dev_major(st.st_rdev)).u32(132, fs::file::dev_minor(st.st_rdev));
    b.u32(136, 0).u32(140, st.st_dev as u32); // device: major 0, minor = mount
    put(buf, &b.0)?;
    Ok(0)
}

// --- 5. sysinfo -------------------------------------------------------------

/// sysinfo (99): Linux's `struct sysinfo` (112 bytes), what `free`,
/// `uptime` and `top` read. (Rustbelt's own, richer sysinfo is 502.)
/// The load averages are how busy the CPUs have been since boot, the
/// same number three times: we don't keep 1/5/15-minute averages.
fn sys_sysinfo(buf: u64) -> Result<u64, Errno> {
    let (total, free) = crate::memory::frame_totals();
    let cpus = crate::smp::cpu_count();
    let (mut ticks, mut busy) = (0, 0);
    for cpu in 0..cpus {
        let (t, b) = crate::thread::cpu_ticks(cpu);
        ticks += t;
        busy += b;
    }
    // Load = busy CPUs on average, in 16.16 fixed point.
    let load = if ticks == 0 { 0 } else { busy * cpus as u64 * 65536 / ticks };
    let mut b = Buf::new(112);
    b.u64(0, crate::time::now_us() / 1_000_000)
        .u64(8, load)
        .u64(16, load)
        .u64(24, load)
        .u64(32, total as u64 * 4096)
        .u64(40, free as u64 * 4096)
        .u16(80, process::count().min(u16::MAX as usize) as u16)
        .u32(104, 1); // mem_unit: sizes are in bytes
    put(buf, &b.0)?;
    Ok(0)
}

// --- 7. Timers -------------------------------------------------------------

fn tv_us(ptr: u64) -> Result<(u64, u64), Errno> {
    // struct itimerval { timeval it_interval; timeval it_value; }
    let (is, iu, vs, vu) = (read_u64(ptr, 0)?, read_u64(ptr, 1)?, read_u64(ptr, 2)?, read_u64(ptr, 3)?);
    if iu >= 1_000_000 || vu >= 1_000_000 || (is as i64) < 0 || (vs as i64) < 0 {
        return Err(Errno::EINVAL);
    }
    Ok((vs.saturating_mul(1_000_000).saturating_add(vu), is.saturating_mul(1_000_000).saturating_add(iu)))
}

fn put_itimerval(ptr: u64, (value, interval): (u64, u64)) -> Result<(), Errno> {
    copy_to_user(ptr, &[interval / 1_000_000, interval % 1_000_000, value / 1_000_000, value % 1_000_000])
}

/// alarm(seconds): SIGALRM that many seconds from now (0 cancels).
/// Returns the seconds left on the previous alarm (rounded, never 0 if
/// one was pending).
fn sys_alarm(secs: u64) -> Result<u64, Errno> {
    let pid = current()?.pid;
    let (old, _) = crate::timer::set_itimer(pid, (secs as u32 as u64) * 1_000_000, 0);
    Ok(if old == 0 { 0 } else { ((old + 500_000) / 1_000_000).max(1) })
}

/// ITIMER_REAL (0) only: ITIMER_VIRTUAL and ITIMER_PROF (CPU-time timers)
/// are EINVAL here.
fn sys_getitimer(which: u64, ptr: u64) -> Result<u64, Errno> {
    if which != 0 {
        return Err(Errno::EINVAL);
    }
    put_itimerval(ptr, crate::timer::get_itimer(current()?.pid))?;
    Ok(0)
}

fn sys_setitimer(which: u64, new: u64, old: u64) -> Result<u64, Errno> {
    if which != 0 {
        return Err(Errno::EINVAL);
    }
    let (value, interval) = if new == 0 { (0, 0) } else { tv_us(new)? };
    let prev = crate::timer::set_itimer(current()?.pid, value, interval);
    if old != 0 {
        put_itimerval(old, prev)?;
    }
    Ok(0)
}

/// A timespec as microseconds, rounded up (a timer never fires early).
fn ts_us(ptr: u64, i: u64) -> Result<u64, Errno> {
    let (s, ns) = (read_u64(ptr, 2 * i)?, read_u64(ptr, 2 * i + 1)?);
    if ns >= 1_000_000_000 || (s as i64) < 0 {
        return Err(Errno::EINVAL);
    }
    Ok(s.saturating_mul(1_000_000).saturating_add(ns.div_ceil(1000)))
}

fn put_itimerspec(ptr: u64, (value, interval): (u64, u64)) -> Result<(), Errno> {
    copy_to_user(ptr, &[interval / 1_000_000, interval % 1_000_000 * 1000, value / 1_000_000, value % 1_000_000 * 1000])
}

/// timer_create(clock, *sigevent, *timerid). Clocks: REALTIME, MONOTONIC,
/// BOOTTIME. Notification: SIGEV_SIGNAL, SIGEV_NONE, SIGEV_THREAD_ID.
/// (SIGEV_THREAD is the C library's job: it asks us for SIGEV_THREAD_ID.)
fn sys_timer_create(clock: u64, sev_ptr: u64, id_ptr: u64) -> Result<u64, Errno> {
    use crate::timer::{SigEvent, CLOCK_BOOTTIME, CLOCK_MONOTONIC, CLOCK_REALTIME, SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD_ID};
    if ![CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_BOOTTIME].contains(&clock) {
        return Err(Errno::EINVAL);
    }
    let p = current()?;
    let ev = if sev_ptr == 0 {
        SigEvent { notify: SIGEV_SIGNAL, signo: crate::signal::SIGALRM, tid: 0, sigval: None }
    } else {
        // struct sigevent { sigval; int signo; int notify; int tid; ... }
        let raw = user_slice(sev_ptr, 20)?;
        let sigval = u64::from_le_bytes(raw[..8].try_into().unwrap());
        let signo = u32::from_le_bytes(raw[8..12].try_into().unwrap());
        let notify = i32::from_le_bytes(raw[12..16].try_into().unwrap());
        let tid = u32::from_le_bytes(raw[16..20].try_into().unwrap()) as u64;
        match notify {
            SIGEV_NONE => {}
            SIGEV_SIGNAL | SIGEV_THREAD_ID if (1..=64).contains(&signo) => {}
            _ => return Err(Errno::EINVAL),
        }
        if notify == SIGEV_THREAD_ID && !p.tasks().iter().any(|t| t.tid() == tid) {
            return Err(Errno::EINVAL);
        }
        SigEvent { notify, signo, tid, sigval: Some(sigval) }
    };
    user_slice_mut(id_ptr, 4)?; // check before making it
    let id = crate::timer::create(p.pid, clock, ev)?;
    copy_to_user(id_ptr, &id)?;
    Ok(0)
}

/// timer_settime(id, flags, *new, *old). TIMER_ABSTIME (1): the value is a
/// time on the timer's clock.
fn sys_timer_settime(id: u64, flags: u64, new: u64, old: u64) -> Result<u64, Errno> {
    if flags & !1 != 0 {
        return Err(Errno::EINVAL);
    }
    let (interval, value) = (ts_us(new, 0)?, ts_us(new, 1)?);
    let prev = crate::timer::settime(current()?.pid, id as i32, flags & 1 != 0, value, interval)?;
    if old != 0 {
        put_itimerspec(old, prev)?;
    }
    Ok(0)
}

fn sys_timer_gettime(id: u64, ptr: u64) -> Result<u64, Errno> {
    let now = crate::timer::gettime(current()?.pid, id as i32)?;
    put_itimerspec(ptr, now)?;
    Ok(0)
}

// --- 8. Ids, priority, host name -----------------------------------------

fn creds() -> Result<Creds, Errno> {
    Ok(*current()?.creds.lock())
}

/// Change the calling process's creds with `f` (on a copy, kept if Ok).
fn change(f: impl FnOnce(&mut Creds) -> Result<u64, Errno>) -> Result<u64, Errno> {
    let p = current()?;
    let mut creds = p.creds.lock();
    let mut new = *creds;
    let r = f(&mut new)?;
    *creds = new;
    Ok(r)
}

/// The (real, effective, saved, fs) ids of `c`, users or groups.
fn ids(c: &mut Creds, group: bool) -> [&mut u32; 4] {
    if group {
        [&mut c.rgid, &mut c.egid, &mut c.sgid, &mut c.fsgid]
    } else {
        [&mut c.ruid, &mut c.euid, &mut c.suid, &mut c.fsuid]
    }
}

/// setuid / setgid: root (euid 0) sets all of them; anyone else may only
/// switch the effective id to their real or saved one.
fn set_id(group: bool, id: u32) -> Result<u64, Errno> {
    if id == u32::MAX {
        return Err(Errno::EINVAL);
    }
    change(|c| {
        let root = c.euid == 0;
        let [r, e, s, f] = ids(c, group);
        if root {
            (*r, *e, *s, *f) = (id, id, id, id);
        } else if id == *r || id == *s {
            (*e, *f) = (id, id);
        } else {
            return Err(Errno::EPERM);
        }
        Ok(0)
    })
}

/// setreuid / setregid(real, effective), -1 = unchanged.
fn set_re(group: bool, real: u32, eff: u32) -> Result<u64, Errno> {
    change(|c| {
        let root = c.euid == 0;
        let [r, e, s, f] = ids(c, group);
        let (old_r, old_e, old_s) = (*r, *e, *s);
        if real != u32::MAX && !root && real != old_r && real != old_e {
            return Err(Errno::EPERM);
        }
        if eff != u32::MAX && !root && eff != old_r && eff != old_e && eff != old_s {
            return Err(Errno::EPERM);
        }
        if real != u32::MAX {
            *r = real;
        }
        if eff != u32::MAX {
            *e = eff;
        }
        // Linux: the saved id follows the effective one when the real id
        // was set, or the effective one changed to something but the old real.
        if real != u32::MAX || (eff != u32::MAX && eff != old_r) {
            *s = *e;
        }
        *f = *e;
        Ok(0)
    })
}

/// setresuid / setresgid(real, effective, saved), -1 = unchanged. Not
/// root: each new value must be one of the current three.
fn set_res(group: bool, new: [u32; 3]) -> Result<u64, Errno> {
    change(|c| {
        let root = c.euid == 0;
        let [r, e, s, f] = ids(c, group);
        let old = [*r, *e, *s];
        if !root && new.iter().any(|&v| v != u32::MAX && !old.contains(&v)) {
            return Err(Errno::EPERM);
        }
        for (slot, v) in [&mut *r, &mut *e, &mut *s].into_iter().zip(new) {
            if v != u32::MAX {
                *slot = v;
            }
        }
        *f = *e;
        Ok(0)
    })
}

fn get_res(group: bool, ptrs: [u64; 3]) -> Result<u64, Errno> {
    let mut c = creds()?;
    let [r, e, s, _] = ids(&mut c, group);
    for (ptr, v) in ptrs.into_iter().zip([*r, *e, *s]) {
        copy_to_user(ptr, &v)?;
    }
    Ok(0)
}

/// setfsuid / setfsgid: returns the old fs id; changes it if allowed
/// (root, or one of the current ids). Never fails, like Linux.
fn set_fs(group: bool, id: u32) -> Result<u64, Errno> {
    let p = current()?;
    let mut creds = p.creds.lock();
    let root = creds.euid == 0;
    let [r, e, s, f] = ids(&mut creds, group);
    let old = *f;
    if id != u32::MAX && (root || [*r, *e, *s, *f].contains(&id)) {
        *f = id;
    }
    Ok(old as u64)
}

/// getgroups(size, *list): size 0 = just say how many.
fn sys_getgroups(size: i64, list: u64) -> Result<u64, Errno> {
    let c = creds()?;
    if size < 0 {
        return Err(Errno::EINVAL);
    }
    if size == 0 {
        return Ok(c.ngroups as u64);
    }
    if (size as usize) < c.ngroups {
        return Err(Errno::EINVAL);
    }
    for (i, g) in c.groups[..c.ngroups].iter().enumerate() {
        copy_to_user(list + 4 * i as u64, g)?;
    }
    Ok(c.ngroups as u64)
}

/// setgroups(size, *list): root only.
fn sys_setgroups(size: u64, list: u64) -> Result<u64, Errno> {
    if size > process::NGROUPS_MAX as u64 {
        return Err(Errno::EINVAL);
    }
    let raw = user_slice(list, 4 * size)?;
    change(|c| {
        if c.euid != 0 {
            return Err(Errno::EPERM);
        }
        for (i, g) in raw.chunks(4).enumerate() {
            c.groups[i] = u32::from_le_bytes(g.try_into().unwrap());
        }
        c.ngroups = size as usize;
        Ok(0)
    })
}

/// The processes get/setpriority mean: PRIO_PROCESS (0) one pid, PRIO_PGRP
/// (1) a process group, PRIO_USER (2) everything of one real uid. 0 = mine.
fn prio_targets(which: u64, who: u64) -> Result<Vec<Arc<process::Process>>, Errno> {
    let me = current()?;
    let who = who as u32 as u64;
    let list: Vec<_> = match which {
        0 => {
            let pid = if who == 0 { me.pid } else { who };
            process::get(pid).into_iter().collect()
        }
        1 => {
            let pgid = if who == 0 { me.pgid() } else { who };
            process::all().into_iter().filter(|p| p.pgid() == pgid).collect()
        }
        2 => {
            let uid = if who == 0 { me.creds.lock().ruid as u64 } else { who };
            process::all().into_iter().filter(|p| p.creds.lock().ruid as u64 == uid).collect()
        }
        _ => return Err(Errno::EINVAL),
    };
    if list.is_empty() {
        return Err(Errno::ESRCH);
    }
    Ok(list)
}

/// getpriority(which, who). The raw syscall returns 20 - nice (1..40) so
/// that it's never negative; the C library turns it back into the nice value.
fn sys_getpriority(which: u64, who: u64) -> Result<u64, Errno> {
    let best = prio_targets(which, who)?.iter().map(|p| p.nice.load(Ordering::Relaxed)).min().unwrap();
    Ok((20 - best) as u64)
}

/// setpriority(which, who, nice): stored (and inherited), but the
/// scheduler doesn't use it yet. Only root may lower a nice value.
fn sys_setpriority(which: u64, who: u64, nice: i32) -> Result<u64, Errno> {
    let nice = nice.clamp(-20, 19);
    let root = creds()?.euid == 0;
    for p in prio_targets(which, who)? {
        if nice < p.nice.load(Ordering::Relaxed) && !root {
            return Err(Errno::EACCES);
        }
        p.nice.store(nice, Ordering::Relaxed);
    }
    Ok(0)
}

static HOSTNAME: Mutex<Option<String>> = Mutex::new(None);
static DOMAINNAME: Mutex<Option<String>> = Mutex::new(None);

/// The host name `uname` reports ("qemu" until sethostname).
pub fn hostname() -> String {
    HOSTNAME.lock().clone().unwrap_or_else(|| String::from("qemu"))
}

/// The NIS domain name `uname` reports ("(none)" until setdomainname).
pub fn domainname() -> String {
    DOMAINNAME.lock().clone().unwrap_or_else(|| String::from("(none)"))
}

/// sethostname / setdomainname(name, len): at most 64 bytes; root only.
fn set_name(slot: &Mutex<Option<String>>, ptr: u64, len: u64) -> Result<u64, Errno> {
    if len > 64 {
        return Err(Errno::EINVAL);
    }
    if creds()?.euid != 0 {
        return Err(Errno::EPERM);
    }
    let raw = user_slice(ptr, len)?;
    let name = core::str::from_utf8(raw).map_err(|_| Errno::EINVAL)?;
    if name.contains('\0') {
        return Err(Errno::EINVAL);
    }
    *slot.lock() = Some(String::from(name));
    Ok(0)
}

// --- 9. flock, preadv/pwritev, sendfile -------------------------------------

const LOCK_SH: u64 = 1;
const LOCK_EX: u64 = 2;
const LOCK_NB: u64 = 4;
const LOCK_UN: u64 = 8;

/// flock(fd, op): a lock on the whole file, owned by the *open file*
/// (shared by dup'ed and inherited fds; released when its last fd
/// closes). LOCK_SH (shared) / LOCK_EX (exclusive) / LOCK_UN, | LOCK_NB =
/// EAGAIN instead of waiting. Pipes and sockets: nobody else can name
/// them, so there's never a conflict and we just say yes.
fn sys_flock(fd: u64, op: u64) -> Result<u64, Errno> {
    let file = current()?.file(fd)?;
    let cmd = op & !LOCK_NB;
    if ![LOCK_SH, LOCK_EX, LOCK_UN].contains(&cmd) {
        return Err(Errno::EINVAL);
    }
    let (Some(path), Some(inode)) = (file.path(), file.vfs_inode()) else { return Ok(0) };
    // The open file (an InodeFile) is the owner: the Arc's data address.
    let owner = Arc::as_ptr(&file) as *const u8 as usize;
    if cmd == LOCK_UN {
        fs::lock::flock_release(owner);
        return Ok(0);
    }
    // Same file = same key, also through another hard link.
    let key = match inode.ino() {
        Some(ino) => fs::file::ino_hash(alloc::format!("{}:{}", fs::dev_of(&path), ino).as_bytes()),
        None => fs::file::ino_hash(path.as_bytes()),
    };
    drop(file); // don't keep the file open while we sleep
    fs::lock::flock(key, owner, cmd == LOCK_EX, op & LOCK_NB == 0)?;
    Ok(0)
}

/// preadv / pwritev(fd, iov, count, offset): readv/writev at an offset
/// (the file offset doesn't move). Buffer by buffer; stops at a short one.
fn sys_preadv(fd: u64, iov: u64, count: u64, offset: u64, write: bool) -> Result<u64, Errno> {
    if (offset as i64) < 0 {
        return Err(Errno::EINVAL);
    }
    let file = current()?.file(fd)?;
    let iovs = read_iovecs(iov, count)?;
    let mut done = 0u64;
    for (base, len) in iovs {
        let n = if write {
            file.write_at(user_slice(base, len)?, offset + done)
        } else {
            file.read_at(user_slice_mut(base, len)?, offset + done)
        };
        let n = match n {
            Ok(n) => n as u64,
            Err(e) if done == 0 => return Err(e),
            Err(_) => break,
        };
        done += n;
        if n < len {
            break;
        }
    }
    Ok(done)
}

/// sendfile(out_fd, in_fd, *offset, count): copy from a file to any fd
/// (a socket, a pipe, another file) without a trip through user memory.
/// With `offset`, read from there and update it (in_fd's offset stays);
/// without, from in_fd's offset, which moves.
fn sys_sendfile(out_fd: u64, in_fd: u64, off_ptr: u64, count: u64) -> Result<u64, Errno> {
    let p = current()?;
    let (out, input) = (p.file(out_fd)?, p.file(in_fd)?);
    drop(p);
    let mut pos = if off_ptr != 0 {
        let v = read_u64(off_ptr, 0)?;
        if (v as i64) < 0 {
            return Err(Errno::EINVAL);
        }
        v
    } else {
        input.seek(0, 1).map_err(|_| Errno::EINVAL)? // SEEK_CUR
    };
    let count = count.min(0x7fff_f000);
    let mut buf = alloc::vec![0u8; 64 * 1024];
    let mut done = 0u64;
    while done < count {
        let want = (count - done).min(buf.len() as u64) as usize;
        let n = match input.read_at(&mut buf[..want], pos) {
            Ok(n) => n,
            Err(Errno::ESPIPE) => return Err(Errno::EINVAL), // in_fd must be a file
            Err(e) if done == 0 => return Err(e),
            Err(_) => break,
        };
        if n == 0 {
            break; // end of file
        }
        let mut written = 0;
        while written < n {
            match out.write(&buf[written..n]) {
                Ok(0) => break,
                Ok(k) => written += k,
                Err(e) if done == 0 && written == 0 => return Err(e),
                Err(_) => break,
            }
        }
        done += written as u64;
        pos += written as u64;
        if written < n {
            break;
        }
    }
    if off_ptr != 0 {
        copy_to_user(off_ptr, &pos)?;
    } else {
        input.seek(pos as i64, 0)?; // SEEK_SET
    }
    Ok(done)
}