4 poin oleh GN⁺ 2023-09-10 | 1 komentar | Bagikan ke WhatsApp
  • Proyek penjelasan jangka panjang yang membaca struktur source GNU coreutils 8.3 per utilitas, ditujukan untuk programmer pemula yang ingin belajar desain alat baris perintah
  • Ini bukan dokumentasi penggunaan, melainkan materi pendamping untuk membaca source code, yang juga membahas namespace tiap perintah, alur eksekusi, keputusan desain, algoritme, serta silsilah UNIX dan Coreutils awal
  • Karena coreutils berisi banyak program C kecil dengan satu tujuan, pola kode yang kasar dari sudut pandang modern seperti variabel global, makro, goto, dan fungsi panjang perlu dibaca dalam konteks sejarahnya
  • Alur umumnya berlanjut dari inisialisasi, parsing opsi, pemrosesan input, eksekusi system call, hingga penanganan kegagalan, dengan getopt_long(), fts, dan helper gnulib yang berulang kali muncul
  • Sebelum berkontribusi atau mengubah kode, perlu lebih dulu memastikan apakah hal itu bisa direproduksi dengan alat yang sudah ada, apakah akan merusak kompatibilitas mundur, dan seberapa jauh menyimpang dari POSIX

Cakupan proyek penjelasan GNU coreutils

  • Ini adalah proyek jangka panjang yang menjelaskan semua utilitas di GNU coreutils 8.3
  • Pembaca yang dituju adalah programmer pemula yang ingin mempelajari desain utilitas baris perintah
  • Materi ini paling cocok dibaca sebagai penjelasan latar saat menelaah source code utilitas yang diminati
  • Materi ini bukan manual pengguna; untuk penggunaan yang sebenarnya, lihat man pages
  • Proyek ini terdiri dari beberapa tahap
    • Phase 1: halaman namespace dan ringkasan eksekusi untuk tiap utilitas telah selesai
    • Phase 2: keputusan desain utama dan algoritme, silsilah UNIX dan Coreutils awal, walkthrough source yang lebih berguna, serta visualisasi evolusi source code telah selesai
    • Phase indefinite: walkthrough kode baris per baris untuk tiap utilitas masih menjadi pekerjaan jangka panjang
    • Tersedia GitHub repo untuk mengumpulkan catatan per baris

Halaman penjelasan per utilitas

  • Nama setiap perintah terhubung ke halaman detail yang menguraikan utilitas tersebut
  • Setiap halaman mencakup pembahasan, source code, dan walkthrough
  • Utilitas yang ditandai tebal adalah entri yang diperluas pada Phase 2
  • Daftar ini mencakup perintah GNU coreutils seperti arch, base64, cat, chmod, cp, date, dd, df, ls, rm, sort, tail, tr, wc, yes, dan lainnya

Hal-hal yang perlu diketahui sebelum membaca kode

  • Banyak utilitas coreutils memiliki sejarah hampir 30 tahun, dan merupakan kode yang telah dimodifikasi banyak orang selama waktu yang panjang
  • Sebagian besar adalah program kecil yang dirancang untuk satu tujuan
    • Umumnya berupa program dengan satu file source
    • Bukan kode yang sejak awal dirancang untuk bertahan lama atau berkembang jauh melampaui peran awalnya
    • Terlihat pola seperti variabel global, makro, goto, dan fungsi panjang dengan switch/loop bertingkat
  • Pengetahuan POSIX penting untuk memahami kode
    • Sebaiknya mulai dari Utility Syntax Guidelines
    • POSIX mendefinisikan interoperabilitas input dan output, tetapi cara kerja aktual diserahkan pada implementasi
    • GNU coreutils mungkin tidak mengikuti POSIX secara ketat, tetapi konsep seperti bit izin, uid/gid, variabel lingkungan, dan status keluar sangat tertanam di dalamnya
  • Karena masalah portabilitas, coreutils sangat bergantung pada gnulib
    • Hampir semua utilitas menyertakan fungsi gnulib yang dirancang untuk menangani masalah umum di berbagai sistem
  • coreutils berjalan dengan asumsi adanya dukungan shell seperti bash, zsh, dan ksh
    • Shell menjalankan utilitas lewat fork/clone, meneruskan argumen, menyiapkan environment, mengalihkan I/O dengan pipe, dan mempertahankan nilai keluar
  • GNU coreutils awalnya berasal dari tiga paket: utilitas shell, teks, dan file, sehingga utilitas sejenis banyak berbagi pola desain

Pola desain dasar

  • Sebagian besar utilitas CLI pada umumnya memiliki tiga tahap
    • Tahap konfigurasi untuk menyiapkan flag, opsi, lokalisasi, dan sebagainya
    • Tahap parsing argumen untuk membaca input dan menentukan parameter eksekusi
    • Tahap pemrosesan/eksekusi untuk menyiapkan input dan meneruskannya ke satu atau lebih system call
  • Selama eksekusi, pembatasan dapat diperiksa dan kegagalan bisa terjadi di berbagai titik
    • Status keluar yang berbeda dapat memberi petunjuk tentang lokasi masalah
    • EXIT_FAILURE sering dipakai sebagai status kegagalan umum
  • Setelah gagal, utilitas memberi umpan balik kepada pengguna
  • Variasi utilitas terbagi menjadi tiga kelompok
    • Trivial utilities: mendefinisikan beberapa baris makro lalu meng-include source utilitas lain, dan memaksa kontrol alur tertentu lewat makro. Contohnya arch, dir, vdir
    • Wrapper utilities: setelah konfigurasi dan parsing opsi, opsi baris perintah diteruskan langsung sebagai argumen system call. Hasil system call menjadi hasil utilitas, dengan pemrosesan internal yang minim. Contohnya link, whoami, hostid, logname
    • Full utilities: memiliki konfigurasi, parsing opsi/argumen, pemrosesan data input, dan eksekusi beberapa system call; mayoritas utilitas termasuk kategori ini

Inisialisasi umum dan parsing opsi

  • Semua utilitas memiliki prosedur inisialisasi singkat di awal main()
    • initialize_main (&argc, &argv);
    • set_program_name (argv[0]);
    • setlocale (LC_ALL, "");
    • bindtextdomain (PACKAGE, LOCALEDIR);
    • textdomain (PACKAGE);
    • atexit (close_stdout);
  • Kode pra-pemrosesan ini menangani pekerjaan administratif seperti internasionalisasi dan pendaftaran perilaku saat program berakhir, tanpa memengaruhi perilaku inti masing-masing utilitas
  • Keluarga alat Getopt berperan penting dalam parsing opsi baris perintah
    • Opsi pendek memakai prefiks -, opsi panjang memakai prefiks --
    • Opsi pendek didefinisikan sebagai string, sementara opsi panjang menggunakan struct
    • Dalam string opsi pendek, karakter tanpa argumen ditulis begitu saja, argumen wajib diberi :, dan argumen opsional diberi ::
    • String opsi pendek Lln:s:t pada kill berarti L, l, dan t tidak memiliki argumen, sedangkan n dan s memerlukan argumen
    • getopt_long() digunakan di semua utilitas untuk mengembalikan opsi berikutnya
    • optind menunjuk posisi argumen berikutnya dalam array argv[]
    • optarg adalah pointer karakter yang menunjuk nilai argumen opsi

Penelusuran file system dan helper system call

  • Pada sistem keluarga Unix, pustaka fts sering didukung untuk memudahkan penelusuran file system
    • fts_open() atau xfts_open() membuat struktur FTS dari sebuah path
    • Node file atau direktori pada pohon direpresentasikan oleh struktur FTSENT
    • Memanggil fts_read() akan menghasilkan FTSENT, dan proses ini adalah penelusuran pohon
    • Field FTSENT->fts_info sering dipakai untuk menjelaskan entri dan menentukan cara memprosesnya
  • coreutils menggunakan banyak wrapper system call dan helper selain yang disediakan libc
  • Dalam pemrosesan tulis, selain wrapper system call write() dan fungsi libc seperti fwrite(), ada pula fungsi nonstandar seperti full_write()
    • full_write() terus mencoba menulis ulang kecuali terjadi kegagalan keras
    • safe_write() mencoba ulang system call write() meski terjadi interupsi
    • Ada juga helper tulis yang hanya digunakan di satu utilitas, seperti iwrite() pada dd dan cwrite() pada split

Fungsi umum dan idiom kode

  • Semua utilitas setidaknya menggunakan tiga fungsi: main(), usage(), dan _()
  • usage() menampilkan bantuan yang mencakup parameter input, arti, dan sintaks yang benar
  • _() adalah makro yang didefinisikan di system.h, yang menghubungkan string biasa ke fitur Native Language Support dari GNU gettext.h
    • Jika sebuah string ditampilkan kepada pengguna, biasanya string itu dibungkus dengan makro ini
  • Sebagian besar utilitas yang tidak sepele mengulang baris kode umum
    • #include "system.h" mendefinisikan makro bergantung sistem, variabel, dan fungsi utilitas nonstandar
    • PROGRAM_NAME mendefinisikan nama resmi utilitas dan dipakai untuk pemeriksaan versi
    • AUTHORS mendefinisikan penulis utilitas dan dipakai untuk pemeriksaan versi
    • emit_try_help() menampilkan saran bantuan setelah output kegagalan
    • emit_ancillary_info (PROGRAM_NAME) menampilkan informasi bantuan tambahan umum setelah output perintah
    • initialize_main(&argc, &argv) menangani ekspansi wildcard bawaan di VMS, dan dihapus pada sebagian besar sistem operasi lain
    • atexit(close_stdout) mendaftarkan fungsi yang akan melakukan flush dan menutup stream buffer saat program berakhir
  • Ada juga idiom C yang mungkin terasa asing bagi pemula
    • !! adalah operasi unary NOT ganda untuk memaksa nilai menjadi boolean
    • do { ... } while (0) adalah pola non-loop untuk membungkus makro multi-pernyataan agar tokenisasi tetap aman setelah substitusi preprocessor

Cara pemeliharaan dan pemeriksaan sebelum berkontribusi

  • Proyek aktif seperti coreutils umumnya berevolusi melalui tiga alur pemeliharaan
  • Perubahan seluruh proyek adalah perubahan besar yang memengaruhi struktur dan dependensi semua utilitas
    • Pada 1995, Native Language Support ditambahkan melalui GNU gettext project, dan makro _() diperkenalkan di sebagian besar baris output teks
    • Pada 1996, dukungan internasionalisasi diperluas dan beberapa kode inisialisasi ditambahkan ke main()
    • Pada 1995, deskripsi singkat tujuan utilitas ditambahkan ke output penggunaan
    • Pada 2003, dukungan wildcard VMS ditambahkan, yang terlihat pada fungsi initialize_main()
    • Pada 2016, untuk menghindari peringatan compiler di jalur kegagalan, makro die() menggantikan sebagian besar fungsi exit() dan error()
  • Perubahan per utilitas dibagi menjadi perbaikan bug, fitur baru, dan optimisasi
    • join, sort, dan uniq rentan terhadap serangan overflow hingga patch pada 2016
    • df mendapat opsi --output yang ditambahkan pada 2013
    • yes mengalami peningkatan performa lewat buffering yang lebih baik
  • Pemeliharaan tahunan setidaknya mencakup pembaruan tahun hak cipta semua utilitas
    • Ada juga perubahan administratif seperti pembaruan alamat FSF, yang tidak memengaruhi eksekusi
  • Kontributor sebaiknya lebih dulu memeriksa GNU project page, contribution guidelines, rejected features, dan mailing list archives
  • Ada tiga pertanyaan yang perlu diperiksa sebelum menulis kode
    • Apakah fungsi yang sama bisa direproduksi dengan alat yang sudah ada
    • Apakah kontribusi tersebut merusak kompatibilitas mundur
    • Apakah perilaku yang diusulkan menyimpang jauh dari POSIX
  • Jika belum yakin, prosedur yang disarankan adalah bertanya kepada komunitas di mailing list

Detail menarik

  • Utilitas terpendek adalah false, hanya 2 baris, setara dengan arch, dir, dan vdir
  • Utilitas mandiri terpendek adalah true dengan 80 baris, dan versi pertamanya nyaris seperti program C minimal
  • Utilitas terpanjang adalah ls dengan 5308 baris
  • Banyak utilitas dapat ditelusuri kembali hingga Research UNIX pada 1970-an, dan sebagian bahkan sampai ke Multics
  • Leluhur konseptual tertua adalah perintah CTSS LISTF sekitar tahun 1963, yang kemudian dipendekkan menjadi ls
  • Sintaks unik dd mengingatkan pada OS/360 job control language dari awal 1960-an
  • sort adalah satu-satunya utilitas yang memanfaatkan multithreading
  • fmt menunjukkan optimisasi baris dan paragraf dengan memakai biaya fungsi
  • yes menerapkan output berkinerja tinggi dengan buffer memori yang disejajarkan ke halaman
  • df lebih cepat karena memakai metadata perangkat, sedangkan du harus memeriksa semua file
  • cksum memiliki dua titik masuk: eksekusi normal dan pembuatan tabel CRC-32
  • echo tidak memiliki kondisi kegagalan
  • Desain test dan expr sangat berbeda dari struktur utilitas pada umumnya
  • su awalnya dipelihara di coreutils/shellutils

Implementasi yang layak dilihat

Informasi dukungan

  • Tidak ada pengaturan untuk menerima dukungan pribadi
  • Jika ingin menyumbangkan waktu atau uang, Anda dapat berkontribusi ke Free Software Foundation

1 komentar

 
GN⁺ 2023-09-10
Opini Hacker News
  • Jika dikatakan bahwa “banyak dari utilitas ini sudah berusia hampir 30 tahun, dan selama itu telah dimodifikasi oleh banyak orang”, tetapi juga “tidak dirancang untuk diperluas melampaui umur panjang atau perannya”, saya ingin melihat contoh program yang dirancang dengan umur panjang dalam pikiran menurut penulis
    Saya penasaran, baik itu program yang sudah bertahan 30 tahun, maupun program yang menurutnya akan bertahan 30 tahun ke depan

    • Masalahnya adalah apa sebenarnya peran itu. Saya bertanya-tanya apakah ada batas pada apa yang bisa dilakukan dengan alat-alat seperti ini
      Saya membuat framework pengujian dengan bash dan coreutils untuk menguji bahasa pemrograman kecil buatan saya sendiri. Awalnya saya merasa kurang nyaman karena tidak memakai bahasa yang “semestinya”, tetapi pada praktiknya berjalan sangat baik dan juga bisa dieksekusi paralel
      Satu-satunya hal yang tidak bisa saya uji adalah argv[0] program, dan setelah mencoba berbagai kombinasi tetap tidak bisa mendapatkan perilaku yang persis saya inginkan, jadi saya mengirim permintaan fitur dan patch untuk env ke coreutils: https://lists.gnu.org/archive/html/coreutils/2023-08/msg0006...
      Sepertinya akan diterima, yang berarti fitur baru masuk ke program lama
    • Sepertinya yang dimaksud di sini adalah “dibuat agar bisa dikembangkan secara aktif untuk waktu lama”
      Praktik-praktik yang disebutkan penulis biasanya dikritik dalam program besar karena semakin besar skalanya, semakin merusak maintainability
    • Mungkin maksudnya “umur pengembangan yang panjang”. Dengan kata lain, ini adalah utilitas yang hampir sekali ditulis lalu dipakai lama, dan itu juga merupakan keunggulan pendekatan “melakukan satu hal dengan baik”
    • Anehnya, saya menafsirkan “long life” sebagai program yang terus berjalan. Saya melihatnya sebagai kebalikan dari alat dengan input yang jelas, eksekusi singkat, dan output yang jelas
    • Emacs?
  • Materi yang layak dijadikan rujukan:
    Cara pengujian GNU coreutils: https://www.pixelbeat.org/docs/coreutils-testing.html
    Eksplorasi tiap perintah coreutils: https://ratfactor.com/slackware/pkgblog/coreutils
    Pemrosesan teks command-line dengan GNU Coreutils: https://learnbyexample.github.io/cli_text_processing_coreuti... — ebook saya yang membahas lebih dari 20 alat pemrosesan teks

  • Artikel terkait:
    Decoded: GNU Coreutils (2018) - https://news.ycombinator.com/item?id=29871037 - Januari 2022, 7 komentar
    Decoded: GNU coreutils (2019) - https://news.ycombinator.com/item?id=26411908 - Maret 2021, 38 komentar
    Decoded: GNU Coreutils - https://news.ycombinator.com/item?id=20328650 - Juli 2019, 55 komentar

  • Fakta menarik: jika Anda memasang coreutils lewat Homebrew di macOS, karena macOS sudah memiliki od(1), od milik coreutils dipasang sebagai god(1)

  • Jika penulis melihat ini, setidaknya ada satu kesalahan. Deskripsi singkat di halaman shred[0] sebenarnya adalah deskripsi csplit[1], dan seharusnya kira-kira menjadi “menimpa isi file untuk menyembunyikannya dan secara opsional menghapusnya”
    [0]: https://maizure.org/projects/decoded-gnu-coreutils/shred.htm...
    [1]: https://maizure.org/projects/decoded-gnu-coreutils/csplit.ht...

  • Saya tidak tahu ada yang seperti ini, keren. Bahkan sesuatu yang sederhana seperti yes pun cukup menarik untuk melihat bagaimana kode dasar sebuah utilitas yang menulis ke standard output dibuat
    https://maizure.org/projects/decoded-gnu-coreutils/yes.html

    • Maksud saya, bahkan utilitas sederhana pun layak ditulis dengan baik dan dioptimalkan
      Misalnya, bagus bahwa ada contoh minimal untuk mempelajari cara menulis output dengan sangat cepat. Karena programnya sangat pendek, jumlah baris tidak terlalu penting, dan jika penulisnya tahu caranya, menurut saya lebih baik membuatnya secepat mungkin agar tidak menjadi hambatan
    • Implementasi Go yang dibuat untuk iseng seperti ini:
      package main
      
      import "flag"
      
      func main() {  
      yes := flag.String("m", "y", "message")  
      flag.Parse()  
      for {  
      println(*yes)  
      }  
      }  
      
  • Cukup menarik sebagai daftar utilitas dasar. Saya sudah sangat lama memakai UNIX, tapi belum pernah mendengar yang seperti shred, shuf, atau factor
    Jadi ingin mencoba menjalankan

    sudo find / -type f -exec shred {} \;  
    

    lalu melihat berapa lama ia bertahan sebelum membunuh dirinya sendiri. Tentu saja di mesin virtual atau perangkat yang mudah di-flash ulang

    • Saya pernah mencoba hal serupa dengan dd, dan ternyata selesai sampai akhir, cukup mengecewakan
      Saya berharap terjadi crash atau setidaknya koneksi terputus, tetapi kernel serta sshd dan bash masih berada di memori, jadi saya kembali ke prompt yang sudah tidak bisa melakukan apa-apa dengan benar
    • Di Linux, kemungkinan besar itu akan berjalan sampai akhir. Executable dan library yang sedang berjalan tidak bisa ditulis (ETXTBUSY), jadi shred tidak bisa merusak dirinya sendiri atau find
  • Saya suka bahwa /bin/true ternyata bisa gagal dan mengembalikan false. Secara teknis, pemanggilan “Not /bin/false” jadi lebih kokoh: https://github.com/coreutils/coreutils/blob/master/src/true....
    Tentu saya tahu ini kasus yang hampir tidak pernah terjadi, tapi tetap saja lucu

    • Hal lain yang menarik dari perintah true adalah ia menjadi jauh lebih rumit daripada yang diperlukan
      Latihan dulu:
      touch mytrue  
      chmod u+x mytrue  
      ./mytrue  
      echo "error code for mytrue is $?"  
      
      true secara harfiah bermula seperti ini. Cukup seperti koan Zen
      Perubahan pertama terjadi karena urusan hukum. Semua kode harus memiliki pemberitahuan hak cipta, dan itu juga diperlukan untuk file kosong. Jadi file tanpa isi berubah menjadi file yang hanya berisi pemberitahuan hak cipta, sehingga muncul pertanyaan, “bisakah seseorang mengklaim hak cipta atas ketiadaan?” AT&T mencobanya
      Lalu seseorang mengatakan program harus terdefinisi dengan baik dan tidak boleh bergantung pada perilaku kebetulan Unix, yang pada saat itu cukup masuk akal. Maka true akhirnya memiliki kode, yaitu exit 0
      Lalu ada lagi yang mengatakan utilitas sistem sebaiknya ditulis dalam C, bukan shell, agar lebih cepat, dan OpenBSD masih punya contoh bagus dari bentuk itu: http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr....
      Pada suatu titik birokrasi GNU ikut campur dan mengatakan semua program harus mendukung flag -h, lalu setelah itu semua program harus mendukung locale, sehingga GNU true sekarang, yang mengejutkan, menjadi 80 baris: https://github.com/coreutils/coreutils/blob/master/src/true....
      Bisa saja dianggap baik-baik saja, tetapi untuk program yang menurut definisi “tidak melakukan apa-apa dan berhasil”, itu banyak sekali kode
      http://trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
    • Bukankah itu hanya terjadi kalau dijalankan dengan opsi? Menjalankan true saja sepertinya tidak terpengaruh, kecuali komentarnya yang membingungkan
  • Kalau seorang programmer pemula ingin mengembangkan intuisi tentang cara menerapkan struktur data dan algoritma secara berguna, adakah hal tertentu di sini yang sebaiknya diperhatikan?

    • Sebagian besar kecerdikan di coreutils bukan pada struktur data dan algoritma, melainkan pada cara berinteraksi secara efektif dengan kernel
      Misalnya memakai copy_file_range() alih-alih read()/write() agar data tidak perlu disalin ke ruang pengguna. Ini lebih dekat ke rekayasa perangkat lunak daripada ilmu komputer
    • Mungkin saya salah paham, tetapi saya tidak tahu apa kaitan struktur data dan algoritma dengan alat baris perintah
  • Mungkin saya melewatkan inti situs ini, tetapi bukankah masing-masing sudah punya halaman man atau halaman info?

    • Bukan informasi penggunaan; situs ini membahas secara detail bagaimana program bekerja secara internal