Dispense prodotte dal LIFO (Laboratorio Informatico Free ed Open)
Transcript
Dispense prodotte dal LIFO (Laboratorio Informatico Free ed Open)
Dispense prodotte dal LIFO (Laboratorio Informatico Free ed Open) 27 novembre 2004 2 Indice 1 Introduzione a Linux 1.1 Che cos’é Linux . . . . . . . . . . . . . . . . . 1.2 Concetti di base sul funzionamento di Linux . 1.3 Introduzione, struttura di base di X e avvio . 1.4 Programmi di uso comune . . . . . . . . . . . 1.4.1 Avvio di programmi . . . . . . . . . . 1.4.2 Operazioni sulle finestre . . . . . . . . 1.4.3 Personalizzazione dell’ambiente . . . . 1.4.4 Operazioni sui file . . . . . . . . . . . 1.4.5 Lavoro d’ufficio . . . . . . . . . . . . . 1.4.6 Internet . . . . . . . . . . . . . . . . . 1.4.7 Multimedia . . . . . . . . . . . . . . . 1.5 Conclusioni . . . . . . . . . . . . . . . . . . . 1.6 Cenni generali sul concetto di shell . . . . . . 1.7 La shell Bash . . . . . . . . . . . . . . . . . . 1.7.1 Inserimento ed esecuzione dei comandi 1.7.2 Parametri . . . . . . . . . . . . . . . . 1.7.3 Caratteri speciali . . . . . . . . . . . . 1.7.4 Protezione dei caratteri speciali . . . . 1.7.5 Variabili di shell e di ambiente . . . . 1.7.6 Alias . . . . . . . . . . . . . . . . . . . 1.7.7 Pipe e redirezione . . . . . . . . . . . 1.8 Lista di alcuni comandi di frequente utilizzo . 1.9 Approfondimenti sulla shell . . . . . . . . . . 1.10 Bibliografia di uso generale . . . . . . . . . . 1.10.1 Help forniti con Linux . . . . . . . . . 1.10.2 Internet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 I Processi 2.1 Cenni generali sui processi . . . . . . . . . . . . . 2.2 Situazione dei processi: /proc e comandi associati 2.2.1 Visualizzare la situazione dei processi . . 2.3 Comunicazione fra processi: i segnali . . . . . . . 2.4 Processi e shell . . . . . . . . . . . . . . . . . . . 2.4.1 Avvio di un job sullo sfondo . . . . . . . . 2.4.2 Invio di segnali ad un job in primo piano 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 8 9 10 10 10 11 11 11 11 11 11 12 12 13 14 15 16 18 19 20 22 23 23 23 23 . . . . . . . 25 25 26 27 28 30 30 30 4 INDICE 3 Dispensa sugli script 3.1 Informazioni preliminari sulla programmazione 3.2 Gli script della shell . . . . . . . . . . . . . . . 3.2.1 Il primo script . . . . . . . . . . . . . . 3.2.2 Parametri . . . . . . . . . . . . . . . . . 3.2.3 Variabili . . . . . . . . . . . . . . . . . . 3.2.4 Costrutto if . . . . . . . . . . . . . . . . 3.2.5 Costrutto while . . . . . . . . . . . . . . 3.2.6 Ciclo for . . . . . . . . . . . . . . . . . . 3.3 Per avere maggiori informazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 33 35 35 36 38 38 41 41 42 4 Boot del sistema 4.1 Boot di sistema . . . . . . . . . . . . . . . . . 4.2 Boot Loader . . . . . . . . . . . . . . . . . . . 4.2.1 LILO . . . . . . . . . . . . . . . . . . 4.2.2 Grub . . . . . . . . . . . . . . . . . . . 4.2.3 Boot magic . . . . . . . . . . . . . . . 4.2.4 NT loader . . . . . . . . . . . . . . . . 4.3 System V init . . . . . . . . . . . . . . . . . . 4.3.1 Cenni generali . . . . . . . . . . . . . 4.3.2 Struttura di /etc/inittab . . . . . . . . 4.4 Script di avvio . . . . . . . . . . . . . . . . . 4.4.1 Cambiamento del livello di esecuzione 4.4.2 rc.d/ . . . . . . . . . . . . . . . . . . . 4.5 BSD (Non-SysV) init . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 43 44 45 45 45 45 46 46 46 49 49 49 51 5 Introduzione a VIM 5.1 Perché vi(m) . . . . . . . . . . . . . . 5.2 Le modalità . . . . . . . . . . . . . . . 5.3 La modalità riga di comando . . . . . 5.3.1 Principali “comandi Ex” . . . . 5.3.2 Ricerca e sostituzione . . . . . 5.4 La modalità comando . . . . . . . . . 5.4.1 Comandi più utilizzati . . . . . 5.5 Le modalità inserimento e sostituzione 5.6 Per approfondire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 53 54 54 55 55 55 56 56 56 6 Archiviazione e pacchetti 6.1 Archiviazione e compressione 6.1.1 tar(1) . . . . . . . . 6.1.2 gzip(1) . . . . . . . . 6.1.3 bzip2(1) . . . . . . . 6.1.4 zip(1) . . . . . . . . 6.1.5 compress(1) . . . . . 6.1.6 cpio(1) . . . . . . . . 6.1.7 Ulteriori informazioni 6.2 I pacchetti rpm . . . . . . . . 6.2.1 Struttura . . . . . . . 6.2.2 Utilizzo di base . . . . 6.2.3 Funzionalità avanzate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 57 57 58 58 59 59 59 59 59 59 59 60 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . INDICE 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 63 64 64 64 64 64 64 65 65 65 66 66 66 66 67 67 67 67 67 68 7 Introduzione alla sicurezza 7.1 Definizione di sicurezza in ambito informatico 7.2 Caratterizzazione degli attaccanti . . . . . . . 7.3 Tipologie di attacco . . . . . . . . . . . . . . 7.3.1 Dall’esterno della rete locale . . . . . . 7.3.2 Man in the middle . . . . . . . . . . . 7.3.3 Interno della rete . . . . . . . . . . . . 7.3.4 Sé stessi . . . . . . . . . . . . . . . . . 7.4 Tecniche di difesa . . . . . . . . . . . . . . . . 7.4.1 Policy di sicurezza . . . . . . . . . . . 7.4.2 Paradigmi di sicurezza . . . . . . . . . 7.5 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 69 70 70 70 71 71 71 71 72 72 73 6.3 6.4 6.5 6.6 6.7 6.8 6.2.4 Creazione . . . . . . . . . . . . . 6.2.5 Ulteriori informazioni . . . . . . I pacchetti deb . . . . . . . . . . . . . . 6.3.1 Struttura . . . . . . . . . . . . . 6.3.2 Utilizzo di base . . . . . . . . . . 6.3.3 Ulteriori informazioni . . . . . . I pacchetti slackware . . . . . . . . . . . 6.4.1 Struttura . . . . . . . . . . . . . 6.4.2 Utilizzo di base . . . . . . . . . . 6.4.3 Funzionalità avanzate . . . . . . 6.4.4 Creazione . . . . . . . . . . . . . 6.4.5 Ulteriori informazioni . . . . . . I pacchetti con i sorgenti . . . . . . . . . 6.5.1 Struttura . . . . . . . . . . . . . 6.5.2 Utilizzo di base . . . . . . . . . . Altri tipi di pacchetti . . . . . . . . . . . 6.6.1 Eseguibili installanti . . . . . . . 6.6.2 Net installer . . . . . . . . . . . . 6.6.3 pacchetti jar . . . . . . . . . . . Glossario . . . . . . . . . . . . . . . . . La struttura convenzionale del filesystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 INDICE Capitolo 1 Introduzione a Linux di Carlo Pinciroli c 2002 Carlo Pinciroli. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. 1.1 Che cos’é Linux Linux è un sistema operativo derivato da UNIX, e perciò conserva molte delle caratteristiche di quest’ultimo, tra cui le più importanti sono: La multiutenza: più utenti possono accedere al sistema (che può essere formato da un terminale o da una rete di terminali) indipendentemente l’uno dall’altro; in altri termini l’attività di un utente non può compromettere l’attività di un altro utente (ad esempio l’utente “gianni” non può cancellare i dati di “pinotto” né modificarli, senza il consenso di “pinotto”). Per garantire tutto questo, il sistema operativo prevede dei veri e propri diritti (di lettura, scrittura ed esecuzione) da applicare a file e directory: in questo modo chi non possiede i diritti necessari non può intervenire sul file o sulla directory che vorrebbe. Nessun utente normale può modificare i file di configurazione e di avvio del sistema, per evidenti ragioni di sicurezza: è dunque necessaria l’esistenza di un utente speciale, denominato root (in inglese “radice”), che ha diritti illimitati sul sitema: l’utente root funge quindi da amministratore. Una conseguenza diretta di tutto ciò è la necessità di identificare l’utente al lavoro, al fine di definire i suoi diritti sul sistema: questa identificazione viene effettuata all’atto del login, senza il quale non si può avere accesso alle risorse del sistema. Durante il login vengono richiesti nome utente e password. Il multitasking: da task, in inglese “compito”. È la possibilità di eseguire più programmi contemporaneamente, senza che un programma possa influenzarne un altro: il blocco di un prorgamma in Linux (e in UNIX, naturalmente) non pregiudica il funzionamento di tutto il sistema come di norma accade in Windows quando appaiono le famose schermate blu. Per questo Linux è considerato un sistema stabile. 7 8 CAPITOLO 1. INTRODUZIONE A LINUX Nonostante storicamente Linux interagisse con l’utente per lo più attraverso la console (l’analogo del “Prompt di MS-DOS” di Windows), esiste anche un ambiente grafico, simile a quello Microsoft, denominato X Window. Può avere diversi aspetti grafici e diversi modi di essere utilizzato, perchè diversi sono i gestori delle finestre (Window Manager in inglese), tra i quali spiccano per fama e semplicità d’uso KDE e GNOME. Una delle più famose e, al tempo stesso, positive caratteristiche di Linux è la libertà di distribuire i sorgenti del sistema operativo (ovvero il sistema operativo non ancora eseguibile, quindi modificabile a piacimento dall’utente al fine di porre migliorie o correggere eventuali errori), e quindi di copiare sul proprio computer Linux semplicemente scaricandolo da Internet. Dal momento però che la grandezza media di un download di questo tipo è piuttosto consistente, e che la scelta delle parti da scaricare e poi installare è vasta e piuttosto difficoltosa, alcune aziende hanno pensato di fornire su CD-ROM il sistema operativo già pronto da installare con le parti del sistema necessarie, i programmi di uso più comune e una procedura di installazione e configurazione semplificata. Queste distribuzioni su CD-ROM (i cui nomi sono, per citarne alcune: RedHat, Mandrake, SuSe, Debian, Slackware. . . ) sono facilmente reperibili, a bassissimo prezzo (spesso meno di 5 euro) e differiscono fra di loro per l’hardware supportato, la scelta dei programmi e la posizione di certi file di configurazione del sistema. 1.2 Concetti di base sul funzionamento di Linux Il filesystem è il complesso dei meccanismi che gestiscono file e directory del sistema: attraverso di esso, è possibile creare, modificare, cancellare e spostare file e directory. Cosı̀come Windows possiede un filesystem, anche Linux possiede il proprio. In Linux però il concetto di file è piuttosto particolare, perché è molto più astratto che in Windows. Infatti si usa dire che in Linux “tutto è un file”, ovvero qualunque parte fisica o logica del computer (che sia un disco, una scheda sonora o di rete, un modem, uno schermo, ecc.) viene gestita dal sistema come se fosse un insieme di file. Ad esempio, per stampare un documento, in effetti Linux scrive su un file che rappresenta la stampante; è poi il “sottobosco” del sistema che si occupa di tradurre questa brutale scrittura in segnali comprensibili alla periferica. In altre parole, Linux vede tutto come se fosse un file. Per poter gestire una periferica, bisogna quindi far sı̀che l’insieme dei file relativi alla periferica stessa faccia parte della struttura delle directory di Linux: questa operazione di aggiunta di file a quelli di Linux è detta montaggio (in inglese mounting). Quindi, se una cosa non è montata, Linux non la vede, e quindi non può essere utilizzata. Un esempio evidente di tutto questo si ottiene quando si cerca di tradurre un indirizzo di un file sotto Windows (ad es. C:\WINDOWS\WIN.INI) in uno per Linux. Tanto per cominciare, sotto Linux non esiste l’espressione A: o C:, perché le varie unità a disco fanno parte della struttura delle directory, se sono già montate. Il montaggio di un’unità a disco fa sı̀che l’intero contenuto del disco appaia come una sottodirectory, quindi ad esempio se l’hard disk C: è stato montato nella directory /mnt/winc, l’indirizzo in formato Linux sarà /mnt/winc/windows/win.ini Attenzione: Linux è case sensitive quando tratta i nomi di file, quindi WIN.INI e win.ini sono due file differenti. La struttura delle directory di Linux è abbastanza standard (alcune sottodirectory cambiano da distribuzione a distribuzione) 1.3. INTRODUZIONE, STRUTTURA DI BASE DI X E AVVIO 9 • /home : qui vengono messi i file personali degli utenti. Ogni utente ha una sua sottodirectory in cui può fare ciò che vuole: ad esempio “gianni” ha /home/gianni e “pinotto” ha /home/pinotto. L’analogo Windows è più o meno C:\Documenti. • /root : la directory personale di “root” • /etc : contiene molti file di configurazione del sistema e di vari programmi. Un pò come C:\Windows\Command o C:\Windows\System. • /usr : contiene i file dei programmi installati, i manuali e molto altro. Più o meno come la C:\Programmi. • /dev : contiene tutti i file che rappresentano una periferica, ad esempio – /dev/hda è l’hard disk primario (il C: di windows, di solito) – /dev/hdb è il drive secondario, slave di /dev/hda (quindi o un hard disk o un cdrom) – /dev/hda1 indica la prima partizione di C: – /dev/hda3 è la terza partizione di C: – /dev/ttsy0 è la porta COM1 – /dev/ttsy1 è la porta COM2 e così via. 1.3 Introduzione, struttura di base di X e avvio X è il più famoso sistema grafico per Unix, e quindi anche per Linux. Sono sinonimi i termini X, X Window e X Window System, mentre la dicitura “X Windows” è errata. La struttura di X è organizzata a strati, di cui il più basso rappresenta l’hardware del computer e il più alto le applicazioni. L’hardware (di solito solo tastiera + mouse + scheda video + schermo) è gestito da X attraverso un SERVER, ovvero un “servitore”, che ha il compito di offrire le funzionalità grafiche utilizzate poi dai CLIENT. I client (in italiano clienti) sono infatti programmi che usano l’ambiente grafico comunicando con il server X e facendo uso di librerie chiamate XLIB. Al fine di garantire la comunicazione fra server e client sono state predisposte delle regole di linguaggio, raccolte in un protocollo detto con scarsa fantasia PROTOCOLLO X. Il rapporto esistente fra server e client di X ricalca quello che sussiste in ambiente di rete, ed infatti il server X fra le funzionalità garantite ha anche quella di permettere una connessione da remoto. Per chiarire con un’immagine il rapporto che sussiste fra server e client, si può pensare che il server sia un venditore che tiene aperto il proprio negozio in attesa di clienti; che i clienti si presentino o meno, il negozio rimane aperto, ed appena un cliente si presenta viene subito servito se è possibile farlo. Il protocollo è in un certo senso la “lingua” (italiano, inglese. . . ) che viene utilizzata per comunicare dal negoziante e dal cliente. Una collezione di serventi grafici molto famosa è XFree86: ogni servente grafico è specifico per un tipo di scheda video, ma non mancano serventi grafici per schede video generiche. I serventi grafici possono quindi essere considerati come delle specie di “driver”, 10 CAPITOLO 1. INTRODUZIONE A LINUX per dirla con una similitudine con Windows, ma come abbiamo visto i server fanno molto di più di quello che fanno i driver in ambiente Microsoft. I client sono invece i programmi, che siano un gioco, Microsoft Office, o altro. Tra i programmi clienti fondamentali, una particolare menzione merita il gestore delle finestre (Window Manager). È un programma che mette a disposizione le funzionalità che l’utente necessita per poter usare il sistema grafico, quindi ad esempio una barra come la “Avvio” di Windows, le caselline per ridimensionare/spostare/chiudere le finestre, e oggetti simili. Tra i più famosi Window Manager ricordiamo KDE, Gnome, fvwm con le varianti fvwm2, e fvwm95-2, twm, Afterstep e IceWM. È importante però sapere che è possibile utilizzare X anche senza un gestore delle finestre, anche se questo fa parte di argomenti che esulano dalla trattazione attuale. Una delle differenze più sorprendenti che X ha nei confronti di Windows è data dal fatto che in X esistono degli schermi virtuali. Anche se lo schermo fisico (quello che si sta guardando) è uno soltanto, X permette di gestire più schermi e di passare da uno schermo ad un altro con la stessa facilità con cui si passa da un programma ad un altro. La quantità di schermi virtuali disponibili dipende dalle impostazioni che si sono adottate. Un’altra differenza con Windows, ma meno marcata, è la necessità di un mouse a tre tasti per X, quando invece Windows richiede solo un mouse a due tasti. Molte operazioni (fra cui quelle di copia-incolla) sono spesso legate al tasto centrale del mouse, e quindi non averlo costringe a configurare X in modo particolare, al fine di emulare il tasto centrale mancante tramite la pressione contemporanea dei due tasti del mouse. È possibile impostare il boot di linux per avviare X all’avvio, nel qual caso il login che si presenta è di tipo grafico. Altrimenti, se si esegue del lavoro in ambiente testuale (console), l’avvio di X si ottiene col comando startx. 1.4 Programmi di uso comune La quantità di applicativi per X è considerevole, e non è possibile né in questa sede né in futuro coprirne l’intera lista. Ci limiteremo a fornire le possibili alternative ai più famosi programmi per Windows offerti da X per Linux con KDE come window manager. Naturalmente anche Gnome o gli altri window manager offrono applicativi analoghi, alcuni dei quali sono comuni a tutti i window manager in quanto vengono forniti insieme a XFree86. Almeno per quanto riguarda KDE, l’utilizzo dei programmi è molto simile allo stile di Windows. Unica vera differenza, in Linux non esiste il “doppio clic”. 1.4.1 Avvio di programmi Le modalità sono le stesse di Windows: si può utlizzare la barra “K” oppure le icone sul desktop, ma senza fare uso del doppio clic! 1.4.2 Operazioni sulle finestre Sulla barra del titolo a destra ci sono tre icone, che come in Windows permettono rispettivamente di ridurre a icona, massimizzare e chiudere la finestra. A sinistra del titolo invece troviamo prima l’icona del programma a cui la finestra si riferisce: cliccando su di esso compare un menù che permette di fare le stesse 1.5. CONCLUSIONI 11 operazioni delle icone a destra. L’altra icona tra quella menzionata e il testo del titolo, se cliccata, fa sì che quella finestra compaia in tutti gli schermi virtuali. 1.4.3 Personalizzazione dell’ambiente È possibile effettuarla tramite il KDE Control Center, navigando fra i menù a tendina. 1.4.4 Operazioni sui file Konqueror può essere tranquillamente paragonato a “Esplora risorse” (o “Gestione risorse”) di Windows e ne ricalca le modalità di utilizzo. Per un analogo di Winzip ci si può rivolgere a GnoZip. xterm è un terminale testuale emulato. Quando si parlerà dei comandi della console, si vedrà l’utlità di poter accedere ad un terminale virtuale. Al momento ci limitiamo a paragonarlo al “Prompt di MS-DOS” di Windows. Una delle comodità di Windows in fase di installazione è data da InstallShield, e anche X offre delle funzionalità analoghe (ma non identiche),anche se diverse da distribuzione a distribuzione: nella RedHat e nella Mandrake sono offerti gli archivi RPM che si possono gestire con kpackage. 1.4.5 Lavoro d’ufficio Se quello di cui si ha bisogno è Office, Linux offre i notevoli OpenOffice.org e KOffice. Il primo dei due è fra l’altro identico al sosia sotto Windows ed è completamente compatibile con esso. Anche le modalità di utilizzo e i nomi delle opzioni sono praticamente gli stessi. 1.4.6 Internet L’Accesso remoto è “tradotto” in KPPP sotto Linux, il gestore della posta Outlook in KMail e i browser disponibili sono Netscape/Mozilla, Opera (disponibili sia per Windows che per Linux), Konqueror (come Explorer, per KDE) e Galeon (per Gnome). 1.4.7 Multimedia Per quanto riguarda la musica e gli mp3, xmms è il parente Unix di Winamp; la grafica è invece maneggiabile con programmi come gimp (fotoritocco), gqview, blender (grafica 3d); anche OpenOffice.org offre programmi di questo genere. 1.5 Conclusioni Per concludere, non è difficile convincersi che praticamente ogni cosa si possa fare con un programma a pagamento per Windows sia tranquillamente realizzabile in Linux con programmi Open Source. L’unica (relativa) falla di Linux è la scarsità di giochi, se paragonata alla considerevole quantità di videogames disponibili per la piattaforma Microsoft. 12 CAPITOLO 1. INTRODUZIONE A LINUX 1.6 Cenni generali sul concetto di shell Il programma più importante in un sistema operativo, dopo il kernel, è l’ambiente in cui è possibile interagire con l’hardware del computer. In realtà il dialogo con l’hardware è già garantito dal kernel, e a questo è dovuta la sua fondamentale importanza; quello che rimane all’utente è quindi il compito di impartire comandi al kernel. Questa attività è piuttosto complessa, e quindi si usa racchiudere il kernel stesso in una “conchiglia” (in inglese shell) con la quale l’utente interagisce effettivamente. In altre parole, la shell è l’ambiente che permette all’utente di interagire con il kernel mettendo a disposizione comandi comprensibili dall’uomo e interpretabili dal kernel. Una volta interpretati, i comandi vengono mandati all’hardware che si occupa di eseguirli. Una shell dunque può essere un sistema grafico, come Windows o X, oppure un ambiente testuale come DOS o la console di Linux; concettualmente non ci sono differenze fra ambiente grafico e testuale in termini di possibilità di interazione con il sistema: entrambi gli ambienti permettono un’efficace utilizzo della macchina, anche se la difficoltà di utilizzo dell’ambiente testuale e la sua versatilità sono spesso molto maggiori. 1.7 La shell Bash Bash è la shell standard di molti sistemi Unix fra cui anche Linux; è l’evoluzione della shell scritta da Stephen Bourne per Unix, chiamata appunto shell Bourne. Bash significa dunque Bourne Again SHell (in inglese “ancora shell Bourne”), ed associa le funzioni della shell Bourne alle funzioni di altre due shell piuttosto famose, la shell Korn (ksh) e la shell C (csh). L’eseguibile della shell Bash è /bin/bash ed è paragonabile a COMMAND.COM degli ambienti Microsoft. Può essere avviato in due modalità: • la modalità interattiva, che permette all’utente di inserire comandi. Viene quindi visualizzato un invito a digitare detto “prompt dei comandi”. • la modalità non interattiva, in cui vengono eseguiti gli script di shell (come i file batch di DOS) e i comandi esterni; gli script verranno trattati più avanti. In questa modalità non viene visualizzato alcun prompt. La shell in modalità interattiva, dunque, è ciò che si presenta quando abbiamo appena finito la procedura di login; sullo schermo appare un messaggio del tipo [pippo@localhost etc]$ _ in cui il prompt è formato da tutti i caratteri fino a $ compreso, mentre il trattino è il cursore lampeggiante che indica dove si sta per scrivere; la linea visualizzata è quindi una “riga di comando”. Il carattere $ si presenta quando l’utente al lavoro è un utente normale; se invece si sta lavorando come root, il carattere visualizzato è #. Non appena avviata, Bash legge i file di configurazione /etc/profile e /.bash profile (se quest’ultimo non esiste legge /.bash login; nel caso nemmeno questo esista, prova con /.profile; altrimenti rinuncia alla lettura). I 1.7. LA SHELL BASH 13 file menzionati contengono le impostazioni dell’ambiente di lavoro; ne parleremo più diffusamente tra qualche pagina, una volta introdotte le variabili. 1.7.1 Inserimento ed esecuzione dei comandi Un comando altro non è che una serie di parole da scrivere al prompt; la pressione del tasto Invio fa sı̀che la shell legga quanto è stato inserito e lo intepreti; una volta intepretato, il comando viene eseguito. I comandi che vengono eseguiti sono raccolti in una lista ordinata cronologicamente (detta “history dei comandi”) che permette di ritrovarli facilmente e di non doverli cosı̀riscrivere. Alcuni comandi sono forniti dalla shell stessa, e allora sono detti “comandi interni”. Ma la maggior parte dei comandi disponibili non è interna, bensı̀è un programma esterno alla shell che spesso risiede nella directory /bin o /sbin. Quando si eseguono i comandi esterni, Bash carica una nuova shell Bash in modo non interattivo ed è questa shell figlia ad eseguire il comando esterno. La shell prevede dei tasti speciali per eseguire particolari e comode funzioni: • Tab: premendo Tab, la shell cerca di completare quanto è stato scritto secondo lo schema seguente: – se la parola inizia con ˜ , la interpreta come un nome di un utente e cerca un utente che inizi con le lettere digitate; se lo trova completa la parola col nome trovato e la visualizza sulla riga di comando; – se la parola inizia con $, la interpreta come un nome di una variabile e si comporta come sopra; – altrimenti interpreta la parola come un nome di file, e completa seguendo le regole succitate. • Freccia su: richiama sulla linea l’ultimo comando digitato; premendo ancora freccia su, compare il penultimo e cosı̀via. • Freccia giù: richiama il comando successivo nella lista a quello visualizzato. • Freccia a destra / sinistra: permette di spostarsi a destra/sinistra tra i caratteri del comando attivo al fine di correggerlo più agevolmente. • Ins: fa passare dalla modalità di inserimento a quella di sovrascrittura. • Inizio / Fine: sposta il cursore all’inizio/fine della riga di comando Il modo più semplice di eseguire un comando è di scrivere il suo nome e premere Invio, ad esempio: [pippo@localhost etc]$ pwd [-> Invio] /home/pippo questo comando visualizza il nome completo della directory in cui si sta lavorando al momento (pwd significa print working directory). 14 CAPITOLO 1. INTRODUZIONE A LINUX 1.7.2 Parametri pwd non richiede particolari informazioni per poter svolgere il proprio lavoro, quindi è possibile eseguirlo nel modo visto. Ma ci sono comandi che invece necessitano di informazioni aggiuntive che l’utente può specificare facendo seguire al nome del comando una lista di altre stringhe (dette parametri o argomenti) separate l’una dall’altra da spazi. Ad esempio il comando rm (remove) cancella i file; eseguendo tale comando senza specificare argomenti: [pippo@localhost etc]$ rm [-> Invio] rm: too few arguments si riceve un messaggio di errore che esprime la necessità di ulteriori informazioni. Il modo corretto di eseguirlo è [pippo@localhost etc]$ rm pippo.txt [-> Invio] in questo modo si cancella il file pippo.txt dalla directory corrente. Una cosa da sapere è che un file, una volta cancellato, non è più recuperabile; il vecchio DOS offriva l’utility UNDELETE che permetteva di recuperare in qualche caso i file rimossi, ma su Linux la cancellazione non prevede questa possibilità. Una ragione in più per lavorare come utente normale e non come root, è appunto la certezza di non cancellare file importanti senza i quali il sistema sarebbe compromesso. In generale, quindi, la struttura di un comando è la seguente: nomecomando parametro1 parametro2 ... parametroN Abbiamo visto che un parametro può essere un nome di un file, ma i parametri possono essere utilizzati per specificare quale opzione di un comando si vuole utilizzare. Ad esempio, rm prevede un’opzione per attivare la richiesta di conferma di rimozione di un file al fine di prevenire cancellazioni indesiderate. Il comando con l’opzione appare così: [pippo@localhost etc]$ rm -i pippo.txt [-> Invio] rm: remove ’pippo.txt’ ? _ l’opzione -i (abbreviazione di interactive) fa sı̀che compaia un messaggio di conferma che aspetta una y o una n come risposta per procedere o annullare l’operazione. Dall’esempio visto, si notano due fatti: • le opzioni precedono i nomi di file • le opzioni si distinguono dai file per la presenza di un - iniziale nessuno dei due fatti menzionati è però una regola fissa, si tratta soltanto di una convenzione. rm prevede anche un’opzione -r che permette di cancellare i file specificati nella directory corrente e anche nelle sottodirectory. Volendo eseguire rm con entrambe le opzioni -i e -r è possibile raggrupparle come segue: [pippo@localhost etc]$ rm -ir pippo.txt [-> Invio] rm: remove ’pippo.txt’ ? _ 1.7. LA SHELL BASH 15 Poniamo ora il caso di essere nella directory /home/pippo, e di voler spostare il file trippa.gnam che si trova nella directory /home/pippo/tantafame, per metterlo nella directory corrente. Un modo per farlo è il seguente: [pippo@localhost pippo]$ mv /home/pippo/tantafame/trippa.gnam \ /home/pippo [-> Invio] il comando mv (move) sposta il file specificato come primo argomento nella directory specificata come secondo argomento. La sintassi usata è un po’ pesante, perchè abbiamo specificato l’intero percorso del file trippa.gnam e della directory corrente. Ma se ci troviamo in /home/pippo, è possibile sottintendere questa parte del percorso, scrivendo: [pippo@localhost pippo]$ mv tantafame/trippa.gnam /home/pippo \ [-> Invio] è da notare che il primo argomento non comincia per /, quindi viene interpretato da Bash come una sottodirectory di /home/pippo. Sarebbe comodo poter sottintendere anche /home/pippo nel secondo argomento, però una scrittura del tipo [pippo@localhost pippo]$ mv /home/pippo/tantafame/trippa.gnam \ [-> Invio] genera un errore in quanto mv vuole che la destinazione dello spostamento sia specificata. Allora si può utilizzare il carattere . che è il nome di un file che rappresenta la directory corrente. Ovvero, si può scrivere [pippo@localhost pippo]$ mv /home/pippo/tantafame/trippa.gnam . \ [-> Invio] e stavolta tutto funziona, perché viene intepretato da mv nel modo esatto. Se . è la directory corrente, .. è invece un file che punta alla directory madre di quella corrente. Nel nostro esempio, il comando [pippo@localhost pippo]$ mv /home/pippo/tantafame/trippa.gnam \ .. [-> Invio] sposta il file trippa.gnam dalla directory /home/pippo/tantafame alla directory /home. mv è usato anche per cambiare nome ai file (come il comando REN di DOS): [pippo@localhost tantafame]$ mv trippa.gnam cosciotto.slurp \ [-> Invio] questo comando, eseguito nella directory /home/pippo/tantafame, cambia nome al file trippa.gnam facendolo diventare cosciotto.slurp. 1.7.3 Caratteri speciali Nel caso in cui si volessero cancellare tutti i file di una directory, con le conoscenze attuali dovremmo eseguire rm specificando come parametri i nomi di tutti i file; la scomodità della procedura è evidente, e per questo la shell Bash prevede dei caratteri speciali che rendono molto più agevole la selezione di liste di file. I 16 CAPITOLO 1. INTRODUZIONE A LINUX caratteri speciali sono i seguenti: * (indica un insieme qualunque di caratteri), ? (è un segnaposto per un carattere qualunque), [...] (definisce un insieme di caratteri) e ˜ (è il nome della directory home). Alcuni esempi chiariranno l’uso dei caratteri speciali. Supponiamo che la directory corrente contenga i file pippo.txt, pluto.txt, paperino.jpg, lippo.dat e gippo.gif. Per cancellare tutti i file che cominciano per p si può scrivere [pippo@localhost etc]$ rm p* [-> Invio] La shell Bash interpreta p* come p seguito da una qualunque stringa, e i file pippo.txt, pluto.txt e paperino.jpg rispondono a questi requisiti. Quindi la shell, prima di mandare il comando in esecuzione, sostituisce p* con i nomi dei tre file indicati, trasformando il comando in rm pippo.txt pluto.txt paperino.jpg Il comando è ora pronto per essere eseguito. La sostituzione dei caratteri speciali non viene notificata dalla shell, cioé è fatta senza visualizzare alcun messaggio, quindi è bene controllare con attenzione che la stringa inserita generi la giusta lista di file. Per cancellare tutti i file di una directory, procedere come segue: [pippo@localhost etc]$ rm * [-> Invio] qui * sta ad indicare una stringa qualsiasi, quindi qualunque nome di file va bene e allora vengono cancellati tutti. Se si vuol cancellare i file gippo.gif, pippo.txt e lippo.dat, si può eseguire il comando [pippo@localhost etc]$ rm ?ippo* [-> Invio] in cui il carattere ? viene interpretato dalla shell come un qualsiasi carattere. Se invece si vuol cancellare solo gippo.gif e lippo.dat, ma non pippo.txt, si può utilizzare la forma [pippo@localhost etc]$ rm [g,l]ippo* [-> Invio] in cui [g,l] indica che la shell deve scegliere solo fra g e l la sostituzione di carattere. Sono possibili selezioni multiple come [a-f,z] che indica l’insieme dei caratteri a, b, c, d, e, f, z. Infine veniamo al carattere ˜: se è da solo viene sostituito dalla shell con il nome completo della directory home, oppure se è seguito da caratteri viene sostituito con la home directory dell’utente il cui nome è quello che segue ˜. Ad esempio, se in un sistema ci sono due utenti chiamati gianni e pinotto, e l’utente al lavoro è gianni, allora ˜ viene sostituito con /home/gianni e ˜pinotto viene sostituito con /home/pinotto. 1.7.4 Protezione dei caratteri speciali Può capitare di dover lavorare su un file il cui nome contiene dei caratteri speciali, ad esempio gi*gi, ma come sappiamo il carattere * viene trasformato da Bash. In realtà, la trasformazione va a buon fine solo se esistono file che cominciano e finiscono per “gi”; se nessun file risponde a questi requisiti, Bash rinuncia alla trasformazione e lascia la stringa gi*gi invariata. Perciò, se l’unico 1.7. LA SHELL BASH 17 file che risponde ai requisiti di cominciare e finire per “gi” è gi*gi, non ci sono equivoci: il file a cui ci si riferisce è quello desiderato. Ma nel caso in cui in una directory si trovino i file gi*gi e giangi, il comando [pippo@localhost etc]$ rm gi*gi [-> Invio] cancellerà solo giangi, perché la sostituzione di * è andata a buon fine. Una volta cancellato questo file l’equivoco scompare, ma se ogni volta che si presenta una situazione ambigua l’unica soluzione fosse di fare piazza pulita dei file che “coprono” gi*gi, la comodità della shell verrebbe meno. Per risolvere le ambiguità che coinvolgono i caratteri speciali è possibile “proteggerli” dalla trasformazione tramite un altro carattere speciale (sembra quasi un circolo vizioso, ma poi le cose funzionano), ovvero il carattere backslash. Per proteggere * basta quindi farlo precedere da backslash in questa maniera: [pippo@localhost etc]$ rm gi\*gi [-> Invio] In questo caso, la shell Bash non trasforma * ma lo lascia invariato, ed elimina la backslash; il comando diventa dunque rm gi*gi proprio come volevamo. E per cancellare un file di nome gi\gi? Basta proteggere \ facendolo precedere da un altro \, in questo modo: [pippo@localhost etc]$ rm gi\\gi [-> Invio] e l’intepretazione che Bash ne dà è rm gi\gi A ben guardare, anche il carattere “ ” (spazio) è speciale per la shell Bash: è infatti il carattere che separa fra loro gli argomenti di un comando. Può capitare però di dover lavorare con file il cui nome contiene uno spazio, ma naturalmente non si può digitare [pippo@localhost etc]$ rm ciao ciao come stai io bene [-> Invio] sperando che serva a cancellare il file ciao ciao come stai io bene. In questo caso la protezione che si può fare è la seguente: [pippo@localhost etc]$ rm ciao\ ciao\ come\ stai\ io\ bene \ [-> Invio] oppure, più intuitivamente e comodamente [pippo@localhost etc]$ rm "ciao ciao come stai io bene" \ [-> Invio] Nel caso in cui, infine, il file da cancellare fosse ’mi chiamo "giangi"’, il comando opportuno è [pippo@localhost etc]$ rm "mi chiamo \"giangi\"" [-> Invio] Se un comando è troppo lungo per stare su un’unica linea, è possibile andare alla linea successiva digitando backslash seguito da Invio. Questa procedura non dovrebbe stupire: il carattere backslash toglie ai caratteri speciali il loro significato speciale: quindi anche il tasto Invio, se preceduto da backslash viene privato della funzione di marcatore di fine comando. Un esempio: 18 CAPITOLO 1. INTRODUZIONE A LINUX [pippo@localhost etc]$ rm nelmezzodelcammin\ [-> Invio] dinostravita [-> Invio] questo cancella il file nelmezzodelcammindinostravita. 1.7.5 Variabili di shell e di ambiente Alcune informazioni che caratterizzano la sessione di lavoro (come la directory corrente, l’utente al lavoro, . . . ) sono immagazzinate in memoria sotto forma di variabili. Una variabile è una specie di “scatola” nella quale è possibile conservare un valore, sia numerico che testuale. Per distinguere fra loro le “scatole”, ovvero le variabili, si è pensato di dotarle di un nome. Le variabili di shell hanno validità solo nella shell con cui si sta lavorando. Ma sappiamo che quando si esegue uno script o un comando esterno viene aperta una nuova shell in modo non interattivo; perciò questa nuova shell non vedrà le variabili della shell madre. Però spesso si vuole che le variabili della shell su cui si lavora siano disponibili anche alle altre shell figlie aperte successivamente; allora è necessario trasformarle in variabili di ambiente. Le variabili di shell più importanti sono: • PATH: questa variabile contiene una lista di directory separate da : e serve alla shell per sapere dove trovare i comandi digitati. Quando si digita un comando, se il comando è interno l’esecuzione non richiede la lettura di PATH, ma se è esterno, la shell vuole sapere dove andare a cercarlo; allora legge la variabile PATH e prova ordinatamente a trovare il comando nella prima directory della lista, poi se non lo trova prova nella seconda e così via. Se il comando non viene trovato in nessuna delle directory, allora la shell emette un messaggio di errore (command not found). PATH di Linux è quindi come PATH di DOS. • HOME: contiene l’indirizzo della directory home dell’utente al lavoro (/home/gennaro ad esempio) • PS1: contiene il formato del prompt dei comandi, ovvero dell’invito a digitare che di solito appare nella forma [pippo@localhost etc]$ Modificando questa variabile, cambia l’aspetto del prompt; è simile alla variabile PROMPT di MS-DOS. • HISTSIZE: il numero massimo di comandi da conservare nella history (di solito 500 o 1000) • HISTFILE: il file in cui scrivere la history (di solito /.bash history) • PWD: la directory corrente • OLDPWD: l’ultima directory visitata prima di andare in quella attuale • BASH: il percorso dell’eseguibile della shell Bash, ovvero /bin/bash • LOGNAME: il nome con cui si è eseguito il login • USER: il nome dell’utente al lavoro Per dichiarare una variabile di shell di nome PIPPO e dargli un contenuto iniziale (ovvero in gergo inizializzarla), digitare 1.7. LA SHELL BASH 19 [pippo@localhost pippo]$ PIPPO="uncane" [-> Invio] Per farla diventare un variabile di ambiente si utilizza il comando export: [pippo@localhost pippo]$ export PIPPO [-> Invio] Ma si può fare tutto in una volta, anziché in due passaggi: [pippo@localhost pippo]$ export PIPPO="uncane" [-> Invio] Per visualizzare il contenuto di una variabile, usare il comando echo in questo modo: [pippo@localhost pippo]$ echo $PIPPO [-> Invio] uncane come si nota, per far capire alla shell che PIPPO non è il nome di un file ma il nome di una variabile si fa precedere PIPPO da un $. Se sulla riga di comando scriviamo [pippo@localhost pippo]$ echo $PI [-> Tab] seguito dalla pressione del tasto Tab, la shell completa la riga visualizzando [pippo@localhost pippo]$ echo $PIPPO cioè proprio come fa coi nomi di file. Per consultare la lista di tutte le variabili (che siano di shell o di ambiente) si può usare il comando set senza parametri, e per eliminare una variabile si deve digitare [pippo@localhost pippo]$ unset PIPPO Adesso possiamo riprendere brevemente il discorso sul contenuto di /etc/profile e /.bash profile: questi file contengono una serie di dichiarazioni di variabili di ambiente e alcuni comandi per configurare l’ambiente di lavoro. /etc/profile ha una configurazione che si applica a tutti gli utenti; /.bash profile invece contiene la configurazione personalizzata dell’utente. 1.7.6 Alias Chi utilizza il DOS, per visualizzare la lista dei file di una directory utilizza il comando dir; su Linux, questo comando non si chiama dir ma ls, e per chi è abituato a digitare dir questo può essere uno spiacevole inconveniente. È allora possibile definire un nuovo nome (detto alias) per il comando ls col comando alias: [pippo@localhost etc]$ alias dir="ls -l" [-> Invio] l’opzione -l (long listing) di ls fa sì che la lista visualizzata sia provvista di molti dettagli sui file, più o meno come la lista presentata da dir. Quando impartiamo il comando dir alla shell, la prima cosa che viene fatta è la sostituzione di dir con ls -l, e poi vengono eseguite tutte le sostituzioni dei caratteri speciali. Ovvero: [pippo@localhost etc]$ dir p* [-> Invio] 20 CAPITOLO 1. INTRODUZIONE A LINUX diventa ls -l pippo.jpg pluto.gif Per eliminare un alias, usare il comando unalias: [pippo@localhost etc]$ unalias dir [-> Invio] Se si vuole far sì che un alias sia impostato automaticamente dopo il login di Linux, bisogna semplicemente aggiungere il comando relativo nel file /.bash profile; non è consigliabile aggiungerlo al file /etc/profile perché quest’ultimo contiene i dettagli di configurazione validi per tutti gli utenti. 1.7.7 Pipe e redirezione Da quello che abbiamo visto finora, possiamo dire che ogni comando ha questa struttura concettuale di base: • riceve dell’input (attraverso i parametri o attraverso domande effettuate in fase di esecuzione, come la richiesta di conferma di rm -i) • emette dell’output (ad esempio presenta la lista dei file di una directory o mostra dei messaggi di errore) Di solito, dunque, la tastiera è l’input e lo schermo è l’output; in gergo informatico, potremmo dire che la tastiera è lo standard input e lo schermo è lo standard output. Sappiamo che su Linux “tutto è un file”, quindi non dovremmo stupirci di trovare dei file associati proprio allo standard input e allo standard output: e infatti, guardando nella directory /dev troviamo i tre file /dev/stdin, /dev/stdout e /dev/stderr. I primi due sono proprio i file che si riferiscono allo standard input e allo standard output; il terzo invece è lo standard error. Per convenzione, i comandi hanno due tipi di output: quello normale che passa attraverso lo standard output, e quello relativo ai messaggi di errore che passa invece per lo standard error. Quando viene eseguito, dunque, un comando legge caratteri dal file stdin, e scrive caratteri sul file stdout o stderr; ma nulla vieta di dirottare il flusso di caratteri verso il file stdout e di dirigerlo ad un file che vogliamo noi, perché sempre di file si tratta. È una procedura che almeno in teoria non dovrebbe generare errori. L’operazione di dirottare il flusso di caratteri in entrata o in uscita da un comando si chiama appunto redirezione, e si effettua usando dei caratteri speciali. Ad esempio, se vogliamo salvare su file la lista delle variabili di shell e di ambiente, dobbiamo redirezionare l’output di set in questo modo: [pippo@localhost pippo]$ set > lista.txt [-> Invio] La lista delle variabili non viene più visualizzata, ma nella directory è stato creato un file lista.txt che contiene proprio quello che volevamo. Il carattere > indica a set che non deve scrivere su sdtout, ma su lista.txt; > costringe set a sovrascrivere il file lista.txt, ma è possibile anche far sı̀che i caratteri vadano ad accodarsi alla fine del file lista.txt usando >>. Se invece vogliamo redirezionare lo standard error, dobbiamo utilizzare 2> nomefile, dove il 2 sta 1.7. LA SHELL BASH 21 ad indicare il secondo canale di output che è proprio quello di stderr. Se vogliamo redirezionare entrambi i flussi di output ad un unico file, possiamo utlizzare &>. Per quanto riguarda lo standard input, il carattere speciale da utilizzare per la redirezione è <. Ad esempio, è possibile ordinare il contenuto di un file col comando sort come segue: [pippo@localhost pippo]$ sort < elenco.txt [-> Invio] abaco babbione zuzzurellone In DOS, per generare la lista ordinata dei file di una directory esiste l’opzione /O del comando DIR. Il suo analogo Linux è il comando ls, come abbiamo già visto, che però non possiede questa opzione. Si potrebbe ovviare al problema usando il comando sort in questo modo: [pippo@localhost pippo]$ ls -l > elenco.txt [-> Invio] [pippo@localhost pippo]$ sort < elenco.txt [-> Invio] il primo comando crea un file di nome elenco.txt che contiene la lista dei file disordinata, il secondo comando visualizza sullo schermo la lista ordinata. È una soluzione un po’ troppo scomoda per risolvere un problema così frequente, e infatti Bash mette a disposizione una scorciatoia per effettuare questa operazione: la pipe. In inglese pipe significa “tubatura, condotto”; questo concetto è proprio quello che serve per risolvere il probema di prima. Infatti noi vogliamo che l’output disordinato di ls venga letto da sort per essere poi ordinato. Ovvero, vorremmo fare una “tubatura” che porta il flusso di caratteri (non d’acqua. . . ) da ls in sort. La soluzione manuale già vista, cioé quella di creare un file intermedio (elenco.txt) è un modo per creare questo condotto, ma le pipe eseguono questa operazione automaticamente. In pratica, si scrive: [pippo@localhost pippo]$ ls | sort [-> Invio] dove il carattere | rappresenta proprio un tubo stilizzato. Questo comando effettua tutte le operazioni che prima facevamo a mano, ovvero prende l’output di ls e lo scrive su un file di tipo speciale (detto tipo pipe, appunto) gestito con una politica a coda, cioè il primo dato scritto sul file è il primo dato che viene letto poi da sort: come la coda alla posta! Una volta completata la creazione del file intermedio, sort legge tutti i dati e li ordina. Tutto questo accade senza che l’utente abbia alcun messaggio sullo schermo, perché le pipe sono silenziose. Finito l’ordinamento, finalmente la lista viene visualizzata sullo schermo. Nel caso in cui la lista dei file sia troppo lunga per stare tutta in una schermata, sarebbe desiderabile poterla leggere una schermata alla volta. ls non prevede nemmeno questa possibilità, ma le pipe ci vengono ancora una volta in aiuto. Per dividere l’output di ls in pagine, di solito si utilizzano i comandi more o less: il primo aspetta la pressione di un tasto per visualizzare la pagina successiva, il secondo è più evoluto e permette anche di scorrere avanti e indietro di una riga o una pagina alla volta. Per ottenere una lista pagina dopo pagina, basta allora digitare: [pippo@localhost pippo]$ ls | more [-> Invio] 22 CAPITOLO 1. INTRODUZIONE A LINUX oppure [pippo@localhost pippo]$ ls | less [-> Invio] 1.8 Lista di alcuni comandi di frequente utilizzo Help in linea (per visualizzare un testo esplicativo sui comandi) man nomecomando info nomecomando nomecomando --help Operazioni su directory cd nomedir: (change directory) cambia la directory corrente mkdir nomedir: (make directory) crea una directory rmdir nomedir: (remove directory) cancella una directory solo se è vuota pwd: (print working directory) visualizza il nome della directory corrente Operazioni su file cp sorgente destinazione: (copy) copia i file da sorgente a destinazione rm nomefile: (remove) cancella i file mv sorgente destinazione: (move) sposta i file da sorgente a destinazione chmod permessi nomefile: (change mode) cambia i permessi di un file Compressione e decompressione di file tar: per lavorare con i file .tar gzip: per lavorare con i file .gz zip: per lavorare con i file .zip Visualizzazione dei file (soprattutto nomi o contenuto) ls: (list) visualizza la lista dei file di una directory find -name "nomefile": cerca un file nel filesystem less nomefile: mostra il contenuto di un file una pagina alla volta more nomefile: mostra il contenuto di un file una pagina alla volta sort: ordina i dati che riceve Informazioni sui dischi df: (disk free) mostra la quantità di spazio libero nelle varie partizioni montate du nomedir: (disk used) mostra la quantità di spazio occupato da una directory mount filesystem nomedir: monta il filesystem nella directory specificata Gestione degli utenti adduser nomeutente: aggiunge un utente passwd nomeutente: cambia la password di un utente su nomeutente: (substitute user) cambia l’utente attivo Comandi particolari echo messaggio: visualizza un messaggio 1.9. APPROFONDIMENTI SULLA SHELL 23 alias nuovonomecomando="comando": dà un altro nome al comando specificato unalias nomealias: elimina un alias set nomevariabile="valore": imposta il valore per la variabile specificata unset nomevariabile: elimina una variabile export variabile: trasforma una variabile di shell in una di ambiente 1.9 Approfondimenti sulla shell Per maggiori informazioni sui comandi, esiste in Bash un ottimo help in inglese che funziona come il comando HELP di DOS: è man. Per utilizzarlo digitare [pippo@localhost pippo]$ man nomecomando [-> Invio] È scritto in lingua inglese, ma è la fonte principale di informazioni per chi vuol conoscere a fondo i comandi della shell. Un ulteriore lettura caldamente consigliata è l’HOWTO DOS2Linux, scritto sia in italiano che in inglese, reperibile al sito www.pluto.linux.it alla voce documentazione. 1.10 Bibliografia di uso generale Sono molte le fonti da cui trarre informazioni su Linux, e in questa sede riporterò le più famose. 1.10.1 Help forniti con Linux da console ci sono “man” e “info” dei quali si è visto l’utilizzo, in KDE ci sono la Guida di KDE e I Suggerimenti di Kandalf 1.10.2 Internet • www.pluto.linux.it (alla voce Documentazione, si possono trovare gli HOWTO in italiano e Appunti di Informatica Libera) • www.linux.com (per tutte le utenze, fornisce anche gli HOWTO) • www.linuxplanet.com • www.gnu.org (per avere informazioni su cos’è il software libero) • www.linuxnewbie.com (per principianti) • www.mandrakesoft.com (è il sito della distribuzione Mandrake, la più adatta a chi si avvicina al mondo Linux. Vedere al link Documentazione) 24 CAPITOLO 1. INTRODUZIONE A LINUX Capitolo 2 I Processi di Carlo Pinciroli c 2002 Carlo Pinciroli. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. 2.1 Cenni generali sui processi In Unix, il termine processo si utilizza per indicare un programma in esecuzione in memoria e il suo ambiente associato, ovvero l’insieme dei file di input e output che gli appartengono e delle sue variabili di ambiente. Un processo è quindi un programma avviato nella sessione di lavoro: perciò anche la shell Bash è un processo. Nuovi processi possono essere creati solo in risposta alla richiesta di un processo già esistente. Ad esempio, quando scriviamo un comando da terminale, la shell Bash crea un nuovo processo utilizzando il comando fork() messo a disposizione dal kernel (i comandi di questo genere sono detti in gergo “chiamate di sistema”). Il comando fork() (in inglese “biforcazione”) permette a un processo di creare una copia di se stesso, che viene poi utilmente modificata per fare spazio al nuovo processo che si vuole avviare. Ad esempio, quando mandiamo in esecuzione un comando dalla shell, quest’ultima crea una copia di se stessa (in modalità non interattiva) e poi esegue il comando digitato. Il meccanismo della biforcazione induce quindi fra i processi un’organizzazione di tipo padre-figlio che genera una struttura ad albero: ci si può rendere conto di questo fatto digitando pstree al prompt dei comandi. pstree visualizza proprio l’albero dei processi con lo stesso aspetto con cui di solito si visualizza la struttura delle directory di un filesystem. Cosı̀come l’albero delle directory possiede una radice, anche l’albero dei processi ne deve avere una: la radice è infatti il processo init, che viene attivato direttamente dal kernel in fase di avvio; init poi crea tutti gli altri processi del sistema con le fork() necessarie. Quando un processo figlio termina la propria esecuzione, lo fa tramite la chiamata di sistema exit(); il processo figlio diventa allora uno “zombie”, perché sebbene sia terminato non è ancora stato rimosso dalla memoria. E’ il processo padre che si occupa di rimuoverlo dalla memoria, facendone scomparire ogni 25 26 CAPITOLO 2. I PROCESSI traccia. Se il processo terminato aveva dei figli non ancora terminati, questi vengono affidati al processo init. Evidentemente, durante una sessione di lavoro sono molti i processi attivi che lavorano in contemporanea. Basti pensare a quanti processi sono legati all’utilizzo dell’ambiente grafico (quelli del server grafico e dei vari client in esecuzione) oppure ai vari comandi che si possono combinare insieme in un’unica riga facendo uso delle pipe. Inoltre molti altri programmi “sotterranei” sono attivi anche se non si notano tracce delle loro azioni. Tutte queste attività contemporanee devono essere svolte dall’unica (di solito) CPU di cui le macchine sono provviste: tanto lavoro per un solo operaio! Per questa ragione, il kernel fa sı̀che un processo venga eseguito per un po’ di tempo e che, terminato tale tempo, il processo venga messo in attesa per lasciare spazio ad un altro processo, che viene eseguito anche lui per una certa quantità di tempo: quindi la CPU continua a eseguire un po’ alla volta i vari processi, passando da uno all’altro senza sosta. Il tempo che la CPU dedica ad ogni processo non è sempre lo stesso, ma dipende dalla priorità (ovvero dall’importanza) che il processo attivo possiede. La priorità è immagazzinata in un numero, che funziona in modo opposto a come ci si aspetterebbe: infatti un basso valore indica un’alta priorità, mentre un valore alto indica una priorità bassa. Il valore di priorità può essere modificato facendo uso di un altro numero, detto valore di nice, che può essere positivo o negativo. Il nice viene sommato alla priorità, quindi se nice è positivo il processo diventa meno importante e quindi ha meno tempo a disposizione, ragion per cui risulta rallentato. Un processo ha diversi stati mutuamente esclusivi in cui può trovarsi: in esecuzione, in attesa di un evento (ad esempio la pressione di un tasto o la ricezione di dati da disco), sospeso, o zombie. Infine, ogni processo ha generalmente i permessi dell’utente che l’ha avviato.1 Questo fatto è una delle ragioni che sta alla base della sicurezza di Linux. Se per ipotesi infatti il sistema venisse infettato da un virus che cancella i file di avvio, tale virus avrebbe i permessi dell’utente che ne ha subito l’ infezione. Se l’utente infettato fosse root, allora il virus avrebbe diritto di fare qualunque cosa, ma se l’utente non fosse root, il virus non potrebbe fare nulla. Ecco, ancora una volta, una ragione per lavorare come utente normale e non come root. 2.2 Situazione dei processi: /proc e comandi associati Come è stato già accennato, ad ogni processo sono associate delle informazioni utili per il kernel al fine di gestire l’organizzazione del lavoro del sistema. Le informazioni sono contenute in memoria e il kernel le mette a disposizione dell’utente perché possa consultarle. Come è prevedibile, le informazioni sono immagazzinate in file fittizi (“Tutto è un file”. . . ) distribuiti nella directory /proc che è un vero e proprio filesystem contenuto in memoria: non occupa quindi spazio fisico su disco, nonostante il kernel permetta di lavorarci come se fossero file e directory qualunque. 1 Questo comportamento può essere modificato mediante l’utilizzo del bit SUID o del programma sudo, che permettono di far partire un processo con i permessi di un altro utente. 2.2. SITUAZIONE DEI PROCESSI: /PROC E COMANDI ASSOCIATI 27 Il filesystem /proc è molto importante perché, oltre alle informazioni sui processi, fornisce anche notizie di altro genere sul sistema, come il tipo di hardware installato e la sua configurazione. Saper leggere i file che contengono questi dati è fondamentale per risolvere molti problemi di conflitti hardware, o solo per conoscere che tipo di componenti sono stati riconosciuti con successo dopo l’installazione del sistema operativo. Ogni processo possiede una sottodirectory di /proc il cui nome è il PID. All’interno della directory relativa ad un processo, ci sono dei file, ognuno dei quali contiene varie informazioni. Non è necessario leggere il contenuto di questi file, perché è un compito effettuato da alcuni comandi messi a disposizione dalla shell, che hanno il pregio di essere più comodi da usare. Inoltre, l’evoluzione continua dei kernel fa sı̀che nelle nuove versioni il modo di gestire il filesystem /proc sia diverso, e così l’utilizzo di comandi che siano in grado di coprire queste differenze dal punto di vista dell’utente sono fondamentali. 2.2.1 Visualizzare la situazione dei processi Per ottenere informazioni sulla situazione dei processi, sono disponibili tre comandi: • ps: questo comando visualizza la “fotografia” della situazione dei processi al momento in cui si è digitato ps e premuto invio. Se viene digitato senza argomenti, visualizza solo la lista dei processi avviati dall’utente: [pippo@localhost pippo]$ ps PID TTY TIME CMD 1651 pts/1 00:00:00 bash 1760 pts/1 00:00:00 ps [-> Invio] La colonna PID è già stata spiegata; TTY è il terminale da cui si è avviato il processo (nell’esempio il terminale 1); TIME è il tempo di utilizzo della CPU; CMD è il nome dell’eseguibile associato al processo. L’opzione a (ovvero “all”, tutti; con o senza trattino è lo stesso) permette di vedere tutti i processi di tutti gli utenti: [pippo@localhost pippo]$ ps a [-> Invio] (non mostro l’output perche‘ e‘ molto lungo) Invece l’opzione l (“long”, lungo) permette di vedere un elenco più dettagliato: [pippo@localhost F UID PID 000 501 1651 000 501 1761 pippo]$ ps l PPID PRI NI 1650 11 0 1651 19 0 [-> Invio] VSZ RSS WCHAN STAT TTY 2900 1724 wait4 S pts/1 3172 1224 R pts/1 dove UID è lo User IDentificator (il numero che caratterizza un utente); PPID è Parent PID, ovvero il PID del processo genitore; PRI è la priorità del processo; NI è il valore nice, ovvero il valore che sommato a PRI permette di modificare la priorità del processo; RSS indica l’occupazione TIME COMMAND 0:00 /bin/bash 0:00 ps l 28 CAPITOLO 2. I PROCESSI effettiva della memoria; WCHAN è l’evento di cui è in attesa il processo; STAT è lo stato del processo; COMMAND è la riga di comando che ha avviato quel processo. • pstree: questo comando è già stato illustrato: visualizza la lista dei processi organizzata ad albero, di modo che la parentela fra i vari processi risulti visivamente evidente. [pippo@localhost pippo]$ pstree [-> Invio] init-+-artsd |-atd |-crond |-devfsd |-gpm |-kapm-idled |-7*[kdeinit] |-kdeinit-+-3*[kdeinit] | ‘-kdeinit---bash---pstree |-kdeinit---cat |-keventd |-khubd |-klogd |-login---bash---startx---xinit-+-X | ‘-startkde---ksmserver |-mdrecoveryd |-5*[mingetty] |-syslogd ‘-xfs • top: è come un ps aggiornato continuamente. Viene mostrata una schermata simile a quella che mostra il comando ps l, solo che top continua ad aggiornarla. È possibile anche inserire dei comandi interattivi costituiti da un carattere singolo. h serve per richiamare l’help sui comandi. 2.3 Comunicazione fra processi: i segnali Può capitare che, durante un lavoro, ci sia un processo in esecuzione che rallenta l’elaborazione, o che un processo bloccato influenzi negativamente la velocità del sistema. Quando questo accade, è desiderabile poter intervenire sul processo che genera il malfunzionamento e avere la possibilità di metterlo in pausa o anche di costringerlo a terminare. In Windows, per eseguire questa operazione si fa uso del Task Manager, ma spesso quando si impartisce il comando “Termina applicazione” vengono visualizzate una serie interminabile schermate blu che informano che il programma non risponde o che addirittura il sistema è diventato instabile. Questo accade perchè in Windows la terminazione forzata di un programma porta con sè degli strascichi legati al fatto che il multitasking non è genuino. Invece Linux è un sistema veramente multitasking, e quindi è possibile terminare un processo senza compromettere la stabilità di tutto il sistema né i processi che non hanno legame col processo terminato. 2.3. COMUNICAZIONE FRA PROCESSI: I SEGNALI 29 Per costringere un processo attivo a mettersi in pausa o a terminare, è necessario inviargli un segnale, ovvero un messaggio che il processo è in grado di ricevere ed intepretare. L’utilizzo dei segnali è molto vasto e va ben oltre l’esempio qui riportato perché la comunicazione fra i processi è alla base della gestione dei processi in Linux. I segnali disponibili sono moltissimi, ma i più importanti sono due: SIGKILL e SIGTERM. Ce ne sono altri tre che vedremo nel prossimo paragrafo. Quando un processo riceve il segnale SIGKILL, termina immediatamente la propria esecuzione senza salvare nulla del proprio lavoro fino a quel punto. Anche SIGTERM costringe un processo a terminare, ma in modo meno improvviso del precedente perché permette di salvare i dati prima della terminazione. Per inviare messaggi ai processi, bisogna naturalmente fare riferimento al loro PID. Il comando kill permette di mandare i segnali in questo modo: [pippo@localhost pippo]$ kill -s SIGKILL 1651 [-> Invio] questo comando invia il segnale SIGKILL al processo il cui PID è 1651. Però è anche possibile digitare: [pippo@localhost pippo]$ kill -s 9 1651 [-> Invio] questo comando invia il segnale numero 9 al processo il cui PID è 1651. Il segnale numero 9 è SIGKILL. Per conoscere i numeri relativi ai vari segnali, si può utilizzare ancora kill: [pippo@localhost pippo]$ kill -l 1) SIGHUP 2) SIGINT 3) 5) SIGTRAP 6) SIGABRT 7) 9) SIGKILL 10) SIGUSR1 11) 13) SIGPIPE 14) SIGALRM 15) 18) SIGCONT 19) SIGSTOP 20) 22) SIGTTOU 23) SIGURG 24) 26) SIGVTALRM 27) SIGPROF 28) 30) SIGPWR 31) SIGSYS 32) 34) SIGRTMIN+2 35) SIGRTMIN+3 36) 38) SIGRTMIN+6 39) SIGRTMIN+7 40) 42) SIGRTMIN+10 43) SIGRTMIN+11 44) 46) SIGRTMIN+14 47) SIGRTMIN+15 48) 50) SIGRTMAX-13 51) SIGRTMAX-12 52) 54) SIGRTMAX-9 55) SIGRTMAX-8 56) 58) SIGRTMAX-5 59) SIGRTMAX-4 60) 62) SIGRTMAX-1 63) SIGRTMAX [-> Invio] SIGQUIT 4) SIGILL SIGBUS 8) SIGFPE SIGSEGV 12) SIGUSR2 SIGTERM 17) SIGCHLD SIGTSTP 21) SIGTTIN SIGXCPU 25) SIGXFSZ SIGWINCH 29) SIGIO SIGRTMIN 33) SIGRTMIN+1 SIGRTMIN+4 37) SIGRTMIN+5 SIGRTMIN+8 41) SIGRTMIN+9 SIGRTMIN+12 45) SIGRTMIN+13 SIGRTMAX-15 49) SIGRTMAX-14 SIGRTMAX-11 53) SIGRTMAX-10 SIGRTMAX-7 57) SIGRTMAX-6 SIGRTMAX-3 61) SIGRTMAX-2 l’opzione -l (list) fa sì che vengano visualizzati tutti i possibili segnali preceduti dal loro numero identificativo. Per inviare un segnale a tutti i processi che eseguono uno stesso comando si può fare uso di killall: [pippo@localhost pippo]$ killall -KILL ls [-> Invio] Invia il segnale SIGKILL a tutti i processi creati col comando ls. 30 2.4 CAPITOLO 2. I PROCESSI Processi e shell Attraverso la shell, come abbiamo già detto, è possibile avviare dei processi, anche più di uno alla volta. Un comando contente delle pipe, ad esempio, o uno script di shell, generano più processi legati fra loro contemporaneamente. Questi tipi di insiemi di processi vengono chiamati job (ovvero “lavori”) di shell. Non bisogna dunque confondere un processo con un job di shell: un processo è un singolo eseguibile avviato, un job è l’insieme dei processi avviati con un comando della shell. I job possono operare in primo piano (foreground) o in secondo piano (background). La differenza fra le due modalità sta nel fatto che nel primo caso il terminale non è disponibile ad accettare nuovi comandi, nel secondo caso invece sı̀. Evidentemente, mettere in background un job che richiede una frequente interazione con l’utente non è molto utile in quanto il fine di mettere in background un job è proprio la liberazione del terminale. È altrettanto chiaro che un job in background che produce molti messaggi sullo schermo non è consigliabile, in quanto non si potrebbero distinguere i suoi messaggi dai messaggi di un altro job in secondo piano o in primo piano che opera contemporaneamente. È quindi importante redirigere l’output dei job in background. 2.4.1 Avvio di un job sullo sfondo Per avviare un job esplicitamente in background, è sufficiente far seguire al comando il carattere &: [pippo@localhost pippo]$ find / -name "pippo" & [1] 1856 [-> Invio] I due numeri visualizzati indicano il numero del job e il numero dell’ultimo processo tra quelli attivati col comando. Se l’output non è stato rediretto, quando il job deve mostrare sullo schermo qualcosa viene visualizzato il messaggio [1]+ Stopped (tty output) find che indica che il processo è in attesa di poter scrivere sullo schermo. Se invece non è stato rediretto l’input, il messaggio [1]+ Stopped (tty input) find indica che il job aspetta di ricevere dalla tastiera dell’input. 2.4.2 Invio di segnali ad un job in primo piano Oltre al comando kill già trattato, è possibile inviare dei messaggi ai job in foreground facendo uso di particolari combinazioni di tasti. Premendo Ctrl+C si invia un segnale SIGINT che interrompe il processo attivo in primo piano, senza possibilità di recuperarlo. Se invece si preme Ctrl+Z si ottiene la sospensione del job (segnale SIGSTP), che rimane così congelato fino a che un comando fg o bg non gli invii un segnale SIGCONT. Nel caso in cui si sia digitato fg, il job riprende l’esecuzione in foreground; se invece è stato digitato bg, il job riprende l’esecuzione in background. 2.4. PROCESSI E SHELL 31 Il comando jobs mostra la lista dei job attivi sullo sfondo o sospesi. Esiste anche un doppione del comando kill, che permette di inviare ai job dei segnali come si faceva con i processi, anche se la sintassi è leggermente diversa: [pippo@localhost pippo]$ kill -9 %1 invia il segnale 9 (SIGKILL) al job numero 1. [-> Invio] 32 CAPITOLO 2. I PROCESSI Capitolo 3 Dispensa sugli script di Carlo Pinciroli c 2002 Carlo Pinciroli. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. 3.1 Informazioni preliminari sulla programmazione L’utilizzo del computer è subordinato ai programmi di cui si dispone: per effettuare una particolare attività, è necessario che l’utente interagisca con un programma appositamente realizzato. La programmazione è proprio l’atto di creare un programma che soddisfa le richieste di chi lo deve utilizzare. I problemi fondamentali, nell’atto del programmare, sono dati da due fattori: 1. che la macchina non “pensa” come un umano, e quindi è necessaria una forma di astrazione da parte del programmatore al fine di avvicinare il proprio modo di ragionare a quello della macchina 2. una volta assunta questa disposizione, è necessario saper progettare, prima ancora che realizzare, una soluzione al proprio problema che sia attuabile da un computer. In altre parole, le difficoltà della programmazione stanno nell’imparare come scrivere programmi che il computer può eseguire, e nel saperli progettare correttamente, cioè il più possibile privi di errori (bug). Solitamente, i neofiti della programmazione pensano che il primo dei due problemi sia il più importante. In realtà, una volta imparato un linguaggio di programmazione (che sia il C, il Java, il Visual Basic, il Pascal. . . ) le difficoltà maggiori affiorano nella fase di progettazione. Un linguaggio di programmazione è una vera e propria lingua che viene utilizzata per descrivere al computer le operazioni da eseguire. Il primo problema a cui si faceva riferimento poc’anzi, era che il computer usa un linguaggio diverso da quello umano. Comandi come “spegni il computer” o “stampa il documento” non hanno alcun significato per la macchina. Il computer utilizza il cosiddetto linguaggio macchina, che è un insieme di comandi formati dai soli caratteri 0 e 1. 33 34 CAPITOLO 3. DISPENSA SUGLI SCRIPT Perché proprio 0 e 1 ? Perché le parti elettroniche del computer, per la loro stessa costruzione, sono impostate in modo da reagire a due soli livelli di tensione elettrica: uno basso (lo 0) e uno alto (l’1). Così, comandi come 0010011001010 (detti binari) sono interpretabili dal computer perché sono eseguibili dalle sue componenti elettroniche. Naturalmente programmare in questa maniera rende praticamente impossibile realizzare anche il più semplice programma, in quanto basterebbe confondere un 1 con uno 0 per rovinare tutto il proprio lavoro. Inoltre, programmare in linguaggio macchina (cioè impartire comandi direttamente ai componenti elettronici) richiede la conoscenza di alcuni particolari costruttivi, cosa anche questa del tutto fuori portata per un qualsiasi progetto, dal momento che il computer è pieno di componenti elettronici e le marche sono molte e tutte con differenti particolari di funzionamento. Si è deciso allora di realizzare dei linguaggi più vicini alla logica del programmatore umano, cosicchè le difficoltà descritte non sussistano più. Il C, il Pascal, il Java sono solo alcuni degli esempi di queste categorie di linguaggi, che vengono detti ad alto livello perché più vicini alla logica umana del linguaggio macchina e dell’assembler (detti a basso livello). Nei linguaggi ad alto livello le espressioni sono perciò ben diverse dagli incomprensibili 01001010 di cui parlavamo prima. Un esempio molto semplice è il famoso programma che visualizza sullo schermo il messaggio “Hello, world!”, che scritto in C risulta: void main() { printf("Hello, world!"); } Anche se non tutto quello che è scritto è chiaro a prima vista, si intuisce senza alcuno sforzo che l’operazione di visualizzazione del messaggio è effettuata dal comando printf("Hello, world!"): una parola che è ben più facilmente interpretabile di una serie di 0 e di 1. . . Sorge dunque un grosso problema. Se il computer capisce solo 0 e 1, ma noi gli impartiamo comandi formati da parole, come è possibile far sı̀che questi comandi possano essere capiti dalla macchina? Ci sarebbe bisogno di una sorta di traduzione dal livello alto dell’utente a quello basso della macchina. Si ripresenta sotto altra forma, allora, la difficoltà sul modo di “pensare” del computer. I modi per realizzare questa traduzione sono fondamentalmente due, e li spiegheremo con un semplice esempio. Supponiamo di essere il presidente di una piccola ditta che produce componenti in metallo, e che una multinazionale giapponese sia interessata all’acquisto di tali componenti. Il presidente non conosce il giapponese, e dunque non è in grado di parlare direttamente con il delegato giapponese. Evidentemente c’è bisogno di una traduzione per potersi accordare. Si può allora procedere in due modi. Il primo modo è di redigere un testo in italiano, incaricare qualcuno di tradurlo e poi farlo leggere al delegato. Il secondo è di parlare direttamente col delegato servendosi di una traduzione simultanea: ogni frase pronunciata viene immediatamente tradotta dall’interprete. Al di fuori dell’esempio, dunque, i modi per effettuare la traduzione da linguaggio ad alto livello a linguaggio a basso livello sono due. Si può prendere l’intero programma originale ed effettuarne una traduzione completa in binario: in questo modo si ottiene un programma perfettamente funzionante, il tipico .EXE del dos. Altrimenti si può leggere una riga di programma, tradurla, eseguirla, e poi leggere la riga successiva, tradurla, eseguirla e cosı̀via fino alla fine 3.2. GLI SCRIPT DELLA SHELL 35 del programma. Nel primo caso si parla di linguaggi compilati, nel secondo caso di linguaggi interpretati. Sono compilati il C e il Pascal, ad esempio. Un famoso linguaggio intepretato è invece il qbasic che veniva fornito con il dos 6.22 e precedenti. Il Java invece, per soddisfare le esigenze di portabilità che la rete Internet richiede, è per metà compilato e per metà interpretato. 3.2 Gli script della shell Gli script della shell sono delle specie di programmi, in quanto la logica con la quale si realizzano è la stessa dei programmi, ma non offrono le tutte le funzionalità di un vero e proprio linguaggio di programmazione. Gli script, nello svolgimento di alcune ripetitive attività, hanno un ruolo di spicco, in quanto permettono all’utente di automatizzare delle operazioni in modo banale. Nella loro forma più semplice, uno script non è altro che un file di testo in cui ogni riga è un comando riconoscibile da Bash: quando lo script viene eseguito, Bash esegue i comandi uno alla volta. Gli script sono quindi interpretati e non compilati. Vedremo i vari aspetti degli script facendo uso di alcuni esempi. 3.2.1 Il primo script Potrebbe capitare di avere il disco pieno, e di voler quindi cancellare dei grossi file inutilizzati da tempo. Un comando che crea un elenco di questi file è il seguente: [pippo@localhost pippo]$ find / -type f -atime +30 -size \ +1024k -exec ls -l {} \; > /tmp/largefiles [-> Invio] questo comando crea il file largefiles che contiene tutti i file più grandi di 1 megabyte e inutilizzati da almeno 30 giorni. Dover riscrivere periodicamente un comando del genere è piuttosto noioso, e se magari non se neè nemmeno ben capita la sintassi ricordarlo è del tutto impossibile. Una buona soluzione potrebbe essere la creazione di uno script che contiene proprio quel comando: --- inizio dello script --#!/bin/bash # uno script per creare un elenco di file voluminosi e # inutilizzati da almeno un mese: vedere il file largefiles find / -type f -atime +30 -size +1024k -exec ls -l {} \; > \ /tmp/largefiles --- fine dello script --La prima riga contiene un’informazione basilare per la shell: infatti dice qualedeve essere l’interprete dei comandi, nel nostro caso naturalmente Bash. Ma nel caso si faccia uno script in qualche altro linguaggio, ad esempio Perl, quella riga dovrà contenere il percorso dell’eseguibile perl. La seconda riga è un commento, in quanto inizia con un #, ed è utile per descrivere cosa fa lo script: quando lo rileggeremo, se non ci ricorderemo cosa significano i comandi, almeno sapremo cosa aspettarci dallo script. La terza riga è il comando che volevamo e non richiede ulteriori spiegazioni. Possiamo utilizzare un editor qualunque (vi, emacs, o altro ancora) per creare un file di testo che contenga quelle righe e 36 CAPITOLO 3. DISPENSA SUGLI SCRIPT salvarlo col nome filegrandi. A questo punto, una volta creato il file contentente lo script, non possiamo ancora eseguirlo. Infatti, se controlliamo col comando ls -l filegrandi, vedremo: [pippo@localhost pippo]$ ls -l filegrandi -rw-rw-r-1 pippo pippo 203 feb [-> Invio] 8 20:05 filegrandi per qualunque utente, non esiste il diritto di esecuzione! Quindi dobbiamo prima impostare questo diritto, col comando chmod: [pippo@localhost pippo]$ chmod +x filegrandi [-> Invio] [pippo@localhost pippo]$ ls -l filegrandi [-> Invio] -rwxrwxr-x 1 pippo pippo 203 feb 8 20:05 filegrandi C’è ancora un problema: per eseguire lo script non si può scrivere [pippo@localhost pippo]$ filegrandi bash: filegrandi: command not found [-> Invio] perchè la directory corrente non c’è fra le directory del path: il comando giusto è [pippo@localhost pippo]$ ./filegrandi [-> Invio] in cui il ./ iniziale specifica che filegrandi si trova nella directory corrente. Abbiamo cosı̀realizzato il nostro primo script, e potremo riutilizzarlo tutte le volte che vorremo. Potrebbe essere una buona idea creare una directory personale /scripts in cui mettere le nostre creazioni e aggiungerla al path, cosicché il problema del ./ iniziale non si ripresenterebbe più. 3.2.2 Parametri I file di testo in DOS hanno come marcatore di fine riga una sequenza di due caratteri: CR e LF (carriage return e line feed, ovvero ritorno carrello e nuova linea). In Linux invece si usa soltanto carattere LF, quindi se si visualizza un file di testo in formato DOS usando less o more si noteranno dei caratteri M̂ a fine riga: sono proprio i CR che Linux non usa per andare a capo, e perciò li tratta come se fossero caratteri qualunque. Evidentemente sarebbe bello poter eliminare questi caratteri, e Linux mette a disposizione il comando tr per farlo. Se il file testodos.txt è in formato dos e si vuole creare un file di testo senza i CR di nome testolinux.txt, si può scrivere: [pippo@localhost pippo]$ tr-d ’\015’ < testodos.txt > \ testolinux.txt [-> Invio] Anche in questo caso, il comando in questione richiede di ricordare dei particolari non proprio intuitivi: il fatto che si debba usare il comando tr, raramente usato nel lavoro di routine, e che il carattere CR abbia il codice ASCII 015. Ci sono tutte le condizioni per realizzare un comodo script dal nome semplice, ad esempio dos2unix. L’utilizzo che ne vorremmo fare è il seguente: [pippo@localhost pippo]$ ./dos2unix testodos.txt \ testolinux.txt [-> Invio] 3.2. GLI SCRIPT DELLA SHELL 37 cioè vorremmo che il nostro script accettasse dei parametri: i nomi dei file sorgente e destinazione. Bash permette di creare script che sappiano gestire i parametri, quindi il nostro script può essere realizzato: --- inizio dello script --#!/bin/bash # uno script per convertire i file di testo dos (con CR e LF) # in file di testo linux (col solo LF) tr-d ’\015’ < $1 > $2 --- fine dello script --nel comando tr, come si vede, al posto dei nomi dei file ci sono dei caratteri speciali: $1 e $2. Questi due indicano rispettivamente il primo argomento dello script, e il secondo argomento dello script. Quindi se si scrive sulla shell [pippo@localhost pippo]$ ./dos2unix testodos.txt \ testolinux.txt [-> Invio] $1 vale testodos.txt e $2 vale testolinux.txt. Prima dell’esecuzione del comando tr nello script, Bash sostituisce $1 e $2 coi rispettivi valori e poi esegue il comando con le sostituzioni. Per gestire i parametri, dunque, basta utilizzare $N, dove N indica il numero del parametro. Se N è maggiore di 9, non si scrive ad esempio $10 ma ${10}. I parametri speciali invece sono i seguenti: • $0 indica il nome dello script (nel caso precedente $0 valeva dos2unix) • $* è la stringa formata dall’insieme di tutti i parametri (quindi nell’esempio precedente valeva "testodos testolinux") • $@ è l’insieme delle stringhe di tutti i parametri (cioè "testodos" "testolinux") • $# è impostata al numero di parametri specificati (nell’esempio 2) Per provare tutto questo, si può creare uno script di nome vediamo un po: --- inizio dello script --#!/bin/bash # serve per provare i vari # parametri speciali echo "Il nome dello script e‘ $0" echo "I parametri sono in tutto $#" echo "Il primo parametro e‘: $1" echo "Il secondo parametro e‘: $2" echo ’Ecco il valore di $*:’ echo $* --- fine dello script --- 38 3.2.3 CAPITOLO 3. DISPENSA SUGLI SCRIPT Variabili Abbiamo visto, nella lezione sulla Bash, l’utilità di poter disporre di variabili, che siano di shell o di ambiente. Questa utilità si ripresenta anche nella programmazione degli script. La dichiarazione e l’utilizzo delle variabili è lo stesso che abbiamo trattato precedentemente. Naturalmente, una variabile creata in uno script è valida soltanto all’interno dello script stesso (ovvero è una variabile di shell); per farla diventare una variabile di ambiente, bisogna utilizzare la consueta formula "export variabile". All’interno degli script di boot (che tratteremo in futuro) ci sono molti esempi di dichiarazioni di variabili, uno per tutti la dichiarazione contenuta nel file /etc/rc.d/rc.sysinit: --- inizio dello script --#!/bin/bash # cose varie... le vedremo poi # Set the path PATH=/bin:/sbin:/usr/bin:/usr/sbin export PATH # cose varie... le vedremo poi --- fine dello script --È anche possibile leggere dell’input da tastiera e immagazzinarlo in una variabile: --- inizio dello script --#!/bin/bash # legge da da tastiera e mostra # quanto e‘ stato scritto echo -n "Immettere una stringa:" read STRINGA echo "Hai inserito $STRINGA" --- fine dello script --Il comando echo -n fa sì che dopo aver scritto "Immettere una stringa:" il cursore non vada a capo; il comando read legge la stringa e la immagazzina nella variabile STRINGA. Un fatto molto importante è che la variabile STRINGA non è stata dichiarata precedentemente: chi ha già programmato in QBasic non troverà questo fatto molto rilevante, ma i programmatori di C o simili potrebbero trovarlo sorprendente. 3.2.4 Costrutto if Nello script dos2unix c’è la necessità di specificare i parametri. Infatti, se scriviamo soltanto [pippo@localhost pippo]$ ./dos2unix ./dos2unix: $1: ambiguous redirect [-> Invio] 3.2. GLI SCRIPT DELLA SHELL 39 il messaggio di errore che viene visualizzato non è molto chiaro per chi non sappia come è fatto lo script. Un buon programmatore deve però rendere utilizzabili a tutti le proprie creazioni: qui dovremmo visualizzare un messaggio di errore più chiaro, che spieghi il perché le cose non funzionano. L’effetto che vorremmo ottenere è di questo genere: [pippo@localhost pippo]$ ./dos2unix Utilizzo di questo script: dos2unix filesorgente filedestinazione [-> Invio] cosı̀è sicuramente più chiaro! Quello di cui abbiamo bisogno all’atto della stesura dello script, quindi, è la possibilità di dire che se non ci sono parametri allora viene visualizzato il messaggio di errore, altrimenti il comando tr viene eseguito normalmente. Lo script che realizza questo effetto è il seguente: --- inizio dello script --#!/bin/bash # uno script per convertire i file di testo dos (con CR e LF) # in file di testo linux (col solo LF) if test $# = 2 then tr-d ’\015’ < $1 > $2 else echo "Utilizzo di questo script:" echo "$0 filesorgente filedestinazione" fi --- fine dello script --Il costrutto if condizione then lista_di_comandi else lista_di_comandi fi realizza proprio quello che volevamo. Se la condizione dopo if è vera, viene eseguita la lista di comandi dopo then; altrimenti viene eseguita la lista di comandi dopo else. La parola fi marca la fine del costrutto. Nella condizione abbiamo messo il comando test. Questo è un comando che restituisce 0 se la condizione che lo segue ($# = 2) è vera e restituisce un numero diverso da zero altrimenti. Quindi in realtà if è in grado di distinguere tra vero e falso utilizzando la distinzione tra 0 e non-0 ! In altri termini, per if lo 0 è vero e il non-0 è falso. Ogni comando, non solo test, restituisce un numero in uscita che dà informazioni sulla riuscita o meno dell’elaborazione. Ad esempio il comando rm restituisce 0 se riesce a fare il suo lavoro, cioè cancellare i file specificati, altrimenti restituisce un valore diverso da 0 che indica il tipo di errore che si è verificato. Nell’utilizzo normale della shell i valori restituiti dai comandi non sono molto importanti, e infatti non ne avevamo fatto menzione in passato, ma nello scripting l’utilità di questi valori è enorme. Vediamo infatti uno script che crea una copia di backup di un file di testo prima di avviare vi per modificarlo: 40 CAPITOLO 3. DISPENSA SUGLI SCRIPT --- inizio dello script --#!/bin/bash # uno script per creare una copia # di backup del file da modificare con vi if test $# = 0 then echo "Utilizzo di questo script:" echo "$0 nomefile" else if cp $1 "~$1" then vi $1 else echo "Copia di backup non riuscita" fi fi --- fine dello script --questo script è molto istruttivo. Ci sono due if, uno dentro l’altro (in gergo si dice “annidati”), e l’if interno è quello che controlla che cp non abbia avuto problemi nella creazione della copia di backup. Una particolare trattazione merita il comando test: serve, come abbiamo visto, per specificare delle condizioni unitamente al comando if (ma non solo, e lo vedremo poco più avanti). Tanto per cominciare, un consiglio da seguire alla lettera è di non chiamare mai un proprio script test. Questo perchè Bash potrebbe capire male, e confondere il vostro script test con il comando omonimo. Inoltre, test può essere utlizzato in modo molto più comodo di quanto non si sia visto finora. Infatti si può realizzare lo script dos2unixanche così: --- inizio dello script --#!/bin/bash # uno script per convertire i file di testo dos (con CR e LF) # in file di testo linux (col solo LF) if [ $# = 2 ] then tr-d ’\015’ < $1 > $2 else echo "Utilizzo di questo script:" echo "$0 filesorgente filedestinazione" fi --- fine dello script --dove al posto di test abbiamo messo delle parentesi quadre. Questo modo di vedere il comando test è più usato e più comodo, quindi d’ora in poi lo utilizzeremo sempre. Alcune condizioni di utilizzo generale sono: • if [ -e nomefile ] se esiste il file chiamato nomefile • if [ -f nomefile ] se esiste ed è un file normale il file chiamato nomefile 3.2. GLI SCRIPT DELLA SHELL 41 • if [ -d nomedir ] se esiste ed è una directory il file chiamato nomefile • if [ -r nomefile ] se esiste ed è leggibile il file chiamato nomefile • if [ -w nomefile ] se esiste ed è modificabile il file chiamato nomefile • if [ -x nomefile ] se esiste ed è eseguibile il file chiamato nomefile per dire “se non. . . ” si usa il carattere ! : • if ! [ -e nomefile ] se non esiste il file chiamato nomefile • if ! mkdir pippo se non è stato possibile creare la directory pippo per sapere se una stringa di caratteri è uguale ad un’altra stringa (ad esempio se $1 vale "-n"): if [ $1 = "-n" ] per sapere se una stringa di caratteri è diversa da un’altra stringa (ad esempio se $1 non vale "-n"): if [ $1 != "-n" ] Per maggiori informazioni sul comando test, digitare man test al prompt dei comandi. 3.2.5 Costrutto while Il costrutto while permette di eseguire un gruppo di operazioni in modo ripetitivo, finché non si verifica una condizione che fa terminare la ripetizione: --- inizio dello script --#!/bin/bash RISPOSTA="n" while [ $RISPOSTA != "s" ] do echo "Per farmi smettere devi scrivere s" echo -n "Immetti una stringa: " read RISPOSTA echo "Hai inserito $RISPOSTA" done --- fine dello script --- 3.2.6 Ciclo for Anche il ciclo for permette di eseguire un gruppo di operazioni in modo ripetitivo finché non si verifica una condizione che fa terminare la ripetizione, ma il numero di ripetizioni in questo caso è fisso e non variabile come nel costrutto while. In altre parole, nel costrutto while è possibile far continuare il ciclo un numero indefinito di volte, mentre nel ciclo for il numero di volte è specificato in partenza. 42 CAPITOLO 3. DISPENSA SUGLI SCRIPT --- inizio dello script --#!/bin/bash for P in $* do echo $P done --- fine dello script --questo script mostra uno per uno tutti gli argomenti digitati. Si può anche fare for F in /home/pippo/* do touch $F done in questo caso, per ogni file della directory /etc viene eseguito il comando touch che aggiorna la data di modifica del file alla data corrente. 3.3 Per avere maggiori informazioni Nella stesura di questa dispensa, si è fatta l’ipotesi che il lettore non avesse alcuna esperienza di programmazione. Per questa ragione molti aspetti dello scripting sono stati omessi, in quanto sono o troppo complicati in un primo approccio o poco importanti al fine di capire lo scripting in generale. Alcuni degli argomenti omessi riguardano le variabili (modi di riferimento e array), varie opzioni di test, valutazione delle operazioni matematiche, costrutti case e select. Sono comunque argomenti affrontabili da soli senza difficoltà e non molto utilizzati nello scripting di routine. Nel caso in cui ci fosse l’esigenza di saperne di più, uno sguardo alle pagine di manuale può essere utile: [pippo@localhost pippo]$ man bash [-> Invio] Altrimenti ci si può riferire alle fonti già elencate nella prima dispensa, con un particolare riguardo agli Appunti di Informatica Libera. Capitolo 4 Boot del sistema di Carlo Pinciroli Francesco Toso c 2002 Carlo Pinciroli, Francesco Toso. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. 4.1 Boot di sistema Questa parte si occupa di descrivere ciò che accade a livello hardware nella macchina; non è necessaria per comprendere e modificare l’avvio di Linux o di un qualsiasi sistema operativo, ma può risultare utile conoscerla. Osservazione: Quanto segue cerca di essere il più generico possibile, tuttavia, in particolar modo le diagnostiche sulla corrente e simili vengono effettuate solo dall’introduzione dello standard ATX per l’alimentazione. Inoltre quanto scritto in questa sezione si intende valido solo per PC IBM o compatibili. Appena acceso il computer, dopo aver atteso che le correnti interne raggiungano il regime stazionario, viene avviato il system BIOS (o più brevemente BIOS ) in modalità superuser del processore. Infatti, per convenzione all’avvio il processore ha impostato il registro Instruction Pointer all’indirizzo del BIOS boot program, che solitamente ha come indirizzo FFFF0h, proprio alla fine della memoria “base”1 del sistema. Questo perché così è possibile mascherare la vera dimensione del BIOS che quindi può cambiare arbitrariamente da versione a versione. Di seguito si elancano i passi principali dell’avvio con una breve spiegazione: POST Il POST 2 notifica eventuali errori hardware, impedendo l’attivazione dei componenti. Video Il BIOS si preoccupa, quindi di caricare ed eseguire il BIOS della sche1 Tristemente questa è una eredità dei primi processori intel che non avevano ancora la modalità protetta del processore 2 Power On Self Test, è una diagnostica veloce sul corretto comportamento elettrico dei componenti principali 43 44 CAPITOLO 4. BOOT DEL SISTEMA da video3 (solitamento posizionato all’indirizzo C000h) che contiene le istruzioni per l’inizializzazione della stessa.4 Altre ROM A questo punto vengono cercate le ROM degli altri dispositivi presenti sulla scheda come il controlle IDE/ATA, quello SCSI ma anche quello della scheda di rete, per esempio. Ciascuna di queste ROM viene poi eseguita secondo vari criteri di priorità. Test Inizializzati tutti i device necessari, vengono effettuati test sul processore, sulla memoria e altre componenti critiche. Inventory Vengono “ordinati” i device presenti e impostati i parametri degli stessi (ad esempio il memory timing, i parametri dei dischi. . . 5 ) BOOT Finalmente, in base alle impostazioni date, viene determinato il dispositivo di BOOT e si cercano le informazioni per avviare il sistema operativo.6 Fail Nel caso di fallimento del BOOT viene mostrato un messaggio di errore Quanto elencato è anche detto cold boot o avvio a freddo, il warm boot è identico a meno di quanto precede il POST (incluso) che viene saltato. Una volta caricato il “core” del sistema operativo (ricordiamo che prima di tale caricamento il sistema non ha alcuna nozione di file system 7 ), questo viene eseguito. La sua esecuzione comporta l’inizializzazione dei device basilari8 e la “messa in comunicazione” degli stessi col sistema operativo. Infine viene caricato il kernel vero e proprio che, in generale, si occupa di interfacciarsi con le periferiche, di fornire i servizi dovuti e così via fino al caricamento dell’ambiente di lavoro dell’utente. Sotto Linux, caricato il kernel, il sistema cercherà di montare il root filesystem 9 ; in caso di successo apparirà la seguente scritta: VFS: Mounted root (ext2 filesystem) readonly. Una volta fatto ciò, il kernel si preoccuperà di eseguire init.10 . 4.2 Boot Loader Il Boot loader è il programma che si occupa all’avvio di indirizzare la ricerca del “core” del sistema operativo all’indirizzo opportuno del disco. In generale tale reindirizzamento è fisso per cui il boot loader è un semplice salto all’indirizzo 3 Per questo motivo nei computer moderni prima si vede la diagnostica della scheda, poi il resto 4 Un tempo, ma neanche tanto indietro, la gestione del video era demandata direttamente al processore per cui questa sezione risultava inesistente 5 Si escludono le periferiche PnP che hanno una inizializzazione a parte 6 Per i dischi fissi si usa l’MBR (Master Boot Record, C0, H0, S1), per i floppy il Volume Boot Sector (C0, H0, S1) 7 Questo implica il fatto che risulti pressoché impossibile accedere ai dati non di boot del disco 8 Ad esempio i due bridge, l’AGP port, i controller dei dischi. . . 9 Si ricordi che sotto unix tutto è una directory. . . 10 Si veda più avanti per informazioni più dettagliate a riguardo 4.2. BOOT LOADER 45 ma nel caso di più sistemi operativi sulla macchina si richiede qualcosa di più complicato. Come potrete notare voi stessi, attualmente i boot loader hanno raggiunto livelli di complessità a volte eccessivi (vd. interfacce grafiche e simili), tuttavia il loro compito lo svolgono (e sono costretti a farlo) in maniera egregia. Elenchiamo ora alcuni dei boot loader più comuni • LILO • Grub • Boot magic • NT loader L’installazione di un boot loader in MBR piuttosto che su dischetto è solo questione di gusto, dato che ciò che concettualmente viene eseguito è sempre lo stesso. Trattiamo brevemente i boot loader più famosi: 4.2.1 LILO LILO è un boot loader multi uso. Non dipende da un particolare file system, può caricare le immagini del kernel Linux direttamente da floppy o disco fisso e può addirittura funzionare come boot manager per altri sistemi operativi come DOS, Windows 9*, SCO . . . . Nella sua ultima versione è anche dotato di una gradevole interfaccia grafica. 4.2.2 Grub Probabilmente il boot loader più giovane, ha dalla sua una estrema compattezza (solo qualche decina di KB) e una buona interfaccia grafica. Come LILO, può essere usato anche per altri sistemi operativi. 4.2.3 Boot magic Il primo loader commerciale della lista, è abbastanza famoso perché fornito con Partition Magic. Ha dalla sua una interfaccia grafica accattivante e la possibilità di usare il mouse nelle scelte. 4.2.4 NT loader Citato solo per dovere di cronaca, questo boot loader supporta solo sistemi operativi Microsoft per cui è completamente inutile per Linux e altri. La scelta del boot loader è puramente arbitraria ed è solo una questione di gusti. A meno di esigenze particolari si consiglia di mantenere il boot loader della propria distribuzione. Osservazione: Si sconsiglia vivamente l’uso di boot loader in fase di test o poco conosciuti; i danni causabili da un programma del genere potrebbero essere anche gravi 46 CAPITOLO 4. BOOT DEL SISTEMA 4.3 System V init System V è la versione di Unix proposta dalla AT&T anni fa che ha dato origine ai sistemi BSD. Per motivi legali, infatti, la AT&T, quando ancora era solo una enorme compagnia telefonica, non potè commercializzare tale sistema che venne quindi donato liberamente all’università di Berkley.11 Il System V, diventato ormai 386BSD, venne quindi sviluppato sotto licenza BSD 12 . Dato che la comunità Linux aveva bisogno di costruire un modello per gli script di inizializzazione, venne preso come esempio quello del System V (che era diverso da quello di BSD). Osservazione: Si noti che distribuzioni come RedHat o SuSE utilizzino script System V style, mentre la Slackware usa script BSD style. 4.3.1 Cenni generali Una volta caricato in memoria, il kernel si preoccupa immediatamente di creare il processo init, che, come è già stato spiegato nella lezione sui processi, è il padre di tutti gli altri processi del sistema. “init” coordina il lavoro facendo uso delle informazioni contenute nel file /etc/inittab che permette di definire gli script di avvio. Purtroppo ogni distribuzione ha un suo modo di organizzare il file /etc/inittab e gli script di avvio, quindi come esmpi porteremo quelli tratti dalla distribuzione RedHat (e quindi, anche sulla Mandrake che deriva da RedHat). In ogni caso le differenze tra distribuzione e distribuzione si concentrano soprattutto su particolari realizzativi (cambiano i nomi degli script o delle directory in cui sono posti, ad esempio), ragion per cui la logica che RedHat usa per avviare Linux non è per nulla differente da quella di Slackware o Debian. 4.3.2 Struttura di /etc/inittab Dato che inittab è un file di configurazione di sistema, la convenzione delle directory di Linux impone che inittab si trovi nella directory /etc. “inittab”, ovvero INITialization TABle (in inglese “tabella di inizializzazione”) è il file in cui sono contenute le informazioni che init usa per impostare la configurazione del sistema e per avviare gli script di shell necessari. Ogni riga contiene un’impostazione, la cui sintassi generale è: id:runlevel:action:process Come si può notare ci sono vari campi separati dal simbolo “:”, elenchiamo ora il significato di ciascuno id: Identifica univocamente una riga del file di inizializzazione, puó essere costituito da un numero variabile di caratteri fino a un massimo di 2 o 4 a seconda delle librerie usate nella compilazione (Linux generalmente ne ammette un massimo di due) runlevel: Indica il “livello di esecuzione” del processo, può contenere più caratteri per far eseguire il processo in più runlevels 11 I veri motivi di tale azione sono tutt’ora oscuri ai più. la licenza permette enormi libertà sul codice. 12 Sostanzialmente 4.3. SYSTEM V INIT 47 action: Descrive che “azione” andrà intrapresa process: È il processo da avviare Runlevels I “livelli di esecuzione” o runlevel sono una puro artificio software per permettere di avere una distinzione logica delle priorità e permessi dei processi in esecuzione sul sistema. Osservazione: Questo significa che il processore non necessariamente cambi priorità di esecuzione del processo in base al suo runlevel, nè che vada in modalità superuser a runlevel riservati (0,1 o 6). Il numero di runlevel è arbitrario, ma per convenzione in Unix è stato fissato a undici: da 0 a 9 più il livello S, ma solo i primi otto e il ivello S sono ufficialmente documentati in Linux e varianti di Unix. Osservazione: Si noti che il significato dei runlevel è puramente arbitrario e che ogni clone unix può implementarli come meglio crede. Addirittura tra diverse distribuzioni Linux spesso accade che ci siano differenze. Noi ci limiteremo a valutare i runlevel secondo la distribuzione RedHat, tuttavia è interessante notare che i runlevel 0, 1 e 6 sono riservati e hanno significati uguali per tutti mentre S non è accessibile direttamente: runlevel 0 Questo è riservato all’arresto del sistema runlevel 1 Corrisponde alla modalià monoutente runlevel 6 È il livello di reboot runlevel S È stato concepito per essere usato dagli script eseguiti in modalità monoutente Interssante notare che in modalità monoutente è anche disponibile un terminale. Spesso si usa questa modalità quando ci sono problemi seri nel boot del sistema. Si sconsiglia vivamente, comunque, di usarlo, dato che tutti i processi vengono eseguiti coi privilegi di root, con le dovute conseguenze del caso. Per quanto riguarda la distribuzione RedHat (e di riflesso Mandrake) sono di particolare interesse i livelli 3 e 5: runlevel 3 Qui viene avviata la shell testuale ed è il runlevel a cui si opera solitamente runlevel 5 A questo livello vengono eseguiti i processi del sistema X I restanti sono usabili a piacimento. Osservazione: Passando da un runlevel ad un altro, tutti i processi in esecuzione che non sono indicati nel nuovo livello verranno eliminati prima tramite segnale SIGTERM poi con SIGKILL in caso di fallimento. Per ulteriori informazioni si consiglia di guardare “man init”, “man inittab” e i readme sui runlevel sparsi nelle directory di init. 48 CAPITOLO 4. BOOT DEL SISTEMA Action Il campo action ammette numerosi valori, i più importanti sono: respawn: Il processo verrà rieseguito non appena termina (ex. getty) boot: Si eseguirà il processo al boot del sistema, il runlevel verrà ignorato initdefault: Serve per impostare il livello di esecuzione predefinito non appena il sistema si avvia wait: il processo viene avviato e init attende che termini prima di avviare il processo successivo sysinit: il processo viene eseguito all’avvio del sistema prima dei “boot” vari ctrlaltdel: il processo viene avviato quando init riceve il segnale SIGINT, che indica l’avvenuta pressione di Ctrl+Alt+Canc Esempi Alcuni esempi presi dal vero file inittab chiariranno la sua struttura, ad esempio id:3:initdefault: imposta il livello di esecuzione a 3, ovvero all’avvio viene eseguito getty in modalità testo, non grafica. Per poter usare X bisogna allora eseguirlo digitando startx al prompt dei comandi. Se il livello fosse stato 5, avremmo avuto una autenticazione grafica col conseguente avvio automatico di X. si::sysinit:/etc/rc.d/rc.sysinit Questa riga viene eseguita esattamente all’avvio del sistema come specificato da sysinit; il livello di esecuzione viene ignorato. Lo script “rc.sysinit”, specificato ne campo process, è eseguito e serve per impostare molte variabili di ambiente (come HOSTNAME, ad esempio) ed eseguire degli script di configurazione. Le seguenti righe indicano di eseguire lo script “rc” rispettivamente con parametro 0 o 3 a seconda del runlevel in cui si è entrati l0:0:wait:/etc/rc.d/rc 0 l3:3:wait:/etc/rc.d/rc 3 Questo, invece, indica come comportarsi quando si rileva la pressione contemporanea dei tasi “ctrl, alt, del”. ca::ctrlaltdel:/sbin/shutdown -t3 -r now Nulla ci vieterebbe di far apparire la scritta “Ha, ha!!Non puoi resettare!!” semplicemente chiedendo di fare echo sul terminale corrente alla pressione dei tre tasti, ma cui prodest? Diamo un breve sguardo ora, ai terminali. \# Run gettys in standard runlevels 1:12345:respawn:/sbin/mingetty tty1 2:2345:respawn:/sbin/mingetty tty2 ... 4.4. SCRIPT DI AVVIO 49 Questo significa che i terminali 1,2. . . devono esistere ai runlevel 2,3,4 e 5 (il primo esiste anche a rl 1, altrimenti non sarebbe possibile la modalitá single user). Vi sarete resi conto voi stessi del fatto che, nonostante carichiate X (quindi imponete un passaggio al runlevel 5) le console siano sempre disponibili e pronte. Per quanto riguarda il login grafico, basta impostare la seguente linea nel file “inittab” con l’accortezza di impostare il runlevel di default a 5. x:5:respawn:/etc/X11/prefdm -nodaemon Si rimanda comunque alle pagine di manuale dei vari processi caricati per avere informazioni più chiare a riguardo. 4.4 Script di avvio Prima di passare ad una breve analisi degli script di configurazione, illustriamo brevente le modalità di cambio di livello. 4.4.1 Cambiamento del livello di esecuzione Supponiamo che il livello di esecuzione predefinito sia 3, ma siccome l’utente pippo ama lavorare con X vorremo agevolarlo cambiando il livello di esecuzione a 5. Una possibile strada per farlo è modificare /etc/inittab e riavviare; dovremmo allora mettere un 5 al posto del 3 alla riga di initdefault oppure, per evitare di riavviare, potremmo semplicemente scrivere da “root”: #init 5 <CR> Con questo il sistema passa al livello di esecuzione 5. Naturalmente, al prossimo riavvio il sistema sarà al livello 3 perché col comando init il file inittab non viene modificato. Nel caso in cui si optasse per la prima modalità (modifica di inittab), dato che 6 è il runlevel per riavviare, il comando #init 6 <CR> farà riavviare la macchina, mentre #init 0 <CR> farà spegnere la macchina. Eseguire init in questo modo può aiutare a comprendere la ragione per cui una delle prime righe di inittab contenga l’azione sysinit, infatti, quando la macchina è già stata accesa e inizializzata, non è più necessario ripetere l’inizializzazione da capo, e quindi viene semplicemente saltata. 4.4.2 rc.d/ Questa directory è costituita da più cartelle che contengono le impostazioni di avvio e chiusura dei demoni per ogni runlevel e da una che contiene gli script veri e propri. Alla prima categoria appartengono le directory rcN.d (con N un numero da 0 a 6 ed eventualmene delle lettere “speciali”13 ), mentre alla seconda la sola cartella init.d. Analizziamo ora in maniera più approfondita le cartelle. 13 Spesso sono dei link ai runlevel corrispondenti 50 CAPITOLO 4. BOOT DEL SISTEMA Script in init.d La locazione di questa directory (come molte di quelli che seguiranno) dipende dalla distribuzione anche se non è raro trovarla in /etc/rc.d/. Gli script qui contenuti servono a caricare o rimuovere i daemon 14 (demoni o servizi come dir si voglia) con gli opportuni parametri. Editando ad esempio il file syslog si vedrà con che parametri viene caricato e rimosso dalla memoria il demone preposto al logging del sistema. L’analisi di ogni singolo file presente in questa directory sta alla volontà dell’utente che ormai dovrebbe avere buoni strumenti per capire il significato di uno script. Per capire, poi, a che cosa serva un particolare demone basta esaminare la pagina di manuale relativa. Osservazione: Si noti che tutti i demoni hanno il nome terminante con la lettera “d” appunto rcN.d/ In base al valore di N corrisponde un dato runlevel ; va da sè che in rc5.d, per esempio, verranno elencati i processi da avviare e terminare quando si passa a runlevel 5. I nomi dei file presenti sono costituiti da una struttura comune: • Il primo carattere è o S o K • I due caratteri successivi sono un numero decimale • Il resto è libero (o quasi. . . ) La lettera “S” indica che il processo in questione andrà eseguito ogniqualvolta il sistema entrerà il corrispondente runlevel ; la lettera “K” indica che il processo andrà terminato appena il sistema lascerà il runlevel in questione. I numeri servono a dare la priorità di esecuzione dello script corrispondente, il resto, invece è il nome del servizio in questione. Per esempio: ../rc2.d/S80sendmail Eseguirà sendmail 15 con “priorità” 80 (ovvero sarà uno degli ultimi a venire eseguito) all’ingresso del sistema a runlevel 2. Al passaggio da un runlevel all’altro, init confrontrà la kill list 16 del livello attuale con la start list del livello di destinazione e determinerà di conseguenza quali demoni andranno caricati o terminati. Osservazione: I file presenti in queste directory sono semplicemente dei link ai corrispondenti script nella cartella init.d/. Per intenderci: S80sendmail -> ../init.d/sendmail è un link a init.d/sendmail che verrà eseguito col paramero start qualora si entri nel runlevel che contiene il file. 14 Un demone è un processo generalmente eseguito in background progettato per fornire servizi 15 Nome del demone MTA (Mail Transport Agent) 16 la lista dei processi da eliminare all’uscita del runlevel 4.5. BSD (NON-SYSV) INIT 4.5 51 BSD (Non-SysV) init Distribuzioni come la Slackware hanno una configurazione di inizializzazione diversa da quella del System V, per cui parte delle indicazioni qua date, non risulta valida. I BSD hanno semplicemente una cartella /etc/rc.d/ contente i file di inizializzazione. Gli script sono abbastanza semplici e ben commentati, inoltre i singoli nomi dei file sono spesso autoesplicativi. (ex. rc.M corrisponde al runlevel di multiuser, rc.inet1 si occupa di caricare i servizi fondamentali di inetd e così via) 52 CAPITOLO 4. BOOT DEL SISTEMA Capitolo 5 Introduzione a VIM di Elena “of Valhalla” Grandi c 2002, 2003 Elena Grandi. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. 5.1 Perché vi(m) Sotto unix “Tutto è un file”, frequentemente di testo; da qui la grande attenzione rivolta verso gli editor relativi in ambiente unix – e quindi anche linux – sia dal punto di vista qualitativo che quantitativo. Nella massa di editor di testo presenti, alcuni sono molto simili a notepad di windows, ne condividono l’estrema semplicita’, ma anche la mancanza di numerose funzioni, e per di più richiedono la presenza di X, che non sempre è disponibile, specialmente quando è necessario modificare dei file di configurazione. Questo suggerisce di acquistare una certa familiarità con almeno un’editor di testo da consolle, tra i quali i più diffusi sono emacs e vi (e i suoi cloni). Emacs 1 è stato uno dei primi programmi open source e comprende davvero tutto, dall’editor di testo al browser, dal lettore di newsgroups ad un linguaggio di programmazione, dal programma per la posta all’assistenza psichiatrica! per contro ha una richiesta di risorse non indifferente, specialmente su macchine non recentissime, e non sempre è disponibile. Vi invece segue la filosofia del “tanti piccoli programmi che eseguono bene un solo compito”, non ha requisiti eccessivi, è presente praticamente su ogni unix ed è disponibile anche per altri sistemi (ad esempio windows); inoltre la sua effettiva osticità verso l’utente è stata parzialmente attenuata dai suoi cloni più recenti, come VIM, che introduce nuove funzioni, ad esempio, l’evidenziazione a colori della sintassi. Può valere la pena di citare anche il “terzo incomodo” nella “guerra degli editor”: ed2 , effettivamente presente sulla gran parte dei sistemi, al pari di vi (quantomeno per quello che riguarda il mondo unix), ma la cui osticità è ancora maggiore di quella del primo impatto con vi, e la cui scomodità per le abitudini 1 Per 2 ed i più maligni tra i fan di vi: Emacs Makes A Computer Slow is the standard editor 53 54 CAPITOLO 5. INTRODUZIONE A VIM attuali (è un editor di linea, ovvero che può lavorare solo su una linea per volta) ne fanno una scelta improponibile per l’uso interattivo. In questa dispensa parleremo di VIM, Vi IMproved, uno dei cloni di vi più diffusi, anche grazie ad un certo numero di migliorie rispetto all’originale, tra cui l’uso del colore per evidenziare la sintassi di numerosi linguaggi. La maggior parte dei comandi indicati comunque fa parte dell’insieme standard e può essere usata anche con gli altri cloni; i casi in cui questo non è possibile verranno segnalati nel testo. 5.2 Le modalità Per motivi storici, vi utilizza soltanto la parte principale della tastiera3 , per cui necessita di diverse modalità: comando (o normale), inserimento, linea di comando ed altre; per passare dall’una all’altra si usano prevalentemente i seguenti comandi: • i o a per passare dalla modalità comando a quella inserimento, partendo dal carattere corrente o da quello successivo rispettivamente; • A per passare dalla modalità comando a quella inserimento, partendo dall’ultimo carattere della linea; • R per passare dalla modalità comando a quella sostituzione; • r per passare dalla modalitá comando a quella sostituzione per la modifica di un solo carattere (dopo torna automaticamente in modalità comando) • : per passare dalla modalità comando a quella linea di comando; • / per passare dalla modalità comando a quella ricerca; • <Esc> per passare dalle modalità inserimento o sostituzione a quella comando; • <Backspace> per passare dalla modalità linea di comando a quella comando. Questo può sembrare una scomodità, ma permette di non spostare mai le mani sulla tastiera, garantendo una velocità di lavoro superiore a quella data da ogni altro editor di testo. 5.3 La modalità riga di comando In questa modalità si possono inserire comandi (anche abbastanza complessi) che generalmente hanno un effetto di tipo “globale”; ad esempio apertura, salvataggio, chiusura, ricerca eccetera. Entrando in questa modalità, il primo carattere dell’ultima riga, seguito dal cursore, indica il tipo di comando che si può inserire: • : permette di inserire comandi “globali” (detti “comandi Ex”); • / o ? permette di effettuare una ricerca. 3 quella con i tasti alfanumerici, invio e il tasto esc, sempre presenti su ogni macchina 5.4. LA MODALITÀ COMANDO 5.3.1 55 Principali “comandi Ex” I comandi “globali” più utilizzati sono quelli per la gestione dei file: • :w per salvare il buffer corrente; • :w nomefile per salvare il buffer corrente con il nome nomefile; • :q per chiudere il buffer corrente ed uscire da vi qualora questo sia l’ultimo (se non sono state fatte modifiche dall’ultimo salvataggio); • :q! per chiudere il buffer corrente, ed eventualmente uscire da vi, senza salvare; • :e nomefile per aprire il file “nomefile” nel buffer corrente; • :help per leggere l’help in linea. 5.3.2 Ricerca e sostituzione La ricerca di un espressione regolare si effettua semplicemente entrando in modalità riga di comando digitando / e quindi digitando la stringa da cercare: il cursore verrà spostato all’inizio di tale stringa e, con VIM, questa verrà evidenziata. La sostituzione invece avviene tramite il “comando Ex” :s, come nel seguente esempio: :%s/old/new/gc oppure :%s/old/new/g dove % indica dove sostituire (su tutto il file), old è la stringa da sostituire, new é la nuova stringa, g indica di sostituire tutte le occorrenze e non solo la prima incontrata ed infine l’eventuale c fa sı̀che venga richiesta conferma prima di ogni sostituzione. 5.4 La modalità comando In modalità comando (o normale) si inseriscono comandi, composti da un solo carattere (o da una combinazione carattere-numero-carattere), che hanno generalmente effetti “locali”, sul carattere, parola o riga dove è posto il cursore. I comandi che possono essere applicati ad un solo elemento (carattere, parola, riga, ecc.) sono composti da un solo carattere, quelli che utilizzano la combinazione carattere-numero-carattere agiscono sui [numero] elementi a partire da quello corrente, e possono essere applicati ad un solo elemento omettendo il numero. 56 CAPITOLO 5. INTRODUZIONE A VIM 5.4.1 Comandi più utilizzati I comandi “locali” più utilizzati sono: • h per spostare il cursore a sinistra; • j per sostare il cursore in basso; • k per spostare il cursore in alto; • l per spostare il cursore a destra; • x per cancellare il carattere corrente; • d[n]d per tagliare le [n] righe a partire da quella corrente; • y[n]y per copiare le [n] righe a partire da quella corrente; • p per copiare le righe tagliate o copiate sotto quella corrente; • u per annullare l’ultima operazione; • . per ripristinare l’ultimo comando annullato. 5.5 Le modalità inserimento e sostituzione Queste modalità sono quelle che permettono di scrivere effettivamente il testo: la prima aggiunge i caratteri digitati nella posizione del cursore, la seconda rimpiazza il carattere corrente con quello digitato. Utilizzando VIM è possibile spostarsi all’interno del testo con i normali tasti (frecce, pag su e pag giù, eccetera), nonché cancellare con il tasto <Backspace>, mentre con vi originale questo non era possibile. 5.6 Per approfondire Una prima fonte da consultare è la pagina ufficiale di VIM (http://www.vim.org) che contiene anche abbondante documentazione. VIM dispone inoltre di un help in linea ben nutrito, raggiungibile tramite il comando :help o :help argomento Dato l’elevato numero di comandi e il loro difficile reperimento, nel caso si voglia usare vi(m) con una certa sistematicitá può essere il caso di procurarsi una delle numerose “reference cards” con elenchi sintetici dei comandi più utili. Capitolo 6 Archiviazione, compressione e gestione dei pacchetti di Elena Grandi Francesco Toso c 2002 Elena Grandi, Francesco Toso. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. Introduzione Da quando esistono i computer si è sempre sentita l’esigenza di “archiviare” (raccogliere più file in uno unico) e comprimere i file, per questioni di comodità nella gestione, di scarsità dello spazio o anche per esigenze dovute a particolari tipi di hardware1 ; quando poi i file da gestire sono quelli di un programma che deve essere distribuito ed installato si aggiunge l’esigenza di automatizzare tale procedura: per questo motivo sono stati inventati i “pachetti” contenenti i file e tutte le informazione necessarie allo scopo. 6.1 Archiviazione e compressione Contrariamente a ciò che avviene sotto altri sistemi operativi, sotto unix le operazioni di archiviazione e compressione vengono effettuate da programmi distinti, secondo la filosofia del “fai una cosa sola ma falla bene”; questo può sembrare una scomodità, ma presenta alcuni vantaggi dal punto di vista dell’efficienza e dell’aggiornabilità. 6.1.1 tar(1) Il programma tradizionale per l’archiviazione, tar, si limita ad accodare ciò che gli viene passato in un’unico file, convenzionalmente dotato dell’estenione .tar, aggiungendo le informazioni necessarie per la successiva suddivisione. Il funzionamento di base è estremamente semplice e consiste nei comandi 1 Ad esempio i nastri, sui quali potrebbe non essere presente un filesystem, ma solo un unico grosso file. 57 58 CAPITOLO 6. ARCHIVIAZIONE E PACCHETTI $tar cvf filename.tar directory1 [ directory2, ...directoryN ] per archiviare (c, Create) le directory da 1 a N nel file (f) filename.tar, dando informazioni su ciò che sta facendo (v, ovvero verbose) $tar tvf filename.tar per visualizzare (t, Tree) il contenuto di filename.tar, e $tar xvf filename.tar per estrarre (x, eXtract) il contenuto di filename.tar nella direcotory corrente. È importante ricordare che quando si archiviano dei files è buona norma inserire nell’archivio anche la directory nella quale sono contenuti, in modo tale che chi compie l’operazione opposta non si trovi con centinaia di file sparsi dove non se li aspettava. Le nuove versioni di tar sono generalmente in grado di gestire file compressi con gzip o bzip2, aggiungendo rispettivamente z o j2 alle opzioni sulla riga di comando: in questo modo tar provvede automaticamente a richiamare l’eseguibile opportuno con le dovute opzioni per effettuare la compressione o la decompressione. I file trattati in questo modo possono avere l’estensione .tar.gz o .tar.bz2 rispettivamente, oppure .tgz, abbreviazione del primo. 6.1.2 gzip(1) Il programma di compressione tipico dei sistemi GNU (come Linux) è gzip: come il suo predecessore compress, si limita alla compressione dei singoli files che gli vengono passati, ai quali aggiunge l’estenzione .gz. Il suo utilizzo elementare è semplicemente $ gzip nomefile per comprimere nomefile in nomefile.gz, $ gzip -d nomefile.gz oppure $ gunzip nomefile.gz per decomprimere nomefile.gz in nomefile; se gli vengono passati più files si limita a ripetere le operazioni su ciascuno di essi e non può lavorare su directory. 6.1.3 bzip2(1) bzip2 è un programma di compressione abbastanza recente, più efficiente di gzip, ma non ancora universalmente diffuso: in particolare molti programmi per windows riescono a gestire i file .gz e .tar.gz, ma non ancora i bz2 generati da questo programma. L’utilizzo è praticamente identico a quello di gzip, utilizzando il comando bzip nomefile per la compressione e bunzip2 nomefile.bz2 per la decompressione. 2 In alcune versioni di tar questa opzione potrebbe essere sostituita da y 6.2. I PACCHETTI RPM 6.1.4 59 zip(1) zip è il programma di archiviazione e compressione compatibile con il PKZIP per dos e con i vari winzip e simili per windows: il suo utilizzo è generalmente limitato a casi in cui sia necessaria la compatibilità con tali sistemi. 6.1.5 compress(1) compress era il programma standard per la compressione dei file sotto unix: è ormai stato superato per efficacia da gzip, che riesce anche a gestirne i file, contraddistinti dall’estensione .Z. 6.1.6 cpio(1) cpio è un’altro programma di archiviazione, dotato di alcune funzionalità aggiuntive rispetto a tar, ma generalmente poco usato, se non per i pacchetti di tipo rpm (e in questo caso in modo invisibile all’utente). 6.1.7 Ulteriori informazioni Per saperne di più la cosa migliore è leggere le pagine di manuale (man) dei rispettivi programmi. 6.2 I pacchetti rpm Di Francesco Toso I pacchetti rpm vennero creati dalla RedHat appositamente per la loro distribuzione con lo scopo di renderla quanto più “user friendly” possibile. Data la loro semplicitá di utilizzo, vennero ben presto adottati da altre distribuzioni come Mandrake 3 o SuSE. Oltre ai dati veri e propri gli rpm contengono (come tutti i pacchetti) anche dati di controllo come la descrizione del pacchetto, le dipendenze, gli script di installazione e altro. 6.2.1 Struttura Un pacchetto rpm viene creato basilarmente con la collaborazione di cpio e gzip; sarebbe quindi possbile, tramite questi due programmi aprire, installare e modificare un pacchetto rpm. Per una maggior semplicitá d’uso e comodità si consiglia l’uso direttamente di rpm con gli opportuni parametri. Tali pacchetti contengono anche un file speciale detto spec file. Lo spec file contiene la descrizione del software nel pacchetto assieme alle istruzioni su come compilarlo e assieme alla lista dei file binari che verranno installati. 6.2.2 Utilizzo di base Generalmente rpm viene usato per installare pacchetti, ad esmpio: # rpm -i prova-1.0-1.i386.rpm 3 che ricordiamo essere nata come una versione modificata della RedHat 60 CAPITOLO 6. ARCHIVIAZIONE E PACCHETTI Installerá il pacchetto prova versione 1.0-1 compilato per i386 nel sistema (se sono presenti le dovute dipendenze. Duale all’installazione c’é la disintallazione di un pacchetto: # rpm -e prova Questo comando rimuoverá il pacchetto prova informandovi di eventuali problemi di dipendenze con altri pacchetti. Passiamo ora ad esaminare il contenuto di un dato pacchetto: # rpm -qpi prova-1.0-1.i386.rpm Questo comando mostrerá la descrizione del pacchetto fatta dagli autori; per sapere che file installerá il pacchetto e dove basterá scrivere: # rpm -qpl prova-1.0-1.i386.rpm Per sapere, invece, quali pacchetti sono stati installati sul sistema basterá scrivere # rpm -qa Attenzione: l’output puó essere molto grosso! Si consiglia di ridirigere il flusso su less o more oppure filtrarlo con grep 6.2.3 Funzionalità avanzate Passiamo alle funzionalitá avanzate di rpm. Se volessimo, ad esempio, installare un pacchetto direttamente via ftp basterebbe scrivere: # rpm -i ftp://ftp.redhat.com/pub/redhat/rh-2.0-beta/RPMS/\ foobar-1.0-1.i386.rpm rpm stesso si occuperá dello scaricamento e installazione dei pacchetti; tuttavia, a differenza del sistema Debian, non effettuerá né lo scaricamento di dipendenze mancanti, né l’aggiornamento dei pacchetti nel caso in cui risultino datati. É anche possibile semplicemente interrogare il pacchetto remotamente cambiando i comandi come indicato nella sezione precedente. Supponiamo ora di voler sapere a che pacchetto appartiene il file iptables in /sbin/: # rpm -qf /sbin/ipchains Avremo cosı̀il nome del pacchetto che contiene il file; ipotizziamo che tale file sia stato cancellato a nostra insaputa, sarebbe opportuno individuarlo e reinstallare il pacchetto “mutilato”: # rpm -Va Interrogherá tutti i pacchetti installati sulla macchina verificando la loro integritá; eventuali mancanze o errori verranno segnalati. Per installare pacchetti in formato sorgente basta eseguire la seguente operazione: # rpm -bai pacchetto In questo modo si compileranno i sorgenti e si installerá il pacchetto compilato eliminando poi i file temporanei creati. Risulterá molto utile alla fine della sezione seguente. 6.2. I PACCHETTI RPM 6.2.4 61 Creazione Per poter creare un vero e proprio pacchetto rpm é necessario creare il file spec che contiene tutte le informazioni sulla installazione, manutenzione e altro del pacchetto. Si consiglia di chiamarlo seguendo la seguente convenzione: nomeversione-release.spec. Quanto segue é lo scheletro di uno spec file Summary: <breve descrizione> Name: <nome pacchetto> Version: <versione> Release: <release> Copyright: <licenza> Requires: <dipendenze> Group: <gruppo del pacchetto> Source: <url dei sorgenti> Patch: <eventuale patch applicata> BuildRoot: <directory di compilazione> %description <descrizione per esteso> %prep %setup -q %patch -p1 -b .buildroot %build make RPM_OPT_FLAGS="$RPM_OPT_FLAGS" %install rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/usr/bin mkdir -p $RPM_BUILD_ROOT/usr/man/man1 install -s -m 755 <nome pacchetto> \ $RPM_BUILD_ROOT/usr/bin/<nome binario> install -m 644 <nome pacchetto>.1 \ $RPM_BUILD_ROOT/usr/man/man1/<nome binario>.1 %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %doc README TODO COPYING ChangeLog /usr/bin/<nome binario> /usr/man/man1/<nome binario>.1 %changelog <cambiamenti ecc.> Analizziamo ora in dettaglio le varie voci. Intestazione Fa parte dell’intestazione tutto ció che viene prima del tag %prep. Il significato di ogni campo é abbastanza autoesplicativo. Si segnala la possibilitá di specificare 62 CAPITOLO 6. ARCHIVIAZIONE E PACCHETTI piú Source e Patch nel seguente modo (l’esempio é solo per Source, per Patch basta sostituire la parola chiave): Source0: blah-0.tar.gz Source1: blah-1.tar.gz Source2: fooblah.tar.gz Il tag Group va compilato secondo quanto specificato in /usr/doc/rpm*/GROUPS, si evita di riportare qui di seguito le categorie dato che sono in continuo cambiamento. Ultimo é il tag BuildRoot che dá informazioni su dove compilare e installare temporaneamente il pacchetto. Prep Qui vengono effettuate tutte le operazioni necessarie per preparare i sorgenti e applicare su questi una o piú patch. Tutto ció che viene preceduto dal simbolo % é da considerarsi un macrocomando. Di base %setup si limita a decomprimere il contenuto del pacchetto e a entrare nella directory in cui é stato decompresso; alcuni parametri sono: -n nome imposterá la directory di compilazione. Di default verrá scelta la directory nome-versione (attenzione, questa é la fase di compilazione, non di installazione!!) -b scompatterá la sorgente prima di cambiare directory (risulta assai utile in caso di piú file sorgente) -D Non cancella la cartella prima di decomprimere. Questa opzione é utile nel caso in cui vi siano piú %setup di seguito che collaborano. Si noti che dovrebbe venir usato dal secondo %setup in poi ma non nel primo! (altrimenti rischieremmo di avere problemi nella compilazione) La macro %patch si occuperá di modificare i sorgenti in accordo con le patch specificate nella sezione principale. Build Qui andranno inseriti tutti i comandi per la compilazione dei sorgenti, generalmente é piú che sufficente il comando make. La variabile $RPM OPT FLAGS indica le ottimizzazioni da applicare durante la compilazione e viene impostata come specificato in /usr/lib/rpm/rpmrc. Install Questa parte riguarda l’installazione vera e propria del pacchetto. Anche qui non vi sono macro per semplificare le operazioni, tuttavia si consiglia di seguire uno schema simile al seguente: 1. Pulizia della BuildRoot tramite rm - rf $RPM BUILD ROOT 2. Creazione delle directory del programma e della documentazione 3. Installazione vera e propria tramite install o make install 6.2. I PACCHETTI RPM 63 4. Impostazione dei permessi Si consiglia di far seguire i comandi di setup da uno di %clean al fine di eliminare i file superflui a fine installazione. Files Qui vanno specificati i file utili creati nella fase di installazione al fine di poterli posizionare correttamente nel file system. In questo caso sono disponibili un paio di macro molto utili: %doc indica quali file di documentazione andranno installati (possono tranquillamente essere messi uno di seguito all’altro); questi verranno posizionati in /usr/doc/$NAME-$VERSION-$RELEASE %defattr specifica gli attributi di default per ogni file %dir indica quali directory installare come parte del pacchetto. Nel caso in cui non si specifichi nulla tutto ció che é contenuto nella BuildRoot verrá incluso (quindi verranno installati anche sorgenti puri, file oggetto ecc.) Una volta creato il file di spec non ci resta che costruire il pacchetto vero e proprio. Scelta una directory di lavoro andranno create le seguenti sottodirectory: BUILD dove RPM metterá i compilati SOURCES dove rpm cercherá i sorgenti e le patch per compilare SPECS dove andranno inseriti i file spec RPMS dove verranno inseriti i compilati SRPMS dove finiranno i sorgenti scompattati Posizionati correttamente i file, per testare il tutto basterá scrivere: # rpm -ba nomefile.spec In questo modo si potrá testare la corretta compilazione dell’archivio, inoltre verranno creati i seguenti file: RPMS/i386/*.*.rpm RPMS/i386/*.*.doc.rpm SRPMS/*.src.rpm I nomi (gli asterischi) dipendono da molti fattori tra cui il nome del pacchetto, l’architettura su cui si é compilato, la versione e altri. Ora siete pronti per distribuire i vostri pacchetti!! 6.2.5 Ulteriori informazioni Si consiglia di usare programmi grafici quali gnorpm e kpackages in caso di installazione, disinstallazione e semplice gestione dei pacchetti. La loro lentezza é pienamente compensata da una estrema facilitá d’uso. In caso di utilizi avanzati o di necessitá di velocitá il comando da shell risulta il migliore a patto di saperlo utilizzare adeguatamente. Si consiglia, come sempre, di guardare la pagina di man di rpm oltre che l’rpm howto. Una buona guida si puó trovare all’indirizzo: http://newbie.linuxbe.org/linux/rpm/seneca-rpm.html 64 6.3 CAPITOLO 6. ARCHIVIAZIONE E PACCHETTI I pacchetti deb La distribuzione Debian utilizza un suo formato di pacchetti: quelli binari hanno l’estensione .deb, inoltre esiste un formato distinto per i pacchetti contenenti i sorgenti, composto da una descrizione (.dsc), un archivio compresso (.orig.tar.gz) ed un file di differenze (.diff.gz). 6.3.1 Struttura Un pacchetto binario debian è un archivio compresso contenente un file control con le informazioni sul pacchetto e sulle dipendenze, un file confiles con l’elenco dei file di configurazione, degli script per l’installazione (preinst e postinst) e per la disinstallazione (prerm e postrm). 6.3.2 Utilizzo di base Il sistema di gestione dei pacchetti Debian comprende numerosi programmi, tuttavia nella maggior parte dei casi sono sufficienti il programma interattivo dselect, oppure apt-get; quest’ultimo richiede un file di configurazione /etc/apt/sources.list con l’elenco delle fonti contenenti distribuzioni debian, quindi lo si può usare con: # apt-get update per aggiornare l’elenco locale dei pacchetti disponibili, # apt-get upgrade per aggiornare tutti i pacchetti installati, # apt-get install nomepacchetto per installare o aggiornare un pacchetto specifico, soddisfando le dipendenze. 6.3.3 Ulteriori informazioni Per una spiegazione approfondita sull’uso dei pacchetti Debian si può consultare “Appunti di informatica libera” di Daniele Giacomini. 6.4 I pacchetti slackware La distribuzione Slackware utilizza un suo formato di pacchetti, non usato da altre distribuzioni. 6.4.1 Struttura I pacchetti Slackware sono dei semplici archivi tar compressi con gzip contenenti i file (generalmente già compilati) del programma ed alcuni script per la gestione dell’installazione: /install/doinst.sh per la creazione dei collegamenti simbolici, 6.4. I PACCHETTI SLACKWARE 65 /var/log/setup/setup.nomepacchetto per la configurazione del pacchetto in questione, /var/log/setup/setup.onlyonce.nomepacchetto per la configurazione subito dopo l’installazione. I pacchetti Slackware hanno convenzionalmente l’estensione .tar.gz o più frequentemente .tgz caratteristica dei programmi usati per l’archiviazione e la compressione. 6.4.2 Utilizzo di base Il modo più intuitivo per gestire i pacchetti slackware è l’utilizzo del programma pkgtool(8), che permette di effettuare l’installazione, la disinstallazione e il controllo dei pacchetti attraverso un’interfaccia in grafica ascii; questo programma tuttavia è solo un front-end per installpkg(8) e removepkg(8). Per l’installazione di un pacchetto è sufficiente il comando # installpkg nomepacchetto (notare il prompt di root), mentre per la sua disinstallazione si usa # removepkg nomepacchetto in entrambi i casi l’opzione -warn permette di simulare soltanto l’operazione richiesta. 6.4.3 Funzionalità avanzate Per l’aggiornamento dei pacchetti è possibile usare il comando # upgradepkg vecchiopacchetto[%nuovopacchetto] che installa nuovopacchetto e quindi rimuove i files di vecchiopacchetto non richesti da quello nuovo. Qualora si vogliano installare i pacchetti Slackware non a partire dalla root “standard” (/), ma da una directory a piacere, ad esempio perchè si sta preparando un’installazione “from scratch” oppure per un’altra macchina, è possibile utilizzare la variabile ROOT, impostata con il nome della directory dalla quale si vuol far partire il “nuovo” filesystem; per tale scopo, ma solo per il comando installpkg, è anche disponibile l’opzione -root /nomenuovaroot. 6.4.4 Creazione Per creare un pacchetto Slackware con il contenuto della directory corrente e di tutte le sue subdirectory si utilizza il comando # makepkg nomepacchetto che crea un’archivio nel quale però i link simbolici sono stati eliminati e sono state aggiunte4 le istruzioni su come ricrearli nello script install/doinst.sh; 4 aggiunte in coda – in inglese appended – al file se questo è già presente, altrimenti viene creato. 66 CAPITOLO 6. ARCHIVIAZIONE E PACCHETTI eventuali comandi da eseguirsi in fase di installazione e/o configurazione possono essere messi in tale script o in quelli presenti in var/log/setup, utilizzando la sintassi della shell Bourne (in alcuni casi in fase di installazione verranno eseguiti usando la shell ash!), avendo cura di non mettere nessun comando interattivo in install/doinst.sh. 6.4.5 Ulteriori informazioni Per ulteriori informazioni si possono leggere le seguenti pagine di manuale: installpkg(8), explodepkg(8), removepkg(8), upgradepkg(8), makepkg(8), setup(8) e pkgtool(8), ed eventualmente per la creazione degli script ash(1). 6.5 I pacchetti con i sorgenti Il formato tradizionale per la distribuzione dei programmi sotto unix, e in alcuni casi il migliore, è il codice sorgente, generalmente raccolto in archivi compressi insieme a script ed altri file utili per la compilazione. 6.5.1 Struttura Questi pacchetti sono dei normali archivi (di solito tar) compressi con uno dei programmi di compressione (di solito gzip o bzip2), contenenti il codice sorgente, dei file di testo di presentazione del programma o di aiuto (README, INSTALL) ed alcuni file utili per la compilazione, come lo script ./configure e il makefile, di solito chiamato makefile o Makefile e spesso generato da ./configure. 6.5.2 Utilizzo di base Quando si scarica un pacchetto di questo tipo, la prima cosa da fare è estrarne i contenuti in una directory opportuna (una subdirectory della propria home può andare bene se si ha intenzione di cancellare i sorgenti, se si ha intenzione di lasciarli sul disco di solito si utilizza una subdirectory di /usr/src). A questo punto è sempre utile leggere i file README e/o INSTALL, che dovrebbero contenere istruzioni dettagliate per l’installazione; nel caso in cui questi non fossero presenti o fossero lacunosi, la procedura standard prevede comunque l’esecuzione dello script ./configure, da richiamare sempre indicandone esplicitamente la posizione: $ ./configure [eventuali opzioni documentate nel README] che provvede a generare il makefile; nel caso in cui tale script non fosse presente può essere necessario modificare a mano il makefile originale, in modo tale da dare al programma make le istruzioni adatte al proprio sistema. Quindi si può usare il comando $ make per procedere alla compilazione del programma. Completati questi passaggi, se il “makefile” li supporta, è possibile usare il comando 6.6. ALTRI TIPI DI PACCHETTI 67 # make install (come utente root!) per copiare i file generati nelle posizioni opportune (secondo l’autore del “makefile”, e quindi il comando $ make clean per eliminare i file oggetto generati nel corso della compilazione. 6.6 Altri tipi di pacchetti Alcuni programmi sono, per vari motivi, distribuiti in pacchetti di tipo diverso da quelli “standard”. 6.6.1 Eseguibili installanti Sono file eseguibili che forniscono una procedura di installazione simile a quella tipica di windows, con un’interfaccia grafica che chiede alcune domande e quindi procede all’installazione. Possono essere distribuiti da soli, oppure archiviati e compressi con i programmi standard, eventualmente insieme a semplici file di aiuto (README). Non sono molto diffusi: da un lato perche’ non esistono programmi “standard” per la loro realizzazione, come avviene invece sotto windows, dall’altro perchè non offrono le funzionalità di controllo delle dipendenze e supporto per la disinstallazione fornite dai pacchetti specifici per le distribuzioni. 6.6.2 Net installer Sono dei file eseguibili di piccole dimensioni che forniscono un’interfaccia simile a quella degli installer precedenti per la configurazione, quindi provvedono a scaricare da internet le sole parti del programma delle quali si è richiesta l’installazione. 6.6.3 pacchetti jar I pacchetti jar contengono file oggetto java archiviati e compressi e possono essere usati direttamente dalla java virtual machine, generalmente con il comando $ java -jar nomepacchetto.jar. 6.7 Glossario Glossario dei termini tecnici usati nella dispensa. archiviazione operazione di raccolta di più files in uno unico, detto archivio, comprendente anche le informazioni necessarie per la reversibilità del processo. compilazione operazione che trasforma del codice sorgente scritto in un linguaggio ad alto livello in codice macchina eseguibile. 68 CAPITOLO 6. ARCHIVIAZIONE E PACCHETTI compressione operazione di riduzione dello spazio occupato da un file, effettuata mediante l’applicazione di un apposito algoritmo matematico. dipendenze si chiamano dipendenze di un pacchetto tutti e soli i pacchetti che siano strettamente necessari per il corretto funzionamento dello stesso. pacchetto mezzo di distribuzione di un programma, comprendente i file dello stesso ed alcuni file per la sua gestione. Nella maggior parte dei casi si può tranquillamente identificare il pacchetto con l’archivio che lo contiene; fa eccezione la distribuzione Debian, nella quale le due cose sono da considerarsi ben distinte. sorgenti (o codice sorgente) istruzioni di un programma scritte in un linguaggio di alto livello. 6.8 La struttura convenzionale del filesystem Nei sitstemi unix è frequentemente presente una struttura di directory convenzionali, per dare un ordine alle miriadi di file presenti: è utile conoscerla5 per poter gestire al meglio il proprio sistema, specialmente in fase di installazione di software. /bin contiene gli eseguibili dei programmi fondamentali per il funzionamento del sistema (ad esempio sh o cd); /sbin contiene gli eseguibili dei programmi fondamentali per il funzionamento e la gestione del sistema, che debbano essere usati solo dall’utente root (Superuser BINaries); /etc contiene i file di configurazione dei programmi contenuti in /bin e /sbin; /lib contiene le librerie fondamentali; /opt contiene le directory relative a programmi “opzionali”, che richiedono una propria directory; /var contiene i files “variabili” dei programmi in /bin e /sbin, come i log eccetera; /usr contiene i file necessari agli utenti, come i programmi aggiuntivi (suddivisi in /usr/bin, /usr/etc /usr/sbin; /boot contiene alcuni file necessari per effettuare il boot del sistema (tra cui in alcuni casi anche il kernel); /dev contiene i file di “device” corrispondenti alle varie periferiche del sistema; /home contiene le directory degli utenti (/home/nome); /mnt contiene eventuali punti di mount di partizioni e/o dischi non montati altrove (ad es. /mnt/cdrom); /root contiene la directory home dell’utente root; /tmp contiene eventuali file temporanei. 5 almeno nella variante utilizzata dalle principali distribuzioni linux Capitolo 7 Introduzione alla sicurezza dei sistemi informativi di Francesco Toso c 2002 Francesco Toso. Tutti i diritti riservati. Copyright Una copia di questa dispensa sotto licenza GNU/FDL è disponibile sul sito http://lifolab.org nella sezione documentazione. Questo documento e l’autore non hanno alcuna pretesa di spigare in maniera esaustiva le problematiche sulla sicurezza dei sistemi informativi. Per poter trattare efficacemente questo argomento non basterebbe un libro intero e trenta ore di lezione ”seria”; quanto segue vuole solo essere una panoramica sullo stato attuale della sicurezza informatica. 7.1 Definizione di sicurezza in ambito informatico Un sistema informatico sicuro anzitutto deve garantire l’integrità e la confidenzialità delle informazioni in esso contenute e fornite; questo significa che le informazioni non devono poter essere manomesse e devono essere accessibili solo ed esclusivamente ai destinatari voluti. In una azienda farmaceutica, ad esempio, non si vuole che un qualsiasi operatore dell’azienda possa modificare le composizioni di un medicinale in maniera arbitraria, inoltre solo chi è nel reparto di ricerca potrà avere accesso alle formule relative al proprio lavoro. Queste esigenze non sono nate negli ultimi anni come si potrebbe pensare ma hanno origini assai più vecchie: in ambito militare, infatti,si è sempre cercato di far giungere le proprie strategie belliche al fronte senza che gli avversari intercettassero il messaggio e in modo che non potessere essere né letto né modificato da un eventuale intercettatore.1 Purtroppo, dato che si ha a che fare con del software spesso mal progettato dal punto di vista della sicurezza, attualmente un sistema informatico sicuro è 1 Si ricorda il crittosistema di Cesare 69 70 CAPITOLO 7. INTRODUZIONE ALLA SICUREZZA un sistema che che garantisce un elevato grado di integrità e confidenzialità dei dati riducendo al minimo le probabilità di intrusione da parte di estranei. Si ricordi che in un sistema informatico il livello totale di sicurezza del sistema è dato da quello della macchina più debole! Per ottenere quanto scritto è improponibile fare affidamento solo su del software che automatizzi i processi, è necessaria la presenza di uomini che costantemente si tengano aggiornati e che non “abbassino mai la guardia”. 7.2 Caratterizzazione degli attaccanti Ma chi sono i “nemici”? In generale quando ci si deve difendere è bene conoscere al meglio i propri nemici in modo da sfruttare le loro debolezze a nostro vantaggio. Difendersi da ignoti è molto più complicato e oneroso e questo è uno dei principali problemi in informatica: difficilmente si vedrà in faccia o si conoscerà chi ha sfondato il nostro sistema e molto spesso ci si accorgerà quando ormai il danno è stato fatto. Questo grosso svantaggio per gli amministratori di sistema ha portato a generare una catalogazione comportamentale dei nemici al fine di ridurre il fattore di incertezza. Inutile elencare tutte le diatribe nate in merito; tuttavia parte di questa classificazione ha dato la possibilità (possbilià assai poco sfruttata) agli amministratori di difendersi da una buona fetta dei “nemici”. I cosiddetti script kiddy, infatti, molto spesso hanno pochissime nozioni informatiche e riescono a entrare nei sistemi solo grazie a degli script di cui ignorano il contenuto e il funzionamento. Basta quindi cambiare qualche parametro come la posizione di un file critico, il nome o altro per mettere in seria difficoltà questa classe di persone (che spesso è una buona fetta dei ”nemici”) e farli desistere. La restante parte solitamente è molto motiviata e difficilmente si fermerà davanti a problemi anche di grossa entità (spesso un hacker ha dalla sua il tempo). 7.3 Tipologie di attacco Effetuiamo ora una tassonomia delle modalità di attacco al sistema per poter megli capire anche dove difendersi. 7.3.1 Dall’esterno della rete locale Un primo tipo di attacco (di gran lunga il più diffuso) consiste nello sfruttare vulnerabilità del sistema vittima connettendosi direttamente (o quasi) ad esso. Per prevenire tali attacchi è utile se non obbligatorio aggiornare il software installato ogni qual volta si scopre che la versione attualmente in uso ha dei problemi di sicurezza. Risulta inoltre indispensabile non installare servizi in versione beta o “troppo avanti” nelle versioni.2 La corretta installazione di un firewall e l’utilizzo di uno dei paradigmi descritti in seguito limita notevolmente i danni causabili al sistema. 2 Si veda qmail contro sendmail 7.4. TECNICHE DI DIFESA 7.3.2 71 Man in the middle Letteralmente Uomo nel mezzo, è il tipo di attacco più comune e frequente in ambito militare. Spesso, infatti, non serve introdursi sul server ma basta semplicemente “spiare” i dati trasmessi dal server ai client per ottenere informazioni preziose quali password, numeri di carte di credito ed altro. Il sistema più efficace per questo tipo di attacco è quello di adottare trasmissioni crittate (eventualmente solo quando c’è necessità) o addirittura ambienti completi come Kerberos. 7.3.3 Interno della rete Come si dovrebbe imparare dall’Iliade, il pericolo peggiore spesso proviene dall’interno. Le classiche reti NT/9x sono delle mine vaganti per la sicurezza. L’utente di client 9x, infatti, lascia spesso e inavvertitamente aperte porte netbios per la condivisione di file su cartelle “critiche” permettendo a chiunque l’accesso3 . Se si ricorda qualche anno fa si parlò di una intrusione nei sistemi Microsoft; questo fu possibile perché qualcuno lasciò aperta la porta netbios4 permettendo una facilissima intrusione nel sistema. Non è anche da escludersi che qualcuno del personale voglia appropriarsi di dati o informazioni, per questo risulta necessario garantire un certo livello fisico di sicurezza limitando l’accesso ai locali server e dando in dotazione al personale computer pensati appositamente a tale scopo. 7.3.4 Sé stessi Per concludere si vuol far presente che spesso sono gli stessi amministratori (o presunti tali) che rendono la rete insicura e vittima di attacchi. La pigrizia di queste persone nel non volere aggiornare il software presente, il fatto di dare password e account liberamente e la mancanza di voglia di cambiare periodicamente la propria password, fanno si che anche i più sprovveduti riescano a sfondare il sistema. L’unico “antidoto” contro questo problema (assai frequente) è quello di far venire la voglia di lavorare all’amministratore; purtroppo non esistono mezzi scientifici per porvi rimedio. 7.4 Tecniche di difesa Dopo aver visto a grandi linee le modalità di attacco passiamo alla descrizione delle modalità passive di difesa. Con difesa passiva si indicano quegli accorgimenti che permettono di aumentare la sicurezza del sistema informativo, che non riguardano direttamente il software installato sul sistema e che sono “sempre attivi”. 3 Il protocollo netbios, infatti, non si preoccupa minimamente di autenticare chi si vuole collegare e non implementa alcun meccanismo di restrizione degli accessi. 4 In questo caso risulta stupido anche l’amministratore che al tempo non si preoccupò minimamente di controllare la rete 72 CAPITOLO 7. INTRODUZIONE ALLA SICUREZZA Figura 7.1: Firewall semplice (G. Serazzi, Cremonesi) 7.4.1 Policy di sicurezza Un primo sistema di difesa passiva che non è assolutamente opzionale per gli ammninistratori è quello di definire una policy 5 per la sicurezza del sistema informativo come ad esmpio le modalità di cessione degli account o gli accessi al sistema. Più è restrittivo (e rispettato) questo insieme di regole, maggiore è la sicurezza del sistema; risulta evidente che è necessario trovare un buon compromesso. Come ogni attività progettuale ben fatta, richiede un’analisi preliminare del sistema informativo e delle esigenze degli utenti cosı̀come una attenta stesura della policy stessa in maniera tale che possa essere facilmente modificata in seguito. Un fattore importante è la segretezza della stessa: allo stato attuale delle cose meno persone conoscono la struttura vera e propria della policy, minori risultano le possibilità di intrusione nella rete dall’interno. 7.4.2 Paradigmi di sicurezza Si elencano ora una serie di paradigmi di configurazione della rete al fine di arginare il più possibile il danno in caso di intrusione nel sistema. Firewall semplice Questa struttura permette di filtrare ogni pacchetto proveniente dall’esterno ed eventualmente bloccarlo secondo certi criteri. Tale configurazione viene spesso adottata quando non si vuole esportare verso l’esterno alcun servizio se non a un insieme di macchine autorizzate. 5 Un insieme di regole 7.5. BIBLIOGRAFIA 73 Figura 7.2: Proxy masquerading (G. Serazzi, Cremonesi) Ha il grosso svantaggio che, se viene in un qualche modo sorpassato il firewall, tutto il resto è facilmente visibile, inoltre non difende da attacchi provenienti dall’interno o anche da porte aperte da virus o simili. Proxy masquerading Il proxy ha due interfacce di rete: una per la rete interna, l’altra per l’esterna. In questo modo si dividono nettamente le due reti: da quella esterna non si può accedere all’interno e viceversa se non tramite filtraggio del proxy. Bastion host con packet filtering a due livelli Si conclude con questo paradigma tanto complicato quanto efficace che permette alle aziende di esportare servizi come ftp o http direttamente dalla rete locale senza (o comunque riducendo al minimo) esporre la rete interna a problemi di intrusione e di accesso non autorizzato ai dati. I cosiddetti bastion host, infatti, contengono solo ed esclusivamente informazioni che possono essere rese pubbliche senza problemi, nascondendo oltre il secondo firewall i dati più sensibili. 7.5 Bibliografia 1. Huges Larry Jr., Tecniche di sicurezza internet Jackson Libri 2. Security HOWTO 74 CAPITOLO 7. INTRODUZIONE ALLA SICUREZZA Figura 7.3: Bastion host con packet filtering a due livelli (G. Serazzi, Cremonesi) 3. Net HOWTO 4. prof. Serazzi & dott. Cremonesi, Dispense sulla sicurezza