Konfigurasi Bagian 2 mendefinisikan aturan mainnya, tetapi aturan tidak menjalankan dirinya sendiri: valid users = @keluarga baru berarti sesuatu setelah ada user yang benar-benar tercatat di grup keluarga, dan directory mask = 2770 baru bekerja setelah kita memahami bit setgid yang dibawanya. Bagian ini mengisi lapisan penghuni dari file server — user, grup, dan permission — lalu membuktikan setiap klaimnya: setgid mewariskan grup ke berkas baru, tamu ditolak saat menulis share yang salah, dan seluruh aktivitas bisa diaudit dari log. Semua demo dijalankan pada fixture lokal dengan permission sungguhan dari chmod, chgrp, dan kernel.
Grup dan User #
Skrip buat-struktur.sh dari Bagian 1 sudah membuat grup keluarga, tamu, dan backup saat dijalankan sebagai root. Yang tersisa adalah mengisi keanggotaannya. Menambahkan anggota adalah pekerjaan usermod -aG — user budi dan siti masuk grup keluarga, akun tamu masuk grup tamu, dan hanya budi yang masuk grup backup. Untuk akses Samba, tiap user juga perlu password Samba sendiri, karena akun Samba selalu berdiri di atas akun Linux:
sudo usermod -aG keluarga budi
sudo usermod -aG keluarga siti
sudo usermod -aG tamu tamu1
sudo usermod -aG backup budi
sudo smbpasswd -a budi
sudo smbpasswd -a sitiKeanggotaan baru berlaku pada login berikutnya — sesi yang sedang berjalan tidak ikut berubah, kecuali kita membuka shell baru dengan newgrp keluarga. Mekanisme dua lapis akun Linux dan Samba ini sudah dibedah di Chapter Samba
.
Di lingkungan latihan kita tidak bisa membuat user sungguhan, tetapi bisa merasakan aturan kernel yang paling menentukan: user biasa hanya boleh memindahkan grup sebuah berkas ke grup tempatnya menjadi anggota. Blok berikut membuat fixture lalu mengalihkan grupnya — di komputer pembaca, ganti z dengan grup bersama seperti keluarga:
mkdir -p latihan/share/dokumen
printf 'catatan belanja bulan ini\n' > latihan/share/dokumen/belanja.txt
chgrp z latihan/share/dokumen/belanja.txt
stat -c '%U:%G %n' latihan/share/dokumen/belanja.txtOutput yang Dihasilkan:
z:z latihan/share/dokumen/belanja.txtPerintah chgrp di atas berhasil karena kita pemilik berkas sekaligus anggota grup z — dan berkas hasilnya terbaca z:z. Persis di sinilah letak bedanya di server: chgrp keluarga membutuhkan sudo karena keluarga bukan grup utama kita, itulah sebabnya pemindahan grup pada struktur share dikerjakan buat-struktur.sh yang memang berjalan sebagai root.
setgid: Grup yang Diwariskan #
Inilah masalah yang membuat share tim berantakan dalam hitungan minggu: tanpa pengaman, berkas baru membawa grup utama pembuatnya. Siti membuat berkas di dokumen, grupnya grup utama Siti; Budi membuat berkas lain, grupnya grup Budi — dan tiba-tiba anggota yang satu tidak bisa membaca berkas anggota yang lain, meski foldernya sama. Solusinya sudah tertanam di mode 2770 sejak Bagian 1: digit 2 memasang bit setgid pada direktori, sehingga berkas dan folder baru di dalamnya mewarisi grup foldernya. Blok berikut membuktikan mesin pewarisannya bekerja:
umask 002
mkdir -p latihan/share/dokumen
chmod 2770 latihan/share/dokumen
touch latihan/share/dokumen/catatan.txt
mkdir latihan/share/dokumen/laporan
echo "== folder share =="
stat -c '%A %G' latihan/share/dokumen
echo "== file baru di dalamnya =="
stat -c '%A %G' latihan/share/dokumen/catatan.txt
echo "== folder baru di dalamnya =="
stat -c '%A %G' latihan/share/dokumen/laporanOutput yang Dihasilkan:
== folder share ==
drwxrws--- z
== file baru di dalamnya ==
-rw-rw-r-- z
== folder baru di dalamnya ==
drwxrwsr-x zMari kita bedah hasilnya:
drwxrws---: hurufsdi posisi execute kelas grup adalah wajah setgid — mode oktal2770tercetak penuh sebagai2(setgid) +770.- Berkas baru
-rw-rw-r--: grupnyaz, grup folder kita di lingkungan latihan ini. Di lingkungan ini grup utama kita memang kebetulan sama, sehingga perubahan namanya tidak terlihat; di server, folder ini sudah dipindahkan ke grupkeluarga, dan berkas yang dibuat budi akan membawakeluargaalih-alih grup utama budi — perubahan inilah inti pewarisan. - Folder baru
drwxrwsr-x: perhatikans-nya — folderlaporanmewarisi bit setgid tanpa perintahchmodapa pun. Ini bukti paling terlihat bahwa mesin pewarisan hidup: setiap subfolder yang dibuat siapa pun di dalam share otomatis meneruskan aturan grup ke generasi berikutnya. umask 002: berkas baru lahir dengan write untuk kelas grup — tanpa ini, anggota lain bisa membaca tetapi tidak bisa mengubah. Pada share Samba, peran serupa dimainkancreate maskdari Bagian 2; NFS memakai umask proses di klien.
Perilaku ini adalah alasan directory mask = 2770 kita tulis di smb.conf: folder yang dibuat klien Windows lewat share juga membawa setgid, sehingga skema grup tidak bocor oleh klien yang tidak tahu apa-apa soal permission Linux. Teori lengkap bit khusus ini ada di Chapter Special Permissions
.
Pengecualian per User dengan ACL #
Model grup menutup 95% kebutuhan, tetapi selalu ada pengecualian: tamu1 ikut mengerjakan satu folder proyek selama sepekan, atau siti butuh akses penuh ke satu subfolder dokumen tanpa menjadi anggota penuh keluarga. Untuk pengecualian seperti ini, ACL jauh lebih tepat daripada melubangi aturan grup:
sudo setfacl -m u:tamu1:rwX /srv/share/dokumen/proyek-bersama
getfacl /srv/share/dokumen/proyek-bersamaEntri ACL u:tamu1:rwX memberi tamu1 baca-tulis pada folder itu saja — X besar berarti execute hanya untuk direktori, bukan berkas — tanpa mengubah keanggotaan grup siapa pun. Tanda + yang muncul di akhir mode (drwxrws---+) adalah penanda bahwa berkas membawa ACL tambahan. Panduan lengkapnya ada di Chapter Access Control Lists
dan setfacl(1)
.
Menguji Akses dari Klien #
Dengan user dan grup beres, saatnya berpindah kursi ke sisi klien. Alat uji pertama adalah smbclient bergaya FTP: opsi -L meminta daftar share yang ditawarkan server, dan daftar itu sendiri sudah satu hasil uji — share backup yang browseable = no tidak boleh muncul. Berikut contoh tampilan sesi dari laptop budi:
$ smbclient -L //server-rumah -U budi
Password for [RUMAH\budi]:
Sharename Type Comment
--------- ---- -------
dokumen Disk Dokumen kerja dan catatan keluarga
media Disk Foto dan video keluarga
IPC$ IPC IPC Service (Server File Rumah)
Dua share kita tampil beserta comment-nya, entri IPC$ adalah saluran internal Samba yang selalu ada, dan backup memang tidak terlihat — meski tetap bisa dibuka bila namanya diketahui, valid users tetap berjaga. Dari klien Linux, share NFS dipasang seperti biasa:
sudo mkdir -p /mnt/media
sudo mount -t nfs server-rumah:/srv/share/media /mnt/media
ls /mnt/mediaPerintah terakhir yang paling penting: coba tulis ke dalamnya. ls berhasil, touch /mnt/media/coba.txt ditolak — share media diekspor ro, dan kernel klien menolak membuka berkas untuk ditulis. Persis perilaku itulah yang kita simulasikan di bawah ini.
Simulasi ro vs rw #
Tiga pintu berujung pada penolakan yang sama: mode 444 di disk, read list di Samba, dan ro di ekspor NFS. Semuanya berakhir pada satu pemeriksaan kernel ketika proses mencoba membuka berkas untuk ditulis. Blok berikut membuat berkas read-only lalu mencoba menambah isinya dari klien — 2>&1 membawa pesan errornya ikut tampil:
mkdir -p latihan/share/media
printf 'video liburan 2026\n' > latihan/share/media/indeks.txt
chmod 444 latihan/share/media/indeks.txt
( echo 'entri baru dari klien' >> latihan/share/media/indeks.txt ) 2>&1Output yang Dihasilkan:
bash: latihan/share/media/indeks.txt: Permission deniedKernel menolak sebelum satu byte pun tersentuh: mode 444 berarti kelas owner, grup, dan other sama-sama tanpa write, dan redirect >> membutuhkan write. Kurung mengelompokkan echo menjadi satu perintah sehingga 2>&1 di luarnya membawa pesan error shell ikut tampil — tanpa itu, pesan seperti ini hanya pergi ke stderr dan tak terlihat di log. Blok kedua memperlihatkan sisi sebaliknya — kelas yang berhak menulis:
mkdir -p latihan/share/media
printf 'video liburan 2026\n' > latihan/share/media/indeks.txt
chmod 664 latihan/share/media/indeks.txt
echo 'entri baru dari klien' >> latihan/share/media/indeks.txt
cat latihan/share/media/indeks.txtOutput yang Dihasilkan:
video liburan 2026
entri baru dari klienMode 664 mengembalikan write untuk owner dan grup — pemetaan persis seperti keadaan server: anggota keluarga menulis media dengan lancar, sementara tamu menabrak Permission denied yang sama seperti blok pertama, baik datang lewat Samba (read list) maupun NFS (ro).
Audit: Siapa Mengakses Apa #
File server yang dipakai banyak orang butuh jawaban atas dua pertanyaan rutin: siapa sedang mengakses apa, dan adakah akses yang tidak seharusnya. Jawaban real-time diberikan sudo smbstatus — tabel koneksi aktif, user, dan share-nya; riwayatnya tersimpan di log per klien /var/log/samba/log.%m yang kita konfigurasi di Bagian 2. Berikut cuplikan dua berkas log yang formatnya disederhanakan untuk latihan, lalu dirangkum awk:
mkdir -p latihan/log
cat > latihan/log/log.hape-siti <<'LOG'
2026/01/15 09:12:01 hape-siti connect service=media user=siti
2026/01/15 10:02:11 hape-siti connect service=dokumen user=siti
LOG
cat > latihan/log/log.laptop-tamu <<'LOG'
2026/01/15 11:30:45 laptop-tamu connect service=media user=tamu1
2026/01/15 11:31:02 laptop-tamu connect service=dokumen user=tamu1
LOG
echo "== akses per user =="
awk '{ sub(/service=/, "", $5); sub(/user=/, "", $6); print $6, $5 }' \
latihan/log/log.* | sort -u
echo "== akses tamu di luar media (perlu ditinjau) =="
awk '{ sub(/service=/, "", $5); sub(/user=/, "", $6)
if ($6 ~ /^tamu/ && $5 != "media") print $1, $2, $3, $6 " -> " $5 }' \
latihan/log/log.*Output yang Dihasilkan:
== akses per user ==
siti dokumen
siti media
tamu1 dokumen
tamu1 media
== akses tamu di luar media (perlu ditinjau) ==
2026/01/15 11:31:02 laptop-tamu tamu1 -> dokumenBagian awal output adalah jawaban pertanyaan pertama: pasangan user dan share yang unik, hasil sort -u dari seluruh berkas log. Bagian kedua adalah alarm buatan kita sendiri: tamu yang tercatat mengakses share di luar media. Baris tamu1 -> dokumen seharusnya mustahil — valid users = @keluarga harus menolaknya — sehingga kemunculannya berarti konfigurasi berubah tanpa sengaja atau ada yang memakai akun tamu lebih jauh dari haknya; dua-duanya layak ditelusuri hari itu juga. Audit isi berkasnya sendiri bisa memakai find /srv/share -type f -not -group keluarga di server untuk memburu berkas yang grupnya melenceng dari skema. Rincian smbstatus ada di smbstatus(8)
.
Seluruh klaim bagian ini — struktur, konfigurasi, pewarisan setgid, penolakan tulis — dirangkum menjadi dua puluh assertion otomatis pada tests/run-tests.sh: skrip menjalankan buat-struktur.sh di folder terisolasi, memanggil verifikasi-share.sh pada config/ bawaan dan pada konfigurasi rusak, memeriksa entri fstab-share, lalu membuktikan setgid terwarisi dan tulis ke berkas read-only ditolak. Jalankan bash tests/run-tests.sh dari folder proyek; keluaran PASSED: 20/20 test lulus berarti file server Anda lulus seluruh uji yang sama dengan milik kami.
File server kini utuh: penyimpanan terencana, dua protokol berbagi dengan batasan yang sejajar, dan lapisan user-grup-permission yang terbukti bekerja. Pada proyek berikutnya, Layanan Home Server , mesin ini kita hidupkan lebih jauh — layanan inti, monitoring sederhana, dan backup terjadwal dibangun di atas share yang barusan kita amankan.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
deploy/buat-struktur.sh
Unduh berkas#!/usr/bin/env bash
# buat-struktur.sh — bangun struktur penyimpanan file server.
#
# Membuat tiga share (dokumen, media, backup), mengatur group dan
# mode permission-nya, lalu memverifikasi hasil akhirnya dengan stat.
#
# Dua mode eksekusi:
# - sebagai root di server : membuat group sungguhan (groupadd bila
# belum ada), folder di bawah TARGET, dan chgrp ke group aslinya.
# - sebagai user biasa : simulasi — folder tetap dibuat, chgrp
# digantikan ke group utama kita, dan rencana group dicatat ke
# TARGET/etc/group sebagai fixture yang bisa dibaca ulang.
#
# Pemakaian:
# sudo bash buat-struktur.sh /srv # di server sungguhan
# bash buat-struktur.sh latihan # latihan lokal tanpa root
#
# Kode keluar:
# 0 sukses 1 argumen tidak valid
# 2 struktur gagal dibuat/tidak sesuai
set -u
TARGET="${1:-latihan}"
# Rencana share: nama:group:mode (mode 4 digit oktal, setgid aktif).
RENCANA="dokumen:keluarga:2770 media:keluarga:2775 backup:backup:2770"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
printf 'buat-struktur.sh: target tidak valid: %s\n' "$TARGET" >&2
exit 1
fi
if [ "$TARGET" = "/srv" ] && [ "$(id -u)" -ne 0 ]; then
printf 'buat-struktur.sh: /srv milik root; jalankan dengan sudo\n' >&2
exit 1
fi
mkdir -p "$TARGET" || exit 1
SHARE_DIR="$TARGET/share"
# --- 1. Siapkan group ----------------------------------------------------
if [ "$(id -u)" -eq 0 ]; then
printf 'mode : server (root)\n'
for grup in keluarga tamu backup; do
if ! getent group "$grup" > /dev/null; then
groupadd "$grup" || exit 2
fi
done
else
printf 'mode : simulasi tanpa root\n'
mkdir -p "$TARGET/etc" || exit 2
cat > "$TARGET/etc/group" <<'GRUP'
keluarga:x:1100:budi,siti
tamu:x:1101:tamu1
backup:x:1102:budi
GRUP
printf 'group : keluarga, tamu, backup dicatat di %s/etc/group\n' "$TARGET"
fi
# --- 2. Buat folder share beserta permission-nya -------------------------
printf 'struktur: %s/{dokumen,media,backup}\n' "$SHARE_DIR"
gagal=0
for entri in $RENCANA; do
nama=${entri%%:*}
sisa=${entri#*:}
grup=${sisa%%:*}
mode=${sisa##*:}
dir="$SHARE_DIR/$nama"
mkdir -p "$dir" || { gagal=$((gagal + 1)); continue; }
if [ "$(id -u)" -eq 0 ]; then
chgrp "$grup" "$dir" || { gagal=$((gagal + 1)); continue; }
else
# Tanpa root kita hanya boleh memindahkan group ke group sendiri;
# di server, baris ini menjadi: chgrp "$grup" "$dir"
chgrp "$(id -gn)" "$dir" || { gagal=$((gagal + 1)); continue; }
fi
chmod "$mode" "$dir"
# --- 3. Verifikasi hasil dengan stat -------------------------------
nyata=$(stat -c '%a' "$dir")
if [ "$nyata" != "$mode" ]; then
printf 'GAGAL %s mode %s, harapan %s\n' "$dir" "$nyata" "$mode"
gagal=$((gagal + 1))
else
printf 'OK %s %s\n' "$mode" "$dir"
fi
done
if [ "$gagal" -gt 0 ]; then
printf 'GAGAL: %d pemeriksaan tidak sesuai\n' "$gagal"
exit 2
fi
printf 'selesai tanpa error\n'
deploy/verifikasi-share.sh
Unduh berkas#!/usr/bin/env bash
# verifikasi-share.sh — periksa directive wajib smb.conf dan format exports.
#
# Pengganti testparm/exportfs di lingkungan tanpa Samba dan NFS: menerima
# folder yang memuat smb.conf dan exports, lalu memeriksa directive
# keamanan yang kita rancang satu per satu dengan grep dan awk.
#
# Pemakaian:
# bash verifikasi-share.sh <folder-config>
#
# Kode keluar = jumlah pemeriksaan yang gagal.
set -u
if [ $# -ne 1 ]; then
printf 'pemakaian: verifikasi-share.sh <folder-config>\n' >&2
exit 1
fi
SMB="$1/smb.conf"
EXP="$1/exports"
gagal=0
for f in "$SMB" "$EXP"; do
if [ ! -f "$f" ]; then
printf 'verifikasi-share.sh: berkas tidak ditemukan: %s\n' "$f" >&2
exit 1
fi
done
ok() { printf 'OK %s\n' "$1"; }
hilang() { printf 'HILANG %s\n' "$1"; gagal=$((gagal + 1)); }
# --- smb.conf: section dan directive wajib --------------------------------
for pola in \
'[global]' \
'[dokumen]' \
'[media]' \
'[backup]' \
'path = /srv/share/dokumen' \
'path = /srv/share/media' \
'path = /srv/share/backup' \
'valid users = @keluarga' \
'read only = no' \
'create mask = 0660' \
'directory mask = 2770' \
'browseable = no' \
'bind interfaces only = yes'
do
if grep -qF -- "$pola" "$SMB"; then
ok "smb.conf: $pola"
else
hilang "smb.conf: $pola"
fi
done
# --- exports: format dan opsi keamanan -----------------------------------
# 1. Setiap entri aktif memuat kurung opsi di kolom klien.
n=$(awk '!/^[[:space:]]*#/ && NF && $2 !~ /\(/ { c++ } END { print c + 0 }' "$EXP")
if [ "$n" -eq 0 ]; then
ok 'exports: semua entri memuat opsi klien'
else
hilang 'exports: ada entri tanpa opsi klien'
fi
# 2. Setiap entri memuat sync dan no_subtree_check.
n=$(awk '
!/^[[:space:]]*#/ && NF && ($2 !~ /sync/ || $2 !~ /no_subtree_check/) { c++ }
END { print c + 0 }' "$EXP")
if [ "$n" -eq 0 ]; then
ok 'exports: semua entri memuat sync + no_subtree_check'
else
hilang 'exports: ada entri tanpa sync / no_subtree_check'
fi
# 3. root_squash tidak boleh dimatikan.
if grep -q 'no_root_squash' "$EXP"; then
hilang 'exports: no_root_squash terdeteksi'
else
ok 'exports: root_squash tetap aktif'
fi
# 4. dokumen rw, media ro.
mode_dokumen=$(awk '$1 == "/srv/share/dokumen" {
print ($2 ~ /\(rw/) ? "rw" : "ro" }' "$EXP")
if [ "$mode_dokumen" = "rw" ]; then
ok 'exports: dokumen dibagikan rw'
else
hilang 'exports: dokumen bukan rw'
fi
mode_media=$(awk '$1 == "/srv/share/media" {
print ($2 ~ /\(ro/) ? "ro" : "rw" }' "$EXP")
if [ "$mode_media" = "ro" ]; then
ok 'exports: media dibagikan ro'
else
hilang 'exports: media bukan ro'
fi
# 5. backup hanya untuk satu klien tepercaya.
klien=$(awk '$1 == "/srv/share/backup" {
k = $2; sub(/\(.*/, "", k); print k }' "$EXP")
if [ "$klien" = "192.0.2.10" ]; then
ok 'exports: backup terbatas ke 192.0.2.10'
else
hilang 'exports: klien backup bukan 192.0.2.10'
fi
exit "$gagal"
tests/run-tests.sh
Unduh berkas#!/usr/bin/env bash
# run-tests.sh — test suite untuk proyek server file lokal.
# Suite menyiapkan folder kerja terisolasi (mktemp), lalu menguji:
# struktur share hasil deploy/buat-struktur.sh (folder, mode, fixture
# group), directive wajib pada config/smb.conf dan config/exports
# lewat deploy/verifikasi-share.sh, entri config/fstab-share, pewarisan
# setgid pada folder share, serta penolakan tulis pada file read-only.
# Jalankan dari mana saja: bash tests/run-tests.sh
set -u
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJ_DIR=$(dirname "$SCRIPT_DIR")
PASS=0
FAIL=0
WORK=""
# assert_eq <harapan> <nyata> <pesan>: bandingkan dua nilai.
assert_eq() {
if [ "$1" = "$2" ]; then
PASS=$((PASS + 1))
printf 'OK %s\n' "$3"
else
FAIL=$((FAIL + 1))
printf 'GAGAL %s: harapan %s, nyata %s\n' "$3" "$1" "$2"
fi
}
bersihkan() {
if [ -n "$WORK" ]; then
rm -rf "$WORK"
fi
}
trap bersihkan EXIT
WORK=$(mktemp -d)
cd "$WORK" || exit 1
# --- uji 1: buat-struktur.sh membangun struktur yang sesuai ---------------
bash "$PROJ_DIR/deploy/buat-struktur.sh" latihan > /dev/null 2>&1
assert_eq 0 "$?" "buat-struktur.sh keluar 0"
for nama in dokumen media backup; do
[ -d "latihan/share/$nama" ]
assert_eq 0 "$?" "share $nama dibuat"
done
assert_eq 2770 "$(stat -c '%a' latihan/share/dokumen)" \
"mode dokumen 2770"
assert_eq 2775 "$(stat -c '%a' latihan/share/media)" \
"mode media 2775"
assert_eq 2770 "$(stat -c '%a' latihan/share/backup)" \
"mode backup 2770"
# --- uji 2: fixture group tercatat, skrip idempoten ------------------------
grep -q '^keluarga:x:1100:budi,siti$' latihan/etc/group
assert_eq 0 "$?" "fixture group memuat keluarga:budi,siti"
grep -q '^tamu:x:1101:tamu1$' latihan/etc/group
assert_eq 0 "$?" "fixture group memuat tamu"
bash "$PROJ_DIR/deploy/buat-struktur.sh" latihan > /dev/null 2>&1
assert_eq 0 "$?" "buat-struktur.sh idempoten (dijalankan dua kali)"
# --- uji 3: konfigurasi bawaan lolos verifikasi-share.sh -------------------
bash "$PROJ_DIR/deploy/verifikasi-share.sh" "$PROJ_DIR/config" \
> /dev/null 2>&1
assert_eq 0 "$?" "config/ lolos verifikasi-share.sh"
# --- uji 4: konfigurasi rusak tertangkap -----------------------------------
mkdir -p rusak
printf '[global]\n[dokumen]\npath = /srv/share/dokumen\nread only = no\n' \
> rusak/smb.conf
printf '/srv/share/dokumen 192.0.2.0/24(rw,async)\n' > rusak/exports
bash "$PROJ_DIR/deploy/verifikasi-share.sh" rusak > /dev/null 2>&1
if [ "$?" -gt 0 ]; then
PASS=$((PASS + 1))
printf 'OK konfigurasi rusak tertangkap (exit bukan 0)\n'
else
FAIL=$((FAIL + 1))
printf 'GAGAL konfigurasi rusak tertangkap: verifikasi-share.sh keluar 0\n'
fi
# --- uji 5: entri fstab-share memuat nofail dan enam field -----------------
grep -q 'nofail' "$PROJ_DIR/config/fstab-share"
assert_eq 0 "$?" "fstab-share memuat nofail"
jumlah=$(awk '!/^[[:space:]]*#/ && NF { print NF }' \
"$PROJ_DIR/config/fstab-share" | sort -u)
assert_eq 6 "$jumlah" "entri fstab-share berisi enam field"
mnt=$(awk '$2 == "/srv/share" { print $2 }' "$PROJ_DIR/config/fstab-share")
assert_eq "/srv/share" "$mnt" "mountpoint fstab-share /srv/share"
# --- uji 6: setgid diwarisi oleh isi baru folder share ---------------------
mkdir -p latihan/share/dokumen/laporan
touch latihan/share/dokumen/laporan/catatan.txt
assert_eq "$(stat -c '%G' latihan/share/dokumen)" \
"$(stat -c '%G' latihan/share/dokumen/laporan/catatan.txt)" \
"file baru mewarisi group folder share"
mode_sub=$(stat -c '%a' latihan/share/dokumen/laporan)
if [ "$(( 8#$mode_sub & 8#2000 ))" -ne 0 ]; then
PASS=$((PASS + 1))
printf 'OK folder baru mewarisi bit setgid (%s)\n' "$mode_sub"
else
FAIL=$((FAIL + 1))
printf 'GAGAL folder baru mewarisi bit setgid: mode %s tanpa bit 2000\n' \
"$mode_sub"
fi
# --- uji 7: tulis ditolak pada file read-only ------------------------------
printf 'isi awal\n' > latihan/kunci.txt
chmod 444 latihan/kunci.txt
if (echo 'tambahan' >> latihan/kunci.txt) 2> /dev/null; then
FAIL=$((FAIL + 1))
printf 'GAGAL tulis ke file ro ditolak: append justru berhasil\n'
else
PASS=$((PASS + 1))
printf 'OK tulis ke file ro ditolak\n'
fi
assert_eq 'isi awal' "$(cat latihan/kunci.txt)" "isi file ro tidak berubah"
chmod 644 latihan/kunci.txt
echo 'tambahan' >> latihan/kunci.txt
assert_eq 0 "$?" "tulis diizinkan kembali setelah mode rw"
# --- ringkasan ----------------------------------------------------------------
if [ "$FAIL" -eq 0 ]; then
printf 'PASSED: %d/%d test lulus\n' "$PASS" "$PASS"
else
printf 'FAILED: %d/%d test lulus\n' "$PASS" "$((PASS + FAIL))"
fi
[ "$FAIL" -eq 0 ]