Guida Runit: come funzionano gli stage e i file rc.*

hackynonpointer

Utente Gold
22 Febbraio 2022
479
44
369
348
Ultima modifica da un moderatore:
gnulinux_art_1.jpg









1    Introduzione

runit e' un init system pensato per essere leggero e' semplice, e' il primo processo che viene avviato e il suo compito e' quello di avviare tutti i processi che permettono il corretto funzionamento del sistema (servizi di rete, schermata di login...):
Bash:
void :: ~ » ps aux | grep run
root         1  0.0  0.0   1004   512 ?        Ss   13:01   0:00 runit
Per noi un init system e' un programma "speciale" in quanto e' un punto cardine durante la fase di boot ma non ha niente di diverso dagli altri processi, viene eseguito come tutti gli altri programmi.

E' disponibile per sistemi GNU/Linux, *BSD, MacOSX e Solaris, e' l'init preimpostato su void e artix GNU/Linux, su altre distro come devuan o antix e' possibile sceglierlo durante la fase d'installazione.

1.1    Il funzionamento di runit

Approfondendo quanto detto nell'introduzione possiamo affermare che runit e' il primo processo che il kernel avvia all'interno dello user-space, in seguito attraverso lo stage 1/2 avviera' altri processi quindi possiamo dire che ogni processo all'interno dello user-space e' figlio del processo padre runit.

Dobbiamo sapere che ogni servizio ha associato una relativa directory di servizio (service directory) che contiene i seguenti file/directory:
  • check: script che verifica se il servizio e' attivo e disponibile (se il controllo termina con 0 e' ok)
  • conf: un file che contiene variabili e altre configurazioni da usare durante l'esecuzione del servizio
  • log (dir):
  • supervise:
  • run: script bash che avvia effetivamente il servizio
i servizi disponibili sono presenti in /etc/sv/<service>:
Bash:
void :: ~ » ll /etc/sv/
total 136K
drwxr-xr-x 3 root root 4.0K Apr 25 17:29 mdadm
drwxr-xr-x 3 root root 4.0K Aug 18 19:14 polkitd
drwxr-xr-x 3 root root 4.0K Aug 18 19:14 sshd
drwxr-xr-x 2 root root 4.0K Aug 18 19:14 sulogin
drwxr-xr-x 3 root root 4.0K Apr 25 17:29 udevd
[...]
per esempio nella directory relativa a sshd sono presenti i file:
Bash:
void :: ~ » ll /etc/sv/sshd
total 8.0K
drwxr-xr-x 2 root root 4.0K Aug 18 19:14 log
-rwxr-xr-x 1 root root  156 Jul  1 12:44 run
lrwxrwxrwx 1 root root   25 Jul  1 12:44 supervise -> /run/runit/supervise.sshd

La directory /etc/runit/runsvdir/default contiene tutti i servizi che sono in esecuzione sul sistema sottoforma di symlink a /etc/sv/:
Bash:
void :: ~ » ll /etc/runit/runsvdir/default
total 0
lrwxrwxrwx 1 root root 13 Jul  9  2023 acpid -> /etc/sv/acpid
lrwxrwxrwx 1 root root 19 Aug 18 19:14 agetty-tty1 -> /etc/sv/agetty-tty1
lrwxrwxrwx 1 root root 19 Aug 18 19:14 agetty-tty2 -> /etc/sv/agetty-tty2
lrwxrwxrwx 1 root root 19 Aug 18 19:14 agetty-tty3 -> /etc/sv/agetty-tty3
lrwxrwxrwx 1 root root 19 Aug 18 19:14 agetty-tty4 -> /etc/sv/agetty-tty4
lrwxrwxrwx 1 root root 19 Aug 18 19:14 agetty-tty5 -> /etc/sv/agetty-tty5
lrwxrwxrwx 1 root root 19 Aug 18 19:14 agetty-tty6 -> /etc/sv/agetty-tty6
lrwxrwxrwx 1 root root 14 Jul  9  2023 dhcpcd -> /etc/sv/dhcpcd
lrwxrwxrwx 1 root root 12 Jul  9  2023 sshd -> /etc/sv/sshd
lrwxrwxrwx 1 root root 13 Aug 18 19:14 udevd -> /etc/sv/udevd
lrwxrwxrwx 1 root root 20 Apr 26 19:39 vboxservice -> /etc/sv/vboxservice/
lrwxrwxrwx 1 root root 22 Jul  9  2023 wpa_supplicant -> /etc/sv/wpa_supplicant
La directory /var/service e' un symlink a /etc/runit/runsvdir/default, usiamo questa per accedervi direttamente.

1.2    Gestione dei servizi

i servizi vengono gestiti attraverso una suite di programmi quali:
  • runsvdir (i.e. run service directory)
  • runsvchdir
  • runsv (i.e. run service)
  • sv
  • e altri che non tratteremo (guardare la wiki ufficiale per qualunque cosa)
il compito di runsvdir (come dice la doc) e' "starts and monitors a collection of runsv(8) processes" ossia avvia e monitora un processo runsv per ogni sottodirectory nella directory dei servizi.
Cattando lo stage 2 alle righe 18-21:
Bash:
    18    ln -s /etc/runit/runsvdir/current /run/runit/runsvdir/current
    19
    20    exec env - PATH=$PATH \
    21       runsvdir -P /run/runit/runsvdir/current 'log:
vediamo che /run/runit/runsvdir/current e' un symlink a /etc/runit/runsvdir/current quindi runsvdir avvia un processo runsv per ogni sottodirectory presente in /etc/runit/runsvdir/current:
Bash:
void :: ~ » ps aux | grep run
root         1  0.0  0.0   1004   512 ?        Ss   21:55   0:00 runit
root       813  0.0  0.0   2540  1152 ?        Ss   21:55   0:00 runsvdir -P /run/runit/runsvdir/current log: ...........................................................................................................................................................................................................................................................................................................................................................................................................
root       820  0.0  0.0   2388  1280 ?        Ss   21:55   0:00 runsv agetty-tty4
root       821  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv agetty-tty3
root       822  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv wpa_supplicant
root       823  0.0  0.0   2388  1280 ?        Ss   21:55   0:00 runsv agetty-tty6
root       824  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv vboxservice
root       825  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv dhcpcd
root       826  0.0  0.0   2388  1280 ?        Ss   21:55   0:00 runsv sshd
root       827  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv udevd
root       828  0.0  0.0   2388  1280 ?        Ss   21:55   0:00 runsv agetty-tty2
root       829  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv acpid
root       830  0.0  0.0   2388  1152 ?        Ss   21:55   0:00 runsv agetty-tty1
root       831  0.0  0.0   2388  1280 ?        Ss   21:55   0:00 runsv agetty-tty5
hacky     7333  0.0  0.0   6604  2432 pts/0    S+   22:22   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox run

ogni directory presente in /etc/runit/runsvdir definisce un runlevel, di base void ha due runlevel:
  • default
  • single
  • current (symlink a default)
ogni runlevel definisce lo stato del sistema dopo il suo avvio. current si riferisce al runlevel corrente, nel nostro caso e' default:
Bash:
hacky@void ~ $ ll /etc/runit/runsvdir/current
lrwxrwxrwx 1 root root 7 Apr 25 16:30 /etc/runit/runsvdir/current -> default
per approfondire questo aspetto guardare la doc
single ha un solo servizio al suo interno:
Bash:
hacky@void ~ $ ll /etc/runit/runsvdir/single
total 0
lrwxrwxrwx 1 root root 15 Aug 18 19:14 sulogin -> /etc/sv/sulogin
hacky@void ~ $ cat /etc/sv/sulogin/run
#!/bin/sh
[ -r conf ] && . ./conf
read -r tty < /sys/class/tty/console/active
tty=/dev/${tty##* }
exec setsid sulogin ${OPTS:=-p} < $tty >$tty 2>&1
sulogin sta per "single-user login" e manda il sistema in modalita single-user.

runsvchdir cambia la directory dei servizi di runsvdir, quindi possiamo dire che cambia il runlevel corrente e lo imposta su un altro.

runsv si aspetta il nome della directory del servizio e lancia il file run contenuto dentro essa (eg runsv wpa_supplicant), se run termina e finish esiste runsv avvia finish, nel caso anche finish terminasse o non esistesse viene riavviato run.
se entrambi terminano runsv aspetta un secondo prima di lanciare finish o riavviare run.
runsv viene controllato tramite sv, il quale puo' arrestare, riavviare o richiedere infomarzioni di qualunque servizio in esecuzione:
Bash:
sv up <services>
sv down <services>
sv restart <services>
sv status <services>
ad esempio per visualizzare lo status di un servizio o di tutti i servizi possiamo fare:
Bash:
void :: ~ » sudo sv status wpa_supplicant
run: wpa_supplicant: (pid 855) 447s; run: log: (pid 851) 447s
void :: ~ » sudo sv status /var/service/*
run: /var/service/acpid: (pid 860) 447s; run: log: (pid 859) 447s
run: /var/service/agetty-tty1: (pid 850) 447s
run: /var/service/agetty-tty2: (pid 845) 447s
[...]
per abilitare un servizio creiamo un symlink che parte da /etc/sv/<service> a /var/service:
Bash:
ln -s /etc/sv/<service> /var/service/    # abilita un servizio
rm /var/service/<service>                # rimuove un servizio

2    Gli stage nel dettaglio

Questo capitolo tratta il funzionamento e l'analisi del codice sorgente degli stage, essi dettano l'avvio del sistema (1), l'uso (2) e lo spegnimento (3).
Sono semplicemente 3 file sh situati nella directory /etc/runit/:
Bash:
void :: ~ » ll /etc/runit
total 40K
-rwxr-xr-x 1 root root  629 Jun 10 19:08 1
-rwxr-xr-x 1 root root  887 Jun 10 19:08 2
-rwxr-xr-x 1 root root  203 Jun 10 19:08 3
[...]

2.1    Stage 1

Passiamo all'analisi dello stage 1:
Il suo compito e' quello di avviare i core-services, i.e. quei servizi che consentono il corretto avvio del sistema base, ad esempio il file swap.sh (contenuto in /etc/runit/core-services) inizializza la partizione swap:

continuamo analizzando la prima parte di codice (righe 1-8):
specifica la shell da usare, la variabile PATH (la imposta su /usr/bin:/usr/sbin) e include il file functions presente in /etc/runit dove sono presenti una serie di funzioni sh usate all'interno dello stage.
infine stampa a schermo il messaggio "Welcome to Void" usando la funzione msg:
Bash:
msg() {
    # bold
    printf "\033[1m=> $@\033[m\n"
}

andando verso la fine il codice diventa un pelo piu' complicato ma niente di troppo difficile (per la spiegazione della riga 10 andare al capitolo ''I file rc.*"):
detect_virt e' una funzione che rileva eventuali cointaners; e' qui dove effetivamente vengono avviati i core-services, il ciclo for scorre in tutta la directory /etc/runit e salva in f ogni file con ext .sh e poi li avvia uno alla volta.

le righe 18-23:
creano un log file contenente l'output di dmesg e ne cambia i permessi in base al valore di sysctl -n kernel.dmesg_restrict, quando questo e' impostato a 1 solo l'utente root puo' leggere i messaggi del kernel, difatti quando proviamo a lanciare dmesg con l'utente normale uscira': dmesg: read kernel buffer failed: Operation not permitted
settando i permessi su 600 (read-write per l'owner, NULL per group e others) si assicura che nessuno oltre a root possa accedere al log file di dmesg:
Bash:
hacky@void ~ $ ll /var/log/dmesg.log
-rw------- 1 root root 59K Sep 15 19:10 /var/log/dmesg.log
hacky@void ~ $ cat /var/log/dmesg.log
cat: /var/log/dmesg.log: Permission denied
questa cosa ha senso perche' non mi fai usare dmesg direttamente da cli ma mi fai guardare il contenuto del file log? nel caso sysctl -n kernel.dmesg_restrict fosse impostato a 0 i permessi vengono settati su 644 (read-write per l'owner e solo read per group e others) consentendo all'utente base di leggerlo.

l'ultima parte del codice:
crea la directory /run/runit e due file per il controllo di runit: stopit e reboot.

2.2    Stage 2

Lo stage 2 viene avviato subito dopo il primo e resta in esecuzione fino allo spegnimento della macchina, quindi finche' non viene avviato lo stage 3:
(per la spiegazione della riga 14 andare al capitolo "I file rc.*") partiamo spiegando il ciclo for:
imposta la variabile runlevel a default e grazie al for fa un check dove per ogni argomento contenuto nell'output di $(cat /proc/cmdline) (output: BOOT_IMAGE=/boot/vmlinuz-6.6.51_1 root=UUID=1ffaf827-329b-4c81-b7fc-eb9db17f41c7 ro loglevel=4) verifica se esiste una directory con quegli argomenti, nel caso esista la variabile runlevel viene impostata a quel valore e il ciclo for conclude.
l'operatore logico -d verifica se la directory /etc/runit/runsvdir/"$arg" esiste.

crea la directory /run/runit/runsvdir e crea un symlink con /etc/runit/runsvdir/current, infine con:
avvia i servizi usando runsvdir.

2.3    Stage 3

Lo stage 3 viene eseguito durante la fase di spegnimento della macchina:
le righe 4-9 le abbiamo viste in precedenza (per la spiegazione della riga 10 andare al capitolo ''I file rc.*"), anche il ciclo for ormai dovrebbe essere familiare, esegue tutti i file salvati nella directory /etc/runit/shutdown.d/:
Bash:
void :: ~ » ll /etc/runit/shutdown.d
total 36K
-rw-r--r-- 1 root root  90 Jun 10 19:08 10-sv-stop.sh
-rw-r--r-- 1 root root  44 Jun 10 19:08 20-rc-shutdown.sh
-rw-r--r-- 1 root root  98 Jun 10 19:08 30-seedrng.sh
-rw-r--r-- 1 root root 137 Jun 10 19:08 40-hwclock.sh
-rw-r--r-- 1 root root   8 Jun 10 19:08 50-wtmp.sh
-rw-r--r-- 1 root root  91 Jun 10 19:08 60-udev.sh
-rw-r--r-- 1 root root 148 Jun 10 19:08 70-pkill.sh
-rw-r--r-- 1 root root 338 Jun 10 19:08 80-filesystems.sh
-rw-r--r-- 1 root root 349 Jun 10 19:08 90-kexec.sh
il primo ad essere eseguito e' 10-sv-stop.sh il quale stoppa tutti i servizi in esecuzione:
Bash:
void :: ~ » cat -n /etc/runit/shutdown.d/10-sv-stop.sh
     1    msg "Waiting for services to stop..."
     2    sv force-stop /var/service/*
     3    sv exit /var/service/*
di conseguenza tutti gli altri scripts verranno eseguiti dopo l'interruzione dei servizi.

3    I file rc.conf, rc.local. rc.shutdown

Si trovano rispettivamente in /etc, rc.conf e' usato per settare variabili (eg KEYMAP e FONT) ed e' generato nello stage 1 e 3, cattando /etc/runit/1 e /etc/runit/3 notiamo alle righe 10 (1) e 8 (3):
Bash:
    10    [ -r /etc/rc.conf ] && . /etc/rc.conf
il [-r] e' un operatore logico e controlla se lo script esiste e dopo di che' lo esegue.

rc.local esegue in ordine i comandi che noi scriviamo al suo interno nello stage 2 prima che i servizi siano eseguiti:
Bash:
void :: ~ » cat /etc/rc.local
#!/bin/sh
# Default rc.local for void; add your custom commands here.
#
# This is run by runit in stage 2 before the services are executed
# (see /etc/runit/2).
/etc/runit/2 riga 14:
Bash:
    14    [ -x /etc/rc.local ] && /etc/rc.local
Il [-x] (exist) e' un operatore logico che esegue lo script in un ambiente controllato per verificare la sua esistenza e dopo di che' lo esegue, quindi runit verifica che lo script rc.local esista e se esiste lo esegue;
Questo e' dovuto dal fatto che in origine la shell unix non usava gli operatori logici "standard" (eg -gt corrisponde a >).

rc.shutdown e' l'opposto di local, esegue in ordine i comandi che li diamo allo shutdown della macchina dopo che i servizi sono stati interrotti:
Bash:
void :: ~ » cat /etc/rc.shutdown
#!/bin/sh
# Default rc.shutdown for void; add your custom commands here.
#
# This is run by runit in stage 3 after the services are stopped
# (see /etc/runit/3).
/etc/runit/3 riga 10-11-12:
Bash:
    10    for f in /etc/runit/shutdown.d/*.sh; do
    11        [ -r $f ] && . $f
    12    done
il ciclo for scorre in tutta la directory /etc/runit/shutdown.d/ e salva in f ogni file e con [-r $f] && $f li avvia, tra questi c'e' anche /etc/runit/shutdown.d/20-rc-shutdown.sh:
Bash:
[ -x /etc/rc.shutdown ] && /etc/rc.shutdown

4    Conclusione

in questo articolo abbiamo visto come funziona runit, cosa fanno gli stage 1, 2, 3 analizzando il loro codice e visto i file rc.*, in futuro usciranno altri articolo inerenti a void linux dove andremo a guardare sempre piu' aspetti (come xbps, partizionamento dei dischi...) quindi restate connessi:orgoglio:
si consiglia di dare una letta anche agli articoli precedenti: introduzione a void linux