fcoreutils 0.22.0

High-performance GNU coreutils replacement with SIMD and parallelism
Documentation
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
; ============================================================
; fsync_unified.asm — sync: synchronize cached writes to persistent storage
; AUTO-GENERATED unified file — do not edit manually
; Build: nasm -f bin fsync_unified.asm -o fsync_release
; ============================================================

BITS 64
ORG 0x400000

; ── Linux x86_64 syscall numbers ───────────────────────────
%define SYS_READ        0
%define SYS_WRITE       1
%define SYS_OPEN        2
%define SYS_CLOSE       3
%define SYS_EXIT       60
%define SYS_SYNC       162
%define SYS_FSYNC      74
%define SYS_FDATASYNC  75
%define SYS_SYNCFS     306

%define O_RDONLY        0
%define STDIN           0
%define STDOUT          1
%define STDERR          2
%define EINTR           4
%define ENOENT          2
%define EACCES          13
%define ENOTDIR         20
%define EISDIR          21
%define ENOMEM          12
%define EMFILE          24
%define ELOOP           40
%define ENAMETOOLONG    36
%define EIO             5
%define EBADF           9
%define EINVAL          22

; ── ELF64 Header (64 bytes) ───────────────────────────────
ehdr:
    db 0x7f, 'E','L','F'       ; magic
    db 2                        ; 64-bit
    db 1                        ; little endian
    db 1                        ; ELF version
    db 0                        ; OS/ABI: System V
    dq 0                        ; padding
    dw 2                        ; ET_EXEC
    dw 0x3e                     ; x86_64
    dd 1                        ; ELF version
    dq _start                   ; entry point
    dq phdr - $$                ; program header offset
    dq 0                        ; section header offset (none)
    dd 0                        ; flags
    dw 64                       ; ELF header size
    dw 56                       ; program header entry size
    dw 2                        ; 2 program headers
    dw 64                       ; section header entry size
    dw 0                        ; section header count
    dw 0                        ; section name index

; ── Program Headers (2 x 56 bytes) ────────────────────────
phdr:
    ; PT_LOAD: code + rodata + BSS (RWX)
    dd 1                        ; PT_LOAD
    dd 7                        ; PF_R | PF_W | PF_X
    dq 0                        ; offset
    dq $$                       ; virtual address
    dq $$                       ; physical address
    dq file_size                ; file size
    dq file_size + bss_size     ; memory size (includes BSS)
    dq 0x200000                 ; alignment

    ; PT_GNU_STACK: non-executable stack
    dd 0x6474e551               ; PT_GNU_STACK
    dd 6                        ; PF_R | PF_W (no PF_X)
    dq 0
    dq 0
    dq 0
    dq 0
    dq 0
    dq 0x10                     ; alignment

; ============================================================
; Code section
; ============================================================

_start:
    ; Get argc and argv from stack
    mov     r14, [rsp]          ; argc
    lea     r15, [rsp + 8]      ; argv

    ; Initialize flags (in BSS area, already zero after load)
    mov     byte [flag_data], 0
    mov     byte [flag_filesystem], 0
    mov     qword [file_count], 0

    ; Parse arguments: skip argv[0], start from argv[1]
    mov     r12, 1              ; arg index
    xor     r13, r13            ; 0 = still parsing options, 1 = after --

.parse_loop:
    cmp     r12, r14
    jge     .parse_done

    mov     rbx, [r15 + r12 * 8]   ; argv[i]

    ; If we've seen --, treat everything as a file
    test    r13, r13
    jnz     .add_file

    ; Check first character
    cmp     byte [rbx], '-'
    jne     .add_file

    ; Check if it's just "-" (treat as file)
    cmp     byte [rbx + 1], 0
    je      .add_file

    ; Check if it starts with "--"
    cmp     byte [rbx + 1], '-'
    je      .long_opt

    ; It's a short option string like -d, -f, -df
    jmp     .short_opts

.long_opt:
    ; Check for "--" (end of options)
    cmp     byte [rbx + 2], 0
    jne     .check_long_opts
    mov     r13, 1              ; set after-- flag
    inc     r12
    jmp     .parse_loop

.check_long_opts:
    ; Check --help
    mov     rdi, rbx
    lea     rsi, [rel opt_help]
    call    _strcmp
    test    rax, rax
    jz      .do_help

    ; Check --version
    mov     rdi, rbx
    lea     rsi, [rel opt_version]
    call    _strcmp
    test    rax, rax
    jz      .do_version

    ; Check --data
    mov     rdi, rbx
    lea     rsi, [rel opt_data]
    call    _strcmp
    test    rax, rax
    jz      .set_data

    ; Check --file-system
    mov     rdi, rbx
    lea     rsi, [rel opt_filesystem]
    call    _strcmp
    test    rax, rax
    jz      .set_filesystem

    ; Unrecognized long option
    jmp     .err_unrecognized_opt

.set_data:
    mov     byte [flag_data], 1
    inc     r12
    jmp     .parse_loop

.set_filesystem:
    mov     byte [flag_filesystem], 1
    inc     r12
    jmp     .parse_loop

.short_opts:
    ; Parse each character in the short option string (skip '-')
    lea     rbp, [rbx + 1]     ; pointer to first char after '-'

.short_loop:
    movzx   eax, byte [rbp]
    test    al, al
    jz      .short_done

    cmp     al, 'd'
    je      .short_d
    cmp     al, 'f'
    je      .short_f

    ; Invalid short option
    jmp     .err_invalid_short

.short_d:
    mov     byte [flag_data], 1
    inc     rbp
    jmp     .short_loop

.short_f:
    mov     byte [flag_filesystem], 1
    inc     rbp
    jmp     .short_loop

.short_done:
    inc     r12
    jmp     .parse_loop

.add_file:
    ; Add file pointer to list
    mov     rcx, [file_count]
    cmp     rcx, 255
    jge     .next_arg
    mov     [file_ptrs + rcx * 8], rbx
    inc     qword [file_count]

.next_arg:
    inc     r12
    jmp     .parse_loop

; ── Argument parsing complete ──────────────────────────────

.parse_done:
    ; Check: cannot specify both --data and --file-system
    cmp     byte [flag_data], 0
    je      .no_both_check
    cmp     byte [flag_filesystem], 0
    je      .no_both_check
    ; Both flags set — error
    mov     rdi, STDERR
    lea     rsi, [rel err_prefix]
    mov     rdx, err_prefix_len
    call    _write
    mov     rdi, STDERR
    lea     rsi, [rel err_both]
    mov     rdx, err_both_len
    call    _write
    mov     rdi, 1
    jmp     _exit

.no_both_check:
    ; Check if we have files
    cmp     qword [file_count], 0
    jne     .sync_files

    ; No files — check if -d is set
    cmp     byte [flag_data], 0
    je      .sync_all

    ; -d without files: error
    mov     rdi, STDERR
    lea     rsi, [rel err_prefix]
    mov     rdx, err_prefix_len
    call    _write
    mov     rdi, STDERR
    lea     rsi, [rel err_data_needs]
    mov     rdx, err_data_needs_len
    call    _write
    mov     rdi, 1
    jmp     _exit

.sync_all:
    ; Call sync() syscall — sync all filesystems
    mov     rax, SYS_SYNC
    syscall
    ; Exit 0
    xor     rdi, rdi
    jmp     _exit

; ── Sync individual files ──────────────────────────────────

.sync_files:
    xor     r12, r12            ; file index
    xor     r13, r13            ; exit code (0 = success)

.file_loop:
    cmp     r12, [file_count]
    jge     .file_done

    ; Get file path
    mov     rbx, [file_ptrs + r12 * 8]

    ; open(path, O_RDONLY)
    mov     rax, SYS_OPEN
    mov     rdi, rbx
    xor     rsi, rsi            ; O_RDONLY
    xor     rdx, rdx
    syscall

    ; Check for error (negative return = error)
    test    rax, rax
    js      .open_error

    ; Save fd
    mov     rbp, rax            ; fd in rbp

    ; Determine which sync to call
    cmp     byte [flag_filesystem], 0
    jne     .do_syncfs
    cmp     byte [flag_data], 0
    jne     .do_fdatasync

    ; fsync(fd)
    mov     rax, SYS_FSYNC
    mov     rdi, rbp
    syscall
    jmp     .check_sync_result

.do_syncfs:
    mov     rax, SYS_SYNCFS
    mov     rdi, rbp
    syscall
    jmp     .check_sync_result

.do_fdatasync:
    mov     rax, SYS_FDATASYNC
    mov     rdi, rbp
    syscall

.check_sync_result:
    ; Save sync result
    mov     rcx, rax

    ; close(fd) regardless of sync result
    push    rcx
    mov     rax, SYS_CLOSE
    mov     rdi, rbp
    syscall
    pop     rcx

    ; Check sync result
    test    rcx, rcx
    js      .sync_error

    ; Success — next file
    inc     r12
    jmp     .file_loop

.open_error:
    ; rax = negative errno, rbx = filename
    neg     rax                 ; make errno positive
    push    rax                 ; save errno
    push    r12
    push    r13

    ; Print: "sync: error opening '"
    mov     rdi, STDERR
    lea     rsi, [rel err_prefix]
    mov     rdx, err_prefix_len
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel err_opening]
    mov     rdx, err_opening_len
    call    _write

    ; Print filename
    mov     rdi, rbx
    call    _strlen
    mov     rdx, rax
    mov     rdi, STDERR
    mov     rsi, rbx
    call    _write

    ; Print "': "
    mov     rdi, STDERR
    lea     rsi, [rel err_quote_colon]
    mov     rdx, err_quote_colon_len
    call    _write

    ; Print errno message
    pop     r13
    pop     r12
    pop     rax                 ; errno
    call    print_errno

    mov     r13, 1              ; set exit code to 1
    inc     r12
    jmp     .file_loop

.sync_error:
    ; rcx = negative errno, rbx = filename
    neg     rcx                 ; make errno positive
    push    rcx
    push    r12
    push    r13

    ; Print: "sync: error syncing '"
    mov     rdi, STDERR
    lea     rsi, [rel err_prefix]
    mov     rdx, err_prefix_len
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel err_syncing]
    mov     rdx, err_syncing_len
    call    _write

    ; Print filename
    mov     rdi, rbx
    call    _strlen
    mov     rdx, rax
    mov     rdi, STDERR
    mov     rsi, rbx
    call    _write

    ; Print "': "
    mov     rdi, STDERR
    lea     rsi, [rel err_quote_colon]
    mov     rdx, err_quote_colon_len
    call    _write

    ; Print errno message
    pop     r13
    pop     r12
    pop     rax                 ; errno
    call    print_errno

    mov     r13, 1              ; set exit code to 1
    inc     r12
    jmp     .file_loop

.file_done:
    mov     rdi, r13
    jmp     _exit

; ── --help ─────────────────────────────────────────────────

.do_help:
    mov     rdi, STDOUT
    lea     rsi, [rel str_help]
    mov     rdx, str_help_len
    call    _write
    xor     rdi, rdi
    jmp     _exit

; ── --version ──────────────────────────────────────────────

.do_version:
    mov     rdi, STDOUT
    lea     rsi, [rel str_version]
    mov     rdx, str_version_len
    call    _write
    xor     rdi, rdi
    jmp     _exit

; ── Error: unrecognized long option ────────────────────────

.err_unrecognized_opt:
    ; rbx = the option string
    push    rbx

    mov     rdi, STDERR
    lea     rsi, [rel err_prefix]
    mov     rdx, err_prefix_len
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel err_unrecognized]
    mov     rdx, err_unrecognized_len
    call    _write

    pop     rbx
    mov     rdi, rbx
    call    _strlen
    mov     rdx, rax
    mov     rdi, STDERR
    mov     rsi, rbx
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel char_quote]
    mov     rdx, 2              ; quote + newline
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel err_try_help]
    mov     rdx, err_try_help_len
    call    _write

    mov     rdi, 1
    jmp     _exit

; ── Error: invalid short option ────────────────────────────

.err_invalid_short:
    ; al = the invalid character
    push    rax

    mov     rdi, STDERR
    lea     rsi, [rel err_prefix]
    mov     rdx, err_prefix_len
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel err_invalid_opt]
    mov     rdx, err_invalid_opt_len
    call    _write

    pop     rax
    sub     rsp, 8
    mov     [rsp], al
    mov     rdi, STDERR
    mov     rsi, rsp
    mov     rdx, 1
    call    _write
    add     rsp, 8

    mov     rdi, STDERR
    lea     rsi, [rel char_quote]
    mov     rdx, 2              ; quote + newline
    call    _write

    mov     rdi, STDERR
    lea     rsi, [rel err_try_help]
    mov     rdx, err_try_help_len
    call    _write

    mov     rdi, 1
    jmp     _exit

; ── print_errno: print error string for errno in rax ───────

print_errno:
    cmp     rax, ENOENT
    je      .pe_enoent
    cmp     rax, EACCES
    je      .pe_eacces
    cmp     rax, ENOTDIR
    je      .pe_enotdir
    cmp     rax, EISDIR
    je      .pe_eisdir
    cmp     rax, ENOMEM
    je      .pe_enomem
    cmp     rax, EMFILE
    je      .pe_emfile
    cmp     rax, ELOOP
    je      .pe_eloop
    cmp     rax, ENAMETOOLONG
    je      .pe_enametoolong
    cmp     rax, EIO
    je      .pe_eio
    cmp     rax, EBADF
    je      .pe_ebadf
    cmp     rax, EINVAL
    je      .pe_einval
    mov     rdi, STDERR
    lea     rsi, [rel errno_unknown]
    mov     rdx, errno_unknown_len
    call    _write
    ret

.pe_enoent:
    lea     rsi, [rel errno_enoent]
    mov     rdx, errno_enoent_len
    jmp     .pe_print
.pe_eacces:
    lea     rsi, [rel errno_eacces]
    mov     rdx, errno_eacces_len
    jmp     .pe_print
.pe_enotdir:
    lea     rsi, [rel errno_enotdir]
    mov     rdx, errno_enotdir_len
    jmp     .pe_print
.pe_eisdir:
    lea     rsi, [rel errno_eisdir]
    mov     rdx, errno_eisdir_len
    jmp     .pe_print
.pe_enomem:
    lea     rsi, [rel errno_enomem]
    mov     rdx, errno_enomem_len
    jmp     .pe_print
.pe_emfile:
    lea     rsi, [rel errno_emfile]
    mov     rdx, errno_emfile_len
    jmp     .pe_print
.pe_eloop:
    lea     rsi, [rel errno_eloop]
    mov     rdx, errno_eloop_len
    jmp     .pe_print
.pe_enametoolong:
    lea     rsi, [rel errno_enametoolong]
    mov     rdx, errno_enametoolong_len
    jmp     .pe_print
.pe_eio:
    lea     rsi, [rel errno_eio]
    mov     rdx, errno_eio_len
    jmp     .pe_print
.pe_ebadf:
    lea     rsi, [rel errno_ebadf]
    mov     rdx, errno_ebadf_len
    jmp     .pe_print
.pe_einval:
    lea     rsi, [rel errno_einval]
    mov     rdx, errno_einval_len
.pe_print:
    mov     rdi, STDERR
    call    _write
    ret

; ── Inlined library functions ──────────────────────────────

; _write(rdi=fd, rsi=buf, rdx=len) -> rax=bytes or negative errno
; Retries on EINTR automatically
_write:
    mov     rax, SYS_WRITE
    syscall
    cmp     rax, -EINTR
    je      _write
    ret

; _exit(rdi=code) — never returns
_exit:
    mov     rax, SYS_EXIT
    syscall

; _strlen(rdi=str) -> rax=length
_strlen:
    xor     rax, rax
.sl_loop:
    cmp     byte [rdi + rax], 0
    je      .sl_done
    inc     rax
    jmp     .sl_loop
.sl_done:
    ret

; _strcmp(rdi=s1, rsi=s2) -> rax=0 if equal, nonzero otherwise
_strcmp:
    xor     rcx, rcx
.sc_loop:
    mov     al, [rdi + rcx]
    mov     dl, [rsi + rcx]
    cmp     al, dl
    jne     .sc_ne
    test    al, al
    jz      .sc_eq
    inc     rcx
    jmp     .sc_loop
.sc_eq:
    xor     rax, rax
    ret
.sc_ne:
    movzx   rax, al
    movzx   rdx, dl
    sub     rax, rdx
    ret

; ============================================================
; Read-only data
; ============================================================

; GNU-identical --help output
; @@DATA_START@@
str_help:
    db "Usage: sync [OPTION] [FILE]...", 10
    db "Synchronize cached writes to persistent storage", 10
    db 10
    db "If one or more files are specified, sync only them,", 10
    db "or their containing file systems.", 10
    db 10
    db "  -d, --data             sync only file data, no unneeded metadata", 10
    db "  -f, --file-system      sync the file systems that contain the files", 10
    db "      --help        display this help and exit", 10
    db "      --version     output version information and exit", 10
    db 10
    db "GNU coreutils online help: <https://www.gnu.org/software/coreutils/>", 10
    db "Full documentation <https://www.gnu.org/software/coreutils/sync>", 10
    db "or available locally via: info '(coreutils) sync invocation'", 10
str_help_end:
str_help_len equ str_help_end - str_help

; GNU-identical --version output
str_version:
    db "sync (GNU coreutils) 9.7", 10
    db "Packaged by Debian (9.7-3)", 10
    db "Copyright (C) 2025 Free Software Foundation, Inc.", 10
    db "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.", 10
    db "This is free software: you are free to change and redistribute it.", 10
    db "There is NO WARRANTY, to the extent permitted by law.", 10
    db 10
    db "Written by Jim Meyering and Giuseppe Scrivano.", 10
str_version_end:
str_version_len equ str_version_end - str_version

; Option strings
opt_help:       db "--help", 0
opt_version:    db "--version", 0
opt_data:       db "--data", 0
opt_filesystem: db "--file-system", 0

; Error message parts
err_prefix:     db "sync: ", 0
err_prefix_len equ 6

err_unrecognized: db "unrecognized option '", 0
err_unrecognized_len equ 21

err_invalid_opt:  db "invalid option -- '", 0
err_invalid_opt_len equ 19

err_try_help:   db "Try 'sync --help' for more information.", 10
err_try_help_len equ 40
; @@DATA_END@@

err_both:       db "cannot specify both --data and --file-system", 10
err_both_len equ 45

err_data_needs: db "--data needs at least one argument", 10
err_data_needs_len equ 35

err_opening:    db "error opening '", 0
err_opening_len equ 15

err_syncing:    db "error syncing '", 0
err_syncing_len equ 15

err_quote_colon: db "': ", 0
err_quote_colon_len equ 3

; Errno strings
errno_enoent:   db "No such file or directory", 10
errno_enoent_len equ 26
errno_eacces:   db "Permission denied", 10
errno_eacces_len equ 18
errno_enotdir:  db "Not a directory", 10
errno_enotdir_len equ 16
errno_eisdir:   db "Is a directory", 10
errno_eisdir_len equ 15
errno_enomem:   db "Cannot allocate memory", 10
errno_enomem_len equ 23
errno_emfile:   db "Too many open files", 10
errno_emfile_len equ 20
errno_eloop:    db "Too many levels of symbolic links", 10
errno_eloop_len equ 34
errno_enametoolong: db "File name too long", 10
errno_enametoolong_len equ 19
errno_eio:      db "Input/output error", 10
errno_eio_len equ 19
errno_ebadf:    db "Bad file descriptor", 10
errno_ebadf_len equ 20
errno_einval:   db "Invalid argument", 10
errno_einval_len equ 17
errno_unknown:  db "Unknown error", 10
errno_unknown_len equ 14

char_quote:     db "'"
char_newline:   db 10

; File size marker (everything above is in the file)
file_size equ $ - $$

; ============================================================
; BSS — virtual addresses after the file image (not in file)
; The PT_LOAD header's memsz > filesz causes the loader to
; zero-fill these addresses automatically.
; ============================================================
bss_base     equ $$ + file_size
file_ptrs    equ bss_base                      ; 256 * 8 = 2048 bytes
file_count   equ file_ptrs + (256 * 8)         ; 8 bytes
flag_data    equ file_count + 8                 ; 1 byte
flag_filesystem equ flag_data + 1               ; 1 byte
bss_size     equ (flag_filesystem + 1) - bss_base