Linux. Podstawy.

Źródła:

  1. Jak działa Linux.
    Podręcznik administratora. Wydanie II.
    Brian Ward
  2. Jak działa Linux.
    Podręcznik administratora. Wydanie III.
    Brian Ward
    __    _                       ____            __     __                      
   / /   (_)___  __  ___  __     / __ \____  ____/ /____/ /_____ __      ____  __
  / /   / / __ \/ / / / |/_/    / /_/ / __ \/ __  / ___/ __/ __ `/ | /| / / / / /
 / /___/ / / / / /_/ />  <_    / ____/ /_/ / /_/ (__  ) /_/ /_/ /| |/ |/ / /_/ / 
/_____/_/_/ /_/\__,_/_/|_(_)  /_/    \____/\__,_/____/\__/\__,_/ |__/|__/\__, (_)
                                                                        /____/   

Opisywany tutaj materiał będzie kompatybilny z dystrybucjami pochodnymi od GNU/Linux Debian zarówno tymi opartymi na systemd jak sysvinit oraz tymi z rodziny RHEL/Fedora/CentOS.

GNU/Linux czy raczej sam Linux? Sama nazwa, jest już tematem dość kontrowersyjnym. Ludzie związani z projektem GNU twierdzą, że ta pierwsza liczba jest właściwa ponieważ wskazuje ona na to, że isotne elemnty projektu GNU zostały wykorzystane do stworzenia tego systemu. W mowie potocznej jednak przyjęło się użycie tej drugiej nazwy. Jest to jedno, łatwe do zapamiętania słowo. Jeśli mówimy następujące zdanie wyrażające chęć zainstalowania na jakiejś maszynie omawianego tutaj systemu, mówimy że "zainstalujemy jakiegoś Linuksa". Słowo "jakiegoś" zostało tu użyte w kontekscie wyboru konkretnej dystrybucji. Co to dystrybucja wyjaśnie za chwilę. Bez projektu GNU niebyło by Linuksa. Wydaje mi się, że każdy kto jest nieco bardziej związany z tym środowiskiem o tym wie. Ja również jestem tego świadom dla tego też w tym dokumencie użyje nazwy Linux. Poprostu.

1. Budowa systemu Linux

Nie zagłebiając się w szczegóły, to Linux składa się z jądra oraz przestrzeni użytkownika. Oba kompomenty rezydują w pamięci więc wiele, nie które teksty popularno-naukowe mogą włączać pamięć lub ogólnie sprzęt do składowych systemu operacyjnego Linux, w mojej opinii jest raczej cecha wykorzystywanych przez nas komputerów konwencjonalnych.

Mówiąc o jądrze możemy wskazać konktretny program, konkretny plik. W przypadku przestrzeni użytkownika, w systemie nie istnieje żaden namacalny byt cyfrowy jak w przypadku jądra. Przestrzeń użytkownika jest bowiem warstwą abstrakcji - czyli terminem, bądź założeniem wykorzystywanym w celu określenia czynności, funkcji, zjawiska bez wdawania się w szczegóły. Przestrzeń użytkownika jest miejscem uruchamiania procesów użytkownika. Procesy to nic innego jak wystąpienia programów uruchomionych przez użytkownika. Nie wszystkie procesy są programami użytkownika w dosłownym tych słów znaczeniu. Część tych procesów to programy wspomagające wykorzystanie komputera i jego zasobów. Bez nich systemy operacyjne dalej mogły by spełniać swoją rolę, jednak nie miały by powszechnie znanej nam dzisiaj formy. Przestrzeń użytkownika składa się z wielu ogólno dostępnych kompnentów ich istnienie w danej wersji systemu oraz ich konfiguracja sprawia, iż nie mamy doczynienia z gotowym jednolitym produktem, ale z dystrybucją. Z jedną z wersji, gdzie ktoś wziął jądro, które jest ogolno dostępne i skomponował przestrzeń użytkownika. Obecnie na rynku mamy dostępnych ok. 600 dystrybucji. Wiekszość z nich to pochodne innych, oryginalnych rozwiązań rozwijanych przez setki osób na całym świecie. Kilka takich głównych dystrybucji, znajduje się w tabeli poniżej. Przejrzałem większość z nich, a z częsci osobiście korzystałem.

Logo Nazwa Opis
linux-mint-logo32 Linux Mint Dystrybucja bardzo przyjazna użytkownikowi. Wykorzystywana przez nowych niedoświadczonych użytkowników system Linux. Pod czas instalacji mogą być instalowane nie wolne moduły oraz nie wolne oprogramownie. Jej głównym zadaniem jest sprzyjanie użytkownikowi i umożliwienie mu wykorzystanie Linuksa przy codziennym wykorzystaniu komputera. Mint rozwijany jest przez społeczność zebraną wokół niego.
ubuntu-logo32 Ubuntu Podobnie jak Linux Mint, Ubuntu również jest skierowane dla osób ceniących sobie wygodne i prostę rozwiązania. Jest przyjazna użytkownikowi, ma nieco bardziej konserwatywne podejście do ideii wolnego oprogramowania, jądro może zawierać nie wolne moduły, jednak zamknięte oprogramowanie nie jest domyślnie instalowane. Ubuntu rozwijane jest przez firmę Canonical. Jej technologię są wdrażane do Ubuntu, dzięki czemu może ona uchdzić za system klasy enterprise wśród dystrybucji opartych o GNU/Linux Debian. Poza wersją na komputery biurkowe istnieją również wersja skierowana na serwery oraz inne wersje z preinstalowanymi różnymi środowiskami graficznymi czy wersja skierowana do obróbki multimediów zawierająca pozwalające do tego oprogramowanie. Społeczność zebrana wokół systemu Linux zarzuca jej siłowe próby wdrożenia manedżera oprogramowania Snap, rozwijanego przez tę firmę przez co może ona pretendować do stopniowego zarzucenia klasycznego schematu dystrybucji pakietów rozwijanego wraz z GNU/Linux Debian.
fedora-logo32 Fedora Linux Fedora jest dystrybucją skierowaną do różnej maści użytkowników, ponieważ istnieje w kilku głównych wersjach. oraz wiele wersji pobocznych tzw. spins. Fedora ma najprzyjźniejszy instalator chyba ze wszystkich możliwych dystrybucji. Wymaga on głównie wybrania miejsca instalacji i kliknięcia przycisku dalej. Fedora została stworzona i jest rozwiajana przez firme Red Hat Inc. (obecnie IBM) jako upstream (poligon doświadczalny dla zmian), dla glównego produktu tej firmy Red Hat Enterprise Linux - płatnej dystrybucji skierowanej do środowisk produkcyjnych (100$ rocznie). Jest to system o dużej stabilości ze wsparciem dla najnowszego sprzetu. Fedora również charakteryzuje się wprowadzeniej jako pierwsza środowiska GNOME w najnowszej wersji 41 oraz innych nowych technologi wśród otwartego oraz wolnego oprogramowania.
debian-logo32 GNU/Linux Debian Debian jest jedną z pierwszy dostępnych dystrybucji, początek jej istnienia jest datowany na 1993 rok. Dystrybucja konserwatywna, posiadała w pierwszych latach swojego istnienia aprobatę FSF (Free Software Fundation). Jednak została ona wycofana, za zezwolenie na instalację zamkniętego oprogramowania. Kernel przygotowywany przez twórców tej dystrybucji pozbawiony jest tzw. blobów binarnych (nie wolnych prekompilowanych modułów, używanych przy budowaniu jądra.) Bloby najczęściej dotyczą sterowników sprzętu. Dystrybucja charkteryzuje się wysoką stabilnościa porównywalną z RHEL, wsparciem dla starszego sprzętu. Jedną z cech, która może odstraszać potencjalnych użytkowników od niej jest długi cykl wydawniczy (co dwa lata) oraz używanie sprawdzone oprogramowania czy technologii (pozostaje dość mocno w tyle jeśli chodzi o najnowsze wersje oprogramowania). Wydaje mi się, że niema stabilniejszego gotowego rozwiązania niż GNU/Linux Debian. Debian wymaga nieco większego zaawansowania niż dystrybucje podane do tej pory. Stosowany jest częściej w środowiskach produkcyjnych niż np. Ubuntu. Rozwój Debiana opiera się na zaangażowaniu społeczności z całego świata.
arch-linux-logo32 Arch Linux Dystrybucja skierowana do zaawansowanych użytkowników. Charakteryzuje się wysoką konfigurowalnością oraz dostępnością najnowszych wersji oprogramowania. Nie posiada oficjalnego instalatora, choć można pobrać skrypt z sieci. Instalacji dokonuj się ręcznie, wpisującac kolejne polecenia z podręcznika instalacji w środowisku LiveCD, gdzie przygotowuje się dysk, pobiera się pakiety i je konfiguruje. Instalacja i konfiguracji Arch Linux nie jest tak pracochłonna jak innych dystrybucji, można by powiedzieć, meta-dystrybucji. Dość ciekawą cechą jest społeczność zebrana wokół niej, która przechwalająca się swoją wyższością na innymi (ponieważ przebrneli przez proces instalacji) używając frazy "I use Arch BTW.". Dystrybucja rozwijana jest przez społeczność na całym świecie.
void-linux-logo32 Void Linux Nie zależna dystrybucjna, trochę odmienna od inny dystrybucji głównego nurtu. Systemd zastąpiono programem runit, zamiast OpenSSL, użyto projektu OpenBSD LibreSSL jak jedyna z dystrybucji Linuksa. Kernel Void-a pozbawiony jest blobów, a domyślna instalacja zawiera tylko wolne oprogramowanie, posiada on jednak oficjalne repozytorium z zamkniętym oprogramowaniem. Instalacja pakietów opiera się stworzonym dla Void menedżerze pakietów XBPS. Pakiety są wydawane stylu rolling release, co daje szybkie i stabline aktualizacje. Obok standardowej biblioteki języka C - GNU libc, mamy również bibliotekę musl. Za pomoca programu xbps-src możemy tworzyć z kodu źródłowego własne pakiety XBPS.
gentoo-linux-logo32 Gentoo Linux Gentoo jest dystybucją na tyle zaawansowaną, że można by się pokusić o nazwanie jej meta-dystrybucją. Jest ona bowiem jedną z najbardziej konfigurowalnych dystrybucji. Jedną z ciekawszych czynności, jakie należy wykonać podczas instalacji, to ręczna kompliacja jądra. Dystrybucja skierowana do jeszcze bardziej zaawansowanych użytkowników niż w przypadku Arch Linux. Instalacja Gentoo na maszynie wirtualnej wraz z poradnikiem, zajeło mi to jakieś dwie godziny.
lfs-logo32 Linux from scratch LFS to w zasadzie projekt, a niżejli sama dystrybucja. Umożliwia on stworzenie oraz skonfigurowanie własnej dystrybucji. Na stronie projektu zawarte są wskazówki, co należy zrobić, aby stworzyć rozwiązanie najbardziej elastyczne dla siebie. LFS z pewnością może nosić miano meta dystrybucji.

W powyższej tabeli przedstawiłem dystrybucje, na które warto zwrócić uwagę. Teraz prawdopodobnie czekać będzie Cię duży dylemat, którą wybrać. W pierwszej kolejności ważny jest sprzęt, na którym będziemy z tego systemu korzystać. Część sprzętu, z którego chcemy korzystać może nie działać out of box, wowczas potrzebne będą sterowniki, które mogą być własnościowe (nie wolne, generalnie być zamkniętym oprogramowaniem), jeśli zależy nam na prywatności, to lepiej upewnić się z jakiego rodzaju sprzętem będzie mieć doczynienia, ponieważ każde zamknięte oprogramowanie można teoretycznie uznać za oprogramowanie szkodliwe. Dobrym wyborem może być zakup Thinkpada z przed 2008 roku. Wówczas będziemy mogli bez obaw wybrać Debiana i zainstalować np. XFCE (to dość lekkie środowisko graficzne, nadające się do codziennej pracy, bez zniechęcania się). Kolejną rzeczą do wyboru dystrybucji jest zapał do pracy. Mimo iż opisując dystrybucje napisałem że ta jest dla początkujących, a ta dla zaawansowanych to żadna z nich nie jest ani dla jednych ani dla drugich. Obsługa czego kolwiek związanego z komputerami wymaga przeczytania dokumentacji ze zrozumieniem i umiejętności radzenie sobie z ewentualnymi problemami. Dlatego dlaczego by nie wybrać Gentoo, zainstalować go z poradnikiem, skonfigurować, a wrazie problemów użyć Googla, lub poprość kogoś ze społeczności o pomoc.

Dość częstym zjawiskiem, wśród społeczności użytkowników Linuksa jest tzw. distro-hopping, czyli przesiadanie się z jednej dystrybucji na drugą. Jest to normalne zjawisko, chciaż można powszechną opinia jego jest raczej negatywna, głównym argumentem oponetów jest stwierdzenie, że przez to nie uczymy się niczego. Moim zdaniem, możemy dojść do wniosku, że tak naprawdę nie ma dystrybucji tylko produkt w ciągłej ewolucji z dostępnym takim a takim oprogramowaniem. Nie mam mendżera pakietów, mam program do instalacji i konfiguracji oprogramowania, nieważne czy jest apt, dnf, yum czy pacman. Mam stronę podręcznika i znajduj sobie potrzebne opcje. Mam dostęp do internetu, i wystarczy wyszukać konkretną potrzebną czynność np.: "Remove packages with all dependencies pacman". I mam gotowy wynik. Wiele miesięcy błądziłem słuchając mendrców jak RMS (Richard Matthew Stallman). Myślcie samodzielnie, przeskakujcie z distro na distro i bawcię się dobrze.

1.1. Sprzęt

Sprzęt sam w sobie nie mozę wchodzić z skład systemu operacyjnego, to jego elementy jak pamieć operacyjna, procesor czy pamięć masowa odgrywają w nim bardzo ważna rolę.

1.1.1. Pamięć operacyjna

W działaniu systemów operacyjnych takich jak Linux, najważniejszym komponentem sprzętowym może być pamięć operacyjna, ponieważ to w niej rezyduje jądro oraz przestrzeń użytkownika. Dane zapisane w pamięci nie są niczym innym jak zbiorem zer i jedynek określanych mianem bitów (najmniejsze przetwarzanej ilości informacji). Procesy oraz jądro są jednymi z takich zbiorów. Takie zbiory określa się mianem obrazu.

1.2. Jądro

Jądro Linux jest to nadrzędy proces w całym systemie, realizuje swoje działania w czterech obszarch funkcjonalności systemu operacyjnego.

Inną ciekawą cechą jądra są pseudourządzenia. Procesy widzą takie urządenia jak każde inne, jednak występują on wyłącznie w warstwie programowej, dzięki temu nie muszą być częścią jądra, ale ze względów praktycznych się je tam umieszcza. Inna implementacja urządzenia /dev/random - służacego do generowania liczb pseudolosowych, które jest urządzeniem programowym mogłoby nie być zbyt bezpieczne.

1.3. Przestrzeń użytkownika

Przestrzeń użytkownika formalnie jest obszarem pamięci, w którym spedzimy 99% czasu pracy na Linuksie. Wewnątrz przestrzeni użytkownika znajdują się procesy definiujące dystrybucje wykonujące różne zadania dla użytkownika, teoretycznie są one wobec siebie równe, to jednak przestrzeń użytkownika można podzielić na trzy warstwy, na której warstwie będzie znajdować się proces zależy jak bardzo skomplikowane zadania wykonuje. Przeglądarka sieci WWW, może się taka nie wydawać ale to potężny subsystem więc będzie znajdować na najwyższej warstwie, z kolei proces służący za rejestrowanie logów, tzw. protokół diagnostyczne będzie znajdować się na najniższej warstwie blisko jądra, ponieważ nie jest on zbyt skomplikowany w porównaniu do na przykład przeglądarki, warstwa środkowa zarezerowana jest dla różnej maści serwerów. Najproście rzecz ujmując podstawowe usługi znajdują się na najniższej warstwie, usługi pomocnicze na warstwie środkowej, a aplikacje, które kontroluje już sam użytkownik będą znajdować się na samej górze. Procesy mogą komunikować się z innymi procesami o ile te znajdują się na tym samym lub niższym poziomie. Używanie tego rozdzaju podziału, może być kłopotliwe ponieważ obecne serwery nie są już tak prostym oprogramowaniem więc powinny znajdować się tej samej warstwie co przeglądarka czy klient pocztowy, jednak to te aplikacje mogą wykorzystywać serwery do realizacji zadań użytkownika, więc ich miejsce jest raczej na warstwie centralnej (środkowej).

1.4. Użytkownicy

Użytkownicy w Linksie są odwzorowaniem rzeczywistych obiektów, czyli encją. Użytkownicy mają prawo do uruchamiania procesów oraz posiadnia (bycia właścielem) plików. Jądro nie rozpoznaje użytkowników po ich nazwach, tak jak mają w zwyczaju to ludzie, używa ono identyfikatorów userid w skrócie UID. Identyfikatory są przedstawiane za pomocą liczb.

Użytkownicy istnieją wyłącznie po to aby wyznaczać granice. Każdy proces ma swojego właściela, dlatego też mówi się że proces uruchamia się z uprawnieniami takiego a takiego użytkownika. Użytkownicy mogą uruchamiać i konczyć procesy w własnych granicach (tylko te, których są właścicielami), przez co nie mogą wpływać na procesy innych użytkowników. Poza procesami, użytkownicy mogą tworzyć własne pliki, których automatycznie stają się właścicielami. Mogą oni decydować czy chcą się nimi dzielić, ustalając im odpowiednie uprawnienia.

Poza użytkownikami przypisanymi do konkretnych osób (raczej spotkamy jednego), istnieje kilku dodatkowych specjalnych użytkowników, głównie mają oni na celu ograniczenie uprawnień serwerów. Po za tymi specjalnymi istnieje jeszcze użytkownik root, którego nie tyczą się zapisane powyżej ograniczenia dlatego jest on nazywany superużytkownikiem.

Osoba pracująca na koncie użytkownika root, nazywana jest administratorem systemu. Root może kończyć procesy innych użytkowników, przeglądać cudze pliki czy instalować oprogramowanie z repozytorium. Praca na tym koncie jest dość niebezpieczna z punktu widzenia systemu, ponieważ ten użytkownik jest wstanie wykonać czynności prowadzące do zniszczenia całego systemu. Na Linuksie root ma do tego pełne prawo, dlatego projektancji dystrybucji starają się ograniczyć konieczność pracy z wykorzystaniem tego użytkownika.

Innym tworem podobnym to użytkowników są grupy. Grupy są zbiorem użytkowników, a ich zadaniem jest współdzielenie plików wewnątrz jednej grupy, między jej użytkownikami.

2. Podstawy obsługi Linuksa

W tym rozdziale przedstawione zostaną podstawy obsługi systemu Linux, oczywiście z poziomu powłoki, ponieważ inne sposóby zależą w dużej mierze od programów, które do tego celu będziemy wykorzystywać. Takich programów może być kilka, powłok również dostępnych jest kilka rodzajów, jednak sam program powłoki nie będzie wpływać na prezentowane w tym rozdziale czynności. Ten rozdział zaczniem od tego czy jest powłoka.

2.1. Powłoka

Powłoka jest chyba jednym z najistoniejszych komponentów systemu Linux, pozwala ona na uruchamianie róznych poleceń wydawanych przez użytkownika. Powłoki są również małymi środowiskami programistycznymi. Nie które narzędzia systemowe są skryptami powłoki - plikami tekstowymi zawierającymi zbiór wykonywanych kolejno (jedno po drugim) poleceń powłoki.

Pierwotną powłoką była powłoka Bourna, opracowana jeszcze dla systemu UNIX w laboratoriach Bell Labs. Mimo niezbyt częstego wykorzystywania, powłoka ta jest stałym kompenetem nie tylko systemu Linux, ale i innych systemów uniksopodbnych. Obecnie wykorzystywaną powłoką jest BASH - ulepszona wersja oryginalnej powłoki. Korzystając z róznych dystrybucji, domyślna powłoka może być inna. Ten materiał zakłada wykorzystanie powłoki BASH, szczególnie w rozdziale poświęconym skryptom powłoki.

2.2. Korzystanie z powłoki

Dostęp do powłoki może odbywać się w dwojaki sposób wykorzystać możemy wbudowaną w każdą dystrybucję konsole, nie zależnie od instalacji wybranej przez nas dystrybucji. Jeśli jest to dystrybucja skierowana do komputery biurkowe, to możemy skorzystać z wbudowanego programu terminal. Po uruchomieniu okna powłoki, w prawym górnym rogu pojawi się symbol zachęty. Jest to ciąg znaków wskazujący wiersz, w którym będziemy wprowadzać polecenia. Znak zachęty może przyjmować różną formę:

W tych symbolach jeden element jest stały jest to znak dolara ($), oznacza on że polecenia wydawane będą jako zwykły użytkownika, innym symbolem jest znak krzyżyka (#), który mówi nam że polecenia będą uruchamiane przez superużytkownika. Najprostsze polecenie jakie możemy wydać jest użycie polecenia echo, które zwraca na standardowe wyjście podajny mu jako argument ciągu znaków:

$ echo Witaj świecie.

W przykładach w tym materiale, jeśli polecenia ma zostać wydane z uprawnieniami zywkłego użytkownika, przed poleceniem będzie pojawiać się znak dolara ($), a jeśli polecenie ma być uruchomione z wyższymi uprawnieniami, będą one poprzedzone znakiem krzyżyka (#) oznaczający uprawnienia użytkownika root.

2.2.1. Polecnie cat

Polecenie cat wypisuje na standardowe wyjście podane w argumentach pliki jeden po drugim dokonując tym samym połączenia (konkatenacji - stąd nazwa polecenia) na jednym strumieniu zawartości tych wszystkich plików.

$ cat plik1 plik2 plik3 ...

2.2.2. Standardowe wyjście i standardowe wejście

Użyłem powyższego polecenia cat, aby nakreślić kontekst dla omówienia dwóch podstawowcyh strumieni. Linux wykorzystuje strumień wejściowy do odczytu danych, a strumień wyjściowy do ich zapisu. Źródłem strumienia wejściowego może być plik, urządzenie, terminal czy strumień wyjściowy innego procesu.

Strumień wejściowy możemy zaobserować poprzez uruchomienie polecenia cat bez żadnego pliku. Program nie zwróci od razu znaku zachęty, ponieważ oczekuje na dane. Możemy wpisać co kolwiek, a po naciśnięciu klawisza enter polecenie powtórzy ten wpisany tekst. Z racji tego iż nie podaliśmy mu żadnego pliku polecenie zaczęło korzystać ze strumienia standardowego wejścia, przekazanego mu przez jądro, w tym przypadku był to terminal, którym zostało uruchomione to polecenie. Aby zakończyć to polecenie należy wciśnąć kombinacje klawiszy Ctrl+d, która oznacza koniec danych ze standardowego wejścia.

Ze standardowym wyjściem jest podobnie, jądro przezkazuje strumień standardowego wyjścia procesom, do którego mogą one zapisywać swoje dane. Polecenie cat zawsze wypisuje swoje dane na standardowe wyjście, które przez uruchomienie polecenia w terminalu jest do niego podłączone. Dzięki temu mogliśmy zobaczyć wypisywane przez polecenie dane.

Standardowe wyjście oraz standardowe wejście możemy zapisać skrótowo stdout oraz stdin. Takich nazw również należy się spodziewać w wszelakiej dokumentacji.

Prócz wspomanianych strumieni istnieje jeszcze trzeci strumień wejścia-wyjścia - standardowy strumień błędów. Opiszę go nieco później.

Strumienie są dość elastycznym mechanizem, można je zmusić do odczytywania i zapisywania danych z innych miejsc niż terminal. O przekierowaniach strumienii będzie nieco poźniej w tym rozdziale.

2.3. Podstawowe polecenia

Poniżej znajduje się pogrupowane przedstawienie najbardziej podstawowych poleceń niezbędnych do pracy w powłoce systemu Linux.

Polecenia działające na katalogach

Uniksy w tym i Linux, korzystają ze standardu hierarchi katalogów, aby utrzymać w porządku dane przestrzeni użytkownika. Za początkowy katalog uznaje się katalog główny oznaczany prawym ukośnikiem lub slashem (/), wewnątrz tego katalogu znajdują się pod katalogi, przechowujące konkretny rodzaj czy typ plików zgodny z ich przeznaczeniem.

Droga do konkretnego katalogu nosi nazwę ścieżki. Jeśli ścieżki zaczynają się od /, czyli od katalogu głównego mamy doczynienia ze ścieżką bezwzględną. Elementy katalogów na ścieżkach katalogi mogą być również wyrażane z pomocą jednej lub dwóch kropek. Dwie kropki (..) oznaczają katalog nadrzędny względem aktualnego katalogu, zaś jedna kropka oznacza (.) aktualny katalog. Ścieżki nie zawierające slasha na początku, czyli nie zaczynające się od katalogu głównego są wówczas określane mianem ścieżki względnej.

2.4.1. Nazwy wieloznaczne.

Dzięki możliwością powłoki możemy porównywać proste wzorce z nazwami plików w obrębie aktualnego katalogu roboczego (katalogu w którym się znajdujemy) czynność ta nazywana jest rozwijaniem nazw lub globbingiem. Jednym z elementów biorących udział w rozwiązywaniu nazw jest gwiazdka (*) oznaczająca dowolną ilość dowolnych znaków. Dla przykładu poniższe polecenie:

$ echo *

Zwróci nazwy wszystkich plików i katalogów znajdujących się w katalogu. Innym znakiem wykorzystywanym przy nazwach wieloznacznych jest znak zapytania (?) reprezentuje on jeden dowolny znak, dla wzorca b?at pasującymi nazwami mogą być blat oraz brat. Rozwinięcia nazw dokonuje powłoka przed uruchomieniem, więc jeśli chcemy aby, któreś ze znaków wieloznacznych trafiło do polecnie to należy umieść je w pojedyńczych cudzysłowach.

2.5. Polecenia pośredniczące

2.6. Zmiana hasła i powłoki

W celu zmiany hasła należy użyć polecenia passwd. Polecenie poprosi o podanie obecnego hasła, po zatwierdzeniu go zostaniemy poproszeni o nowe hasło i jego potwierdzenie (wpisanie ponowne nowego hasła).

Zmiana aktywnej powłoki odbywa się za pomocą polecenia chsh, albo użyć poleceń odpowiadających nazwom innych powłok, kolejno ksh - Korn SHell, tcsh - TENEX C SHell. Użycie tych poleceń w aktywnej powłoce, spowoduje uruchomienie podpowłoki. Zamkniecie jej spowoduje powrót do pierwotnej powłoki.

2.7. Pliki z kropką

Przeglądając pliki nawet w własnym katalogu domowym możemy znaleźć pliki, których nazwa zaczyna się od kropki. Nie które źródła mówią tym o że te pliki są ukryte. Do takich wniosków może dojść, ponieważ te pliki nie są domyślnie wyświetlane przez polecenie ls bez opcji -a lub przez menedżery plików dostępne w desktopowych wersja Linuksa. Jednak te pliki nie różnia się niczym od inny plików, poza właśnie tym przypadkiem opisanym powyżej. Oprócz plików, nazwy katalogów również mogą zaczynać się od kropki. Za pomocą prostego wzorca możemy wyświetlić wszystkie dot-files, jeśli wsród nich trafi się katalog, wówczas zostanie wyświetlona jego nazwa a pod nią jego zawartość.

$ ls .??*

2.8. Zmienne środowiskowe i powłoki

Powłoka może przechowywać zmienne tymczasowe, które mogą przechowywać różne wartości, mogą one kontrolować zachowanie samej powłoki jedną z takich zmiennych jest zmienna PS1 zawierająca znak zachęty. Takie zmienne najczęsćiej wykorzystywane są w skryptach powłoki i nazywane są zmiennymi powłoki. Definicja zmiennych tego składa się z nazwy zmiennej, operatora przypisania (znaku równości =) oraz wartości samej zmiennej.

$ zmienna=12

Odwołać się do wartości zmiennej możemy w dowolnym momencie, podając jej nazwę poprzedzoną znakiem dolara ($).

$ echo $zmienna

Zmienna środowiskowa jest podobna do zmiennej powłoki, ale nie jest ściśle związana z powłoką, bowiem do pamięci zmiennych środowiskowych systemach uniksopodobnych mają wszystkie aplikacje, system operacyjny przezkazuje je do każdego programu uruchomionego w powłoce, programy te nie mają jednak dostępu do zmiennych powłoki. Zmienne środowiskowe definiuje się w ten sam sposób jak zmienne powłoki, jedna aby taka zmienna stała się zmienną środowiskową musi zostać przeniesiona do pamięci tych zmiennych za pomocą polecenia export.

$ zmienna=21
$ export zmienna

Nie które programy mogą wykorzystywać zmienne środowiskowe do własnej konfiguracji. Dla przykładu niektóre programy uruchamiane w powłoce korzystają ze zmiennej środowiskowej EDITOR definiujące domyślny program do edycji plików tekstowych. Wykorzystanie zmiennych środowiskowych zapewne jest opisane w na stronie podręcznika programu.

2.9. Ścieżka poleceń

Istnieje specjalna zmienna środowiskowa PATH, przechowywująca katalogi, w których to powłoka będzie szukać programów odpowiadających wpisanym poleceniom. Jeśli wśród przeszukiwanych katalogów znajduje się kilka programów o tej samej nazwie to powłoka uruchomi pierwszy przez nią znaleziony. Ścieżki katalogów w tej zmiennej odzielone są dwukropkiem (:). Posiadając swoje programy, możemy również umieść katalog z nim wewnątrz zmiennej $PATH. Opcje dodanie katalogu są dwie i mogą mieć wpływ na funkcjonowanie systemu. Możemy dodać nasz katalog na początku zmiennej, wówczas powłoka zacznie od niego poszukiwania, jednak należy pamiętąc przy tym, aby nazwy programów nie pokrywały się istniejącymi dotychczas poleceniami.

$ PATH=kat:${PATH}

Na powyższym przykładzie kat, to nasz katalog z oprogramowaniem. Możemy jednak skorzystać bezpieczeniejszego rozwiązania - dopisać nasz katalog na końcu listy katalogów zmiennej PATH, wówczas nawet jeśli nasz program będzie nazywać się jak jedno z instniejących już poleceń w systemie, nie będzie miało to wpływu na działanie systemu.

$ PATH=${PATH}:kat

Na powyższych przykładach użyłem znaku dolara wraz z nawiasami klamrowymi. Jest to sposób na separacje nazwy zmiennej od innych znaków, po to aby powłoka nie potraktowała jak w przykładzie powyżej ciągu znaków ":kat" jak części nazwy zmiennej. Przedstawione w przykładach polecenia są nie groźne, jeśli uszkodzimy zawartość zmiennej PATH, to należy zamknąć okno terminala i otworzyć nowe.

2.10. Znaki specjalne

W systemach uniksopodbnych wiele znaków ma szczególne znaczenie. Poniżej znajduje się tabela przedstawiająca wykorzystwane podczas używania systemu znaki specjalne.

Znak Nazwa Opis
* gwiazdka Wyrażenie regularne, znak nazwy wieloznacznej
. kropka Aktualny katalog, ogranicznik nazwy pliku lub hosta
! wykrzyknik Negacja, historia poleceń
| potok Potoki poleceń
/ slash Ogranicznik katalogów, polecenie szukania
\ backslash Literały, makra (nigdy katalogi)
$ dolar Oznaczenie zmiennych, koniec wiersza
' pojedynczy cudzysłów Ciągi znaków literałów
` lewy cudzysłów Podmiana polecenia
" podwójny cudzysłów Ciągi znaków pseudoliterałów
^ daszek Negacja, początek wiersza
~ tylda Negacja, skrót katalogu
# krzyżyk Komentarze, dyrektywy preprocesora, podmiany
[] nawiasy kwadratowe Zakresy
{} nawiasy klamrowe Bloki poleceń, zakresy
_ podkreślenie Prosty zamiennik spacji

Często możemy napotkać symbol daszka (^) zastępujący klawisz Control, przez co zapis ^C jest równe kombinacji klawiszy Ctrl+C.

2.11. Edycja wiersza poleceń

Znak zachęty wskazuje wiersz polecenia, który możemy edytować przesuwając kursor za pomocą strzałek. Chcąc powtórzyć jakąś czynność nie musimy pisać na nowo tego polecenia, możemy wybrać je z historii poleceń za pomocą strzałek w góre i w dół. Warto jednak obsługę wiersza poleceń za pomocą strzałek odstawić na bok. Wykorzystując skróty z poniższej tabeli, możemy nimi śmiało zastąpić strzałki. Istnieją ku temu dwa powody.

Klawisze Operacja
Ctrl+b Przesunięcie kursora w lewo
Ctrl+f Przesunięcie kursora w prawo
Ctrl+p Powrót do poprzedniego polecenia (lub przesunięcie kursora w górę)
Ctrl+n Przejście do następnego polecenia (lub przesunięcie klawisza w dół)
Ctrl+a Przesunięcie kursora na początek wiersza
Ctrl+e Przesunięcie kursora na koniec wiesza
Ctrl+w Usunięcie słowa poprzedzjącego kursor
Ctrl+u Usunięcie tekstu od kursora do początku wiersza
Ctrl+k Usunięcie tekstu od kursora do końca wiersza
Ctrl+Y Wyklejanie usuniętego tekstu (na przykłda usuniętego poleceniem Ctrl+u)
Ctrl+h Substytut klawisza Backspace
Ctrl+d Substytut klawisza delete
Ctrl+j, Ctrl+m Substytut klawisza enter

2.12. Edytory tekstu

Na Linuksie mamy podobną ilość edytorów tekstowych do wyboru jak w przypadku systemów MS Windows czy Apple macOS. Jak nie więcej. Co ciekawe macOS, również jest system uniksopodobnym. Więc to co zostało omówione w tym rozdziale również będzie kompatybilne z tym systemem. Wracając jednak do edytorów tekstu. Tak naprawdę to istnieją dwa, na które warto zwrócić uwagę, oba są standardem jeśli chodzi o edycje tekstu i oba wymagają nauki obsługi. Wybór pozostawiam do roztrzygniecia Tobie.

Jeśli potrzebujemy edytora, który jest wstanie zatąpić nam środowisko graficzne, wybierzmy edytor Emacs. Jeśli jednak chcemy poprostu edytować pliki, w każdym możliwym środowisku wybierzmy edytor Vim. Osobiście jestem przyzwyczajony już do edytora Vim.

2.13. Uzyskiwanie pomocy

Dystrybucje Linuksa są rozporowadzane z dużą ilością różnej dokumentacji. Informacje temat poleceń możemy znaleźć na stronach podręcznika, wydając polecenie man i podając jako argument interesujące nas polecenie. Na przykład:

$ man ls

W ten sposób uruchomimy stronę podręcznika polecenia ls. Większosć stron podręcznika podaje suche informacje na temat polecenia, nie ma co tam szukać jakiś samouczków. Opcje podawana są usystematyzowany sposób (najczęściej alfabetyczny), nie które strony podręcznika mogą zawierać przykłady.

Strony podręcznika możemy przeszukać pod kątem słowa kluczowego, za pomocą opcji -k, polecenia man. Wynikiem tego polecenie jest lista poleceń, oraz krótki opis zawierający podane słowo kluczowe, ciekawa jest liczba podana w nawiasie obok nazwy polecenia, jest to numer rozdziału.

Strony podręcznika są podzielone rozdziały oznaczone numerami, każdy z nich zawiera innego rodzaju strony podręcznika. Rozdziały zostały opisane w tabeli poniżej.

Rozdział Opis
1 Polecenia użytkownika
2 Niskopoziomowe wywołania systemowe
3 Dokunentacja wysokopoziomowych bibliotek Uniksa
4 Informacje o interfejsach urządzeń i sterownikach
5 Opisy plików (konfiguracji systemu)
6 Gry
7 Formaty plików, konwencje i kodowaniaa (ASCII, przyrostki itd)
8 Polecenia systemowe i serwery

Jak uzupełnienie tego materiału świetnie sprawdzą się rodziały 1,5,7 i 8. Wywołanie konkretnej strony a danego rozdziału wymaga podania jego numeru jak pierwszego argumentu, wówczas polecenie będzie wszukać informacji na temat podanego słowa w danym rodziale. Świetnym przykładem może być, chęć sprawdzenia na stronach podręcznika pliku /etc/passwd. Należy wydać polecenie:

$ man 5 passwd

Dość często wykorzystywanym sposóbem na uzyskanie informacji o poleceniach mogą być same polecenia, sprawdźmy czy możemy je uruchomić z opcją -h lub --help.

Projekt GNU jakiś czas temu zadecydował, że będzie używać innego formatu plików pomocy niż strony podręcznika, format nazywana jest info lub texinfo. Format ten zawiera więcej informacji choć jest od niego bardziej skomplikowany. Przypomina prostą stronę internetowa, otworzoną w terminalowej przeglądarce. Tego typu pliki pomocy uruchamiane są za pomocą polecenia info, po czym podaje mu się jako argument interesujące nas polecenie.

Nie które z pakietów umieszczają swoją dokumentację w katalogu /usr/share/doc nie zwracając uwagi na format. Warto pamiętać o tych miejscach szukając pomocy, oczywiście pozostaje na do dyspozycji jeszcze internet.

2.14. Wejście i wyjście powłoki

Omawiając po krótce powłokę, wspomniałem o strumieniach standardowego wejścia i wyjścia. Wiele poleceń używa wyjścia aby wypisywać wyniki działa lub komunikaty diagonstyczne. Jeśli nie chcemy ich widzieć lub nie nadąrzymy ich analizować możemy je przekierować na przykład do pliku, za pomocą znaku przekierowania (>).

Używając tego znaku musimy omówić sobie jedną ważną rzecz, znak ten powoduje nadpisanie wszystkiego co znajduje się w pliku. Tego typu czynność nazywana jest wymazywaniem (ang. clobbering). Możemy jednak zablokwać to działanie za pomocą odpowiednich ustawień powłoki, dla BASH wystarczy użyć polecenia set -C. Poza blokowaniem wymazywania, istnieje jeszcze jeden znak przekierowania, który powoduje dopisanie przekierowanego wyjścia do pliku: >>.

Za pomocą przekierowań możemy w prosty sposób połączyć wyjście jednego polecenia z wejściem innego. Służy temu znak potoku (|). Postawienie ponowej kreski, pomiędzy poleceniami w wierszu polecenia połączy wyjście pierwszego z wejściem drugiego.

2.14.1 Standardowy strumień błędów

Korzystając powyższych znaków przekierowania polecenia, dane wyjściowe programów zostaną przekierowane np. do pliku. Ale komunikaty diagnostyczne nadal są wyświetlane. Dzieje się to dlatego iż komunikaty diagnostyczny wykorzystują trzeci dodatkowy strumień standardowy strumień błędów zapisywany skrótowo stderr, który podobnie do stdout jest podłączony do terminala.

Do przekierowania tego strumienia musimy użyć identyfikatorów strumienii jest to liczba, którą posługuje się jądro do rozrózniania strumieni. W fachowej literaturze bądź dokumentacji możemy natknąć się na termin deskryptor pliku. Taki identyfikator dla stderr ma wartość 2, a dla stdout 1. Chcąc przekierować stderr do innego pliku musimy podać jego identyfikator przed znakiem przekierowania. Innym przypadkiem jest przekierowanie stderr do tego samego pliku co stdout, wówczas najprostszym sposób jest podłączenie strumienia błedów do standardowego wyjścia za pomocą znaku >& podając przekierowywany strumień po lewej stronie znaku (przed nim) a strumień docelowy po prawej (za nim).

$ ls /ffffffff >p 2>&1

2.14.2. Przekierowanie standardowego wejścia

Przekierowanie standardowego wejścia występuję dość rzadko ponieważ większość poleceń przyjmuje plik jako argument, jednak może zdarzyć się potrzeba przekierowania wejścia do polecenia. Służy temu znak przekierowania wejścia <.

2.15. Odczytywanie komunikatów o błędach

Prędzej czy później gdzieś podczas naszej pracy z powłoką zdarzy się błąd. Ważne jest aby umieć go odczytać i prawidłowo z interpretować.

Sam komunikat składa się przeważnie z nazwy polecenia oraz komunikatu błędu, w nie których przypadkach w komunikacie znajduje się nazwa pliku, jednak dotyczy specyficznych komunikatów o błędach. Poniżej znajduje się lista, błędów z którym będziemy się spotykać podczas pracy z systemem Linux.

Jeśli natrafimy na jeden z dwóch ostatnich błędów, to przyczyną mogą być dane przekazane do programu, których on się nie spodziewał.

2.16. Przeglądanie procesów i manipulowanie nimi

Każdy proces jest programem. Jądro pododobnie jak użytkowników procesy widzi za pomocą liczbowych identyfikator - process ID - PID. Za pomocą polecenia ps możemy wylistować wszystkie procesy uruchomione w powłoce. Domyślnie wynik polecenia podzielony jest na cztery kolumny.

Polecenie ps, możemy obsługiwać za pomocą opcji zapisanych w trzech stylach, jednak najbardziej powszechnym jest styl BSD, i to zapis opcji w tym stylu przedstawie.

Tak jak w przypadku innych poleceń, w poleceniu ps, również możemy łączyć opcje, dla przykładu chcąc wyświetlić wszystkie procesy w systemie wraz ze szczegółami należy wydać polecenie ps aux.

Wyświetlenie informacji na temat, konkertnych procesów polega na podaniu po opcjach identyfikatora procesu.

2.16.1. Przerywanie działania procesów

Możemy zakończyć działanie procesu poprzez wysłanie do niego sygnału za pomocą polecenia kill. Wykorzystują to polecenie, jądro systemu proszone jest wysłanie sygnału do wybranego procesu. Domyślnie wysyłanym sygnałem jest TERM, a polecenie do swojego działania potrzebuje tylko identyfikatora PID.

Proces możemy zatrzymać wysyłając do niego sygnał STOP. po nazwie sygnału podajemy PID, tak zatrzymany proces da się wznowić za pomocą sygnału CONT. Nazwy sygnałów podajemy po myślniku (-).

Naciśnięcie kombinacji Ctrl+c podczas działania programu w powłoce, jest równoznaczne z wysłaniem sygnału INT (ang. Interrupt - przerwanie).

Jedną z metod zakończenia procesu jest natychmiastowe zakończenie jego pracy oraz usunięcie go siłą z pamięci, jest to osiągalne poprzez wysłanie do niego sygnału KILL. Jest to ostateczność, gdy dany proces nie odpowiada na inne sygnały. Inne sygnały dają procesom możliwość poprzątania po sobie.

Oczywiście nie należy przerywać pracy dowolnym procesom, kiedy nie wiemy co robią.

2.16.2. Kontrola zadań

Powłoki posiadają mechanizm dzięki, któremu możemy zatrzymywać oraz wznawiać prace procesów za pomocą kombinacji Ctrl+z oraz poleceniami fg, bg. Ten mechanizm nazywa się kontolą zadań. Podczas działania procesu w powłoce, możemy wysłać sygnał TSTP (podobny do sygnału STOP), a następnie kiedy zechcemy do niego wrócić to wystarczy wydać polecenie fg, które wznowi działanie polecenia w terminalu, lub polecenia bg wznawiającego działanie procesu w tle.

Podobne działanie ma program GNU Screen, który jest multiplekserem terminala, a co najlepsze jesteśmy wstanie odłączyć sesję programu od pierwszego planu i pozostanie ona w niezmienionej postaci, tak długo jak włączony jest komputer.

2.16.3. Procesy działające w tle

Uruchamiając polecenie w powłoce dostęp do znaku zachęty, a co za tym idzie wolnego wiersza polecenia otrzymujemy dopiero po zakończeniu działania programu. Jednak możemy odłożyć wykonanie polecenia do tła, poczym od razu uzyskamy dostęp znaku zachęty. Jest to przydatna funkcją, gdy uruchamiamy polecenie, którego wykonanie może zająć trochę czasu. Wykonanie odkładamy do tła dopisując ampersand (&) na końcu polecenia (jako ostatni argument). Działanie takiego programu może trwać nawet po naszym wylogowaniu. Jeśli proces zakończy działanie zostaniemy o tym poinformowani.

Problem związanym z procesami działającymi w tle jest, możliwe pobieranie informacji z standardowego wejścia. Jeśli nie przewidzieliśmy takiego zachowania programu, to wówczas może zostać on zatrzymany lub zakończony. Można go wznowić za pomocą polecenia fg o ile został zatrzymany. Innym problemem jest wypisywanie danych przez proces w tle na standardowe wyjście oraz strumień błędów. Przed uruchomieniem takiego polecenia należy przekierować te strumienie, ponieważ podczas pracy w terminalu z innymi aplikacjami dane ze strumieni mogą zaburzać ich wyświetlanie. Jeśli powłoka będzie dziwnie się zachowywać, wystarczy wydać polecenie reset i wszystko wróci do normy.

2.17. Tryb pliku i uprawnienia

Każdy plik na Uniksie (więc na Linuksie też), posiada komplet uprawnień określajacy czy można go odczytać, zapisać do niego lub go uruchomić. Pierwsza kolumna wyniku polecenia ls -l zawiera tryb pliku. Dane trybu możemy podzielić na cztery części Typ, Uprawnienia użytkownika (właściciela), Uprawnienia grupy, Uprawnienia pozostałych użytkowników..

Pierwszy znak to typ pliku, jesli występuję w nim myślnik (-), to mamy doczynienia ze zwykłym plikiem, innym typem pliku może być litera d oznaczająca katalog.

Pozostałe znaki, definiują uprawnienia. Dzieli się je na trzy grupy wypisane powyżej, po trzy znaki na każdą z nich. W grupie (zestawie uprawnień, przeznaczonym dla konkretnej grupy lub osoby) mogą wystąpić 4 rodzaje znaków.

Mówiąc o użytkowniku w pierwszym rodziale, wspomniałem że może być on właścicielem pliku i do niego należy pierwszy zestaw uprawnień. Drugi zestaw określa uprawnienia dla grupy, jaka została przypisana temu plikowi, z tych uprawnień będzie korzystać każdy użytkownik tej grupy, próbujący uzyskać dostęp do pliku. Trzeci grupa, należy do pozostałych użytkowników systemu. Użytkownika root nie obejmują żadne z powyższych grup, choć to może zależć od konfiguracji systemu, mimo to superużytkownik może sobie zmieniać dowolnie uprawnienia.

Nie wymieniony na powyższej liście dodatkowym bitem (o uprawnieniach możemy mówić jak o bitach, np. "potrzebuje bitu odczytu aby odczytać dane z pliku") jest bit s - wybierz identyfikator użytkownika. Pojawia się on zamiast bitu wykonywania x i tyczy się wyłącznie plików wykonywalnych. Programy z ustawionym tym bitem zawsze uruchamiają się z uprawnieniami ich właściciela bez znaczenia, kto uruchamia ten program. Wiele programów korzysta z tego rozwiązania, aby uzyskać uprawnienia superużytkownika i móc zapisywać dane w różnych plikach systemowych.

2.17.1. Modyfikacja uprawnień

Do zamiany uprawnień wykorzystamy polecenie chmod, jako pierwszy podaje się zbiór uprawnień, a następnie bit uprawnienia ze znakiem "+" jeśli chcemy dodać ten bit lub "-" jeśli chcemy ten bit usunąć. Zbiór uprawnień podajemy za pomocą pierwszych liter angielskich nazw u (ang. user)- użytkownika/właściciel, g (ang. group) - grupa, o (ang. others)- pozostali użytkownicy systemu.

$ chmod go+r test.sh

Zbiory uprawnień można łączyć ze sobą, tak jak na powyższym przykładzie lub jeśli chcemy dodać bit do wszystkich zbiorów to możemy je pominąć jak na poniższym przykładzie.

-rw-r--r-- 1 xf0r3m xf0r3m 26 03-08 13:13 test.sh
$ chmod +x test.sh
-rwxr-xr-x 1 xf0r3m xf0r3m 26 03-08 13:13 test.sh

Przy plikach osobistych, nie warto dawać za dużych oprawnień pozostałym użytkownikom. Chociaż obecnie może mieć to jedynie znaczenie, gdy z serwera korzysta wielu wyspecjalizowanych użytkowników.

Innym sposobem na zmianę uprawnień jest użycie liczb, gdzie każda z trzech liczb określa uprawnienia dla jednego zbioru. Liczby te są sumami bitów, które reprezentowane są przez poszczególne wartości.

$ chmod 644 test.sh 

Uprawnienia właściciela mają wartość 6. co jest równe 4+2 - u+rw, grupa oraz pozostali mają 4 co jest identyczne z zapisem go+r. Liczby wykorzysywane tutaj pochodzą z oktalnego systemu liczbowego.

Zmiana uprawnień nosi nazwę bezwzględnej, ponieważ zmieniane są uprawnienia wszystkich grup.

Odnośnie uprawnień to istnieje bardzo ważna zależność pomiędzy bitami odczytu oraz wykonania. Nadając katalogowi domowemu uprawnienia rwxr--r-- czy 744. Pozostali użytkownicy będą mogli odczytać zawartości katalog, ale nie uzyskają dostępu do pliku podając go w jakimś poleceniu na ścieżce, do tego potrzebny jest jeszcze bit wykonania.

Za pomocą polecenia umask, możemy zdefiniować domyślne uprawnienia dla plików. Polecenie to przyjmuje jako argument te uprawnienia w postaci bezwględnej, które mają zostać usunięte z nowoutworzonych plików i katalogów. Wydanie polecenia umask:

$ umask 022

Spowoduje, że nowoutworzone pliki i katalogi będą mięc uprawnienia w postaci rwxr-xr-x lub 755. Może wydawać się zbyt liberalne, więc możemy ustawić argument polecenia nas 077, wówczas wszystkie utworzone pliki i katalogi będą wyłącznie dla nas. Polecenie umask, czesto występuje w plikach konfiguracyjnych powłoki.

Dowiązanie syboliczne to jest alias będący plikiem wskazującym na inny pliki lub katalog. Można uciec się do jednego słowa, że dowiązanie symboliczne jest poprostu skrótem.

Jeśli dowiązanie wskazuje na katalog, to przejście do dowiązania przeniesie nas w miejsce, na które wskazuje. Cel dowiązania nie musi nawet istnieć, jeśli jednak spróbuje przejść pod takie dowiązanie wówczas uzyskamy typowy błąd, o tym że katalog nie istnieje. Dowiązania uniemożliwają również sprawdzenie charakterystyki wskazywanego elementu, nie będzie wiadomo czy jest to plik, katalog czy inne dowiązanie. Wiele połączonych ze sobą dowiązań symbolicznych nazywane jest łańcuchem dowiązań

Dowiązania symboliczne tworzone są za pomocą polecenia ln z opcją -s (Ważne, aby użyć tej opcji). Argumentami jest na początku cel a poźniej nazwa dowiązania. Zachowanie kolejności argumentów jest ważne, ponieważ możemy utworzyć dowiązanie, które prowadzi do nikąd i wprowadza bałagan (być może w plikach systemowych).

Mimo swoich wad dowiązanią są wygodną metodą na współdzielenie plików oraz dodatkowo rozwiązują kilka drobnych problemów.

2.18. Archiwizowanie i kompresowanie danych

Przesyłając duża ilość małych plików przez sieć czy tez na pamięć masową, możemy odczuć że trwa to wieki, na pewno trwa to dłużej niż przesłanie jednego dużego pliku. Tutaj przedstawię sposoby na stworzenie jednego większego pliku z całego katalogu, przy czym użyjemy jeszcze kilku algorytmów kompresii, przez co zaoszczędzimy na czasie i trochę na zajmowanym miejscu.

2.18.1. Program tar

Pierwsze narzędzie będzie służyć do tworzenia archiwum. Archiwa łączą pliki i katalogi w jeden plik. Tar jest standardowym program do archiwizacji na uniksach. Tworzenie archwium za pomocą tar wymaga kilku opcji. Natomiast składania polecenia jest następująca:

$ tar -cvf archiwum.tar plik1 plik2...

Opcja -c mówi programowi, że tworzone będzie nowe archiwum, opcja -v włącza komunikaty diagnostyczne wyświetlać one będą po kolei pakowane do archiwum pliki; opcja -f przekazuje programowi informacje o tym, że archwium będzie plikiem. Domyślnie tar tworzył archiwa na taśmach. Obecnie pominięcie tej opcji kończy pracę programu z komunikatem o błędzie. Możemy natomiast użyć stdout podajac zamiast nazwy archiwum myślnik (-). Póki co to archiwum nie jest jeszcze skompresowane.

Rozpakowywanie pliku

Rozpakowawanie różni się tylko jedną opcją - zamiast -c jest -x. Następnie podajemy pozostałe opcje, a na końcu nazwę pliku archiwum.

Wyświetlenie zawartości archiwum

Wypakowanie całego archiwum może nie być do końca porządane, załóżmy że potrzebujemy tylko jednego pliki. Za pomocą polecenia tar z odpowiednim przełącznikiem możemy wyświetlić listę plików w archiwum. Zamiast -x, używamy -t reszta pozostaje taka sama, jeśli archiwum jest duże to możemy podłączyć wyjście tar potokiem do polecenia less. Samego wypakowania dokonujemy podając wypakowywanego pliku za nazwą archiwum.

Ostanią dość istotną opcję jest -p, która powoduje zachowanie oryginalnych atrybutów plików, jakie miały podczas pakowania. Kiedy superużytkownika używa tar, ta opcja jest domyślnie włączona.

2.18.2. Program gzip

Program gzip (GNU zip) jest standardowym narzędziem kompresującym w systemach uniksowych. Pliki skompresowane za jego pomocą otrzymują rozszerzenie .gz. Dekompresuje się je za pomocą polecenia gunzip, jako argument podaje się nazwę pliku. Natomiast kompresji dokonuje za pomocą polecenia gzip, podając plik do skompresowania jako argument.

2.18.3. Skompresowane archiwa tar.gz

Obsługę skompresowanych archwów przy użyciu gzip, rozpoczniemy od rozpakowania takiego archiwum. Nie ma sensu używania do tego dwóch osobnych poleceń, jest to z resztą marnowanie zasobów. Chcąc rozpakować skompresowane gzip archiwum, należy użyć polecenia tar a po opcji -x dodać, opcję -z następnie pozostałe czyli -vf i na końcu podać nazwę archiwum. Tak jak na przykładzie:

$ tar -xzvf archiwum.tar.gz

Przy tego typu archiwach, możemy spodziewać się rozszerzenia .tgz. Są to te same archiwa, jak te mające rozszerzenie tar.gz.

Przy wyświetlaniu zawartości takiego archiwum, zamieniamy opcję -x na -t. A chcąc takie archwiwum utworzyć to opcję -x na opcję -c oraz podać katalog lub listę plików, która ma zostać umieszczona w archiwum po jego nazwie.

2.18.4. Inne metody kompresji

Poza archiwami spakowanymi za pomocą gzip, możemy też spotkać archiwa spakowane za pomocą bzip2 oraz xz. W przypadku bzip2, to programem dekompresującym jest bunzip2, a opcją programu tar jest -j (mała litera j). Jeśli natrafimy na archiwum skompresowane xz, to programem dekompresującym jest unxz, a opcją programu tar jest -J (wielka litera j).

Część dystrybucji wyposażona jest w program unzip pozwalający na rozpakowanie plików .zip przygotowanych pod systemem MS Windows, jak i samo rozpakowywujących się plików .exe.

2.19. Hierarchia katalogów

Struktura katalogów głównego, jest utworzona na podstawie standardu hierarchii systemu plików, określającego jakie podkatalogi ma zawierać katalog główny, oraz co te podkatalogi mają przechowywać. Poniżej opisano na ten czas najważniejsze z nich.

2.19.1. Pozostałe katalogi główne

2.19.2. Katalog /usr

To właśnie w katalogu /usr znajduje się większość programów i danych przestrzeni użytkownika, a są one rozlokowane po jego podkatalogach. Poniżej znajduje się opis co znajduje się w poszczególnych podkatalogach tego katalogu:

2.19.3. Umiejscowanie jądra w systemie

Wspomniałem już że plik jądra znajduje się w katalogu /boot, plik ten rozpoczyna się od nazwy vmlinuz, po tych znakach mogą wystąpić inne inforamcje oznaczające jego wersje. Po załadowaniu jądra przez program rozruchowy, sam plik przestaje być potrzebny. W trakcie pracy systemu operacyjnego jądro wykorzystuje najróżniejsze ładowane i usuwane dodatkowo modułu. Ładowane moduły jądra umieszczane są w katalogu /lib/modules.

2.20. Uruchamianie poleceń przez superużytkownika

Korzystając z linuksa na naszym osobistym komputerze, przyjdzie taki moment że będziemy musieli skorzystać z konta użytkownika root. Aby to zrobić możemy przelogować się na jego konto wykonać potrzebne czynności a następnie się wylogować. Ta czyność przyniosła by zamierzony efekt ale nie jest bez wad. Dlatego też możemy skorzystać z polecenia sudo, które pozwoli, na uruchomienie polecenia podanego jako arugment z uprawnieniami superużytkownika. Jeśli polecenie nie występuje w systemie, to jest dobry czas aby przelogować się na użytkownika root, i je zainstalować. Polecenie po zainstalowaniu nie zadziała samo w sobie potrzebne jest jeszcze określenie, którzy użytkownicy mogą używać tego polecenia i co za jego pomocą mogą zrobić. Za to odpowiada pliki /etc/sudoers.

2.20.1. Plik /etc/sudoers

Samo polecenie sudo ma bardzo duża ilość opcji, jednak na tym etapie nie skorzystamy z większości z nich. Najprostszym sposobem na konfiguracje pliku /etc/sudoers jest odnalezienie w pliku linii rozpoczynającej się pod słowa root a następnie pod tą linią wpisać linię rozpoczynjącą się nazwy użytkownika oraz dopisaniu kilku opcji, tak jak na poniższym przykładzie.

user ALL=(ALL) ALL

Pierwsze ALL, oznaczna dowolny komputer. Drugie (ALL) w nawiasach oznacza, że możemy wydać polecenie jako dowolny użytkownik, być może spotkamy się z takim zapisem w nawiasie (ALL:ALL), oznacza ono dowolnego użytkownika i dowolną grupę. Trzecie ALL oznacza dowolne polecenie.

Jeśli drażnić nas będzie ciągłe wpisywanie haseł, to możemy przed trzecim ALL w konfiguracji umieścić opcję NOPASSWD, pamiętając aby pomiędzy te opcje wstawić dwukropek (:) bo tak naprawdę określamy jakie polecenia mają być uruchamiane bez podawania hasła.

2.21. Podsumowanie

Po przeczytaniu tego rodziały wydaje mi się, że każdy ma solidne podstawy obsługi systemu Linux z poziomu powłoki. Powłoka jest jednym ze stałych komponentów dystrybucji, a wiele z nich dalej obstaje przy BASH-u, jako domyślnej powłoce.

3. Urządzenia

Odkąd powstał system Linux, w sposobach prezentowania urządzeń użytkownikowi zachodziło wiele zmian. Na początku tego rozdziału omówimy sobie tradycjny system sysfs. Aby potem zająć się omówieniem systemu udev, pozwalającego programom przestrzeni użytkownika automatycznie konfigurować oraz używać nowych urządzeń. Podczas normalnego użytkowania systemu rzadko będziemy mieć okazję do zarządzania urządzeniami. Nasza interakcja z urządzeniami będzie ograniczać się do obsługi pamięci masowych i korzystania najstarszego i naprostszego interfejsu jakim jest katalog /dev. Mimo to, warto jednak wiedzieć w systemie co jest od czego.

3.1. Pliki urządzeń

Jądro udostępnia wiele urządzeń pod postacią plików, co daje nam możliwość prostej manipulacji nimi. Te pliki są często nazywane węzłami urządzeń. Korzystać z urządzeń możemy za pomocą zwykłych operacji na plikach. Tego typu rozwiązanie nie jest bez wad dlatego też nie wszystkie urządzenia lub ich funkcje są udostępnianie w ten sposób.

Pliki urządzeń są przechowywane w katalogu /dev. A najprostszym sposobem interakcji z urządziem jest przekierowanie wyniku jakiegoś polecenia do urządzenia /dev/null. Urządzenie to jest miejscem na nie potrzebne nam dane ze strumieni, ponieważ cokolwiek trafi do tego urządzenia, jest przez jądro poprostu ignorowane.

Wyświetlając sobie zawartość katalogu w bardziej szczegółowej liście może zauważyć dziwne oznaczenia w trybie pliku. Litery te określają rodzaje urządzeń a wśród nich możemy wyszczególnić:

Inną dość rzucającą się w oczy informacją na listingu katalogu, są dwie liczy odzielone przecinkiem zamiast rozmiaru pliku, jest numer główny i numer poboczny. Te numer ułatwiają jądru identyfikacje urządzeń. Dla przykładu partycje tego samego dysku mają ten sam numer główny ale inny numer poboczny.

Nie wszystkie urządzenia mają swoje pliki, takim przykładem są interfejsy sieciowe. Jądro wykorzystuje dla nich inny interfejs wejścia-wyjścia.

3.2. Ścieżka urządzeń sysfs

Ze względu na uproszczoną interakcje z urządzeniami poprzez odwołowywanie się do pliku w katalogu /dev oraz fakt, że jądro systemu nadaje plikom z tego katalogu nazwy na podstawie koleności wykrywania urządzeń podczas uruchamiania systemu, wewnątrz jądra zaimplementowano interfejs sysfs. Sysfs jest ujednoliconym sposobem prezentacji urządzeń bazującym na atrybutach sprzętowych, mający formę struktury katalogów i plików. Główny katalogiem tego systemu jest katalog /sys/devices. Przykładowa ścieżka dla pierwszego dysku SATA mającego swój plik /dev/sda może wyglądać następująco:

/sys/devices/pci0000:00/0000:00:1f:2/host0/target0:0:0/0:0:0:0/block/sda

Warto tutaj zaznaczyć iż, scieżki systemu sysfs nie służą uzyskaniu dostępu do urządzeń, umożliwają przeglądanie informacji oraz zarządzanie urządzeniami. Dane zawarte na plikach na ścieżkach sysfs powinny być odczytywane przez programy nie przez ludzi.

Chcąc sprawdzić scieżkę sysfs dla dowolnego urządzenia z katalogu /dev należało by uzyć programu systemu udev udevadm.

$ udevadm info --query=all --name=/dev/sda

Wykonując to polecenie dowiemy się przy okazji ile danych można uzyskać informacji z systemu udev.

3.3. Polecenie dd

Polecenie dd jest dość prostym, aczkolwiek przydatnym narzędziem jeśli choodzi o prace z urządzeniami znakowymi czy blokowymi. Jedyną rzeczą, którą robi to polecenie jest odczyt danych z pliku wejściowego lub ze strumienia i zapisanie go do wyjściowego pliku lub strumienia, przy okazji dokonując pewnych konwersji. Najczęsciej używane przeze mnie polecenie znajduje się poniżej.

$ sudo dd if=/dev/zero bs=1M of=/dev/sdX count=1

Polecenie wykorzystuje uprawnienia superużytkownika, aby uzyskać dostęp do urządzenia blokowego. Samo polecenie zapisuje jeden blok o wielkości 1M za pomocą zer z pliku /dev/zero (nieskończony strumień zer), co powoduje usunięcie tablicy partycji (o której będzie w następnym rodziale).

Poniżej zostaną opisane najważniejsze opcje programu dd, ich format różni się od innych programów uniksowych. Do przypisania wartości opcją używa się tutaj znaku równości (=).

Przy korzystaniu z dd, należy uważać gdyż literówka w poleceniu wystarczy, aby uszkodzić system lub spowodować utratę ważnych dla nas danych.

3.4. Podsumowanie nazewnictwa urządzeń

Do pracy z urządzeniami potrzebujemy jego nazwy. W systemie istnieje kilka metod pozwalających na ustalenia nazwy urządzenia.

Dzisiaj praca z urządzeniami na Linuksie sprowadza się głównie do partycjonowania dysku, więc aby znależć właściwe urządzenie wystarczy użyć polecenia fdisk -l.

Na poniższej liście znajdują się najczęściej wykorzystywane na Linkusie konwencje nazwenicze.

3.4.1. Tworzenie plików urządzeń

We współczesnych systemach nie ma potrzeby samodzielnego tworzenia urządzeń, jednak czasami w specyficznych konfiguracjach może dość do potrzeby utworzenia urządzenia. Osobiście spotkałem się z takim przypadkiem konfigurując VPN na dystrybucji Alpine Linux, należało osobiście utworzyć urządzęnie znakowe TUN.

Urządzenie tworzymy za pomocą polecenia mknod, podając nazwę urządzenia, jego rodzaj i w zależności od rodzaju numer główny oraz numer poboczny (w przypadku nazwanych potoków, nie trzeba podawać numery głównego i numeru pobocznego.

3.5 System udev

Zarządzanie plikami urządzeń jest jedną z cech jądra, która mogła działać w przestrzeni użytkownika. Jądro tylko gdyby wykryło nowe urządzenie wysłało by powiadomienie do procesu udevd. Proces ten zbadał by charakterystykę urządzenia, utworzył dla niego odpowiedni plik, a na koniec przewprowadził jego inicjację. Niestety to tylko teoria.

Rozwiązanie tego typu nie uwzględnia kilku problemów. Pliki urządzeń są potrzebne już na wszczesnych etapach uruchamiania, zatem proces udev musiał by zostać uruchomiony bardzo wcześnie, nie może mieć żadnych zależności wobec plików urządzeń i uruchomić się błyskawicznie, aby nie spowalniać procedury rozruchu systemu.

3.5.1. System plików devtmpfs

System plików (będzie o tym w dalszej części materiału) devtmpfs, został opracowany w celu rozwiąznia problemów z dostęp do urządzeń w czasie uruchamiania systemu. W razie gdy jądro będzie potrzebować pliku urządzenia to utworzy je oraz powiadamia o tym fakcie system udev, który zamiast zajmować się tworzeniem pliku przystępuje do inicjacji urządzenia i informuje o tym pozostałe procesy. Po za tym proces udev tworzy kilka dowiązań symbolicznych w katalogu /dev, które bardziej szczegółowo identyfikują urządzenie, wyniki tego działania możemy obejrzeć w katalogu /dev/disk/by-id.

System udev, tworzy nazwy dowiązań bazdując na typie interfejsu, nazwie producenta, informacji o modelu, numerze seryjnym oraz partycji. Proces pobiera te informacji na podstawie reguł systemu udev, jednak nie będziemy się tym tutaj zajmować.

4. Dyski i systemy plików

Dyski w systemach Linux przedstawiane są jako urządzenia blokowe z nazwami pochodzącymi od podsystemu SCSI - /dev/sdX. Z punktu widzenia systemu na dysku znajduje się wiele warstw oraz komponentów. Wybrane częsci dysku możemu zaalokować na partycje, które system prezentuje w taki sam sposób jak dyski, dodają liczbę na końcu nazwy dysku. Wystąpienia partycji na dysku przechowywane są w tablicy partycji.

Jądro systemu umożliwia dostęp do całego urządzenia (dysku) oraz do partycji dzięki osobnym plikom urządzeń.

Każdy dysk musi posiadać chociażby jedną partycję, aby był użyteczny w systemie, z kolei taka partycja musi zostać sformatowana pod wybrany system plików, aby mogła przez chowywać jakie kolwiek dane. System plików możemy określić mianem bazy danych przechowywującej informacje na temat plików i katalogów.

4.1. Partycjonowanie dysków

Partycjonowanie dysku, odbywa się w oparciu o schematy. Schematy partycjonowania okreslają ilość możliwych do utworzenia na dysku partycji oraz ewentualne dodatkowe informacje przechowywane w tablicy partycji. Wśród obecnie stosowanych możemy wyróżnić takie schematy jak MBR oraz GPT.

Na Linuksie dostępnych jest wiele narzędzi partycjonujących dysk, jedne są obsługiwane jak z poziomu środowiska graficznego inne zaś z poziomu terminala. Osobiście używam programu fdisk i to na nim skupię się jeśli chodzi o partycjonowanie. Program ten ma dwie istotne zalety. Po pierwsze nic nie zostanie zapisane do momentu gdy nie wydamy polecenia (Tak, w fdisk wydaje się polecenia, ale są one ograniczone do wpisania jednej litery i naciśnięcia klawisza enter); po drugie w pakiecie fdisk zawarty jest również program sfdisk (co prawda z nieco dziwną składnią), ktory umożliwia manipulowanie dyski z poziomu pojedynczych polecenie (w przypadku fdisk wykorzystywany jest tryb interaktywny), przez co możemy użyć sfdisk w skrypcie.

4.1.1. Przeglądanie tablicy partycji

Przy użyciu polecenia:

$ sudo fdisk -l

możemy wyświetlić zawartość tablicy partycji wszystkich dysków w systemie, Jeśli zaś interesuje nas wybrane urządzenie możemy wpisać jego nazwę po opcji -l. Informacja zwracana przez polecenie zawiera informacje o schemacie partycjonowania w polu Disklabel type: oraz tabele przedstawiającą nazwę urządzenia, informacje o ustwionej fladze rozruchu, początku, końcu i rozmiarze podanym w sektorach (informacja ile wynosi sektor znajduje się w linii Sector size) oraz identyfikatorze i nazwie typu partycji. Identyfikatory można wypisać podczas nadawania typu.

Schemat partycjonowania MBR nazwany jest fdisk dos. Identyfikator dysk jest krótszy. W przypadku tablicy partycji GPT identyfikator dysku zawiera ciągu znaków odzielone spacjami, nie występują w tabeli identyfikatory typów partycji ponieważ są tak duże jak identyfikator dysku (podczas ustalania wybierane są z wyświetlonej listy) oraz może wystąpić dodatkowa kolumna przechowywująca etykietę partycji. Jednak główną różnicą wśród tych schematów jest zarządzanie miejscem na dysku. Dyski z tablicami MBR mogą mieć maksymalną pojemność do 2TB, jeśli użyjemy wiekszego, to stosując tego typu partycję pozbędziemy się pozostałej części dysku, kolejny minus dla tego rodzaju to możlwość tworzenie maksymalnie czterech partycji podstawowych (zwykłych partycji na dane), jeśli chcielibyśmy więcej owszem możemy jednak, musimy użyć jedno miejsce na partycję rozszerzoną bedącą kontenerem dla dysków logicznych. W przypadku GPT raczej maksymalna wielkość dysku nie jest póki co osiąglna (9,4 mld TB) a partycji podstawowych możemy utworzyć, aż 128. To są tak naprawę za i przeciw, które decydują o użytym schemacie.

4.1.2. Modyfikowanie tablicy partycji

Modyfikowanie tablicy partycji należy rozpocząć, od zastanowienia się na temat przydatności danych, które znajduja się obecnie na dysku. Ponieważ modyfikowanie tablicy partycji, często będzie wiązać się z potrzebą reformatowania modyfikowanej partycji. W stopniu podstawowym skupimy się tworzeniu oraz usuwaniu partycji. Modyfikowanie tablicy za pomocą fdisk, jest dość łatwe, polega na interaktywnym wydawaniu poleceń oraz odpowiadniu na pytania programu, dlatego też dla urozmaicenia użyjemy sfdisk zamiast wymienionego wcześniej programu. Dla przykładu stworzymy dysk do klasycznej instalacji systemu Linux z tablicą partycji typu MBR. Za dysk testowy może posłużyć nam pendrive, karta pamięci lub plik na dysku. Ja skorzystam z pliku.

Plik przygotowuje za pomocą polecenia dd poznanego w poprzednim rozdziale.

$ sudo dd if=/dev/zero bs=1M of=vhd.img count=8192

Zapisanie 8GB zer może chwilę potrwać. Po utworzeniu pliku przechodzę do jego inicjalizacji.

$ echo "label:dos" | sudo sfdisk vhd.img

Polecenia sfdisk muszą pochodzić ze strumienia lub z wcześniej przygotowanego skryptu. Polecenie tworzy na dysku tablice partycji typu MBR. Kolejne polcenie będą już poleceniami właściwymi tworzącymi partycje na naszym dysku.

Polecenia sfdisk składają się z czterech pól:

Po utworzeniu pierwszej partycji, każda kolejna będzie wymagać podania podania opcji -a, która spowoduje wykorzystanie wcześniej utworzonej tablicy partycji oraz jej numeru po opcji -N przed wskazaniem poleceniu urządzenia. Zatem pierwszą partycję tworzymy za pomocą poniższego polecenia:

$ echo ",+7G,L,*" | sudo sfdisk vhd.img

Pierwszej partycji przydzielono większość miejsca na dysku, będzie ona przechowywać katalog główny. Resztę miejsca wykorzystamy na partycję rozszerzoną, a wewnątrz niej utworzymy dysk logiczny będący partycją wymiany.

$ echo ",,E," | sudo sfdisk -a -N 2 vhd.img
$ echo ",,S," | sudo sfdisk -a -N 5 vhd.img

Teraz możemy wyświetlić sobie tablice dysku, który partycjonowaliśmy. Zwróćmy uwagę na to, że wystarczy odpowiedni numer partycji aby utworzyć dysk logiczny. Pominięcie rozmiaru spowoduje zaalokowanie pozostałego wolnego miejsca. Tak przygotowane partycje są gotowe do sformatowania pod wybrany system plików.

Usuwanie partycji

Eksperymentując poraz pierwszy z sfdisk może nam coś nie wyjść dlatego też zamiast rozpoczynać partycjonowanie od nowa możemy źle przygotowaną partycję usunąć. Wydając polecenie sfdisk opcję --delete następnie nazwę urządzenia oraz numer partycji, którą chcemy usunąć.

Korzystając z sfdisk pozbawiamy się bufora, ponieważ program ten zmienia tablicę partycji z każdą modyfikacją. Jeśli chcemy tylko sprawdzić jak będą wyglądać pewne zmiany to lepiej użyć polecenia fdisk. Pomoc uruchamiana jest za pomocą polecenia m po uruchomieniu programu.

4.2. Systemy plików

Systemy plików umożliwiają zamianę prostego urządzenia blokowego w sktrukturę plików i katalogów zarozumiałą dla końcowego użytkownika. Dawniej służyły głównie przchowywaniu plików jednak obecne ich funkcje umożliwiają wykorzystanie ich jako interfejsów systemowych w takich katalogach jak /proc czy /sys.

Normalnie systemy plików są implementowane w jądrze systemu, jednak rozwiązana zastosowane w następcach Uniksa, takich jak Plan 9. umożliwiły stworzenie systemów plików działających w przestrzeni użytkownika, tzw. FUSE. Dzięki tej funkcji możemy zapisywać dane na nośnikach z takim system plików jak NTFS.

Istotną funkcję jeśli chodzi o sposób działania systemów plików, jest wykorzystanie VFS, który standaryzuje dostęp do plików i katalogów dla aplikacji użytkownika, dlatego też Linux obsługuje tak wiele systemów plików.

4.2.1. Typy systemów plików

Mimo iż Linux, może obsługiwać chyba wszystkie możliwe systemy plików, to większość z nich wymaga dodatkowego oprogramowania. Natywnie obsługiwane systemy plików Linuksa znajduje się na liście poniżej.

4.2.2. Tworzenie systemu plików

Tworząc partycje w poprzednim punkcie pozostało jeszcze sformatować pod konkretny system plików, aby można było przechowywać na nich informacje. System plików tworzony jest za pomocą polecenia mkfs. Polecenie ma inną nieco inna składnię, ponieważ żądany system plików podaje się po kropce w nazwie polecenia np. mkfs.ext4. Jako argument podajemy nazwę urządzenia partycji.

$ sudo mkfs.ext4 /dev/sda1

Podczas formatowania partycji program wyświetla komunikaty diagnostyczne. Wśród nich znajdują się liczby oddzielone do siebie przecinkami. Te liczby to kopie zapasowe superbloku. Superblok to najwyższy poziom bazy danych systemu plików, jest on na tyle ważny że program tworzy kilka jego kopii. Numery bloków zawierających kopie superbloku należy zachować ponieważ może być ona potrzebna do ewentualnego odzyskiwania danych.

Przyglądając się samemu programowi mkfs dojedziemy do wniosku, że jest to swojego rodzaju interfejs do całego zbioru programów tworzących systemy plików. Nie które wystąpienia tego interfejsu są dowiązaniami sybolicznymi do innych programów. np mkfs.ext4 wskazuje na program mke2fs, będący głównym programem do służącym do tworzenia systemów plików z rodziny EXT, warto o tym pamiętać ponieważ możemy natknąć się na systemy bez polecenia mkfs.

4.2.3. Montowanie systemów plików

Proces dołączania systemu plików w uniksach nazywany jest montowaniem. Aby zamontować w systemie jakiś system plików należy użyć polecenia mount, użyć tego polecnia bez żadnej opcji spowoduje wyświetlenie podmonotowanych systemów plików. Montowanie jak i późniejsze odmontowywanie wymagają uprawnień superużytkownika.

Każdy wpis to jedno montowanie systemu plików, wpisy zwierają kolejno nazwę urządzenia, docelowe miejsce montowania, typ systemu oraz opcje specyficzne dla systemu.

Montowanie systemu plików odbywa się za pomocą tego samego polecenia, jednak wymaga podania kilku argumentów, kolejno:

Montując systemy takie jak EXT, czy któryś z FAT możemy pominąć rodzaj podczas montowania, program sam to ustali. Jednak montowanie udziałów sieciowych CIFS, wymaga podania typu aby program mount mógł rozpoznać wartości zapisane w argumentach.

Po skończeniu prac z system plików, możemy go odmontować za pomocą polecenia umount. Polecenie wymaga podania albo urządzenia albo punktu montowania jako argumentu.

4.2.4. Identyfikator UUID systemu plików

Montowanie systemu plików wymaga podania nazyw urządzenia. Pliki konfiguracyjne odpowiedzialne za automatyczne montowanie systemów plików w systemie podczas jego startu, nie mogą polegać na tych samych nazwach urządzeń co użytkownicy, ponieważ są one ustalane pod czas startu systemu, i ich nazwy zależą od kolejności wykrycia ich przez jądro. W takich plikach używa się identyfikatorów UUID swoistych numerów seryjnych systemów plików nadawanych podczas formatowania. Listę urządzeń wraz z UUID-ami, możemy wywołać za pomocą polecenia:

$ sudo blkid

Identyfikatorów możemy używać nie tylko z plikami konfiguracyjnymi jak /etc/fstab, ale równie przy polecniu mount zamiast klasycznej nazwy urządzenia. Jednak posługiwanie się tak długim i skomplikowanym ciągiem znaków nie jest za wygodne.

Polecenie blkid może zwracać w polu UUID numery identyfikacyjne innych systemów plików takich jak na przykład FAT, gdzie UUID-em jest numer seryjny woluminu FAT. Oczywiście takie identyfikatory możemy używać podczas konfiguracji pliku /etc/fstab.

UUID musi być unikatowy, dlatego też jeśli zasła potrzeba skopiowania całego systemu plików, to należy zmienić ten identyfikator aby odróżnić kopię od oryginału.

4.2.5. Buforowanie dysku i systemu plików

Uniksy w tym i Linux nie zapisują wszysktkich zmian w systemie plików po otrzymaniu takiego żądania. Zmiany przechowywane są w pamięci RAM od momentu kiedy jądro będzie mogło swobodnie zapisać je na dysku.

W momencie odmontowywania systemu plików jądro automatycznie synchronizuje zawartość dysku. Jeśli z jakiego powodu nie będziemy mogli odmontować systemu plików to wówczas możemy wydać polecenie sync, które wymusza zapisanie na dysku wszyskich zmian w systemie plików. Oczywiście w wiekszej liczbie przypadku problemów z odmonotowaniem systemu plików jest proces używający któregoś z plików na dysku.

Jądro dysponują całą serią mechanizmów wykorzystujących pamięć RAM do buforowania danych odczytywanych z dysków, przez co jeśli proces wielokrotnie będzie odczytywać dane z tego samego pliku, jądro nie będzie musiało odwoływać się do danych na dysku przez każde dane do procesu z bufora, oszczędzając tym samym czas i zasoby.

4.2.6. Opcje montowania

Polecenie mount posiada dużą ilość opcji. Jest ona tak duża że wprowadzenie opcji długich wynikało z obowiązku aniżeli wygody, ponieważ nazwyczajniej zaczynało tych liter w alfabecie brakować. Z posród opcji krótkich - jedno literowych możemy wyróżnić najważniejsze:

Opcje długie podawane wraz z opcjami specyficznymi dla systemu plików po opcji -o. Z opcji długich możemy wyróżnić takie jak:

4.2.7. Pownowne montowanie systemu plików

W trakcie odzyskiwania danych może zajść potrzeba ponownego zamontowania systemu plików w celu zmiany opcji montowania. Najczęściej chodzi o przełączenie systemu plików zawierającego katalog główny z trybu tylko do odczytu w tryb pełnego dostępu. Ponownemu montowaniu służy opcja remount.

4.2.8. Tablica systemów plików /etc/fstab

Plik /etc/fstab przechowuje informacje o systemach plików oraz ich punktach montowania, dzięki czemu montuje te systemy podczas uruchamiania systemu. Każdy wiersz tego pliku przechowuje informacje o jednym systemie plików i jest podzielony na sześć pól.

Posiadając odpowiednie wpisy w plik /etc/fstab, możemy montować system plików podając poleceniu mount tylko punkty montowania co może być wygodne podczas montowania systemów plików dużą ilością opcji.

Istnieje kilka opcji które mają zastosowanie tylko w omawianym przez nas pliku.

4.2.9 Pojemność systemu plików

Sprawdzenia zajętości systemu plików możemy dokonać za pomocą polecenia df. Polecenie to domyślnie zwraca wszelkie wartość w postaci kilobajtów, które nie są zbyt czytelne dla człowieka. Aby przeskalować jednostki możemy posłużyć się opcją -h. Polecenie wyświetla wynik swojego działania w postaci pięciu kolumn przedstawiających kolejno system plików, jego rozmiar, użyte miejsce, dostępne miejsce, stopień użycia w procentach oraz punkt montowania. W przypadku użycia programu bez podanej opcji rozmiar systemu nosi nazwę 1K-bl jest to wielkość systemu plikach w jednokilobajtowych blokach.

$ df -h

Jeśli przjrzymy się na chwilę wynikom działania tego polecenia, możemy dość do wniosku, że albo mamy doczynienia z błędem albo tolerancja błędu przybliżenia jest bardzo. Otóż nie. Kilku gigabajtów brakuje ze względu na to, że zostały zarezerwowane i są do dyspozycji superużytkownika w momencie wyczerpania się miejsca na danym systemie plików, aby zapewnić systemówi dalsze funkcjonowanie oraz umożliwić administratorowi odzyskanie chociaż części miejsca na dysku.

4.2.10. Sprawdzanie i naprawnia systemu plików

Jądro do pracy systemu musi mieć pewność, że zamontowane systemy plików są pozbawione błędów. Błędy systemów plików mogą powodować utratę danych lub załamanie systemu. Najczęstszym powodem występowania błędów w systemie plików, są zaniki zasialania komputera spowodowane ludzką niewiedzą lub czynnikami środowiskowymi. Najnowszej generacji systemy plików wykorzystują pliki dziennika, dzięki, przerwanie działania systemu w wyniku różnych czynników nie doprowadza do katastrofy to są przypadki gdzie i one zawodzą.

Narzędzie przeznaczone do sprawdzania oraz naprawy systemu plików nazywa się fsck. Fsck podobobnie do mkfs uruchamia odpowiedni dla użytego na partycji systemu plików. Program w trybie interaktywnym uruchamiamy wydając polecenie fsck następnie podając nazwę urządzenia.

$ fsck /dev/sdb1

Nie wolno uruchamiać programu na zamontowanym systemie plików, gdyż grozi to utratą danych oraz załamaniem systemu. Inaczej sprawa ma się gdy system plików jest w trybie tylko do odczytu.

W trybie interaktywnym program będzie zwracać raport z kolejnych etapów, jeśli napotka jakiś problem program zapytanie o usunięcie błędu. W wyniku błedów w systemie plików może zdarzyć się, że pewne pliki zostaną pozbawione nazwy (nazwy plików są w uniksach elementami systemu plików). Program kiedy napotka na taki to zostanie on przeniesiony do katalogu lost+found z nazwą odpowiadającą numerowi identyfikacyjnemu z systemu plików (węzła i-node). Rzeczywistą nazwę musimy ustalić samodzielnie na podstawie analizy jego zawartości.

Program e2fsck - właściwy program fsck dla rodziny systemów plików EXT, posiada opcję -p zajmującą się naprawą drobnych błędów. Program zatrzyma się wówczas tylko wtedy gdy napotka poważny błąd. Jeśli mamy podejrzenie, że coś się dzieje z system plików, to możemy sprawdzić system plików bez dokonywania w nim żadnych modyfikacji, korzystając z opcji -n. Co w przypadku uszkodzenia superbloku? Podstawową bazę danych możemy odbudować za pomocą opcji -b po opcji należy podać lokalizację kopii superbloku (numer sektora podawany przez mkfs podczas tworzenia systemu plików). W przypadku gdy zapomnieliśmy spisać te numery, możemy spróbować je odzyskać wydając polecenie mke2fs wraz z opcją -n dla urządzenia. Należy upewnić się, że na pewno użyliśmy tej opcji jej pominięcie sformatuje partycję.

Istnieją przypadki uszkodzeń, które wykraczają po za sferę programową program fsck, nie jednokrotnie pokazał mi, że by się wydawało katastrofę, naprawiał pojedyńczym domyślnym uruchomieniem. Jeśli nasze systemy dyskowe przechowują ważne informacje, to najlepszą ochroną jest kopia zapasowa warto je robić. Lepiej jest wymieć dysk, zainicjalizować dysk i przegrać dane niż liczyć na to, że może coś się uda odzyskać. Może się uda, jakieś szanse istnieją. Jeśli posiadamy kopię, to szanse przekraczają 90% reszta to ich aktualność.

4.2.11. Systemy plików o specjalnym znaczeniu

Nie wszyskie systemy plików służą zapisywaniu informacji na fizycznych nośnikach, nie które z nich mogą służyć jako intefejsy systemowe lub przezentować informacje systemowe. Takimi systemami są:

4.3. Przestrzeń wymiany

Za pomocą przestrzeni na dysku jesteśmy wstanie powiększyć ilość użytkowej pamięci operacyjnej. System będzie automatycznie przenosić strony pamięci (obszary) na dysk i z dysku, wykorzystująca tzw. pamięć wirtualną. Operacja przenoszenia stron pamięci na dysk i z powrotem nosi nazwę wymiany (ang. swapping), ponieważ polega na wymianie nieaktywnych stron w pamięci z aktywowany zanajdującymi się aktualnie na dysku. Przestrzeń, w której zapisywane są strony pamięci nazywa się przestrzenią wymiany.

4.3.1. Wykorzystanie partycji jako przestrzeni wymiany.

Wykorzystanie partycji jako przestrzeni wymiany jest standardową procedurą wykonywaną podczas instalacji systemu. Wiele dystrybucji zwraca uwagę na to, gdy brakuje partycji wymiany. Partycja wymiany może nie być nigdy wykorzystana, jednak chroni system przed załamianiem gdy zaczyna brakować pamięci RAM.

Podczas modyfikowania tablicy partycji, utworzyliśmy dysk logiczny, który posłuży jako przestrzeń wymiany. Przestrzeń wymiany należy sformatować, tak jak każdą partycje, jednak już nie za pomocą polecenia mkfs, ale mkswap podając nazwę urządzenia jako parametr.

$ sudo mkswap /dev/sda5

Partycję wymiany możemy montować automatycznie w systemie za pomocą wpisu w pliku /etc/fstab. Poniżej znajduje się wpis, który można wykorzystać w instalacji. Zgaduje jednak, ża zainstalowana przez nas dystrybucja Linuksa do Mint, Ubuntu lub Kali więc raczej taki wpis znajduje się już tym pliku.

UUID="..."  none  swap  sw  0 0

Przestrzeń wymiany możemy włączać oraz wyłączać na żądanie za pomocą poleceń swapon oraz swapoff podając urządzenie lub plik jako argument.

4.3.2. Wykorzystanie pliku jako przestrzeni wymiany

Jeśli nie mamy dostępnego wolnego miejsca na dysku, możemy wówczas stworzyć plik, który będzie służyć nam za przestrzeń wymiany. Aktywujemy go, wówczas gdy ilość wolnej pamięci RAM, będzie niebezpieczenie niska. Aby utworzyć taki plik, musi on posiadać żądaną przez nas wielkość. Najprościej zapisać do niego określoną liczbę zer z strumienia.

$ sudo dd if=/dev/zero bs=1M of=swap.img count=2048

Polecenie to zapisze dwa gigabajty zer do pliku swap.img. Taki plik będziemy mogli potraktować jak dysk lub partycję. Chciałbym tutaj również zaznaczyć, że w tym momencie spotykamy się w praktyce z jedną z fundamentalnych zasad Uniksów otóż "Wszystko jest plikiem". Taki plik pozostaje jeszcze sformatować jako partycję wymiany.

$ sudo mkswap swap.img

Przygotowaną w ten sposób przestrzeń możemy uruchomić za pomocą polecenia swapon.

4.3.3. Jak dużej przestrzeni wymiany potrzeuje?

Odpowiedź na to pytanie nie jest trudna do sformułowania. Otóż powszechnie przyjeło się, że przestrzeń wymiany powinna mieć wielkość dwukrotności zainstalowanej pamięci RAM. Biorąc pod uwagę to, że obecnie jesteśmy w posiadaniu dysków o bardzo dużej pojemności to jest to nawet śmieszna wartość, jednakże to przekonanie poważnie podważyło upowszechnienie się dysków SSD, które już dużych wartości nie muszą mieć. Weźmy sprzęt wbudowany (tzw. embedded) lub sprzęt mobilny, nie mówię tu o telefonach, ale np. chrombookach czy rozwiązaniach typu HP Stream. To tym przypadku przestrzeni dyskowej może być za mało jak na 8GB swapu. Weźmy również pod uwagę fakt iż prawdopodbnie nie skorzystamy z przestrzeni wymiany używając Linuksa na współczesnym sprzęcie. Więc wydaje mi się, że najbardziej optymalną wielkości swapu będzie 1GB. Na poparcie dodam że wiele instalatorów właśnie tyle alokuje podczas automatycznego partycjonowania.

4.4. Przyszłość systemów plików

Jedną z zauważalnych zmian w komputerach jest odejście powoli od dysków talerzowych na rzecz dysków SSD, więc możemy spodziewać się systemów domyślnie zoptymalizowanych pod kątem ich pełnego, właściwego wykorzystania. Zauważymy lub już jesteśmy świadkami odchodzenia od rodziny systemu plików EXT, na rzecz takich systemów jak brtfs, który obecnie jest domyślnym systemem dla takich dystrybucji jak Fedora. Alternatywą dla tego systemu będzie system plików xfs. Ich porównanie możemy znaleźć w internecie.

5. Uruchamianie jądra Linux

W tym rodziale rozpoczeniemy przyglądanie się procedurze uruchamiania systemu operacyjnego, z czego ten fragment materiału poświęcimy na procedurę uruchamiania jądra, programy rozruchowe oraz praktyczną konfigurację najpopularniejszego programu rozruchowego jakim nie wątpliwie jest GRUB.

Procedura uruchamiania systemu wygląda w następujący sposób:

  1. System BIOS lub firmware (w przypadku UEFI) ładuje program rozruchowy z dysku i uruchamia go.
  2. Program rozruchowy szuka na dysku obrazu jądra, następnie ładuje go do pamięci i uruchamia.
  3. Jądro inicjuje wszystkie urządzenia wraz ze sterownikami
  4. Jądro montuje partycję z katalogiem głównym.
  5. Jądro uruchamia program o nazwie init, proces tego programu zawsze ma PID o wartości 1. Od momentu startu procesu typu init, rozpoczyna się uruchamianie przestrzeni użytkownika.
  6. Za pomocą programu init uruchamiane są pozostałe elementy systemu (różnego rodzaju usługi).
  7. Na koniec uruchamiany jest proces pozwalający się zalogować do systemu.

W tym rozdziale skupimy się na punktach tej listy od 2 do 4. Pozostałe z nich, po za pierwszym (pierwszy wykracza po za ramy merytoryczne, tego materiału) omówimy w następnym rozdziale.

Niestety możliwość identyfikowania poszczególnych etapów uruchamiania systemu jest osiągalna w zależności od dystrybucji. Te przeznaczone na desktopy, ukrywają wiele informacji na temat pierwszych etapów rozruchu systemu pod graficznymi ekranami, zawierającymi logo dystrybucji oraz pasek postępu. Jeśli spotkamy się z takim ekranem, to wówczas możemy naciśnać klawisz ESC, aby wyświetlić komunikaty wypisywane podczas uruchamiania systemu. Umiejętość identyfikacji poszczególnych etapów może pomoć w ewentualnych problemach podczas rozruchu.

5.1. Komunikaty rozruchowe

Większość systemów uniksopodobnych generuje wiele komunikatów diagnostycznych. Część z nich pochodzi o samego jądra, pożniej pojawiają się komunikaty z poźniejszych etapów uruchamiania. Komunikaty te nie są zbyt przyjazne zwykłemu użytkownikowi, a dystrybuje dążą do tego aby być jak najbardzie przyjazne dla użytkownikowi nietechnicznemu, dlatego też ukrywają je za wyżej wspomnianymi ekranami, drugą ważną rzeczą jest ciągły rozwój jądra oraz sprzętu przez to systemy uruchamiają się tak szybko, że nawet mając te komunikaty przed oczami zdołalibyśmy za nimi nadąrzyć.

Na szczęście są one zapisywane w plikach dziennika w katalogu /var/log oraz nie tylko, poniżej znajduje się lista miejsc, w których możemy szukać komunikatów rozruchowych.

Nie wszystkie etapy są uwzględnione w wyżej wymienionych miejscach, część z nich może być wypisywana jedynie na konsole przez co przepada bezpowrotnie. Programy typu init nowszej generacji mogą przechwytywać te komunikaty a następnie zapisywać je za pomocą swoich rozwiązań protokołowania (prowadzenia plików dziennika).

5.2. Inicjowanie jądra i opcje rozruchu

Podczas rozruchu systemu jądro uruchamiane jest następującej kolejności.

Jednym z etapów inicjacji jądra jest montowanie katalogu głównego. Generalnie to nic w tym nazwyczajnego oczywiście gdy potrzebne do tego komponenty są wbudowane w jądro. Jeśli jednak te modułu są w postaci odrębnych modułów, może wówczas zajść potrzeba załadowania ich przed zamontowaniem głównego katalogu. Tym właśnie zajmuje się początkowych system plików w pamięci RAM - initramfs.

Zakończenie inicjacji i przekazanie uruchamiania procesu init możemy zaobserować w komunikatach diagnstycznych szukając poniższej linii.

Freeing unused kernel memory: ... freed

W tym momencie jądro zwalnia zaalokowaną nie używaną pamięć.

5.3. Parametry jądra

Parametry jądra pozwajają na określenie jego zachowania, na przykład ilość komunikatów diagnostycznych lub podają opcje właściwe dla sterowników urządzeń. Parametry jądra użyte przy jego uruchamianiu dostępne są pliku /proc/cmdline.

BOOT_IMAGE=/vmlinuz-4.19.0-19-amd64 root=UUID=59382884-accb-4106-9d25-44d1ba914530 ro quiet

Na parametry mogą składać się pojedyńcze słowa jak np. ro czy quiet lub opcje w formacje klucz=wartość. Opcja root jest najważniejszą opcją, ponieważ bez niej jądro nie będzie mogło odnaleźć plików programu typu init i uruchomić go. W większości dystrybucji będzie UUID systemu plików zawierającego katalog główny.

Warto zwrócić uwagę również na opcję ro, która nakazuje jądru zamontować system plików w trybie tylko do odczytu. Ta czynność umożliwi bezpieczene sprawdzenie systemu plików przez program fsck.

Jeśli jądro nie rozumie jakiegoś parametru to zostanie on przezkazany do programu init. Przykładem jest wartość -s, który nakaże uruchomić przestrzeń użytkownika w trybie pojedyńczego użytkownika.

5.4. Programy rozruchowe

Zadaniem programu rozruchowego jest załadowanie jądra do pamięci i uruchomienie go z odpowiednimi parametrami. Dla linuksa dostępnych jest wiele bootloaderów, poniżej znajduje się przedstawiająca je lista.

Z racji tego iż, duża liczba dystrybucji, korzysta z GRUB. To jego omówimy sobie pod względem praktycznym. Coreboot stosuje się jako zamiennik BIOS-u. A SYSLINUX raczej ma zastosowanie specjalistyczne wykraczające po za ramy tego materiału.

Aby program rozruchowy mógł załadować do pamięci jądro systemu, musi uzyskać dostęp do dysku. Nie posiadając żadnych sterowników program rozruchowy uzyskuje dostęp do dysku na poziomie BIOS-u, za pomocą adresowania Linear Block Addressing mimo bardzo niskiej wydajności zapewnia ono uniwersalny dostęp do dysku.

5.4.1. Linux i Secure Boot

Na nowych komputerach korzystających z UEFI, mogliśmy spotkać się przypadkiem, gdy komputer odmówił uruchomienia z płyty czy też z pamięci flash. Wiele z nich wyświetlało monit o tym, że włączona jest opcja bezpiecznego rozruchu. Opcja ta wymaga podpisu przez zaufaną organizację programu rozruchowego, aby mógł on być uruchomiony. Microsoft w ramach dystrybucji systemu Windows 8 wymógł na producentach sprzętu domyślne włącznie tej opcji. Przyczyny nie są mi znane, chciaż podejrzewam chęć utrudnienia instalacji innego systemu nawet Windows 7. Oczywiście tę opcję możemy wyłączyć odszukując opcję w panelu sterowania fimwarem (w BIOSie). Jeśli obawiamy się o nasze bezpieczeństwo, to wyłączenie tej opcji potrzebne jest wyłącznie na czas instalacji, ponieważ najnowsze wersje programów rozruchowych są już podpisane, więc będą zostaną zaaprobowane przez tę opcję.

5.5. Praktyczne użycie programu rozruchowego GRUB

GRUB jest obecnie najszerzej wykorzystywanym bootloaderem, dlatego też warto poznać podstawy jego obsługi, bez owijania w bawełnę, bez wertowania kolejnych kart książek z teorią na temat tego programu rozruchowego.

5.5.1. Pierwszy kontakt z GRUBm

Po przejściu procedur testowych firmware naszego komputera bez znaczenia czy jest to BIOS czy UEFI, pokaże nam się tabela z której będziemy mogli wybrać jedną z opcji. Podobna do tej na poniższym rysunku.

grub-boot-manager

Przedstawiona na rysunku tabela może różnić się wyglądem ale funkcjonalność pozostaje taka sama. Na samym dole jest napisane Wyróżniony wpis zostanie wykonany automatycznie za 5s.. Wyróżniony wpis to ten zaznaczony na biało, a po pięciu sekundach zostanie on uruchomiony, ładując system przy standardowych ustawieniach. Za pomocą strzałek możemy poruszać się pod tabeli dokonując wyboru interesującego nas wpisu. Wpis zawierający napis Opcje zaawansowane dla systemu... jest podmenu zwierającym (najczęściej) wpisy ładujące system z poprzednimi wersjami jądra. Wpis w tabeli możemy edytować wybierając go strzałką następnie naciskając klawisz e. GRUB daje nam możliwość załadowani systemu z własnego wiersz poleceń. Ta opcja przeznaczona jest bardziej zaawansowanych czynności, takich jak na przykład diagnostyka konfiguracji GRUB-a. W tym materiale nie będziemy się jednak zajmować wierszem polecenia. Jeśli ruszymy się chociaż w menu to odliczanie zostanie przerwane, więc aby załadować system trzeba wybrać wpis. Naciśnięcie jakiego kolwiek klawisza w menu spowoduje przerwanie odliczania.

5.5.2. Instalacja GRUB w trybie BIOS

Instalacja GRUB w tryb BIOS, jest banalnie prosta. Wystarczy użyć dwóch poleceń. Zazwyczaj korzystając z mainstreamowych dystrybucji przeznaczonych dla użytkowników desktopowowych, nigdy nie będziemy musieli tego robić, ponieważ zrobi to za nas instalator. Natomiast są dwa scenariusze, kiedy niezbędna będzie ponowna instalacja programu GRUB.

Pierwsze polecenie jest takie same dla każdej dystrybucji. Jako argument podajemy dysk (urządzenie główne - np. sda, sdb itp.). Polecenie musi zapisać dane bezpośrednio na urządzeniu, więc niezbędne będą uprawnienia administratora.

# grub-install /dev/sdX

Drugie polecenie zależy już od dystrybucji. Możemy wówczas spotkać takie polecenie jak:

Debian / Ubuntu i pochodne:
# update-grub
Arch Linux i pochodne:
# grub-mkconfig -o /boot/grub/grub.cfg

Drugie polecenie jest odpowiedzialne za wygenerowanie pliku konfiguracyjnego. W przypadku GRUB jest to normalne, ponieważ ze względu na jego skomplikowanie przygotowanie plików składowych (będzie o tym później) spoczywa w rękach twórców samego GRUB-a lub twórców dystrybucji. Użytkownik końcowy otrzymuje gotowe polecenie, które stworzy taki plik konfiguracyjny za niego. Polecenie z Arch Linux, jest dłuższe ale pochodzi bezpośrednio z pakietu. Natomiast w przypadku Debiana i pochodnych mamy dostępne narzędzie przygotowane przez dystrybucję, jest ono bez obsługowe i uruchamia program do poszukiwania innych systemów operacyjnych na innych dyskach podłączonych do komputera. Na Debianie i pochodnych na pewno dostępne jest również to drugie polecenie, jednak lepiej skorzystać z polecenia dedykowanego dla naszej dystrybucji. Ponieważ to tak naprawdę jej twórcy zajmują się przygotowanie GRUB-a do użycia co nie jest takie proste.

5.5.3. Instalacja GRUB w trybie UEFI

Instalacja w trybie UEFI również składa się dwóch poleceń jednak samo polecenie instalacyjne jest cieco dłuższe od tego z trybu BIOS. Wymaga ona również więcej zachodu niż w trybie BIOS oraz może zakończyć się niepowodzeniem. Tak się zdarza to, przy nie których specyficznych sprzętach.

Polecenie instalacyjne wygląda w następująco:

# grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=debian

Instalacja GRUB w trybie uefi wymaga, aby podać docelowy system, w tym przypadku jest x86_64-efi, w trybie BIOS, nie trzeba było podawać docelowego systemu gdyż w przypadku BIOS cel i386-pc jest celem domyślnym, kolejny argumentem jest podanie punktu montowania partycji efi. Taka partycja jest zazwyczaj montowana albo bezpośrednio w głównym katalogu - /efi lub wewnątrz katalogu /boot - /boot/efi. Ostatnim argumentem jest --bootload-id i tutaj panuje pewnego rodzaju niewiadoma. Bo jeśli korzystamy instalujemy program rozruchowy pod Arch Linuxem, to bootloader-id to GRUB. z kolei pod Debianem musi być to debian. Te nazwy wynikają z faktu potrzeby podpisania programu rozruchowego aby mógł on być uruchamiany z włączoną opcją bezpiecznego rozruchu. Warto dodać, że to co podamy w tej opcji będzie wyświetlane w boot menu komputera. Jeśli chcemy użyć jakiejś niestandardowej nazwy i nie za bardzo przejmujemy się secure bootem, to wówczas możemy użyć opcji --no-uefi-secure-boot.

Do w pełni zainstalowanego GRUB-a potrzebujemy tylko pliku konfiguracyjnego. Do tego celu możemy wykorzystać polecenia podane w punkcie odnośnie instalacji GRUB w trybie BIOS.

5.5.4. Zmiana koleności w menu GRUB

Aby zamknąć już temat GRUB-a, przjedziemy do ostatniego zagadnienia. Rozważmy taki przypadek. Mamy jeden komputer w domu, z którego nie korzystamy tylko my. Mamy pozwolenie na wydzielenie pozostałej wolnej części dysku (w duży uproszczemiu) i obok Windows 10 chcemy zainstalować jakąś dystrybucje Linuksa. Załóżmy, że instalacja się powiodła. Niestety mamy problem, gdyż współużytkownicy komputera narzekają, że jak uruchamia się komputer to uruchamia się Linux a nie Windows. Uruchamiając komputer widzisz, że początkowym i domyślnym wyborem w menu GRUB-a jest Linux, a Windows z kolei znajduje się na samym końcu listy.

Pierwsza myśl jest przychodzi nam do głowy to ręczna edycja pliku GRUB-a. Na pewno jest jakieś rozwiązanie. Zatem wyedytowaliśmy plik zapisaliśmy zmiany i póki co jest spokój. Wszyscy zadowoleni, korzystają z komputera. Któregoś dnia siedząc przy komputerze i korzystając z naszej dystrybucji, zostajemy poinformowani o tym, że są nowe aktualizacje. Instalujemy je zatem. Aktualizacje się zainstalowały, zauważyliśmy że była również aktualizacja jądra. Dobra, wyłączamy komputer. Następnego dnia gdy nasz współużytkownik komputera chce skorzystać z niego, znów mu się uruchamia Linux a nie Windows. Jaki z tego morał? Instalacja nowego jądra wymaga z aktualizowania pliku konfiguracjnego GRUB-a, aby można było korzystać ze świerzo zainstalowanego jądra automatycznie po ponownym uruchomieniu komputera, więc nie należy ręcznie zmieniać ręcznie pliku konfiguracjnego GRUB-a ponieważ aktualizacja czy to jądra czy samego programu rozruchowego nadpisze te zmiany.

Sposobów wykonania tej czynności jest kilka, ja znam jedną. Jak wiemy plik konfiguracjny jest tworzony z plików składowych. Pliki te znajdują się w katalogu /etc/grub.d, oto listing tych plików z mojego komputera:

00_header
05_debian_theme
10_linux
20_linux_xen
30_os-prober
30_uefi-firmware
40_custom
41_custom
README

Jeśli obserwowaliśmy generowanie pliku konfiguracjnego GRUB-a, to już powinniśmy znaleźć potrzebny nam plik. A jeśli nie to przyjrzyjmy się nazwom tych plików. Mamy header, debian_theme, linux, linux-xen i os-prober. OS to akronim od Operating System, z kolei prober oznacza to samo co der Untersucher po niemiecku, czyli kontroler/badacz. Czyli chodzi o kontrolera systemów operacyjnych. Jeśli wpiszemy os-p w wierszu polecenia i naciśniemy tab, dowiemy się, że istnieje nawet takie polecenie, wyszukaniu strony podręcznika dla wiem że jest narzędzie odpowiedzialne za poszukiwanie innych systemów operacyjnych na wszystkich dyskach komputera. Więc znamy już część konfiguracji GRUB-a odpowiedzialną za Windows. Teraz jak zmienić kolejność? Otóż przed każdą z tych nazw stoi liczba, wystarczy zmienić nazwę, tak aby ten plik znajdował się przed plikiem 10_linux.

$ sudo mv /etc/grub.d/30_os-prober /etc/grub.d/09_os-prober

Teraz trzeba już tylko wygenerować nowy plik GRUB-a. Rozwiązanie powinno być odporne aktualizacje. Nie jest to może najpopularniejsze rozwiązanie, ale działa.

5.6. Wykorzystanie rEFInd jako menedżera rozruchu

Instalacja GRUB-a w trybie UEFI może się nie powieść, gdyż jednym z jego etapów jest zapisanie w pamięci firmware-u informacji o nowym programie rozruchowym o nazwie podanej podanej w parametrze bootloader-id. Z błedem jak ja się spotkałem by:

Could not prepare boot variable: No space left on device

Nie które firmware mają ograniczoną ilość pamięci przechowywującej wskazania programów rozruchowych. Mimo usunięcia jednej ze zmiennych problem dalej występował. Usunięcie wszystkich spowodowało tak jakby uziemienie komputera, ponieważ nie był on wstanie uruchomić się z żadnego podpiętego dysku. Przełączenie trybu na BIOS, na tym sprzęcie nie było możliwe. Na sprzęcie tej samej klasy oraz tego samego producenta, ale nowszym spotkałem się z problemem z takim problemem, iż system zainstalował się poprawnie nawet GRUB w trybi UEFI, ale system nie był wstanie wystartować z wbudowanej pamięci, ani z system zainstalowanym w trybie UEFI ani w trybie BIOS.

Rozwiązanie może i jest proste, niestety może nie za bardzo estetyczne oraz wymaga użycia dodatkowego pendrive-a. Pamięć USB nie musi być duża, wystarczy 1GB. Polega ono na użyciu odrębnego mendżera rozruchu jakim jest rEFInd. Program ten przeszukuje dyski w poszukiwaniu systemów operacyjnych i uruchamia je.

Sama dystrybucja przy wykorzystaniu programu rEFInd może być zainstalowana trybie BIOS. W środowisku LiveCD instalujemy pakiet refind. Podczas instalacji zostanie nam wyświetlony monit z pytaniem czy zainstalować rEFInd na partycji ESP. Wybieramy opcję No.

# apt update
# apt install refind

Po zainstalowaniu, tworzymy na dysku USB 1 jedną partycję pod system plików FAT32, następnie należy ją sformatować.

# dd if=/dev/zero bs=1M of=/dev/sdX count=1
# echo ',,b,' | sfdisk /dev/sdX
# mkfs.vfat -F32 /dev/sdXY

W powyższym przykładzie X to litera porządkowa dysku, z kolei Y to liczba partycji. Partycji nie musimy montować. Poniższe polecenie zainstaluje na przygotowanym dysku program rEFInd.

# refind-install --usedefault /dev/sdXY --alldrivers

Po zainstalowaniu programu, możemy zrestartować komputer a następnie w ustawienia UEFI, ustawić kolejkę rozruchu tak, aby komputer startował z pendrive-a, na którym zainstalowaliśmy rEFInd. W menu powinien pojawić się wpis z Linuksem zainstalowanym naszym dysku.

6. Uruchamianie przestrzeni użytkownika

Istotnym momentem podczas startu systemu operacjnego jest uruchomienie przez jądro procesu init oznacza to, że pamięć oraz procesor są gotowe normalnej pracy. Po za tym uruchomie tego programu może przynieść również korzyść dydaktyczną, pozwalając obserwować montowanie różnych komponentów w gotowy do pracy system. Budowa programów typu init jest bardzie modułowa, więc jeśli chcielibyśmy zmienić coś w tym procesie (uruchamiania przestrzeni użytkownika) nie potrzebujemy umiejętności niskopoziomowego programowania oraz trzymania się ściśle określonej ścieżki, jak ma to miejsce w przypadku jądra. Uruchamianie przestrzeni użytkownika zostało przedstawione na poniższej liście kroków:

  1. Jądro uruchamia program typu init.
  2. Uruchamiane są usługi niskiego poziomu takie jak udevd czy syslogd.
  3. Sieć zostaje skonfigurowana.
  4. Uruchamiane zostają pozostałe usługi jak (cron, cups, itp.)
  5. Startują aplikacje wyskokiego poziomu, ekrany logowania oraz środowiska graficzne.

6.1. Proces init

Głównym zadaniem procesu typu init jest uruchamianie, zatrzymywanie i ponowne uruchamianie procesów istotnych dla pracy całego systemu, chciaż jego najnowsze implementacje mają o wiele więcej zadań, to jest to program jak każdy inny. Możemy go znaleźć w katalogu /sbin. W dystrybucja Linuksa ma zastosowanie kilka różnych implementacji procesu init. Na poniższej liście znajdują się te wciąż używane.

6.2. Poziomy uruchomienia

Poziomem uruchomienia nazywamy stan maszyny, w którym na dany moment uruchomione są wybrane komponenty. Są one oznaczne liczbą od 0 do 6. Podczas pracy systemu, większość czasu spędzamy na tylko na jednym poziomie uruchomienia, z momencie zamykania systemu lub uruchamiania go ponownie przełączamy się na inny poziom odpowiedzialny za zatrzymanie pracy jądra oraz poprawne zakończenie pracy usług. Na poziomach uruchomienia bazuje tradycjny sysvinit.

Za pomocą poniższego polecenia możemy, sprawdzić na jakim poziomie uruchomienia się znajdujemy:

$ who -r

Polecenie zwraca również moment załączenia wyświetlanego poziomu.

Poziomy uruchomienia służą okreslaniu stanu systemu operacyjnego. Może on być stanie uruchamiania, zamykania, trybie konsoli (serwery, instalacje bez środowiska graficznego), trybie awaryjny (tryb pojedyńczego użytkownika, będzie o tym pod koniec rozdziału). 5 poziom uruchomienia oznacza najczęściej w pełnii uruchomiony system wraz z trybem graficznym.

Dzisiaj poziomy uruchomienia są domeną programu sysvinit. Dystrybucje z systemd wykorzystują je, aby zapewnić obsługę z usług, które nadal korzystają ze skryptów sysvinit.

6.3. Rozpoznawanie programu typu init.

Identyfikacja programu typu init polega, na przeczytaniu oprogramowania oferowanego przez dystrybucje. Jeśli program typu init nie jest wyszczególniony, możemy przyjąć ze jest to systemd. Wiele dystrybucji posiada w swoich cechach wymienioną informacje o tym że używa innego programu typu init niż systemd.

6.4. Wprowadzenie do wybranych programów typu init

Ze względu na fakt, jak ograniczonych ram tego materiału. Nie będę szczegółowo zagłebiał się w tematykę omawianych tutaj programów typu init. Opisze ich zalety, na czym opiera się ich działanie, jakich plików używają oraz co najważniejsze dla nas na tym etapie w jaki sposób możemy zarządzać usługami przy użyciu tych programów. Pierwszym jaki procesem typu init jaki poruszę z racji popularności jest systemd.

6.4.1. Systemd

Systemd wykonując swoje zadania osiąga cel. Cel jest defiowany przez nas wraz z wszystkimi zależnościami (wymaganiami) oraz z góry ustalonym momentem realizacji tego celu. Następnie system rozwiązuje wszystkie zależności oraz wykonuje postawione przed nim zadanie. Wynika z tego jedna z jego cech, możemy opoźnić np. start usługi do momentu gdy będzie ona niezbędna.

Poza obsługą usług, której wszyscy oczekują od programu init, systemd stara się z integrować ze sobą wiele klasycznych usług takich jak cron czy inetd.

Podczas uruchamiania usług systemd nie kieruje się żadną kolejnością, co więcej większość jego konfiguracji stara unikać jakiej kolwiek sekwencyjności, nawet pod czas spełniania zależności. Takie działanie pozwala na zachowanie dużej dozy elastyczności w procesie uruchamiania systemu.

W zanadrzu swoich możliwości systemd może: montować systemy plików, monitorować gniazda sieciowe czy uruchamiać zegary. Takie rodzaju funkcje w tej impementacji programu typu init nazywane są jednostkami (ang. units). A operacja uruchomienia takiej jednostki nazywana jest aktywowanie. Typ jednostek systemd dostępne są na stronie podręcznika: systemd(1) (1 w nawiasie to numer rozdziału).

Każda jednostka może mieć zależności wobec innej jednostki. Może wymagać lub chcieć jej działania na potrzeby zadania, które sama realizuje. Zależności są definiowane w plikach jednostek dostępnych w dwóch miejscach, w konfiguracji globalnej skierowanej do całego systemu - /usr/lib/systemd/system oraz w definicjach lokalnych - /etc/systemd/system. Jeśli będziemy musieli wprowadzić jakieś zmiany w konfiguracji to zalecanym miejscem jest katalog definicji lokalnych. Rozwiązywanie zależności najczęściej polega na uruchomieniu samej jednostki, wobec której ważna dla nas jednostka jest zależna. Liczy się czy natomiast czy daną jednostkę udało się aktywować lub czy jednostka jest już uruchomiona w trakcie uruchamiania naszej jednostki, tego typu uwarunkowania definiowane przez typy zależności. Poza zależnościami wobec innych jednostek, mogą występować również zależności bazujące na stanie (istnieje, nie istnieje, jest niepusty) elementów takich jak ścieżka, katalog czy plik.

Uruchomienie jednostki nazywane jest aktywowaniem. Dlaczego nie włączeniem? Otóż, aby uruchomić należy ją aktywować poniższym poleceniem:

$ sudo systemctl start unit.service

To czy każdą jednostkę możemy tak sobie uruchomić zależy od jej zależności. Zależności można definiować na odwrót, użwając sekcji [Install], a wewnątrz niej typów zależności: RequiredBy oraz WantedBy, najczęściej wykorzystywana jest ta druga opcja. Za pomocą tej sekcji oraz wymienionych zależności, jednostki instalowane są w celach. Większość usług wykorzystywanych w linuksach, posiadają zależność WantedBy ustawioną na cel multi-user.target. Jest to spowodowane tym, że samo istnienie pliku jednostki może powodować jej aktywacje, co w przypadku usług sieciowych nie jest porządane. Zatem włączenie jednostki, jest w przypadku systemd równoznaczne z włączeniem jej do zależności jednostki zapisanej w zależności odwrotnej (cele w systemd, też są jednostkami ale ich rola raczej ogranicza się do grupowanie różnego rodzaju innych jednostek). Wydaje mi się, że wszystko rozjaśni się gdy zobaczymy polecenie służące do włączania.

$ sudo systemctl enable unit.service

Dezaktywacja oraz wyłaczenie jednostki to kolejno:

#Dezaktywacja:
$ sudo systemctl stop unit.service

#Wyłączenie:
$ sudo systemctl disable unit.service

Do obsługi systemd używamy pojedyńczego polecenia systemctl, ma ono bardzo wiele opcji jednak na poziomie podstawowym wystarczym to co napisałem powyżej oraz sprawdzenie stanu jednostki poleceniem:

$ sudo systemctl status unit.service

Dawniej w czasch powszechnego panowania sysvinit oraz obecnie w dystrybucja wykorzystujących ten rodzaj programu typu init, gdy usługa definiowała zasób, z którego korzystały inne usługi, to musiały one zostać opóźnione do momentu uruchomienia usługi macierzytej oraz udostępnienia przez nią zasobu. Za pomocą systemd możemy na podstawie dokumentacji usługi utworzyć jednostkę zasobu. Taka jednostka zostanie uruchomiona w momencie aktywowania jednostki macierzystej usługi. Dzięki temu inne usługi korzystające z tego zasobu będą mogły zostać aktywowane w tym samym czasie co ta usługa. Usługi nie zrócą żadnego błędu, ponieważ zasób jest dostępny. W gdy coś spróbuje uzyskać dostęp do tego zasobu, to zostanie zablokowane przez systemd do momentu pełnego uruchomienia usługi i przekazania jej kontroli nad zasobem. Jeśli podczas próby dostępu do zasobu napłyną jakieś dane to zostaną one zbuforowane i przekazne do usługi, gdy przejmie ona pieczę na zasobem. Cała ta procedura pozwala na znaczne przyspieszenie uruchamiania systemu.

Systemd zapewnia pewien stopień zgodności z tradycyjnym sysvinit, dla usług nie wspierających plików jednostek. Ta funkcjonalność jest na tyle rozwinięta, że pozwala na podobne zarządzanie taką usługą, jakoby miała ona plik jednostki.

Implementacja procesu typu init jaką jest systemd jest na prawdę dość sporym tematem. Więc zostanie on poruszony ponownie na tej stronie.

6.4.2. Proces typu init w stylu System V

Klasyczny proces typu init jakim jest sysvinit, opiera się o poziomy uruchomienia oraz wykonywane w nich polecenia. Polecenia te opierają się na sekwencyjny wykonywaniu skryptów umieszczanych w odpowiednich katalogach. Najważniejszym plikem jest w tym przypadku plik /etc/inittab, w jego zawrtości znajdują się wspomniane już polecenia, rozpisane dla poszczególnych poziomów uruchomienia. Polecenie dla 5 poziomu uruchomienia może wyglądać w następujący sposób:

l5:5:wait:/etc/rc.d/rc 5

To polecenie przy wejściu na 5 poziom uruchomi wszystkie skrypty w katologu /etc/rc5.d, o ile będą miały odpowiednią nazwę. Słowo wait spowoduje, że proces init nie przejdzie na kolejny poziom uruchomieniowy do momentu zakończenia pracy tego polecenia. Inne akcje niż wait mogą uruchamiać ponownie polecenie po jego zakończeniu czy definiować co należy zrobić po naciśnięciu kombinacji klawiszy Ctrl+Alt+Delete, z kolei akcja initdefault określa domyślny poziom uruchomieniowy dla przestrzeniu użytkownika.

Skrypt zawarte w katalogu rc5.d, który jest akronimem od polecenia run command. Posiadają dość specyficzne nazwy, zaczynają się one od wielkiej litery S lub K. Następny jest numer, na końcu zaś znajduje się nazwa własna. Jeśli wylistujemy ten katalog bardziej szczegółowo, to zauważymy, że te skrypty to tak naprawdę dowiązania symboliczne. Wiele dowiązań w jednym katalogu nazywane jest farmą dowiązań. Te dowiązania zawarte w katalogach rc wskazują na właściwe skrypty znajdujące się w katalogu /etc/init.d.

Wielkie litery na początku nazw dowiązań oznaczają operację podejmowaną na usłudze czy ma ona zostać uruchomiony - S (ang. start), czy jej działanie ma zostać zakończone - K (ang. kill). Te czynności wykonywane są poprzez uruchomienie skryptu z argumentem start lub stop. Numery w nazwach określają miejsce tej czynności w sekwencji uruchomieniowej sysvinit. Usługi niskiego poziomu jak np. syslogd uruchamiane są bardzo wcześnie (mają niskie numery), demony świadczące użytkownikom jakieś usługi zazwyczaj mają numery powyżej 90. Nazwa wskazuje na uruchamianego daemona.

Zatem jeśli nie chcemy, aby jakaś usługa startowała należy wówczas zmienić nazwę dowiązania. Warto jednak pozostawić sobie późniejszą możliwość jej włączenia. Powiedzmy że chcemy wyłączyć daemona httpd jego nazwa to S99httpd, więc najlepiej postawić na początku nazwy znak podkreślenia:

$ mv /etc/rc5.d/S99httpd /etc/rc5.d/_S99httpd

W ten sposób daemon zostanie wyłączony z sekwencji uruchomieniowej. Jeśli chcemy uruchomić/zatrzymać usługę na żądanie, należy uruchomić skrypt z katalogu /etc/init.d z argumentem start lub stop.

#Uruchomienie:
$ sudo /etc/init.d/httpd start

#Zatrzymanie:
$ sudo /etc/init.d/httpd 

Wraz z sysvinit i nie tylko, rozprowadzane jest narzędzie, które działa w systemach nie używających już sysvinit. Run-parts, jest to bardzo proste na rzędzie, które uruchamia wszystkie pliki wykonywalne w danym katalogu według ściśle określonego porządku. Implementacja tego narzędzia zależy od dystrybucji. Te bardziej złożone pozwalają na użycie wyrażenia regularnego do określenia porządku ich uruchamiania. Na potrzeby tego materiału wystarczy wiedzieć, że takie narzędzie w ogóle istnieje.

Wspomnieniem o run-parts kończymy wprowadzenie do programów typu init. Omówiłem tylko te dwa, gdyż są w najpowszechniejszym użyciu. Poznając podstawy Linuksa, nie ma co zniechęcać się obszernymi szczegółami. Najważniejsze dla nas na ten moment jest uruchamienia/zatrzymywanie usług oraz włączanie i wyłączanie ich z sekwencji uruchomieniowej. W ramach ćwiczeń możemy wydedukować jak należy włączyć usługę, do sekwencji uruchomieniowej przy procesie init w stylu System V, chociaż nie powinno to zająć więcej niż 30 sekund.

6.5. Wyłączanie systemu

Jedynym prawidłowym sposobem na wyłącznie systemu, jest użycie polecenia shutdown. Wyłączyć system możemy na dwa różne sposoby. Pierwszym z nich jest jego programowe zamknięcie systemu oraz wyłącznie zasilania osiągane przez poniższe polecenie.

$ sudo shutdown -h now

Do wyżej wymienionego celu możemy, użyć polecenia, które może być nieco bardziej powszechne:

$ sudo poweroff

Ma ono działanie identyczne działanie jak polecenie z przykładu powyżej.

Przy poleceniu shutdown należy wybrać czy system ma zostać zamknięty, zatrzymany, lub uruchomiony ponownie. Kolejnym wymaganym argumentem jest czas. Najczęściej używany jest argument now co przy przyjmowanym zapisze czasonym jest +0 minut. Zapis czasowy możemy określić w minutach jak podałem powyżej lub lub przy zapisie hh:mm , który pozwala określić konkretną godzinę zamknięcia systemu.

Innym sposobem na zamknięcie systemu jest ponowne uruchomienie komputera. System będzie musiał zostać poprawnie zamknięty, aby uruchomić komputer ponownie. W tym celu możemy użyć polecenia,

$ sudo shutdown -r now

#lub

$ sudo reboot

Wyłączenie systemu dla systemd oznacza aktywację jednostek zatrzymywania (systemd, posiada wiele typów jednostek), dla sysvinit przejście z poziomu 5 na 6 lub 0.

6.6. Początkowy system plików w pamięci RAM

initramfs czy initrd, z tych nazw korzysta się zamiennie mimo iż oznaczają coś innego, to odnoszą się do tego samego komponentu, czyli początkowego systemu plików w pamięci RAM. Jest to bardzo proste archiwum przechowujące mini przestrzeń użytkownika z mini katalogiem głównym. Zadaniem initramfs jest stworzenie optymalnego środowiska dla narzędzi pozwalających na załadowanie do jądra zewnętrznych modułów, wśród których może znajdować się sterownik dysku, co pozwoli zamontować już właściwyw katalog główny z dysku i przejść na kolejne etapy uruchamiania systemu.

Wykorzystując nasz system w stopniu podstawowym raczej nie będziemy mieć styczności z initramfs, chyba że używamy bardziej zaawansowanej dystrybucji, która może wymagać załadowania do niego dodatkowych modułów. Takie przypadki będą zazwyczaj opisane w na stronach dokumentacji dystrybucji. Przykładem takiego działania może być szyfrowana partycja z katalogiem głównym, w takich dystrybucjach jak Arch Linux. Do obsługi początkowego systemu w pamięci RAM służa takie polecenia jak mkinitramfs lub update-initramfs.

System plików pamięci RAM może zostać pominięty w momencie gdy, w jądrze znajdują się wszystkie potrzebne mu sterowniki, jednak obecnie nie jest to praktykowane.

Istnieją dystrybucje, których działanie opiera się na initramfs, pliki dystrybucji znajdują się w tym archiwum, a oprogramowanie jest znajduje się wówczas w archiwach squashfs montowanych podczas w odpowiednich miejscach w systemie podczas jego ładowania. Taką dystrybucją jest na przykład TinyCore, którego obraz płyty waży 21 MB.

6.7. Tryb jednego użytkownika

Tryb pojedyńczego użytkownika, jest swojego rodzaju trybem awaryjnym na Linuksie, a jedynym dostępnym użytkownikiem będzie superużytkownik. W tym trybie zostanie załadowane jądro, za montowany katalog główny, a proces typu init zapewni dostęp tylko do niezbędnych dla działania systemu usług, wyeliminowywując tym samym potencjalnie wadliwe komponenty. To środowisko ma za zadanie umożliwić nam naprawę systemu.

Ze względu na to, iż to środowisko może nie zapewnić potrzebnych do takiej naprawy narzędzi, najlepiej jest skorzystać jednak z obrazu LiveCD, który pozwala na korzystanie z systemu bez konieczności jego instalacji. Jednak na podstawie wiedzy zawartej w tym materiale nie sądze, aby można byłoby naprawić system. Dlatego też jeśli zdarzy się awaria systemu, to najlepszym rozwiązaniem na teraz jest zabezpieczenie osobistych danych i przeinstalowanie systemu, chodziaż nie chce nikogo zniechęcić do grzebania w systemie, w ten sposób możemy się wiele nauczyć. Z drugiej strony, ciężko jest aby system przestał działać tak sam z siebie. Dlatego jesli tak się stało, więcej niż raz to warto zgłość problem społeczności za pomocą jednego z kanałów udostepnionych na stronie dystrybucji i zmienić dystrybucje na jakiś czas. Linux to głównie narzędzie do pracy na naszym komputerze, na tym etapie - podstawowym.

7. Konfiguracje systemowe oraz użytkownicy

W tym rozdziale zajmiemy się drobnymi konfigracjami, nie których komponentów systemowych takich jak syslog czy cron zajmiemy się również tematem czasu systemowego na Linuksie. Rozdział zakończymy dodatkową wiedzą, (choć nadal w stopniu podstawowym) na temat użytkowników.

Wszystkie te powyższe zagadnienia łączy jedna rzecz, ich pliki konfiguracyjne znajdują się w katalog /etc i od omówienia tego katalogu zaczniemy.

7.1. Katalog /etc

Jak wiemy z opisu hierarchii systemu plików (katalogu głównego), w katalogu /etc przechowywane są różnego rodzaju konfiguracje, i to nie zależnie do wielkości czy istotności programu w systemie. Kiedyś każdy z programów przechowywał luzem swoją konfigurację tym katalogu. Obecnie jak możemy się przekonać większość zawartości /etc stanowią podkatalogi. Oczywiście wiele plików nadal się w nim znajduje, najczęsciej są to takie pliki jak /etc/fstab czy /etc/passwd lub /etc/shadow służące do przechowywania informacji o użytkownikach. Katalogi w /etc mają nazwy przeważnie odpowiadające nazwom programów, które konfigurują. Wyjątkiem są katalogi z końcówką .d. Pliki konfiguracyjne zostały umieszczone w tych katalogach, aby nie zostały one nadpisane przez aktualizacje pakietów. Obecnie nie ma to już miejsca, a mimo to konfiguracje wielu pakietów są umieszczane w tych katalogach.

Pliki konfiguracyjne, nie których pakietów mogą występować w dwóch różnych wersjach. Pierwsza to jest, ta którą wszyscy znamy czyli katalog /etc taka konfiguracja nazywana jest konfiguracją z możliwością dostosowania. Druga wersją jest konfiguracja bez możliwości dostosowania znajdująca się w katalogu /usr/lib. Oczywiście to jest tylko koncepcja, aby administratorzy zajęli się konfiguracją w /etc a konfiguracje w /usr/lib zostawili opiekunom pakietów (osobom przygotowywującym pakiet oprogramowania dla danej dystrybucji, z wybranym programem) oraz twórcom samej dystrybucji. Możemy zmieniać oczywiście konfiguracje w tym katalogu, jednak trzeba mieć na uwadze dwie rzeczy:

  1. Trzeba wiedzieć co się robi
  2. Zmiany w konfiguracji znajdującej w /usr/lib mogą zostać nadpisane przez aktualizacje pakietu.

System Linuks nie jest miejscem, gdzie się cokolwiek komu kolwiek zabrania. Zasady i jakieś regułu wprowadza się po to aby zapewnić względne bezpieczeństwo oraz stabilność dystrybucji czy też ogolnie systemu.

7.2. syslog

Znaczna część komunikatów dignostycznych z różnych komponentów systemowych spływa do protokołowania czy rejestrowania lub prowadzenia plików dziennika. W języku polskim istnieje kilka określeń na to co konkretnie robi usługa syslog, której zadaniem jest nasłuchiwanie na komunikaty diagnostyczne i przekazywanie ich do pliku, na ekran poszczególnych użytkowników lub całkowite zignorowanie. Wszystko zależy od konfiguracji.

Obecnie wykorzystywana jest nowsza wersja syslog - rsyslog. Funkcje tej wersji nie ograniczają się tylko do zapisywania komunikatów diagnostycznych do pliku, program może na przykład przesyłać je do bazy danych. Na tym etapie nie będziemy się jednak tym zajmować, póki co będziemy musieli się zadowolić zwykłymi plikami tekstowymi przechowywanymi w katalogu /var/log. Warto mieć jednak na uwadze fakt, iż nie wszystkie pliki przechowywane w tym katalogu są zarządzane, przez tę usługę. Nie które daemony mogą posiadać swoje sposoby na utrzymanie i prezentowanie użytkownikowi własnych komunikaty diagnostycznych. Więcej informacji na temat jakie logi są przechwytywane przez rsyslog znajduje się w pliku konfiguracyjnym - /etc/rsyslog.conf.

Na konfiguracje składają się tradycyjne reguły oraz dyrektywy dostępne w rozszerzonej wersji rsyslog. Dyrektywy możemy poznać po tym, że rozpoczynają się od symbolu dolara ($). Natomiast reguły konfiguracyne klasycznej wersji protokołu są nieco bardziej złożone.

Reguły protokołu syslog określają sposób przychwytywania komunikatów diagostnycznych oraz docelowe miejsce ich zapisu. Zasady składają się z selektora i akcji, o to kilka z nich.

*.info;mail.none;authpriv.none;cron.none  /var/log/messages
authpriv.*  /var/log/secure,root
mail.*      -/var/log/maillog
*.emerg     :omusrmsg:*

Reguły syslog możemy podzielić na dwie części prawą i lewą. Po lewej stronie znajduje się selektor, określający przechwywane dane. Natomiast po prawej znajduje się akcja zazwyczaj jest ścieżka do pliku docelowego dla przychwconych komunikatów. Przy jednej ze ścieżek znajduje się myślnik, który powoduje nie synchronizowanie tego pliku jeśli włączono by synchronizacje (jest ona domyślnie wyłączona). Synchronizacja powoduje znaczny spadek wydajność i może doprowadzić do gubienia komunikatów.

Selektor zaś składa się z kolejnych dwóch części: funkcji oraz priorytetu. Funkcja określa źródło komunikatów i są one na stałe zaimplementowane w rsyslog a priorytety ich rodzaj wśród, których możemy wymienić (ułożenie według od najniższego do najwyższego):

debug, info, notice, warning, error, crit, alert, emerg

Tworząc selektor oddziela się funkcję od priorytetu za pomocą kropki. Priorytet służy do ograniczania wielkość przechwytywanych komunikatów, albowiem rsyslog rozpoczyna przechwytywanie komunikatów od tego priorytetu w górę. Jeśli przypatrzmy się pierwszej linii konfiguracji, zauważymy, że do pliku /var/log/messages będzie spływać masa informacji, ponieważ selektor uwzględnia komunikaty ze wszystkich funkcji z minimalnym priorytetem debug. Istnieją jednak pewne wykluczenia. Na selektor może składać się więcej niż jedna para funkcja.priorytet, co również widać w pierwszej linii przykładu. Kolejne pary rozdzielone są średnikami. W omawianym przykładzie, pary mają ten sam priorytet, który nie został uwzględniony na powyższej liście none powoduje wyłączenie przechwytywania z użytych w raz z nim funkcji. Tak więc w pierwszej linii przechwytywane będą komunikaty ze wszystkich funkcji z minmalnym priorytetem info, poza takimi funkcjami jak mail, authpriv oraz cron. Komunikaty będą zapisywane zgodnie z akcją w pliku /var/log/messages.

Innymi ciekawymi przypadkami w pokazanymi na przykładzie jest podanie w akcji w drugiej linii dwóch miejsc docelowych. Pliku /var/log/secure oraz nazwy superużytkownika. Podanie jakiej kolwiek nazwy użytkownika w akcji spowoduje przesłanie mu (za pomocą polecenia write) komunikatu diagnostycznego, o ile użytkownik zezwala na wyświetlanie tego typu komunikatów (polecenie mesg). Chociaż komunikaty wysłane przez superużytkownika są wyświetlane mimo tych ustawień. Kolejnym przypadkiem związanym z wysyłaniem jest użycie specjalnego modułu wyjściowego reprezentującego konkretną akcje, w tym przypadku jest wysyłanie wiadomości do zobrazowane w ostatniej linii przykładu. Jak mogliśmy zauważyć w liniach reguł możemy używać symbolu wieloznacznego gwiazdki (*).

Dyrektywy nowszej wersji daemona rejestrującego są dość proste do zrozumienia i nie wymgają dodatkowego opisu.

$FileOwner syslog
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0775
$Umask 0022

Jeśli chodzi o syslog, to w przypadku usługi rejestrowania może stać się nie wiele złego, jedynym problemem jaki możemy napotkać jest brak przechwytywania komunikatów z powodu nieuwzględnienia jakiejś funkcji lub priorytetu w selektorze. Niemniej jednak rejestrator systemowy możemy przetestować za pomocą polecenia logger podając mu parę funkcja.priorytet po opcji -p (na stronie podręcznika opcja określona jest jako priorytet) oraz komunikat do zapisania. Jeśli priorytet został pominięty, zostanie użyty domyślny user.notice. W zależności od konfiguracji rsyslog oraz użytej funkcji nasz komunikat powinien pojawić jednym z plików wyszczególnionych w konfiguracji. W przypadku użycia narzędzia logger z domyślnymi wartościami komunikat zostanie zapisany do /var/log/messages.

Większość rejestratorów istnieje nie tylko w postaci odrębnego programu jakim jest rsyslog, ale także w postaci funkcji, nie których daemonów jak np. serwer WWW Apache2 one również zapisuje swoje komunikaty diagnostyczne do /var/log. Bardzo duża ilość danych spływająca do jednego katalogu może powodować szybkie zapełnienie przestrzeni dyskowej. Jednak się to nie dzieje, dzięki programowi logrotate, którego zadaniem jest (w zależności od konfiguracji) usuwanie lub kompresja starych plików dzienników i utworzenie miejsca na nowe komunikaty.

Z racji tego, iż demony działają w trakcje czynności wykonywanych przez logrotate, skrypty obsługujące konkretne pliki dziennika, tworzą puste pliki o takiej samej nazwie jak te utworzone przez demon.

7.3. Konfiguracja użytkowników

Komputery jak i systemy operacyjne mają za zadanie służyć użytkownikom. Jak wiemy użytkownicy w systemach istnieją aby wyznaczać granicę. Mówiąc kolokwialnie każdy z nas ma swoją piaskownice i swoje zabawki. W Linuksach użytkownicy są opisywani za pomocą kilku plików w katalogu /etc.

7.3.1. Plik /etc/passwd

Plik /etc/passwd jest podstawowym źródłem informacji o użytkownikach w systemie. W tym pliku każdy wiersz to jeden wpis definiujący użytkownika. Każdy wiersz podzielony jest na 7 pól. Poniżej znajduje się kilka przykładowych wpisów:

pulse:x:109:114:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
saned:x:110:117::/var/lib/saned:/usr/sbin/nologin
colord:x:111:118:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
lightdm:x:112:119:Light Display Manager:/var/lib/lightdm:/bin/false
libvirt-qemu:x:64055:105:Libvirt Qemu,,,:/var/lib/libvirt:/usr/sbin/nologin
geoclue:x:113:124::/var/lib/geoclue:/usr/sbin/nologin
user:x:1000:1000::/home/user:/bin/bash
xf0r3m:x:1001:1001::/home/xf0r3m:/bin/bash

Pola te zawierają kolejno:

Drugie pole wpisu w /etc/passwd może przyjmować dwie dodatkowe wartości. Pole może zawierać gwiazdkę (*), która uniemożliwia logowanie lub pole może być puste co pozwala na zalogowanie się bez podawania hasła.

Jeśli przefiltrujemy za pomocą polecenia grep plik /etc/passwd pod kątem ustawionych programów (powłok), to na powiedzmy ok. 50 (zależności od dystrybucji) tylko dwóch, trzech użytkowników ma ustawioną powłokę. Pozostali użytkownicy zazwyczaj mają ustawiony program, który uniemożliwia im korzystanie z systemu nawet gdy się zalogują. Tacy użytkownicy nazwani są pseudoużytkownikami. Tego rodzaju użytkownicy istnieją w jednym celu, aby uruchamiać z ich uprawnieniami różne programy, głównie demony sieciowe. Co w razie włamania spowoduje, że atakujący zostanie uwięziony na koncie, na którym nic nie może zrobić.

Pseudoużytkownicy zaliczają się do użytkowników specjalnych obok nich istnieje jeszcze jeden użytkownik - root, który ma niczym nie ograniczone uprawnienia. Dlatego też nosi nazwę superużytkownika. Posiada on UID i GID równy 0 oraz jego katalog domowy znajduje się bezpośrednio w głównym katalogu. Innym ciekawym użytkownikiem specjalnym jest użytkownik nobody, który nie ma możliwości zapisu niczego w systemie.

Kombinacja wpisu w pliku /etc/passwd oraz katalogu domowego może być określana jako konto.

7.3.2. Plik /etc/shadow

Plik /etc/shadow jest podobnym plikiem /etc/passwd, jednak zamiast przechowywać informacje o użytkownika plik ten przechowuje informacje o hasłach. Plik /etc/shadow posiada jedno wspólne pole wraz z plikiem /etc/passwd a jest nim nazwa użytkownika. Hasło podobnie do pliku /etc/passwd znajduje się na drugim polu we wpisie. Pozostałe pola są odpowiedzialne za ważność hasła. Wpisy w pliku /etc/shadow biorą również czynny udział w blokowaniu dostępu użytkownikom. Zawartośc pliku wygląda w następujący sposób:

root:$y$j9T$h19rJ2ObBXMdBdXOHB0wF.$.Lqb5iG3.HpsO0FcghqSkXbcA6D5rIp9woC/Ovj40Q7:19251:0:99999:7:::
...
user:$y$j9T$bAf/P4bLm00VJQyS3Lf8I1$dzie3XL5lORpP7jmo4CeanOqhuWMpPzdQArAlQ9AfG0:19327:0:99999:7:::

Na tym etapie nauki dalsza analiza tych wpisów nie ma sensu, jeśli jednak ktoś jest ciekawy pozostałych pól to może zajrzeć do mojego materiału przygotowywującego do RHCSA: https://morketsmerke.github.io/articles/terminallog/RedHat_-_RHCSA.html#6.1.passwordaging

Plik /etc/shadow jest dość mocno zabezpieczonym plikiem, zwykli użytkownicy nie posiadają do niego żadnych praw. Jedyną uprawnioną osobą do manipulacji nim jest superużytkownik.

7.3.3. Manipulowanie użytkownikami i hasłami

Omawiane dotychczas pliki są zwykłymi plikami tekstowymi, których zawartością możemy manipulować za pomocą ulubionego edytora tekstowego. Nie jest to jednak zalecane działanie. Ze względu na ścisłą budowę (nie możliwe jest aby umieszczać w tych plikach komentarze lub puste wiersze) oraz restrykcyjną składnie wierszy. Możemy użyć specjalnego narzędzia jakim jest vipw. Polecenie to tworzy kopię zmienianych plików i następnie otwiera ten plik w terminalowym edytorze. Konfiguruje ono również ten edytor aby zaznaczał składnię wpisów w plików, po zapisaniu zmian polecenie sprawdzi składnie zmienionych lub dodanych wpisów. Jeśli wszystko będzie dobrze, to polecenie zapisze zmiany jeśli coś będzie nie tak to polecenie zaproponuje poprawienie błędu. Polecenie wymaga oczywiście uprawnień administratora. Wydając polecenie bez żadnej opcji otworzy ono plik /etc/passwd do edycji a jesli dodamy opcje -s to wówczas zostanie otworzony plik /etc/shadow.

Inną metodą jest wykorzystanie np. opcji polecnia passwd jego domyślną rolą jest zmiana hasła, bądź ustawianie ważności hasła, jednak dodając opcję -s mozemy zmienić domyślny program startowy (powłokę) lub za pomocą opcji -f możemy zmienić nazwę użytkownika. Po więcej informacji zapraszam do strony podręcznika polecenia passwd. Jeśli na naszym przypadku polecenie passwd może nie być zadowalające. Można wówczas skorzystać z polecenia typu usermod.

7.3.4. Grupy

Rolą grup w systemach uniksopodobnych jest współdzielenie plików. Dzięki nim możemy zwiększyć uprawnienia do pliku, nie którym użytkownikom, chroniąc je przed innymi. Wynika to z możliwosci nadania uprawnień grupie, natomiast można je zabrać wszystkim pozostałym. Obecnie stacje uniksowe stały się bardziej spersonalizowanie to współdzielenie plików przy użyciu grup straciło na znaczeniu. Jednak grupy tak jak użytkowników możemy wykorzystać do uzyskiwania dodatkowych uprawnień lub ich ograniczania.

Wraz z każdym nowym użytkownikiem tworzona jest nowa grupa o tej samej nazwie co użytkownik. Grupa ta jest również ustawiana jako grupa podstawowa tworzonego użytkownika. Definicje grup znajdują się w pliku /etc/group. Poniżej znajduje się fragment:

libvirt:x:123:user,xf0r3m
libvirt-qemu:x:64055:libvirt-qemu,user,xf0r3m
geoclue:x:124:
user:x:1000:
xf0r3m:x:1001:

Jak możemy zauważyć wpisy dzielą się na cztery pola, które kolejno oznaczają:

Grupy odpowiadające istniejącym użytkownikom nie posiadają żadnych członków w tym pliku, jednak odpowiadający tym grupom użytkownicy są ich domyślnymi członkami.

Jądro Linux, nie posługuje się nazwami użytkowników czy grup odwołując się do jednego lub drugiego bytu. W tym celu używa wartości UID oraz GID, aby ich używanie miało sens muszą być one unikatowe w skali całego systemu. Oczywiście może zdażyć się przypadek, że dwóch użytkowników będzie miało ten sam UID, ale są to bardzo rzadkie i specyficzne przypadki.

7.4. Getty oraz login

Te dwa programy nie są zbyt skomplikowane w swoim działaniu. Działanie programu getty polega na podłaczeniu się pod terminal (nie ważne czy wirtualny czy fizyczny podłączony przez port COM) i wyświetlenie znaku zachęty logowania (napisu "login: ") i oczekiwania na podanie nazwy użytkownika. Kiedy nazwa zostanie podana polecenie to zastępuje same siebie wywołując program login. Ten program odpowiedzialny jest za wyświetlenie znaku zachęty hasła (napisu "hasło:") po podaniu przez użytkownika hasła program spróbuje go uwierzytelnić. Jeśli to się powiedzie wówczas program login zastąpi sam siebie po przez wywołanie systemowe exec() programem startowym ustawionym w ostatnim polu wpisu w pliku /etc/passwd.

Prawdopodbnie nigdy nie będzie potrzeby konfiguracji tych programów, nie mniej jednak jest taka możliwość.

7.5. Ustawienia czasu.

Działanie wielu operacji w systemach uniksopodobnych opiera się o czas. Za zegar w tych systemach odpowiedzialne jest jądro. Jednak nie jest ono wstanie wziąć precyzyjnych ustawień czasowych samo z siebie. Większość komputerów klasy PC posiada potrzmywany bateryjnie zegar czasu rzeczywistego, jądro synchronizuje swój zegar z tym zegarem jednak z racji tego, iż maszyny uniksowe nie są uruchamiane ponownie przez miesiące a nawet lata. W czasie systemowym pojawia się odchylenie.

Zegar systemowy jądra również jest korygowany na podstawie ustawień strefy czasowej w jakiej się znajdujemy, dlatego też RTC (ang. Real Time Clock) w BIOS lub UEFI powinien być ustawiony zgodnie ze strefą czasową UTC. Za pomocą poniższego polecenia możemy ustawić czas UTC jądra względem RTC.

$ sudo hwclock --hctosys --utc

7.5.1. Strefy czasowe i reprezentacja czasu

Jądro przechowuje swój czas w formacie uniksowym, czyli ilości sekund od północy 1 stycznia 1970 roku. Na podstawie tych informacji takie polecenia jak date dokonuja wszelkich konwersji i wyświetlają nam zrozumiałą dla nas datę i czas. Czas uniksowy możemy wyświetlić sobie za pomocą polecenia date z opcją +%s (niestety polecenie date ma nieco inny format opcji).

xf0r3m@immudex:~$ date
śro, 28 gru 2022, 13:11:53 CET
xf0r3m@immudex:~$ date +%s
1672229517

Domyślnie polecenie date wyświetla datę i czas skorygowany o ustawioną w naszym systemie, na podstawie przybliżonej lokalizacji strefę czasową. Ustawiona strefa czasowa znajduje się katalogu /etc jako dowiązanie symboliczne do jednego z plików stref dostępnych w systemie w katalogu /usr/share/zoneinfo.

xf0r3m@immudex:~$ ls -al /etc/localtime 
lrwxrwxrwx 1 root root 33 11-04 17:46 /etc/localtime -> /usr/share/zoneinfo/Europe/Warsaw

Strefę czasową możemy zmienić za pomocą polecenia interaktywnego polecenia tzselect. Polecenie to spróbuje określić twoją przybliżoną lokalizacje na podstawie stolicy kraju w jakim się znajdujesz. Następnie utworzy nowe dowiązanie symboliczne /etc/localtime do odpowiedniego pliku strefy.

Jeśli z jakiś przyczyn potrzebujemy sprawdzić jaki bedzie czas w wybranej przez nas strefie czasowej, możemy zmienić zmienną środowiskową TZ, która przechowuje informacje o ustawionej strefie czasowej na czas wykonania polecenia. Tę czynność możemy wykonać na czas wykonania pojedyńczego polecenia, ustawiając wartość zmiennej w tej samej linii co polecenie date.

xf0r3m@immudex:~$ TZ=Asia/Tokyo date
śro, 28 gru 2022, 21:29:01 JST

7.5.2. Czas sieciowy

Jeśli nasza maszyna sieciowa jest na przykład serwerem ze stałym dostępem do sieci. To aby zniwelować to odchylenie czasowe, którym wspomniałem na początku tego podrozdziału możemy uruchomić na nim specjalną usługę sieciową, która pobierze czas z serwerów podłączonych do bardzo precyzyjnych zegarów atomowych.

Pierwszą rzeczą jak należy zrobić to pobrać pakiet odpowiedzialny za demon NTP w naszym systemie. Następnie skonfigurować go zgodnie jego dokumentacją. Najprostsza konfigurację możemy zapisać w poniższych krokach.

  1. Znalezienie najbliższego dla siebie serwera NTP, najlepiej na poziomie STRATUM nie większym niż 3.
  2. Zapisanie adresów serwera (serwerów, może być pula) w pliku konfiguracyjnym demona NTP, najczęściej jest to /etc/ntp.conf.
  3. Uruchomienie polecenia ntpdate.
  4. Włącznienie demona NTP do uruchomienia w trakcie autostartu.

Jeśli nasz komputery nie posiada stałego łącza internetowego możemy wówczas uruchomić takiego demona jak chronyd. Ostatecznie mozemy zsynchonizowany czas systemowy załadować do zegara RTC za pomocą poniższego polecenia.

$ sudo hwclock --systohc --utc

W tym podrozdziale umieściłem polecenie, którego nie wyjaśniłem. Jest nim poziom STRATUM. Wyjasnienie tego pojecia wymaga zagłebienia się w protokół NTP, co zrobiłem w materiale przygotowawczym do RHCSA znajdującym się tutaj: https://morketsmerke.github.io/articles/terminallog/RedHat_-_RHCSA.html#18.1.ntp.

7.6. Tworzenie powtarzalnych zadań za pomocą cron

Wróćmy na chwile do drugiego podrozdziału. Tam pod koniec omówiliśmy sobie gdzie znajdują się pliki dziennika i w jaki sposób system radzi sobie z problem szybko przyrastających plików zawierających komunikaty diagnostyczne z przeróżnych demonów. Wspomnialiśmy tam o logrotate. Ten program aby zapobiec zużyciu całego miejsca na dysku musi uruchamiać się co jakiś czas.

W uniksach aby wykonywać jakieś czynności co określoną ilość czasu, musimy skorzystać z programu o nazwie cron. Ten program to swojego rodzaju harmonogram zadań, w którym to może zdefiniować co uruchomić, kiedy lub co ile czasu i przez kogo. Definicje zadań znajdując się w specjalnych plikach zwanych crontab. Każdy użytkownik ma swój plik. Ponieważ pliki te znajdując się w miejscu, do którego zwykli użytkownicy nie mają dostępu muszą używać dostarczonym wraz z cron narzędziem.

Aby móc zdefiniować zadania należy wydać następujące polecenie:

$ crontab -e

Wówczas otworzy nam się wybrany przez nas wcześniej edytor terminalowy. Wewnątrz pliku znajdziemy obszerny komentarz, w którym to rozpisano dokładnie całą składnie definicji zadania cron.

Otóż zadania cron to jednowierszowe wpisy definiujące na początku czas (kiedy, co ile czasu) w którym należy uruchomić zadanie Następnie polecenie, które należy uruchomić.

21 0 * * * /usr/local/bin/backup.sh > /var/log/backup_sh.log 2>&1

To zadanie zostanie uruchomione codziennie 21 minut po północy. Skąd to wiem, otóż już śpieszę z wyjaśnieniami. Najpierw objaśnie poszczególne pola.

Ustawienia czasu wykonania zadania jest w miarę proste o ile nie wymagamy bardzo złożonego warunku czasowego lub nie próbujemy go zrozumieć (zadając sobie pytanie: kiedy to dokładnie się wykona?). Załóżmy że chcemy aby zadanie wykonało się w każdy weekend wakacji w Polsce. Warunek czasowy może przyjmować: wartości krokowe (/2) co dwie minuty, godziny, dni, miesiące; zakresy (5-10) od 5-10 minuty, godziny, dnia itd lub listy (11,12) zadanie wykoania się np. 11 i 12 każdego miesiąca. Zakresy oraz listy można ze sobą łączyć, jak i dodać pojedyńczą wartość do zakresu, np. 5-10,12. Przy tworzeniu warunku czasowego możemy posłużyć się stronami takimi jak:

7.6.1. Instalacja tablicy cron.

Po zapisaniu zmian tablica z zadaniami crona (crontab) zostanie automatycznie zainstalowana w katalogu /var/spool/cron/crontab. Polecenie crontab daje możliwość instalacji tablicy zadań z zwykłego pliku tekstowego, dokonujemy tego za pomocą polecenia crontab wraz z opcją -l. Jednak korzystanie z opcji -e jest bardziej zalecane. Wówczas podczas zapisywania zmian polecenie sprawdzi zadania pod kątem poprawności składni i przypadku błedu, zapyta czy nie chcemy go poprawić.

Tablice z zdaniami cron możemy usunąć za pomocą opcji -r polecania crontab.

7.6.2. Systemowa tablica zadań crontab.

Prócz tablic użytkowników, istnieje jescze systemowa tablica z zadaniami cron. Ją edytujemy już pomocą edytora tekstowego ponieważ jej wpisy mają nieco inną składnie niż tablice użytkowników. W jej wpisach znajduje się dodatkowe pole między dniem tygodnia a poleceniem. W nim definiuje się użytkownika, z którego uprawnienami realizowane jest zapisane zadanie. Dlatego też polecenie crontab z opcją -e nie ma tutaj zastosowania. Jako systemowej tablicy cron, nie wykorzystuje się również tablicy superużytkownika. Za tablicę systemową odpowiedzialny jest plik /etc/crontab. Plik ten edytujemy za pomocą zwykłego edytora, kiedy zakończymy edycję, należy uruchomić demon cron ponownie.

xf0r3m@immudex:~$ sudo vim /etc/crontab 
xf0r3m@immudex:~$ sudo systemctl restart cron

Inną metodą na powtarzalne wykonywanie czynności jest skorzystania z jednego katalogów: /etc/cron.daily, /etc/cron.hourly, /etc/cron.monthly lub /etc/cron.weekly. Wykorzystanie tych katalogów polega na umieszczeniu skryptu (powłoki, nawet jednej linii z konkretnym poleceniem) w jednym z nich. W systemowej tablicy cron jest napisane o której wykonywane są zadania zapisane w tych katalogach. Skrypty uruchamiane są przez znane nam z poprzedniego rozdziału narzędzie jakim jest run-parts. W nowszych dystrybucjach obsługą tych katalogów może zajmować się demon anacron.

7.6.3. Przysłość narzędzia cron.

Narzędzie cron jest starsze od samego Linuksa. Jeśli coś jest, aż tak stare to kwalifikuje się do wymiany. Obecnie są czynione postępy w temacie zastąpienia cron-a jakimś innym rozwiązaniem. Kandydatem mogą być elementy licznika czasu systemd. Jednak utworzenie ulepszonej funkcjonalności to jedno, a zapewnienie wstecznej zgodność z systemami oraz narzedziami dalej wykorzystującymi cron to drugie, bez zapewnienia wstecznej zgodności zmian nie będzie, więc póki co cron jeszcze z nami pozostanie.

7.7. Planowanie jednorazowych zadań.

Jedno narzędzie do planowania zadań w przyszłości już poznaliśmy. Narzędzie at na pewno nie ma tak szerokiego użycia jak cron, to pomaga wykonać pewne zadania po za czasem czynnego użytkowania komputera. Jeśli musimy poczekać do powiedzmy północy, aby móc wykonać jakąś czynność, możemy ją zaplanować za pomocą polecenia at, pozostawić komputer włączony i iść spać.

Za pomocą polecenia at, możemy nie tylko zaplanować wykonanie zadania na kilka godzin do przodu ale i nawet na kilka miesięcy. Aby jednak to zrealizować należy poznać polecenie at, które może nie być domyślnie dostępne w każdej dystrybucji i trzeba je będzie zainstalować.

Aby rozpocząć planowanie zadania należy wydać polecenie at i jako argument podać czas ewentualnie datę jeśli zadanie ma zostać wykonane w dalszej przyszłości. Po wydaniu tego polecenia zostanie nam wyświetlony prompt at>. Po wyświetleniu prompta podajemy polecenie do wykonania w zadaniu. Polecenia możemy podawać do momentu kiedy naciśniemy kombinację klawiszy Ctrl+d, przed zatwiedzeniem polecenia należy upewnić się, że zostało poprawnie zapisane ponieważ nie będzie możliwości jego edycji. Po naciśnięciu wspomnianiej kombinacji zadanie zostanie zatwierdzone Na poniższym przykładzie utworzyłem jedno zadanie z jednym poleceniem:

xf0r3m@immudex:~$ at 00:00 01.01.2023
warning: commands will be executed using /bin/sh
at> echo "Happy New Year" > /dev/pts/1
at> <EOT>
job 1 at Sun Jan  1 00:00:00 2023

Za pomocą opcji -l polecenia at lub polecenia atq możemy wyświetlić znajdujące się w pamieci demona atd zadania.

xf0r3m@immudex:~$ at -l
1	Sun Jan  1 00:00:00 2023 a xf0r3m
xf0r3m@immudex:~$ atq
1	Sun Jan  1 00:00:00 2023 a xf0r3m

Natomiast za pomocą opcji -r polecenia at lub za pomocą polecenia atrm, możemy usunąć zdanie, przyczym należy podać numer zadnia widniejący w pierwszej kolumnie danych wyjściowych zwracanych przez polecenie at w raz z opcją -l lub polecenie atq.

xf0r3m@immudex:~$ atq
1	Sun Jan  1 00:00:00 2023 a xf0r3m
xf0r3m@immudex:~$ at -r 1
xf0r3m@immudex:~$ atq

7.8. Identyfikatory użytkowników oraz ich przełączanie.

Jak pamiętamy, lub może nie bit setuid powodował fakt iż program uruchomiony z tym bitem dział (jego proces) z uprawnieniami właściciela pliku. Tutaj poznamy inne możliwości przełączania użytkowników wraz regułami oraz jaki udział ma w tym wszystkim jądro.

Na Linuksie istnieją dwie metody na przełączanie użytkowników. Pierwszym z nich jest ustawienie wspomnianego wcześniej bitu dla pliku wykonywalnego. A drugim sposobem jest wykorzystanie wywołania systemowego setuid(), a żeby sprawę jeszcze bardziej skomplikować to wersji tego wywołania systemowego jest kilka, w zależności o tego na jaki identyfikator użytkownika chcemy się przełączyć. Zanim poznamy możliwe identyfikator (a przynajmniej ich część) warto poznać reguły dotyczące możliwości procesów w zakresie przełączania identyfikatorów. Otóż:

Dla ścisłości w tym przełączaniu użytkowników nie chodzi o przełączanie między kontami. A raczej o to, że na podstawie dostępnych mechanizmów w systemie proces może zmieniać uprawnienia wrazie potrzeby oraz możliwości z jakim został uruchomiony.

7.8.1. Właściciel procesu oraz identyfikatory użytkownikow

Dotych czas można było sądzić, że procesy, które uruchamiamy w Linuksie posiadły jeden identyfikator użytkownika. Użytkownika, który ten proces zainicjował. Rzeczywistość jest niestety zgoła inna. Każdy proces posiada co najmniej dwa identyfikatory użytkowników. Pierwszym z nich jest euid (ang. Effective User IDentifier) - efektywy identyfikator użytkownika. Ten identyfikator wskazuje na użytkownika, z ktorego uprawnienia działa ten proces. Drugim jest ruid (ang. Real User IDentifier) - rzeczywisty identyfikator użytkownika. Ten wskazuje natomiast użytkownika, który zainicjował ten proces.

Ze względu na niejasność związane z rozgraniczeniem miedzy euid a ruid. Rzeczywistemu identyfikatorowi użytkownika przypisje się rolę właściciela procesu. Może on prowadzić interakcje z procesem wysyłać do niego sygnały w tym kończyć jego działanie. W przypadku większości procesów działających w systemie ruid oraz euid będą równe. Jednak w przypadku gdy uruchomimy proces z ustawionym bitem setuid, efektywny identyfikator zostanie ustawiony na właściciela pliku, zaś rzeczywisty identyfikator będzie przechowywać nasz UID. Najprostszym przykładem uruchomienia programu z bitem setuid a za razem doświadczenia przełączenia identyfikatorów jest użycie polecenia sudo.

Żeby całość jeszcze bardziej skomplikować, do tych dwóch identyfikatorów należy dodać jescze większego poziomu skomplikowania to istnieje trzeci identyfikator suid (ang. Saved User IDentifier) - zapisany identyfikator użytkownika. Podczas działania, proces może przełączać się między zapisanym a rzeczywistym identyfikatorem.

Jeśli uruchomimy jakiś proces przy użyciu polecenia sudo i ten proces będzie trwać, i spróbujemy go zabić to wówczas dostaniemy informacje o braku dostępu. Polecenie sudo z ustawionym bitem setuid (jak i inne) zamienia jawnie identyfikatory użytkowników za pomocą wywołania systemowego setuid(). Wynika to z kilku problemów jakie wynikają z braku zgodności między identyfikatorami, przez co nie musimy się zbytnio przejmować nimi oraz ich przełączaniem.

Ze względu na duże uprawnienia (czynny udział jądra w przełączaniu użytkowników oraz obsłudzie uprawnień dostępu do pliku) programów z ustawionym bitem setuid i działającym wraz z nim wywowałań systemowych. Należy uważać jakim programom nadaje się ten bit. Pozostawienie kopii powłoki z ustawionym takim bitem, daje możliwość zwykłym użytkownikom przejęcia kontroli nad całym systemem. Wydając jedno polecenie.

7.9. Identyfikacja i uwierzytelnianie

Każdy wieloużytkowy system musi zapewnić mechanizmy czuwające nad kontrolą użytkowników. Użytkownicy powinni przedstawić się systemowi przekazać tajną informację (hasło), wówczas mechanizmy w systemie będą wiedzieć, że ten użytkownik jest tym za kogo się podaje. Jeśli użytkownik został uwierzytelniony ma on dostęp do swojego konta, które jest autoryzowane (posiada pewne prawa w systemie) i w ten sposób systemy identyfikują użytkowników. To tak wygląda tylko w teorii. Z racji tego, iż jądro zna tylko UID, ustalenie nazwy użytkownika wymaga kilku czynności. Można przedstawić tutaj krokowy algorytm, który byłby w stanie ustalić taką nazwę, jednak stosuje sie funkcję biblioteki standardowej, które pomagają w ustaleniu takich informacji. Problem stanowią natomiast hasła, ponieważ funkcje biblioteki standardowej posiadają w sobie pewne założenia, które niestety nie sprawdzają się przy obecnym sposobie przechowywania haseł w systemie. Więc zatem w jaki sposób obecne dystrybucje dokonuja uwierzytelniania? Wykorzystują on do tego podsystem PAM.

7.10. System PAM

System PAM (ang. Pluggable Authentication Modules) jest to zbiór bibliotek współdzielonych, których zadaniem jest udostępnienie uwierzytelniania użytkownika, w taki sposób aby aplikacja nie musiała się tym zajmować. System PAM może również kontrolować autoryzację użytkownika np. blokując mu dostęp do pewnych usług. Jak sama nazwa wskazuje system ten wykorzystuje dynamicznie ładowane moduły, które realizują poszczególne zadania w zależności od użytej funkcji (o tym za chwilę). Jeśli system ma sprawdzić hasło użytkownika użyje modułu pam_unix.so. Ze względu te informacje dość łatwym zdaniem jest stworzenie dodatkowych modułów obsługujących uwierzytelniania dwuskładnikowe lub klucze fizyczne, mimo to system PAM dalej pozostaje spuścizną Uniksa przez co, niektóre zagadnienia bywają skomplikowane a intefejs programowania nie jest zbyt prosty. To z tego systemu korzysta większość aplikacji wymagających uwierzytelniania na dystrybucjach Linuksa, ponieważ połączenie aplikacji z PAM wymaga niewiele pracy lub wcale.

7.10.1. Konfiguracja PAM

Ze względu na to, iż konfiguracja różni się w zależności od dystrybucji, cieżko jest się odnaleźć i wyjaśnić to w ogólny sposób. pliki konfiguracyjne systemu powinny znajdować się w katalogu /etc/pam.d/. Ich nazwy często zawierają nazwy komponentów systemu, które wykorzystują system PAM. Najprostszym z nich jest plik /etc/pam.c/chsh. Polecenie chsh służy do zmiany powłowki. Pierwszy wiersz pliku wygląda następująco:

auth  requisite pam_shell.so

Nakłada on wymóg, aby podana podczas zmiany powłoka była wymieniona w pliku /etc/shells, w przeciwym wypadku uwierzytelnienie użytkownika kończy się nie powodzeniem.

Każda linia konfiguracji PAM składa się z co najmniej trzech części. Między innymi z:

Typy funkcji

System PAM oferuje aplikacji wykonanie czynności określonej jedną z z poniższych funkcji:

Warto wspomnieć, że moduły mogą zachować się inaczej (wykonywać inną czynność, gdy zestawimy je z innymi funkcjami. Dla przykładu moduł pan_unix.so dla modułu auth sprawdzi poprawność podanego hasła, natomiast dla funkcji password ustawi je użytkownikowi. Dlatego też funkcję i moduł podczas konfiguracji PAM należy traktować jako parę.

Argumenty kontrolne

Inną częścią, wartą omówienie są argumenty kontrolne. Są one stosowane ze względu na to, iż wiersz zapisane w konfiguracji są zestawiane, co oznacza, że nie zawsze status (powodzenie, niepowodzenie) jednego wiersza może oznaczać zwrócenie do aplikacji informacji o powodzeniu lub niepowodzeniu funkcji przez nią żądanej. Inną kwestią jest rozszerzona składnia argumentów kontolnych dopuszająca inne wartości niż tylko prawda czy fałsz. Więcej na ten temat znajduje się na stronie podręcznika pliku pam.conf. Poniżej znajdują się arguementy kontrolne o prostej składni.

Znając składnię reguł systemu PAM, możemy spóbować samodzielnie przeanalizować drugą linię znajdującą się w pliku chsh w katalogu /etc/pam.d.

auth  sufficient  pam_rootok.so

Pamiętając o tym, że typ funkcji oraz moduł należy taktować jako parę. Skupimy się na początku na argumencie kontrolonym. Otóż jeśli ta reguła zakończy się powodzeniem system PAM nie będzie dalej sprawdzać reguł. Natomiast funkcja (wraz z modułem) mają za zadanie ustalenie czy to superużytkownik próbuje się uwierzytelnić.

Zwrócićmy uwagę na to, iż mimo tego co poznaliśmy konfigrację PAM oraz przeanalizowaliśmy skonstruowany plik konfiguracyjny dla chsh, to root nadal może ustawić dowolną powłokę. Wynika to z konstrukcji samego narzędzia. W tym przypadku PAM ma działać nad uwierzytelnianiem zwykłego użytkownika i jego działań.

Moduły w regułach PAM mogą posiadać argumenty i są one umieszczane po nazwie modułu, na przykład:

auth  sufficient  pam_unix.so nullok

Argument ten pozwala na stosowanie pustego hasła podczas uwierzytelnienia.

7.10.2. Uwagi dotyczące PAM

Ze względu na to, iż wiekszość mechanizmów opisanych w tym materiale zostało przedstawionych pobierznie, przecież to podstawy. To poniżej znajduje się kilka wskazówek odnośnie systemu PAM.

7.10.3. System PAM i hasła

System PAM możemy wykorzystać do uzyskiwania informacji na temat haseł w systemie. Wykorzystanie modułu pam_unix.so wraz z funkcją auth powoduje sprawdzenie hasła. Natomiast jeśli użyjemy tego modułu wraz z funkcją password moduł ustawi podane przez uzytkownika hasło. Wyszukując odpowiednią regułę możemy dowiedzieć się na przykład jakiego algorytmu użyto do tworzenia skrótu hasła. Do odnalezienia tej linii posłużymy się poniższym poleceniem.

xf0r3m@immudex:/ic0$ grep password.*unix /etc/pam.d/*
/etc/pam.d/common-password:# used to change user passwords.  The default is pam_unix.
/etc/pam.d/common-password:password	[success=1 default=ignore]	pam_unix.so obscure yescrypt

W drugiej linii znajduje się poszukiwana przez nas reguła. Skupmy się tylko na argumentach modułu. Otóż argument obscure, najprościej rzecz ujmując powoduje on sprawdzenie czy podane hasło jest wystarczająco "przesłonięte" (nie jest zbliżone do obecnie używanego). Kolejny argument to algorytm szyfrowania w tym przypadku jest to nowy algorytm yescrypt, do Debiana został on wdrożony wraz z wypuszeniem wersji 11 "Bullseye".

No dobrze, w przypadku ustawiania hasła mamy jawnie podany algorytm. A co w przypadku gdy moduł PAM musi sprawdzić czy podane hasło jest poprawne. Niestety w tym przypadku PAM próbuje odgadnąć algorytm wykorzystując do tego bibliotekę libcrypt, ktora wypróbowuje wszystkie dostępne możliwości do momentu aż coś zadziała lub nie pozostanie nic sprawdzenia.

8. Procesy oraz monitorowania zasobów

Jeśli pamiętamy definicję procesu, to wiemy, że proces to nic innego jak wystąpienie uruchomionego programu. Każdy proces aby mógł wykonać swoje zadanie potrzebuje zasobów sprzetowych naszych komputerów oferowanych przez system operacyjny. Jądro odpowiada za sprawiedliwy przydział zasobów systemowych. Samo jądro również może być zasobem - programowym wykorzystywanym przez procesy do uzyskiwania dostępu do plików czy do urządzeń wejścia-wyjścia.

W tym rozdziale objaśnimy sobie nieco bardziej procesy oraz zajmiemy się monitorowaniem zasobów. Jednak nie po to aby optymalizować system, ponieważ ten na domyślnych ustawieniach dystrybucji działa całkiem dobrze i nie potrzeba nic zmieniać. Zajmiemy się natomiast monitorowaniem zasobów by lepiej zrozumieć co jest dokładnie mierzone, dzięki czemu przybliżymy sobie, niektóre działania jądra.

8.1. Śledzenie procesów

Procesy możemy śledzić za pomocą polecenia ps i w zależności od użytych przełączników możemy uzyskać różne rezultaty działania tego polecenia. Osobiście polecam kombinację trzech przełączników -aux. Poza tym to polecenie posiada trzy rózne możliwości wprowadzania do niego opcji. Ja skupie się na jednym myślniku i łączeniu razem opcji. Inną godną polecenia kombinacją opcji jest -elf te opcje zwracają najważniesze dla nas informacje, na przykład jak wartość priorytetu oraz wartość nice, które biorą udział w szeregowaniu procesów do wykonania. Dlaczego polecam tę pierwszą kombinacją, ponieważ najczęściej do zarządzania jakimś procesami będzie nam potrzebny PID, właściciel procesu, z jakiego polecenia proces pochodzi lub procentowe wartości zużycia pamięci czy procesora.

Innym przydatnym narzędziem dla śledzenia procesów jest polecenie top. Ponieważ informacje wyświetlane przez to polecenie są odświeżane co sekundę dając aktualny obraz tego co się aktualnie dzieje w systemie. Po uruchomieniu tego programu na samej górze listy procesów znajdują się najbardziej aktywne z nich. Podczas działania programu, można przekazywać do niego opcje za pomocą naciśniecia odpowiedniego klawisza. Poniżej znajduje się kilka opisanych klawiszy.

W dystrybucjach Linuksa dostępne są różne odmiany polecenia top, takie jak htop lub atop. Polecenie htop jest znacznie bardziej rozbudowane, a jego interaktywna konfiguracja pozwala nie tylko na zmianę wyświetlanych danych, ale również zmianę tematu wyświetlania (kolorów). Za pomocą htop możemy monitorować stan baterii. Po za tym polecenie to posiada, niektóre możliwości innego przydatnego polecenia jakim jest lsof.

8.2. Wyszukiwanie otwartych plików z pomocą polecenia lsof

Polecenie lsof może być bardzo przydatne ponieważ pozwala wyświetlić listę plików otwartych przez różnego rodzaju procesy. Co może okazać się przydatne przypadku gdy chcemy odmontować jakiś system plików, ale otrzymujemy informacje o tym, że target is busy. Ten komunikat może być spowowany tym, że w systemie istnieją jeszcze procesy działające na plikach znajdujących się na odmontowywanym systemie plików. Poza tym polecenie to generuje masę danych ze względu na to, że w systemach uniksopodobnych wszystko jest plikiem, a więc nie otrzymamy informacji tylko i wyłącznie o konwencjonalnych plikach, ale również o gniazdkach czy nazwanych potokach. Poniżej znajduje się linia z otwartym plikiem podczas pisania tego tekstu.

COMMAND    PID   USER   FD      TYPE             DEVICE SIZE/OFF       NODE NAME
vim.gtk3  3645 xf0r3m    7u      REG              254,0    20480    8391246 /media/xf0r3m/immudex_crypt0/
Repos/morketsmerke-dev/articles/terminallog/.Linux.Podstawy.html.swp

Linia przedstawia otwarty plik z materiałem w trakcie redagowania. Zwróćmy uwagę na nazwę pliku. Wygląda na to, że edytor Vim ładuję zawartość pożądanego przez nas pliku do pliku bufora (rozszenienie .swp). W momencie zapisu otwiera właściwy plik, zapisuje dane poczym go zamyka. Edytor ten jest znany z tej metody obsługi plików. Dzięki temu w przypadku nagłego wyłącznia komputera, dane wciąż pozostają w pliku bufora. Nawet w przypadku, kiedy będziemy otwierać ten plik to edytor zauważy pozostawiony plik bufora, który o opuszczeniu programu powinien zostać usunięty. Jeśli dla otwieranego przez ten edytor istnieje już plik wymiany, wówczas edytor zapyta co zrobić z jego zawartością.

Wracając, linie zwracane przez to polecenie podzielone są na 9 kolumn Każda z nich zawiera:

Ze względu na przytłaczającą ilość danych zwracanych przez to polecenie, możemy uruchomić je na dwa sposóby. Pierwszym z nich jest przepuszcznie danych zwracanych przez to polecenie przez jakiś filtr, najprostszym jest chyba polecenie less jednak lepiej wyszukać informacji z użyciem wyrażeń regularnych (polecenia grep). Drugą metodą jest zawężenie informacji zwracanych poprzez wykorzystanie dostępnych opcji narzędzia. Najłatwiejszym w użyciu jest podanie jako argumentu po prostu ścieżki do katalogu, z którego chcemy widzieć otwarte pliki. Na przykład:

xf0r3m@immudex:~$ lsof /home/xf0r3m
COMMAND    PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
pipewire   978 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
dbus-daem  983 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
pipewire-  988 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
xfce4-ses  989 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
at-spi-bu 1042 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
dbus-daem 1047 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
xfconfd   1051 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
...
mpv       2923 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m
atrild    3227 xf0r3m  cwd    DIR   0,27      400  638 /home/xf0r3m

A jeśli chcelibyśmy się dowiedzieć jakie otawrte pliki posiada proces mpv, to możemy na przykład skorzystać z opcji -p a jako jej argument podać PID procesu mpv tak jak przedstawiłem to na przykładzie.

xf0r3m@immudex:~$ lsof -p 2923
COMMAND  PID   USER   FD      TYPE             DEVICE SIZE/OFF   NODE NAME
mpv     2923 xf0r3m  cwd       DIR               0,27      400    638 /home/xf0r3m
mpv     2923 xf0r3m  rtd       DIR               0,27      200      2 /
mpv     2923 xf0r3m  txt       REG               0,29  2158056   4302 /usr/bin/mpv
mpv     2923 xf0r3m  mem       REG                7,0            4302 /usr/bin/mpv (path dev=0,29)
...
mpv     2923 xf0r3m    5u     IPv4              26738      0t0    TCP 192.168.168.29:34108->
prg03s12-in-f14.1e100.net:https (CLOSE_WAIT)
mpv     2923 xf0r3m    6u     IPv4              26743      0t0    TCP 192.168.168.29:34122->
prg03s12-in-f14.1e100.net:https (CLOSE_WAIT)
mpv     2923 xf0r3m    7u     IPv4              88390      0t0    TCP 192.168.168.29:38584->
prg03s13-in-f14.1e100.net:https (ESTABLISHED)
mpv     2923 xf0r3m    8u     IPv4              86801      0t0    TCP 192.168.168.29:48618->
85.162.162.204:https (ESTABLISHED)
mpv     2923 xf0r3m    9u     unix 0x000000006b6ad31e      0t0  26745 type=STREAM
mpv     2923 xf0r3m   10r     FIFO               0,12      0t0  26746 pipe
mpv     2923 xf0r3m   11w     FIFO               0,12      0t0  26746 pipe
mpv     2923 xf0r3m   12u      CHR              226,0      0t0    237 /dev/dri/card0
mpv     2923 xf0r3m   13u      CHR              226,0      0t0    237 /dev/dri/card0
mpv     2923 xf0r3m   14u      CHR              226,0      0t0    237 /dev/dri/card0
mpv     2923 xf0r3m   15u      CHR              226,0      0t0    237 /dev/dri/card0
mpv     2923 xf0r3m   16r     FIFO               0,12      0t0  26079 pipe
mpv     2923 xf0r3m   17w     FIFO               0,12      0t0  26079 pipe
mpv     2923 xf0r3m   18u  a_inode               0,13        0   7971 [eventfd]
mpv     2923 xf0r3m   19u     unix 0x0000000075d44df6      0t0  26081 type=STREAM
mpv     2923 xf0r3m   20u  a_inode               0,13        0   7971 [eventfd]
mpv     2923 xf0r3m   21r     FIFO               0,12      0t0  26082 pipe
mpv     2923 xf0r3m   22w     FIFO               0,12      0t0  26082 pipe

Zastanawiające może być, dlaczego mpv korzysta z połączeń sieciowych, otóż za pomocą mpv oraz pakietu youtube-dl, można korzystać z serwisu YouTube bez nadmiernego obciążenia komputera przez nieoptymalną aplikację internetową.

Jeśli często aktualizujemy jądro, poza aktualizacją całej dystrybucji to należy pamiętać o aktualizacji programu lsof. Po aktualizacji jądra oraz programu lsof, może ono nie nie działać prawidłowo, dopóki nie uruchomimy nowego jądra.

8.3. Śledzenie wykonania programów oraz wywołań systemowych

Zazwyczaj program jeśli się uruchamia i napotka podczas wykonywania swoich czynności błąd to zwróci jakąś informację o tym co się stało (większość programów uruchomianych na uniksach). Możemy jednak napotkać taki przypadek, że uruchomimy program i on odrazu się zamknie. Wówczas pojawia się problem w jaki sposób mamy dowiedzieć się co jest nie tak z programem czy naszym środowiskiem (czy wszyskie wymagane pakiety zostały zainstalowane, na przykład). Rozwiązania, które przedstawię w tym podrozdziale na pewno nie są idealne i nie sprawdzą się w przypadku każdego "migającego" programu. Nie mniej jednak warto uruchomić dla niego chodziaż jedno z zaprezentowanych tutaj poleceń.

8.3.1. Polecenie strace

Polecenie strace pozwala na uruchomienie programu wraz ze śledzeniem wywołań systemowych (interfejsu jądra, pozwalającego na wykonanie wielu czynności systemowych, na przykład otwarcia pliku zapisanego gdzieś w systemie plików). Najprostszą poleceniem jakie możemy wykonać dla przykładu jest wyświetlenie zawartości jakiegoś pliku. Polecenie strace może nie występować we wszystkich dystrybucjach, więc będzie trzeba je zainstalować. Z racji tego, iż informacji zwracanych przez to polecenie jest na prawdę dużo to poniżej przedstawiłem wywołanie prostego wyświetlenia zawartości pliku przy użyciu polecenia cat:

xf0r3m@immudex:~$ strace cat yt-links
execve("/usr/bin/cat", ["cat", "yt-links"], 0x7ffc8ee949b8 /* 39 vars */) = 0
brk(NULL)                               = 0x564490232000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (Nie ma takiego pliku ani katalogu)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=105669, ...}) = 0
mmap(NULL, 105669, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f770de000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@>\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1905632, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f770dc000
mmap(NULL, 1918592, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f76f07000
mmap(0x7f2f76f29000, 1417216, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f76f29000
mmap(0x7f2f77083000, 323584, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17c000) = 0x7f2f77083000
mmap(0x7f2f770d2000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ca000) = 0x7f2f770d2000
mmap(0x7f2f770d8000, 13952, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f770d8000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f770dd580) = 0
mprotect(0x7f2f770d2000, 16384, PROT_READ) = 0
mprotect(0x56448ed5a000, 4096, PROT_READ) = 0
mprotect(0x7f2f77122000, 4096, PROT_READ) = 0
munmap(0x7f2f770de000, 105669)          = 0
brk(NULL)                               = 0x564490232000
brk(0x564490253000)                     = 0x564490253000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=3041312, ...}) = 0
mmap(NULL, 3041312, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f76c20000
close(3)                                = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "yt-links", O_RDONLY)  = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=512, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f76bfe000
read(3, "lofi girl stream: www.youtube.co"..., 131072) = 512
write(1, "lofi girl stream: www.youtube.co"..., 512lofi girl stream: www.youtube.com/watch?v=jfKfPfyJRdk
Stalker czyste niebo: www.youtube.com/playlist?list=PLMnTK-S7An4KqvvXn6AMkE00s_aDNZ29o
Stalker zew prypeci: www.youtube.com/playlist?list=PLMnTK-S7An4K8BMXzFCVA0Y8KLSSHRfwV
MysteryTV CP S03: www.youtube.com/playlist?list=PLjkTsi__dtwWJm4gUqaFLBva0k1TljXcn
MysteryTC CP S04: www.youtube.com/playlist?list=PLjkTsi__dtwWR247M06TZ_2FxccG91f1u
Tu skończyłem: https://www.youtube.com/watch?v=wnY3cTAujMc

Iceberg o stalkerze: www.youtube.com/watch?v=uMHvO7LXVz8
) = 512
read(3, "", 131072)                     = 0
munmap(0x7f2f76bfe000, 139264)          = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Polecenie strace wykorzystuje wyołanie systemowe fork() aby utworzyć kopię swojego procesu następnie ta kopia jest zastępowana przez wywołanie systemowe execve(), które uruchamia podany przez nas proces, w tym przypadku jest wyświetlenie zawartości pliku yt-links, po zinicjowaniu pamięci oraz odpytaniu się bibliotek proces cat chcę otworzyć plik za pomocą wywołania systemowego openat, jeśli otwarcie pliku się powiedzie, otwartemu plikowi zostanie nadany pierwszy wolny deskryptor, najczęsiej będzie to 3, co zostało przedstawione na przykładzie. Jeśli samodzielnie będziemy uruchamiać podobne polecenie (nie każdy w swoim systemie posiada, pliki yt-links), to zauważym że ten deskrytor jest przypisywany i zwalniany kilkukrotnie podczas wykonywania czynności Przed pobraniem zawartości pliku, pobierane są jego atrybuty za pomocą wywołania fstat() oraz alokowana jest pamięci operacyjna (mmap) i wówczas następuje wywołanie systemowe read(), które pobiera zawartości pliku. Następne wywołanie systemowe odpowiedzialne jest za wypisanie zawartości, do deskryptora o numerze 1. Jak pamiętamy polecenie cat wyświetla zawartość pliku podanego jako ścieżka albo wyświetla podane dane z standardowego wejścia na standardowe wyjście. A więc numer standardowego wyjścia, którym jest 1 jest deskryptorem otwartego pliku. Dlatego też wywołanie systemowe write() odwołuje się do deskryptora o numerze 1. Następnie pamięć oraz wykorzystywane deskrytory zostają zwolnione i w ten sposób kończy się wykonanie procesu polecenia cat.

Czytając ten obszerny opis możemy przyjrzeć się jak działają programy na uniksach. Nie mniej jednak polecenie strace może pomóc nam przy, nie których problemach z programami. Często sytuacją, w której program może nam "mignąć" jest brak jakiegoś pliku lub problem z jego dostępnością. Kiedy analizujemy wykonanie programu za pomocą polecenia strace to należy szczególną uwagę zwrócić na wywołania systemowe openat(). Poniżej znajdują się dwa przykładowe komunikaty:

openat(AT_FDCWD, "test.txt", O_RDONLY)  = -1 ENOENT (Nie ma takiego pliku ani katalogu)
openat(AT_FDCWD, "/etc/shadow", O_RDONLY) = -1 EACCES (Brak dostępu)

W przypadku błędu, zazwyczaj otrzymujemy deskryptor o numerze -1, jednak zwróćmy uwagę na komunikaty błedów różnia się od siebie (ENOENT a EACCES).

8.3.2. Polecenie ltrace

Podobnym do strace narzędziem jest ltrace. Jednak zamiast wywołań systemowych śledzi on wywołania bibliotek wspóldzielonych, dane wyjściowe tego programu są zbliżone do strace. Program ltrace nie śledzi niczego na poziomie jądra, to jednak warto mieć na uwadzę fakt, iż programy o wiele częściej korzystają z bibliotek wspódzielonych niż z wywołań systemowych. Polecenie ltrace nie zadziała w przypadku bibliotek dołączonych statycznie. A jego dane wyjściowe możemy odfiltrować za pomocą opcji polecenia, których opis dostępny jest na stronie podręcznika.

8.4. Wątki

Procesy mogą dzielić się na podobne byty zwane wątkami. Wątki w pewnym sensie są podobne do procesów, również zawierają identyfikator zwany TID (ang. Thread IDentifier). Podobnie jak program może mieć kilka procesów, odpowiadających wykonywanym przez niego czynnością, to wątki mogą być efektem podziału czynności procesu na jeszcze mniejsze części. W przypadku komputerów metoda rozwiązywania problemów "dziel i rządź", będzie miała zastosowanie jeszcze nie jednokrotnie.

8.4.1. Procesy jedno oraz wielowątkowe

Część procesów uruchamianych w systemie zawiera tylko jeden wątek. Wówczas taki proces jest procesem jednowątkowym. Na początku, każdy proces posiada jeden wątek, zwany wątkiem głównym. Wątek główny może tworzyć kolejne wątki, zmieniając ten proces tym samym w proces wielowątkowy.

Podstawową zaletą procesów wielowątkowych jest fakt wykonania zaplanowanych w procesie czynności o wiele szybciej, gdyż każdy wątek może być wykonywany przez jeden procesor (wątek procesora, tak rdzenie procesorów też mogą zawierać w sobie wątki, przeważnie na jeden rdzeń przypadają dwa wątki). Innym cechą wątków jest to iż wykorzystują one wspólne obszary pamięci, (nie tak jak w przypadku procesów, gdzie procesy nie mają dostępu do obszaru pamięci innych procesów) usprawniając tym samy komunikację miedzy wątkami. Wątki najczęsciej wykorzystywane są do obsługi operacji wejscia-wyjścia. Użycie w tym przypadku wątków zamiast procesów pozwala nam zaoszczędzić trochę czasu procesora.

8.4.2. Wyświetlanie wątków.

Pozanane do tej pory narzędzia służące do obserowania procesów również sprawdzą się gdy będziemy chcieli wyświetlić informacje na temat wątków. W przypadku polecenia ps wystarczy dodać do polecenia opcję m, warto jednak zaznaczyć by nie mieszać opcji m wraz z opcją u. Na poniższym przykładzie wyświetliłem moje procesy uruchomione w terminalach.

xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ ps -am -opid,tid,time,cmd
    PID     TID     TIME CMD
   1799       - 00:00:02 /usr/bin/python3 -O /usr/bin/ranger
      -    1799 00:00:02 -
   7507       - 00:00:00 /bin/sh -c set -- '/ic0/Repos/morketsmerke-dev/articles/terminallog/Linux.P
      -    7507 00:00:00 -
   7508       - 00:00:00 /bin/sh /usr/bin/sensible-editor -- /ic0/Repos/morketsmerke-dev/articles/te
      -    7508 00:00:00 -
   7512       - 00:00:05 /usr/bin/vim.gtk3 -- /ic0/Repos/morketsmerke-dev/articles/terminallog/Linux
      -    7512 00:00:05 -
  12070       - 00:00:00 ps -am -opid,tid,time,cmd
      -   12070 00:00:00 -

Zwróćmy uwagę na to numery PID oraz TID są takie same. W przypadku poleceń jednowątkowych, wątek główny posiada taki sam TID jak proces macierzysty. Jeśli pojawiłby się procesy, wkorzystujące wątki, to wówczas TIDy wynośiły by kolejne numery rozpoczynając od PID-u lub TID-u wątku głównego.

Jeśli preferujemy narzędzie top to wówczas użycie kombinacji klawiszy Śhift + h pozwoli nam na wyświetlenie wątków. Możemy je rozpoznać po tym, że wyświetlone tam "procesy" są uruchomione z tego samego polecenia i posiadają następujące po sobie identyfikatory.

Wątki w tym materiale zostały przedstawione, aby zaprezetować ich istnienie i na tym temat się kończy.

8.5. Monitorowanie zasobów

Monitorowanie zasobów komputerów przeprowadza się głównie, aby dowiedzieć się, które z komponentów systemów lub komputerów należy z optymalizować, aby nasza praca była jescze bardziej wydajna, abyśmy mogli zrobić coś lepiej, szybciej oraz niższym nakładem pracy. Jak już wspomniałem jądro Linuksa jest bardzo wydajne przy domyślnych ustawieniach i nie trzeba się tym przejmować. Dlatego też wykorzystamy monitorowanie zasobów czasu procesora, pamięci operacyjnej oraz operacji wejścia-wyjścia do sprawdzenia w jaki sposób dzieli je między procesami.

8.6. Pomiar czasu procesora

Do monitorowania w czasie rzeczywistym pojedynczych procesów możemy wykorzystać polecenie top wraz z opcją -p. Jako argumenty opcji podajemy listę identyfikatorów procesów.

$ top -p 3329,1230

Wówczas polecenie top pokaże na swojej liście tylko te dwa procesy.

Aby dowiedzieć się ile czasu procesora w trakcie swojego działania wykorzystało konkretne polecenie, to należy je uruchomić za pomocą polecenia time, jednak tu musimy się na chwilę zatrzymać gdyż przeważnie w większości dystrybucji istnieją dwa polecenia time. Jedno jest polecenie wbudowanym w powłokę i nie ma z niego za bardzo pożytku. Dla przykładu poniżej umieszczam pomiar czasu procesora wbudowanym w powłokę polecenie time:

xf0r3m@immudex:~$ time ls
...
real	0m0,007s
user	0m0,001s
sys	  0m0,007s

Z kolei dostęp do właściwego polecenie uzyskamy uruchamiając konkretny plik: /usr/bin/time, jednak twórcy wiodących dystrybucji uznają, że polecenie wbudowane w powłokę wystarczy, dlatego też prawdopodbne jest, że omawiany przez nas program nie będzie domyślnie zainstalowany w naszym systemie. Poniżej znajduje się to samo polecenie wykonane za pomocą właściwego programu.

xf0r3m@immudex:~$ /usr/bin/time ls
...
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 2516maxresident)k
0inputs+0outputs (0major+123minor)pagefaults 0swaps

To polecenie zwraca znacznie więcej informacji na temat wykonanego polecenia. Na tym etapie będą interesować trzy pierwsze wartości: 0.00user 0.00system 0:00.00elapsed. Wskazują one kolejno:

Pozostałe wartości zwracane przez to polecenie dotyczą pamięci, operacji wejścia-wyjścia oraz stronicowania (do stronicowania jeszcze wrócimy w tym rozdziale).

8.7. Priorytetyzacja procesów

W pierwszym rodziale poruszyliśmy temat zarządzania procesami przez jądro, dowiedzielśmy się, że każdy proces otrzymuje dostęp do procesora na ułamek sekundy. Modułem odpowiedzialnym za to, który z procesów uzyska w chwili obecnej dostęp do procesora jest program szeregujący, który na podstawie priorytetu procesu może przydzielć mu więcej lub mniej czasu. W dystrybucja Linuksa priorytety funkcji przedstawiane są dwojako. Polecenie top przestawia domyślny priorytet wartością 20 (kolumna PR), jeśli jednak wywołamy polecenie ps z opcjami -elf (ta sama kolumna), to domyślnym priorytetem będzie 80. My na tym etapie będziemy trzymać się raczej wartosci przedstawianych przez polecenie top, ponieważ łatwiej będzie nam je zrozumieć. Więc domyślnym priorytem jest 20, poniżej przedstawiam kilka procesów wyświetlonych przez narzędzie top:

xf0r3m@immudex:~$ top

top - 09:32:04 up  1:55,  1 user,  load average: 0,73, 0,65, 0,61
...
PID UŻYTK.    PR  NI    WIRT    REZ    WSP S  %CPU  %PAM     CZAS+ KOMENDA
2364 xf0r3m    20   0 2504764 158992  90048 S   0,3   2,0   0:37.66 Isolated Web Co
2418 xf0r3m    20   0  474048  94468  66272 S   0,3   1,2   1:39.81 xfce4-terminal
4395 root      20   0       0      0      0 I   0,3   0,0   0:01.40 kworker/3:0-events
33705 xf0r3m    20   0   10404   4212   3452 R   0,3   0,1   0:00.36 top 

Najwyższym priortem w tym przypadku jest działanie w czasie rzeczywistym, ale na tym etapie nie będziemy się tym zajmować. Tak więc na chwilę obecną najwyższym priortetem jest 1, a najniższym 39.

Do manipulacji priorytetami wartość nice (kolumna NI), przechowuje ona wartość wpływającą na priorytet zwiększając go lub zmieniajszając. Do ustawienia wartości nice służy polecenie renice. To polecenie możemy wykonać bez uprawnień administrator o ile zmniejszamy priorytet (podając wysoką wartość nice, która dodawana jest do domyślnej wartości priortetu), zmniejszenie priorytetu (podanie ujemnej wartości nice) wymaga już uprawnień superużytkownika. Polecenie to poza nową wartością nice wymaga podania PID-u procesu.

xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ pidof top
37426
xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ renice 20 37426
37426 (process ID) old priority 0, new priority 19

#lub krócej:

xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ renice 20 $(pidof top) 
37426 (process ID) old priority 0, new priority 19

Polecenie pidof pozwala uzyskać PID procesu na podstawie polecenia, jeśli uruchomionych jest więcej niż jedna instancja danego programu polecenie zwróci listę PID-ów. Podczas zmiany priorytetu mimo, iż podaliśmy wartość 20 to największą wartością nice jest 19 tak samo jest w drugą stronę. Najmniejszą wartością nice (a zatem proces będzie mieć największy priortet) jest -19.

Priortety i manipulacja nimi miała dużo większe znaczenie w czasch gdy z jednego systemu korzystało wielużytkowników. Obecnie maja one mniejsze znaczenie. Warto też dodać, aby nie wymuszać wysokich priortetów, gdyż mogą one zablokować istotne dla funkcjonowania systemu procesy i go zdestabilizować.

8.8. Średnie obciążenia

Średnie obciążenia jest to o szaczowana liczba procesów do uruchomienia. Ten parametr określa ilość procesów gotowych w każdej chwili użyć procesora. Jak pamiętamy nie wszystkie procesy są gotowe do działania, część z nich czeka na dane. Średnie obciążenia są wyświetlane przez polecenie uptime.

xf0r3m@immudex:~$ uptime
 10:40:15 up  3:03,  1 user,  load average: 1,14, 0,99, 1,17

Wartości przedstawione obok etykiety load average przedstawiaja średnie obciążenia z minuty, 5 i 15 minut. Na powszszym przykładzie widzimy że ciągu ostatniej minuty z użyto na wykonanie procesów użytkownika 114% procent procesora. Jeśli obiążenia tak jak na przykładzie stale utrzymują się powyżej 1, to oznacza to, że jeden proces cały czas wykorzystuje jeden rdzeń procesora. Pełne obiążenie komputera w przypadku tego wskaźnika będzie oscylować w granicach ilości rdzeni/wątków procesora zamontowanego w naszym komputerze.

Wysokie średnie obciążenia mogą wynikać nie tylko z działania w systemie procesów ale również ze względu na pozostałą nie wielką ilość pamięci dostępnej w systemie. Wówczas jądro może zarządzić proces przeładowania (ang. trashing), powoduje to szybkie przenoszenie stron pamięci na dysk oraz z dysku. Gdy ma to miejsce ilość procesów gotowych do uruchomienia zwiększa sią powodując znacznie zwiększenie średniej obiążenia. Ze względu na małą ilość wolnej pamięci system może działać znacznie wolniej i niż zwykle.

8.9. Pamięc

Pamięć operacyjna jest bardzo ważnym komponentem komputera, jeśli chodzi o maszyny uniksowe. W pamięci rezydują obszary, w których procesy przechowują swoje dane, a jej ilość jest ograniczona. Do monitorowania pamięci możemy posłużyć się takimi narzędziami jak polecenie free (w przypadku tego polecenia, warto przeskalować sobie wartości zwracane za pomocą opcji -h) lub skorzystać z interfejsu systemowego wyświetlając zawartość pliku /proc/meminfo (tutaj jednak jestśmy skazani na wartość wyrażone w kilobajtach). Jeśli pamięć podręczna/bufora nie zajmuje dużego obszaru pamięci fizycznej, a mimo to nie mamy w zanadrzu wiekszej ilości wolnej pamięci to niezbędne może być dołożenie pamięci do naszego komputera, aby poprawić jego wydajność.

8.9.1. Zarządzenie pamięcią

Jądro w tym zadaniu opera się na jednosce MMU (ang. Memory Management Unit), której zadaniem jest zamiana adresów pamięci wirtualnej na adresu pamięci fizycznej. Pamięć wirtualna jest wykorzystywana przez procesy. Jądro współpracuje z MMU dzieląc obszary procesów na mniejsze strony, trzymając tym samym dane służące MMU do odzworowania adresów w strukturze danych zwanej tabelą stron. W momencie uzyskania przez proces dostępu do pamięci jednostka MMU dzięki tej strukturze może dokonywać translacji adresów.

Procesy zazwyczaj nie wymagają dostępu do pełnego obszaru w pamięci od razu. Jądro ładuje wówczas tylko te strony, których proces wymaga. Taki rodzaj przydzielania pamięci nazywany jest stronicowaniem na żądanie.

Przydzielenie pamięci nowemu procesowi, możemy zapisać w czterech krokach.

  1. Jądro ładuje do stron pamięci początek kodu programu
  2. Jeśli zajdzie taka potrzeba jądro może przypisać procesowi kilka stron pamięci.
  3. W trakcie działania procesu, może zajść potrzeba załadowania większej ilości kodu, ponieważ następna instrukcja do wykonania nie znajduje się na załadowanych początkowo stronach. W takiej sytuacji jądro przejmuje kontrolę, ładuje wymagane strony i pozwala programowi wznowic działanie.
  4. Jeśli zajdzie potrzeba przydzielenia więcej pamięci niż zakładano na początku, jądro znajduje nieużywane strony, zwalnia je i przydziela procesowi.

8.9.2. Błędy stron

Nie zawsze wyżej wymienione czynności da się spiąć w czasie. Jeśli żądana przez proces strona w pamięci nie jest jeszcze gotowa to generują on błąd strony. Gdy taki błąd zostanie wygenerowany, to kontrolę nad procesorem przejmuje jądro aby załadować żądaną stronę. Błędy dzielą się na podstawowe oraz drugorzędne.

Drugorzędne błedy stron

Błędy tego typu nię są poważnym błedami, i zdarzają się dość często. Ich najczęstszym powodem występowania jest fakt iż MMU nie zna położenia strony z instrukcjami programu. Samo MMU może nie mieć odpowiednio dużo miejsca aby przechować adresy wszystkich obszarów. W przypadku występowania takiego błędu jądro przekazuje do MMU informacje o położeniu strony i pozwala na wznowienie działania procesu.

Podstawowe błędy stron

Podstawowe błędy występują wówczas gdy strony nie ma w ogóle w pamięci. Jądro musi ją załadować z jakiegoś nośnika najczęsciej jest to dysk. Duża ilość tego typu błędów może przeciążyć system ponieważ procesor jest zajęty przez jądro ładujące kod z niekoniecznie szybkich nośników, blokując go tym samym dla innych procesów. Niestety pewna ilość tego typu błedów jest nie unikniona, a mają one miejsce kiedy po raz pierwszy uruchamiamy jakiś program, wówczas należy załadować kod z dysku.

Obserowowanie błędów stron

Do obserwowania błędów stron przydatne staje się polecenie time, o którym wspomniałem przy okazji pomiaru czasu procesora. Jednak w tym przypadku będzie interesować nas wartość w nawiasie obok napisu pagefaults.

xf0r3m@immudex:~$ /usr/bin/time timedatectl
               Local time: nie 2023-01-08 17:59:08 CET
           Universal time: nie 2023-01-08 16:59:08 UTC
                 RTC time: nie 2023-01-08 16:59:08
                Time zone: Europe/Warsaw (CET, +0100)
System clock synchronized: no
              NTP service: n/a
          RTC in local TZ: no
0.00user 0.01system 0:00.10elapsed 24%CPU (0avgtext+0avgdata 7416maxresident)k
522inputs+0outputs (8major+359minor)pagefaults 0swaps

W przypadku uruchomienia w moim systemie polecenia timedatectl wystąpiły 8 błędów podstawowych (8major) oraz 359 błedów drugorzędnych.

Inne narzędzia takie top oraz ps również mogą wyświetlać informacje o błędach stron pamięć, w przypadku polecenia top należy włączyć wyświetlanie nMaj oraz nMin, aby przejść do konfiguracji należy nacisnąć literę f, a następnie postępować zgodnie z instrukcją. Program ps pozwala na wyświetlenie błędów strony poprzez podanie niestandardowego wyświetlania kolumn (opcja -o) kolumny noszą kolejno nazwy maj_ftl i min_ftl.

8.10. Monitorowanie wydajności za pomocą polecenia vmstat

Polecenie vmstat pozwala na monitorowanie wielu aspektów wydajności systemu, a jest jednym z najstarszych narzędzi tego typu. Dane wyświetlane również pozostawiają wiele do życzenia i osoba nie mająca styczności z tym narzędziem, może uznać je za mało czytelne.

xf0r3m@immudex:~$ vmstat 2
proc. -----------pamięć---------- ---swap-- ---we/wy--- -system-- ------cpu-----
dz bl   swap  wolna  bufor  cache   si   so    bi    bo   in   cs uż sy be io sk
 0  0      0 861264  13196 5331448    0    0    56     3  189  210 13  4 83  0  0
 0  0      0 861112  13196 5330624    0    0     0     0 2071 4073  8  4 88  0  0
 0  0      0 861712  13196 5330664    0    0     0     0 1944 3661  7  4 88  0  0
 0  0      0 862012  13196 5330608    0    0     0     0 2007 3866  7  4 89  0  0

Polecenie to przyjmuje jako argument interwał czasowym co ile sekund ma wyświetlać nowe statystki. Każda linia to w tym przypadku statystyki pobrane co dwie sekundy. Wyjście polecenia zawiera tematyczne kolumny. Pierwszą z nich są procesy, ta kolumna zawiera jeszcze dwie inne kolumny wskazujące procesy gotowe do uruchomienia (dz) oraz te zablokowane (bl). W następnej kolumnie znajdują się informacje na temat pamięci, a w niej informacje o wykorzystaniu przestrzeni wymiany (swap), ilości wolnej pamięci, pamięci przeznaczonej na bufor oraz pamięci przeznaczonej na pamięć podręczną. W trzeciej kolumnie znajdują się informacje o przestrzeni wymiany, ile stron zostało przeniesionych na dysk (si) oraz ile stron zostało załadowanych z dysku do pamięci (so). Czwarta kolumna zawiera informacje o użyciu urządzeń wejścia-wyjścia, dane odczytane z dysku (bi) oraz dane zapisane na dysku (bo). Piąta kolumna zwiera informacje systemowe, w niej znajdują się liczniki wywołań systemowych (in) oraz przełączeń kontekstu (cs). Ostatnia kolumna zawiera procentowe zużycie czasu procesora dla kolejno: aplikacji użytkownika (), jądra oraz obsługi procesów (sy), stanu bezczynności (be), czasu przeznaczonego na obsługę operacji wejścia-wyjścia (io), czasu skradziony wirtualnej maszynie (sk).

Polecenie to zawiera wiele przydanych opcji, które są zawarte na stronie podręcznika programu. Jak na przykład opcję -d, która pozwala na monitorowanie dysków.

8.11. Monitorowanie operacji wejścia-wyjścia

Na dystrybucje Linuksa dostępnych jest kilka narzędzi służących do monitorowania operacji wejścia-wyjścia, które w dużej mierze są operacjami dyskowymi.

8.11.1. Polecenie iostat

Jedno znich przypomina omawiany w wcześniejszym podrozdziale program vmstat, a jest nim polecenie iostat, to polecenie może nie być domyślnie zainstalowane i jeśli chcemy z niego skorzystać to należy je zainstalować. Pakiet zawierający ten program zajduje się w repozytoriach Debiana po nazwą sysstat. Poniżej znajduje się przykład:

xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ iostat Linux 5.10.0-20-amd64 (immudex) 10.01.2023 _x86_64_ (4 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 17,27 0,00 4,22 0,01 0,00 78,50 Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd dm-0 0,35 3,52 0,61 0,00 25273 4396 0 loop0 6,27 73,04 0,00 0,00 524237 0 0 sda 1,55 66,41 0,56 0,00 476690 4048 0

Oryginalny wydruk jest kolorowy. Ciemno niebieskie pola, mogą być trochę nieczytelne, ale oznaczają one wartość 0,00. Pierwsza linia zawiera nazwę jądra, nazwę hosta w nawiasie, aktualną datę, architekturę procesora oraz liczbę logicznych procesorów (rdzenie/wątków). Druga oraz trzecia linia zawierają średnie zużycie czasu procesora oraz objaśnienia tych wartości. Poniżej znajduje się tabelka przedstawiająca urządzenia (Device), liczba transferów na sekundę (liczba operacji wejścia-wyjścia na sekundę wystosowana wobec urządzenia, tps); liczba danych odczytanych na sekundę (kB_read/s); liczba danych zapisanych na sekundę (kB_write/s); liczba danych odrzuconych na sekundę dla urządznia (kB_dscd), ostatnie trzy kolmny wrażają podobne wartości tylko zamiast prędkości jest przedstawiona tam łączna ilość.

Domyślnie dane wyrażane są w kilobajtach, jak prawie wszystko w systemie co dotyczy pamięci masowych, jednostki możemy przeskalować do megabajtów za pomocą opcji -m, natomiast za pomocą opcji -p wraz z argumentem ALL możemy wyświetlić statystki dla wszystkich urządzeń blokowych dostępnych w systemie. Podobnie do vmstat podanie gołej liczby jako argumentu spowoduje włączenie interwału czasowego o podanej wartości. Więcej opcji oraz bardziej szczegółowe wyjaśnienia znajdują się na stronie podręcznika programu.

8.11.2. Polecenie iotop

Innym poleceniem służącym do monitorowania operacji wejścia-wyjścia jest program iotop, zasada działania tego programu jest podobna do znanego nam już narzędzia top tylko tym razem zamiast skupiać się na procesach, położono nacisk na operacje wejścia wyjścia.

xf0r3m@immudex:~$ sudo iotop
Total DISK READ:         0.00 B/s | Total DISK WRITE:         0.00 B/s
Current DISK READ:       0.00 B/s | Current DISK WRITE:       0.00 B/s
    TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND                            
      1 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % init
      2 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kthreadd]
      3 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_gp]
      4 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_par_gp]
      6 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/0:0H-events_highpri]
      8 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [mm_percpu_wq]
      9 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_tasks_rude_]
     10 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_tasks_trace]
     11 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/0]
     12 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_sched]
     13 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/0]
     15 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [cpuhp/0]
     16 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [cpuhp/1]
     17 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/1]
     18 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/1]

Jak możemy zauważyć to polecenie wyświetla nam identyfikatory wątków. Jest to jedno z nielicznych narzędzi wyświetlających wyświetlających wątki, ponieważ często procesy dzielą się na nie aby własnie zająć się obsługą wejścia-wyjścia. Inna kolumną wartą opisania jest PRIO, podobnie jak procesy jądro stara się szergować operacje wejścia-wyjścia. Przyczym priorytet tutaj dzieli się na dwie wartości, klasę oraz poziom samego priorytetu. Wątki o priorytecie be/0 otrzymają więcej czasu procesora na realizacje operacji niż wątki z priorytetem be/4. Samych klas priorytetów mogą wystąpić trzy rodzaje takie jak:

Do manipulacji priortetami operacji wejścia-wyjścia służy polecenie ionice, więcej na jego temat znajdziemy na stronie podręcznika.

8.12. Monitorowanie procesów za pomocą narzędzia pidstat

Za pomocą polecenia top możemy śledzić wykorzystanie zasobów przez wybrany proces. Problem w przypadku tego rozwiązania jest brak poprzednich wartości, ponieważ dane odświerzane są co interwał czasowy (w przypadku top jest to jedna sekunda). Rozwiązaniem tej niedogodności może być zastosowanie narzędzia pidstat, ponieważ zachowuje on się tak jak vmstat, ale dla procesów. Jeśli nie podamy konkretnego PID-u, polecenie wyświetli wszystkie procesy, które zostały uruchomione w systemie. Polecenie to nie jest domyślnie dostępne w dystrybucjach opartych na Debianie, jest ono częścią pakietu sysstat, ten pakiet zawiera również polecenie iostat.

xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ pidstat 
Linux 5.10.0-20-amd64 (immudex) 	11.01.2023 	_x86_64_	(4 CPU)

17:02:56      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
...
17:02:56     1001      2820    4,84    0,99    0,00    0,08    5,83     1  mpv
17:02:56     1001      2886    0,04    0,01    0,00    0,00    0,05     1  ranger
17:02:56     1001      3366    0,18    0,04    0,00    0,00    0,21     1  atril
17:02:56     1001      3398    0,00    0,00    0,00    0,00    0,00     0  WebKitNetworkPr
17:02:56     1001      3805    0,03    0,01    0,00    0,00    0,04     1  vim.gtk3
17:02:56     1001      3829    0,00    0,00    0,00    0,00    0,00     2  bash
17:02:56        0      4176    0,00    0,01    0,00    0,00    0,01     0  kworker/u8:2-kcryptd/254:0
17:02:56        0      5013    0,00    0,00    0,00    0,00    0,00     0  kworker/u8:3-i915
17:02:56     1001      5610    0,00    0,00    0,00    0,00    0,00     0  Web Content
17:02:56     1001     28551    0,00    0,00    0,00    0,00    0,00     2  pidstat

Polecenie to w pierwszej linii zwraca nam podsumowanie odnośnie systemu (wersję jądra, nazwę hosta, architekturę procesora oraz ilość rdzeni/wątków) i datę. Następnie wyświetlana jest tabela z procesami, w kolumnach kolejno od lewej znajduje się czas wywołania polecenia; UID użytkownika, który ten proces zainicjował; PID procesu. Następne pięć kolumn pokazuje informacje o zużyciu czasu procesora. Kolumna %wait jest procentowym żużyciem czasu procesora poświęconym na operacje wejścia-wyjścia. Ciekawą wartością, nie zwracaną przez inne polecenia jest kolumna %guest, która przedstawia zużycie procesora na potrzeby zadań wykonywanych przez maszynę wirtualną. Kolejnymi kolumnami są sumaryczne procentowe zużycie czasu procesora; wskazanie, z którego z rdzeni/wątków procesora korzysta ten proces oraz nazwa polecenia.

Uruchomienie tego polecenia dla pojedyńczego procesu wymaga podania opcji -p wraz z wartością, którą jest PID procesu oraz najlepiej podanie interwału czasowego w postaci suchej liczby po PID-zie.

xf0r3m@immudex:/media/xf0r3m/immudex_crypt0$ pidstat -p 2820 2
Linux 5.10.0-20-amd64 (immudex) 	11.01.2023 	_x86_64_	(4 CPU)

17:32:37      UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
17:32:39     1001      2820   13,50    5,00    0,00    0,50   18,50     1  mpv
17:32:41     1001      2820   13,00    3,00    0,00    0,00   16,00     0  mpv
17:32:43     1001      2820   14,00    4,50    0,00    0,50   18,50     0  mpv
17:32:45     1001      2820   12,50    4,00    0,00    0,00   16,50     1  mpv
17:32:47     1001      2820   14,00    4,00    0,00    0,00   18,00     1  mpv
17:32:49     1001      2820   15,00    2,50    0,00    0,00   17,50     3  mpv
17:32:51     1001      2820   14,00    2,50    0,00    0,50   16,50     2  mpv
17:32:53     1001      2820   15,00    3,50    0,00    0,00   18,50     1  mpv
17:32:55     1001      2820   11,50    5,50    0,00    0,00   17,00     3  mpv
^C
Średnia:    1001      2820   13,61    3,83    0,00    0,17   17,44     -  mpv

Przerywając działanie polecenia przed jego zakończeniem wyświetli nam o ono jedną dodatkową linię zawierającą średnią poszczególnych wartości uzyskanych podczas działania polecenia.

Więcej przydanych opcji dla tego polecenie znajduje się na stronie podręcznika.

8.13. Informacje dodatkowe

Powodem istnienia tak szerokiej gamy różnych narzedzi do monitowania wydajności jest możliwość użycia, nie których komponentów na różne sposoby. Najprostszym przykładem jaki przychodzi mi teraz na myśl jest wykorzystanie pamięci operacyjnej jako dysku. Inną rzeczą jest, iż te zasobo są ograniczone, a takie systemy jak serwery sieciowe wymagają stałego i intesywnego monitoringu. Dlatego jeśli chcemy lub musimy monitorować systemy, którymi zarządzamy lub będziemy zarządzać warto zapoznać się z poniższymi zagadnieniami.

9. Sieć

Komputery służą do przetwarzania informacji. Informacje wykorzystywane do obliczeń mogą pochodzić z różnych źródeł a jednym z nich może być inny system komputerowy. Jednak, aby to miało miejsce komputery muszą się w jakiś sposób komunikować ze sobą. W celach w miarę swobodnej komunikacji między systemami, komputery łączy się w sieci. Połączenie ze sobą dwóch komputerów za pomocą jednego kabla również możemy nazwać się siecią, ponieważ mechanizmy oraz komponenty związane z komunikacją pozozstają takie same jak dla większej ilości komputerów. Zagadnienia łączności sieciowej zostały w taki sposób zdefiniowane, aby były niezależne od systemu operacyjnego, ale to systemy definiują narzędzia do ich konfiguracji. W tym rodziale zapoznamy się z konfiguracją sieci w Linuksie.

9.1. Podstawy sieci

Dla łatwiejszego zrozumienia sieci, zaczniemy od zapoznania się z nazewnictwem, niektórych jej elementów. Nasz komputer podłączony do sieci nazywany jest hostem. Hosty często są podłączone do sieci lokalnej określanej jako LAN. Sieci tego typu są wszechobecne. Tego typu sieci posiadamy w naszych domach, nie przekraczają one swoim obszarem działania jednego budynku lub grupy pomiesczeń. Składają się one z routera łączącego sieć internet z siecią LAN oraz (zazwyczaj) z podłączonych do niego hostów. Definicja LAN-u nie ogranicza się tylko do hostów podłączonych za pomocą przewodów, łączność może zostać zapewniona tutaj bezprzewodowo nie ma na tym polu żadnych ograniczeń.

Poza możliwością podłączenia sieci LAN do internetu routery klasy SOHO (Small Office/Home Office) posiadają wiele usług przez co konfiguracja i obsługa hosta w sieci staje się bezobsługowa. Kilka z tych usług omówimy sobie w kontekscie Linuksa w tym rozdziale.

9.1.1. Pakiety

Jeśli żądana strona internetowa ma zostać przekazana do chcącego ją obejrzeć użytkownika, musi zostać specjalnie przygotowana do transmisji sieciowej. Takim przygotowaniem zajmują się poszczególne warstwy stosu TCP/IP. Dane strony (powiedzmy kod) przechodzi z jednej warstwy do drugiej będąc zamienianym w pakiety zrozumiałe dla tych samych warstw po drugiej stronie transmisji. Dane są przetwarzane przez poszczególne warstwy stosu aż staną się możliwe do przesłania przez fizyczny nośnik w postaci kabla miedzianego, światłowodu lub fal radiowych. Pakiety składają się najczęsciej z danych z poprzednej warstwy (zwanych ładunkiem) oraz dedykowanego tej warstwie nagłówka. Przetwarzanie danych przez stos TCP/IP nazwywane jest enkapsulacją Dwie warstwy stosu zostaną opisane w tym rodziale. Warstwą najwyższą zajmiemy się następnym rodziale. Natomiast warstwę najniższą omówimy sobie w dużym uogólnieniu.

9.1.2. Stos TCP/IP

W stosie TCP/IP możemy wyróżnić cztery różne warstwy:

  1. Warstwa aplikacji - określa sposób komunikacji między aplikacjami. Wewnątrz tej warstwy rezydują protokoły aplikacji wykorzystywane przez użytkowników takiej np. HTTP.
  2. Warstwa transportowa - określa sposób transmisji danych z warstwy aplikacji. W tej warstwie znajdują się protokoły takie jak TCP oraz UDP. Tutaj także rezyduje pojęcie portu. Warstwa transportowa będzie jeszcze omawiana w tym materiale.
  3. Warstwa internetowa - określa sposób dostarczania pakietów z hostów źródłowych do docelowych i odwrotnie. W tej warstwie rezyduje protokół IP oraz protokoły odpowiedzialne za trasowanie (znalezienie jak najlepszej drogi z jednego do drugiego hosta). W tej warstwie znajduje się również protokół ICMP odpowiedzialny z diagnozowanie problemów z protokołem IP.
  4. Warstwa łącza - ta warstwa definuje topologię logiczną sieci. Okresla ona metody transmisji danych przez użyty w sieci nośnik fizyczny. W tej warstwie znajdują się takie protokoły jak Ethernet oraz standard 802.11, definiujący transmisję bezprzewodową.

Warto wspomnieć o tym, że w dystrybucjach Linuksa trzy z czterch warstw znajdują się w jądrze systemu. Mogą jednak pojawić się wyjątki, wówczas pakiet może trafic do przetworzenia w przestrzeni użytkownika.

9.2. Warstwa sieciowa

Jak wiemy warstwa sieciowa definiuje sposoby dostarczenia pakietów z jednego do drugiego hosta. Ze względu na to, iż będziemy zajmować się sieciami internetowymi, skupimy się na jednym protokole tej warstwy na - protokole IP. Ten protokół posiada kilka funkcji, jednak dla nas na tym etapie najważniejsza będzie jedna z nich - adresacja. Każdy host aby mógł komunikować się w sieci musi mieć przypisany taki adres. Adres ten składa się (a przynajmniej w wersji 4 protokołu IP) z czterech grup liczb z zakresu od 0 do 255 rozdzielonych kropkami. Na przykład adres IP mojego komputera to:

192.168.8.101

Uzyskałem te informacje za pomocą poniższych poleceń. Te polecenia są tożsame:

xf0r3m@immudex:~$ ip address show
#lub
xf0r3m@immudex:~$ ip a

Drugi zapis jest skrótem pierwszego polecenia. Analizując wynik działania tego polecenia musimy zwrócić uwagę na dwie rzeczy. Pierwszą z nich są "dziwne" nazwy intefejsów sieciowych. Jeśli nikt nie korzystał z dystrybucji Linuksa przed 2016 rokiem to wówczas nie mógł spotkać się z klasycznymi nazwami interfejsów sieciowych. To jest pierwszy interfejs przewodowy = eth0 i tak dalej do ethX. Miało to jedną podstawową wadę znaną już z dysków. Otóż po którymś uruchomieniu ponownym system mógł wykryć karty sieciowe w innej kolejności niż poprzednio co powodowało problemy ze statyczną (zapisana na stałe w systemie) konfiguracją sieciową, ponieważ adresy niezgadzały się z adresacją podpiętej sieci. Obecnie ich nazwy są zaczerpnięte z modułów jądra, które są wykorzystywane do ich obsługi. Nie wszystkie dystrybucje stosują nową nomenklaturę dla interfejsów sieciowych. W dużej mierze są to systemy korzystające z innych programów typu init niż systemd, choć co prawda nie jest to regułą.

9.2.1. Adresacja IP

Inną rzeczą jest sposób prezentowania adresów IP. W linii rozpoczynającej się od słowa inet znajduje się adres IP przedstawioy w ten sposób 192.168.8.101/24. Jest to zapis w notacji CIDR. Na zapis ten składa się adres IP oraz maska podsieci w postacji ilości bitów.

Maska podsieci jest rodzajem adresu IP, który określa gdzie i jakie liczby z zakresu możemy wpisać do adresów IP hostów. Wyzanczając tym samym pierwszy oraz ostatni adres w sieci (początek i koniec sieci), na podstawie maski możemy określić ilość hostów w sieci. W jaki sposób się to dzieje? Otóż adres IP jak i maskę możemy przedstawić w postaci binarnej. W na poprzedni przykładzie maska wynosiła 24 bity. Co to oznacza? Aby sobie to wyjaśnić przedstawimy adres IP w postaci binarnej.

192.168.8.101 = 11000000.10101000.00001000.01100101

Z adresem poszło w miarę łatwo, ponieważ znaliśmy konkretne liczby. Jeśli policzymy wszystkie cyferki w postaci binarnej otrzymam wynik 32 cyfr, z racji tego że są to wartości binarne bardziej mówimy o bitach - podstawowej jednostce informacji, klasycznemu 0 oraz 1. Adresy IP w wersji 4 (można zapisać to skrótowo IPv4) mają długość 32-bitów. Powyżej omawiając maskę podsieci zostało wspomniane, że maska wyznacza początek oraz koniec sieci. Adres IP możemy przyrównać do adresów pocztowych, kolejne jego części mogą wskazywać na miasto, ulicę, budynek oraz na mieszkanie. Adresy pocztowe zawierają zazwyczaj stałe elementy jak nazwy miast, ulic czy numerację budynków. Tak samo jest w przypadku adresów IP. Te stałe elementy wydzielane są maskę podsieci. Mając maskę w postaci ilości bitów z notacji CIDR, możemy zapisać ją w ten sposób.

/24 = 11111111.11111111.11111111.00000000
/24 = 255.255.255.0

Więc zapis maski w notacji CIDR, to nic innego jak ilość bitów o wartości jeden (1) zapisanych od lewej w reprezentacji binarnej adresu IP. No dobrze, ale 32 - 24 = 8. Co zrobić z tymi 8 bitami? Otóż te bity pozostają do dyspozycji administratora i stanowią przestrzeń adresową. Maskę podsieci można podzieli na dwie częsci. Jedną częścią jest część sieciowa, która wyznacza stałą część adresu IP w adresacji hostów w sieci i ta część adresu IP jest niezmienna podczas adresacji. Drugą częścią jest część hosta, wskazująca, która część adresu IP będzie zawierać konkretną wartość wskazującą na hosta w sieci. Teraz złożymy ze sobą adres IP oraz maskę zapisaną w postaci dziesiętnej (255.255.255.0).

Adres IP: 192.168.8.101
Maska:    255.255.255.0

Poszczególne części adresu IP odzielone kropkami noszą nazwe oktetów, ponieważ w reprezentacji binarnej każda z częsci posiada 8 bitów. Zatem przyglądając się powyższemu przykładowi możemy dojść do wniosku, że czwarty ostatni oktet będzie przeznaczony na częśc hostów, i tylko on będzie się zmieniać podczas adresacji kolejnych hostów w tej sieci.

Warto zaznaczyć że przy prostych sieciach, maski mogą pozostać typowe tj. 24, 12 oraz 8 bit. Gdzie niegdzie spotykałem się również z maską 16 bitową. Dającą dwa oktety na część przeznaczną dla hostów. W przypadku 12-bitowej maski, nie ma już takiego eleganckiego podziału. Część bitów z drugiego oktetu pozostanie w części sieciowej, a druga część przejdzie do części hosta. Rozpiszmy sobie ten przykład. Załóżmy że nasz komputer jest podłączony do sieci 172.16.X.Y/12 i chcielibyśmy dowiedzieć się jak duża jest ta podsieć. Za stałą możemy przyjąć, że jeśli na konkretnym oktecie maski występuje 0, to ten sam oktet adresu IP może przyjąć wartości od 0 do 255.

172.16.0.0/12
10101100.00010000.00000000.00000000 = 172.16.0.0
11111111.11110000.00000000.00000000 = /12 = 255.240.0.0
#Część bazowa adresu IP po usunięciu wszystkich bitów z zermi na masce:
10101100.0001

10101100.00010000.00000000.00000000 = 172.16.0.0
1.           0000                   = 172.16
2.           0001                   = 172.17
3.           0010                   = 172.18
4.           0011                   = 172.19
5.           0100                   = 172.20
6.           0101                   = 172.21
7.           0110                   = 172.22
8.           0111                   = 172.23
9.           1000                   = 172.24
10.          1001                   = 172.25
11.          1010                   = 172.26
12.          1011                   = 172.27
13.          1100                   = 172.28
14.          1101                   = 172.29
15.          1110                   = 172.30
16.          1111                   = 172.31
10101100.00011111.00000000.00000000 = 172.31.0.0
(16 x 256 ^ 2) - 2 = 1048574

Myślę że powyższy przykład jasno przedstawia jak wygląda podział klasowych sieci. A co jeśli będziemy chcieli rozszerzyć sieci 192.168.8.0/24? Jeśli założym, że potrzebowalibyśmy kolejne 255 adresów to wówczas wystarczy zabrać z maski 1 bit. Da to maskę 23-bitową a my swojej sieci będziemy mogli zaadresować hosty od 192.168.8.1 - 192.168.9.254.

Każdy komputer posiadający skonfigurowaną w ten sposób warstwę internetową/sieciową może komunikować się z hostami w sieci. Konfigurowanie warsty sieciowej, jest chyba jedyną czynnością, w w której użytkownik będzie mieć styczność z stosem TCP/IP. Jednak aby móc skomunikować się za pośrednictwem routera z internetem potrzebujemy jeszcze kilku informacji.

9.2.2. Routowanie oraz tabela routingu

Każdy funkcjonujący w sieci system musi posiadać źródło informacji na temat gdzie należy przesłać pakiety lub jakiego interfejsu do tego celu należy użyć. Czynność ustalenia optymalnej trasy dla pakietu nazywa się routowaniem, natomiast informacje na temat jakie sieci są osiągalne i przez jakie interfejsy znajduje się w tabeli routingu. W systemie, który nie jest jakmiś routerem, tabela routingu będzie zawierać prawdopodobnie tylko dwa wpisy. Na poniższym przykładzie widnieje zrzut tabeli routingu z mojego komputera.

default via 192.168.8.1 dev enp0s31f6 onlink
192.168.8.0/24 dev enp0s31f6 proto kernel scope link src 192.168.8.154

Pierwsza linia zawiera definicję bramy domyślnej, którą zajmiemy się za chwilę. Natomiast druga linia wskazuje jaka sieć jest osiągalna przez jaki interfejs w tym przypadku sieć 192.168.8.0/24 jest dostępną przez interfejs enp0s31f6.

W systemach, które łączą ze sobą sieci (routerach) wpisów w tabeli routingu takich jak druga linia może być znacznie więcej. Możemym sobie wówczas zadać pytanie na jakiej podstawie jądro wybiera właściwy port. Otóż jądro przy wyborze będzie bazować na dwóch czynnikach. Pierwszy z nich jest oczywisty i chodzi tu adres częsci sieciowej adresu IP, musi on pasować do adresu sieci inaczej ma się rzecz jeśli jedna z sieci jest na tyle duża, że obejmuje swoim zakresem także adresacje innej sieci. Tutaj wówczas pojawia się drugi czynnik, którym jest długość maski/części sieciowe. Czasami nazywanej także prefiksem. Jeśli przy którymś z wpisów adresy sieci zachodzą na siebie, to wówczas dłuższy prefiks (a co za tym idzie, mniejsza sieć) jest wybierana jako trasa dla pakietu.

9.2.3. Brama domyślna

Omawiając tablę routingu wspomnieliśmy o bramie domyślnej. Brama domyślna jest rodzajem wpisu we wspomnianej tabeli a jej zadaniem jest przechowywanie domyślniej trasy, która wybierana jest jeśli żadna inna nie jest odpowiednia. Wynika to z faktu jak jądro wybiera właściwe dla pakietów trasy oraz adresu jaki kryje się za słowem default a jest nim 0.0.0.0/0. Oznacza on wszystkie hosty w adresacji IPv4. Co daje najmniejszy możliwy prefiks, dlatego też jeśli komputer nie posiada właściwej dla pakietu trasy to jest ona przekzywana do adresu przez który taka sieć jest osiągalna.

Tym adresem jest przeważnie adres routera, który łączy sieć LAN z inną siecią np. siecią usługodawcy internetowego. Zwyczajowo takie urządzenia zwykło się nazywać routerami, mimo ich możliwość to najczęściej wykorzystywaną funkcjonalnością jest automatyczna konfiguracja hostów (protokół DHCP, będzie o nim w tym rozdziale), pamięć podręczna i przekazywanie zapytań systemu DNS (o systemie DNS, też będzie tutaj) oraz translacja adresów (o adresach prywatnych oraz translacji adresów też sobie wspomnimy) oraz przekazywanie pakietów dalej do jednego z routerów usługodawcy, więc równie dobrze urządzenia tego typu można nazwać bramkami.

9.3. Adres IPv6

Adres IPv4 posiada długość 32 bitów, co daje nam możliwość zaadresowania 4,3 miliarda hostów w Internecie. Co jest o wiele mniejszym wynikem niż ilość ludzi na świecie, biorąc pod uwagę rozwój przedsiębiorstw oraz samego internetu wymaga od organizacji czuwających nad jego standaryzacją rozwiązań, które są w stanie sprostać wymaganiom czasów obecnych oraz przyszłości. Jedym z takich rozwiązań są prace na rozwojem oraz wdrożeniem nowego standardu jakim protokół IP w wersji 6.

Adres IP w wersji 6, ma długość 128-bitów, co czyni go 4 razy dłuższym od adresu IPv4. Adres ten nie przypomina adresu z poprzedniej wersji. Zamiast cyfr dziesiętnych użytko cyfr systemu heksadecymalnego, a kropki zastąponio dwukropkami. Poniżej znajduje się adres IPv6 komputera na którym pisze ten tekst.

fe80::921b:eff:fe6a:717d/64

Cyfry heksadecymalne są reprezentowane przez znaki: 0-9 oraz a-f. Każda grupa cyfr jest odzielona dwukropkiem, a każda z nich posiada po 4 znaki. Jeśli zero jest napoczątku grupy, to można je pominąć, jeszcze inną zależność jak możemy zauważyć jest to, jeśli grupa składa się z samych zero to można ją pominąć, wówczas w zapisie adresu widnieją dwa dwukropki obok siebie. Nie tyczy się to tylko jednej grupy, ale jeśli grupy zer następują po sobie to rownież można je pomniąć. Zapis natomiast nie ulegnie zmianie, jesli będzie więcej niż jedna grupa zer do pominięcia.

Jak możemy zauważyć adresy IPv6 również możemy zapisać w notacji CIDR. Tutaj maska wynosi 64-bit, co oznacza ze połowa adresu zajmuje prefiks. Natomiast druga połowa jest już częścią hosta.

Adresy IPv6 możemym podzielić na globalne, która mogą być osiągalne z internetu lub lokalne, które są przypisywane automatycznie przez systemy z obsługą IPv6. Każdy z tych typów posiada swój prefix i dla adresów globalnych (znanych także jako globalny adres hosta) prefix wynosi 2000::/3, przez co globalny adres hosta zaczyna się od 2 lub od 3 (ze zwględu na prefix, 3 pierwsze bity pierwszej cyfry są stałe (001), a zatem do dyspozycji mamym tylko jeden bit, którego zmiana nie dam nam innych wartości jak 2 lub 3). Pozostałe zera służa jako dopełnienie do zapisu. Z kolei prefiksem dla adresów lokalnych jest fe80::/10. W przypadku adresu łącza lokalnego resztę częsci sieciowej adresu wypełniają 54-bity zer. Na podstawie przedstawionych tutaj prefiksów możemy ustalić z jakiego rodzaju adresem mamy doczynienia.

9.3.1. Wyświetletnie adresów IPv6

Do wyświetlenia adresów IPv6 może posłużyć nam to samo polecenie, z którego korzystaliśmy do wyświetlenia adres IP w wersji 4. Mianowicie polecenie ip, po za samymi podpoleceniami tego polecenia dla adresów IPv6 podaje się także opcję -6.

$ ip -6 addr show

Podobnie rzecz ma się adresami IP tras, tutaj wykorzystuje się tę samą opcją natomiast inne podpolecenia.

$ ip -6 route show

9.4. Podstawowe narzędzia ICMP oraz DNS

W tym podrozdziale omówimy sobie podstawowe narzędzia wykorzystywane przy połączeniach sieciowych. Służą one do ustalania adresów IP oraz sprawdzania dostępności hosta.

9.4.1. Narzędzie ping

Protokół ICMP jest protokołem diagostycznym dla protokołu IP. Do jego podstawowych zadań należy sprawdzenie dostępności hosta w sieci. Do tego celu wykorzystujemy narzędzie ping. To niepozorne narzędzie wysyła specjalny pakiet z żądaniem odesłania pakietu z odpowiedzią. W przypadku tego narzędzia istotna jest sama odpowiedź ale poza nią zwracanych jest kilka statystyk, które pozwolają na przykład na określenie jakość połączenia oraz czasem stanu samej sieci (gdy odpowiedzi z bramy domyślnej przychodzą po ok. 1 sekundzie).

xf0r3m@immudex:~$ ping wp.pl
PING wp.pl (212.77.98.9) 56(84) bytes of data.
64 bytes from www.wp.pl (212.77.98.9): icmp_seq=1 ttl=54 time=11.2 ms
64 bytes from www.wp.pl (212.77.98.9): icmp_seq=2 ttl=54 time=11.0 ms
64 bytes from www.wp.pl (212.77.98.9): icmp_seq=3 ttl=54 time=14.1 ms
^C
--- wp.pl ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 11.049/12.096/14.062/1.391 ms

Na 56-bajtowe żądanie echa wysłane z naszego komputera host docelowy odpowiada 64-bajtowym pakietem. Jego zawartość nie jest istotna. Dla nas ważne są pola icmp_seq oraz time, gdyż swiadczą on one jakości połączenia pomiędzy naszym komputerem, a hostem docelowym. icmp_seq wskazuje kolejności otrzymywanych odpowiedzi. Jeśli jakiegoś pakietu brakuje, oznacza to że został on zgubiony podczas transmisji. Zagubione pakiety zdarzają się gdy korzystamy z połączenia bezprzewodowego przy słabym zasięgu. Drugim ważnym polem jest czas wyrażony w kolumnie time. Czas poniżej 30 ms oznacza że mamy bardzo dobre połącznie z hostem. Czas w sieci lokalnej oscyluje poniżej 1 ms. Natomiast jeśli jest powyżej jednej sekundy (1000 ms) lub w jej granicach oznacza, że gdzieś w sieci może znajdować się problem. Polecenie ping w dystrybucjach Linuksa ale i ogólnie w implementacji Uniksowej działa do momentu, aż nie przerwiemy mu działania. Na sam koniec zostanie wyświetlona statystyka zawierająca informacje o tym ile pakietów zostało wysłanych ile zostało odebranych, jaki procent pakietów został utracony. W drugiej linii podsumowania znajduje się podstawowe statystki związane z odpowiedzia na żądania.

Ze względów bezpieczeństwa nie wszystkie hosty (z właszcza w internecie są w stanie odpowiedzieć na żądania narzędzia ping. Pakiety mogą zostać do nich wysłane, ale jest wielce prawdopodobne, że zostaną zablokowane po drodze. Takie zachowanie jest przecidziałaniem przeciwko atakowi odmowy usługi, który opiera się na przesłaniu wielu żądań (echo request, typ pakietu nazywa się właśnie w ten sposób), tak aby host nie był w stanie obsłużyć żadnego innego połączenia sieciowego.

Istnieje możliwość wykorzystania pakietów echo request zarówno z protokołu IPv4 oraz IPv6. Wówczas należy wskazać to za pomocą opcji -4 lub -6 w poleceniu ping.

9.4.2. Narzędzie host

Narzędzie host jest najprostszym sposobem na skorzystanie z systemu DNS.

System DNS pozwala on zapamiętać ludziom typowy adres strony, bez potrzeby znajomości jej adresu IP, aby zrealizować to połączenie. Jeśli wpiszemy adres strony do przeglądarki, zapyta ona system DNS o jej adres IP i ten udzieli jej takiej odpowiedzi. Oczywiście w skrócie rzecz ujmując. Czasami możemy znaleźć się w odwrotnej sytuacji, kiedy będziemy znać adres IP, a chcielibyśmy się dowiedzieć do jakiej organizacji jest on przypisany. Jednak ta funkcjonalność nie jest kluczowa i rzadko bywa dobrze skonfigurowana, nie mniej jednak warto spróbować.

Aby skorzystać z narzędzia host, podajemy nazwę lub adres IP jako argument polecenia.

xf0r3m@immudex:~$ host morketsmerke.org
morketsmerke.org has address 213.186.33.5
morketsmerke.org mail is handled by 10 mx3.mail.ovh.net.
morketsmerke.org mail is handled by 1 mx4.mail.ovh.net.

xf0r3m@immudex:~$ host 93.184.216.34
Host 34.216.184.93.in-addr.arpa. not found: 3(NXDOMAIN)

xf0r3m@immudex:~$ host 213.186.33.5
5.33.186.213.in-addr.arpa domain name pointer redirect.ovh.net.

Na powyższym przykładzie widzimy trzy wywołania polecenia host jedno z nich klasyczne pytające o adres IP i dwa pytające o nazwę domenową, zwróćmy uwagę na to w jaki sposób zostały zapisane adresy IP. Nie będę tutaj przytaczać więcej informacji o sposobie działania systemu DNS, gdyż wykraczają one poza ramy tego materiału. Do systemu DNS wrócimy podczas konfiguracji interfejsu hosta.

9.5. Warstwa fizyczna

Wiemy, że sieć internetowa oparta jest o oprogramowanie. Nie miej jednak gdzieś zagadnienia sieciowe muszą stykać się ze swiatem realnym, z tym że komputery koniec, końców są ze sobą połączone za pomocą specjalnych przewodów oraz z lub bez pośrednictwa dodatkowych urządzeń. Tymi elementami zajmuje się warstwa fizyczna. Standardów implementacji warstwy fizycznej (tego w jaki sposób informacje przesyłane są przez media (przewody lub fale radiowe)) jest wiele, jednak wykorzystywaną po dzień dzisiejszy oraz najpowszechniejszą z nich jest Ethernet. Sprzęt sieciowy, mimo tej samej implementacji będzie różnić się od siebie ze względu na użyte medium transmisji, ale mają one wiele cech wspólnych. Oto kilka z nich:

Host mając nawet dwa interfejsy sieciowe podłączone do różnych sieci Ethernet nie może przekazać pakietu z jednej do drugiej sieci, chyba skonfigurowano na nim most. Więc jeśli dane mają zostać wysłane do internetu, potrzebny jest udział warstw wyższych, albowiem każda sieć fizyczna jest podsiecią sieci warstwy wyższej, zatem router może odebrać ramkę, wyodrębnić jej dane a następnie przepakować do nowej ramki i wysłać dalej w świat.

9.6. Sieciowe interfejsy jądra

Jądro samodzielnie odróżnia warstwę fizyczną od sieciowej, przez co musi zapewniać im standard w komunikacji. Czymś takim właśnie jest interfejs sieciowy. W trakcie jego konfiguracji przypisane parametry warstwy internetowej są łączone z parametrami urządzenia. Nazwy interfejsów opierają się o rodzaj sprzętu na którym bazują. Np. enp0s4, oznacza zazwyczaj kartę sieciową podłączoną do magistrali PCI. Nazwy tego typu są określane nazwami przewidywalnymi i nie zmieniają się one ponownym uruchomieniu systemu. Jest to jednak prowadzone głównie program typu init - systemd. Aby wyświetlić interfejsy wydajemy polecenie:

xf0r3m@immudex:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 90:1b:0e:6a:71:7d brd ff:ff:ff:ff:ff:ff
    inet 172.16.1.2/16 brd 172.16.255.255 scope global enp4s0
       valid_lft forever preferred_lft forever
    inet6 fe80::921b:eff:fe6a:717d/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s25: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 90:1b:0e:87:3c:43 brd ff:ff:ff:ff:ff:ff

Polecenie ip a, jest poleceniem skrótowym dla polecenia ip address show. Interfejsy sieciowe są numerowane kolejno do numeru 2, numer 1 zarezerowany jest dla specjalnego interfejsu systemowego - zwanego pętlą zwrotną. Każdy działający interfejs w linii z numerem oraz nazwą posiada flagę UP. Poza informacjami warstwy sieciowej możmy również zauważyć informacje z warstwy fizycznej takie jak adres MAC. Polecenie ip a, głównie skupia się na warstwie sieciowej. Jeśli chcielibyś poznać więcej szczegółów związanych z warstwą fizyczną, wówczas należało by użyć polecenia takiego jak ethtool.

9.7. Konfiguracja interfejsu sieciowego

Do tej pory omawialiśmy kwestie sieciowe wyłącznie teoretycznie, a to przeanalizowaliśmy sobie wynik jednego czy drugiego polecenia, ale teraz zajmiemy konfiguracją interfejsu sieciowego. Aby użytkownik dowolnej dystrybucji mógł połączyć się z internetem musimy wykonać cztery poniższe czynności:

  1. Zapewnienie obecności sterownika karty sieciowej w jądrze. Jeśli sterownika będzie brakować, to nawet nieskonfigurowany interfejs nie zostanie wyświetlony po wydaniu polecenia ip a.
  2. Dodatkowa konfiguracja warstwy fizycznej, na przykład poprzez podanie nazwy sieci oraz hasła (głównie sieci bezprzewodowe).
  3. Ustawienie dla interfejsu adresu IP oraz maski podsieci (dzięki temu warstwy będą mogły się ze sobą komunikować).
  4. Ustawienie wszystkich wymaganych tras w tym trasy domyślnej.

Wiedząc teraz co musimy zrobić. Przejdziemy do wykonania tych czynności w systemie.

9.7.1. Ręczna konfiguracja interfejsu

Reczna konfiguracja interfejsów sieciowych rzadko jest wymagana, chyba eksperymentujemy z systemem lub próbujemy skonfigurować urządzenie, które samo nam interfejsu nie skonfiguruje. Nie mniej jednak, a własnego doświadczenia wiem, że warto posiadać taką wiedzę. Więc tak załóżmy, że posiadamy sterowniki do karty sieciowej (interfejsu), którą chcemy skonfigurować oraz łączymy się z siecią Ethernet za pomocą kabla miedzianego, dlatego też punkt nr. 2 też możemy pominąć. Jeśli tak rzeczywiście jest to wówczas konfiguracja interfejsu sprowadza się do dwóch poleceń.

Aby skonfigurować ręcznie interfejs musimy musimy użyć polecenia ip wraz z podpoleceniem address lub addr, następnie podać komendę add po niej adres IP wraz z maską w notacji CIDR do tego opcję dev po której należy wskazać nazwę interfejsu jaki chcemy skonfigurować.

xf0r3m@immudex:~$ sudo ip address add 172.16.1.1/16 dev enp4s0

Wykorzystując powyższe polecenie mamy pierwszą część za sobą. Pamiętajmy jednak aby zmienić dane.

Drugą czynnością jest dodanie tras. Do połączenia z internetem potrzebna będzie na pewno brama domyślna. Jeśli będziemy potrzebować dodatkowych tras wystarczy zmodyfikować poniższe polecenie:

xf0r3m@immudex:~$ sudo ip route add default via 172.16.0.1 dev enp4s0

Za trasy odpowiada podpolecenie route, komenda add odpowiada za dodawanie tras. Następnie podawana jest sieć jaka ma być osiągalna. Po opcji via podawny jest adres IP na jaki mają być kierowane pakiety do docelowej sieci, tym adresem może być adres routera sieci lokalnej lub nawet jeden z interfejsów zainstalowanych na tym samym komputerze, na koniec po opcji dev podajemy interfejs, przez który adres IP podany po opcji via jest osiągalny.

Jeśli trasa jest nam nie potrzebna lub pomyliliśmy się podczas jej dodawnia, to taką trasę bez obaw można usunać, przy użyciu komendy del podpolecenia route. Komenda przyjmuje adres sieci jako argument.

xf0r3m@immudex:~$ sudo ip route del 192.168.60.0/24

9.8. Konfiguracja interfejsów sieciowych podczas rozruchu

Próba konfiguracji interfejsu podczas rozruchu mogła by wyglądać w taki sposób, że gdzieś podczas sekwencji rozruchowej program typu init (lub jeden z jego skryptów) mógłby wydać polecenia ip (takie jak te, które wkorzystywaliśmy do ręcznej konfiguracji) lub innego narzędzia. Pytanie tylko jakiego? A odpowiedzi tyle co wiodących dystrybucji.

Do tej pory próbowano ustandaryzować ten proces za pomocą poleceń ifup oraz ifdown. Z niezbyt zadawalającym skutkiem ze względu na różne implementacje tych narzędzi przez osoby zajmujące się rozwojem dystrybucji, czego następstwem są różnice w plikach konfiguracyjnych. Kolejną problematyczną rzeczą jest fakt iż wiele elementów sieciowych wykorzystywanych do konfiguracji znajduje się różnych miescach w systemie i odpowiadają za nie inne komponenty. W Linuksie panuje również zasada aby nie rozpowszechniać plików konfiguracyjnych w przypadku wielu osobnych narzędzi czy bibliotek, ponieważ zmiany wprowadzone w jednym narzędziu mogą spowodować, że inne przestaną działać.

Więc tak chcąc skonfigurować interfejsy sieciowe podczas rozruchu musimy skorzystać z jednego z dostępnych managerów lub dostępnego w systemie kompnentu. W Debianie, interfejsy sieciowe są zarządzane przez dodatek ifupdown, którego składową jest plik /etc/network/interfaces. Na Ubuntu system Netplan, od najnowszysch wersji dystrybucji opartych o Red Hat, korzysta się głównie z managerów. Natomiast managerem godnym uwagi jest NetworkManager i to na nim się skupimy podczas ich omawiania. Aby ten podrozdział wniósł coś pożytecznego poza kilkoma teoretycznymi dywagacjami omówimy sobie konfigurację interfejsów na przykładzie dystrybucji Debian.

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto enp4s0
iface enp4s0 inet static
        address 172.16.1.2
        netmask 255.255.0.0
        gateway 172.16.0.1
        dns-nameservers 172.16.0.1

Na powyższym przykładzie mamy dwie deklaracje interfejsów sieciowych. Pierwszym z nich jest interfejs pętli zwrotnej. Jego rola w systemie jeszcze zostanie omówiona. Nie miej jednak jego deklaracja zawiera słowo auto, która powoduje załączenie interfejsu podczas startu systemu. Po linii ze słowem auto następuje definicja interfejsu (linia ze słowem iface) w niej podawana jest nazwa interfejsu (enp4s0), stos protokółów (inet, co oznacza IPv4. Mogą być też inne). Następnie podawana jest metoda konfiguracji interfejsu. Najczęściej wykorzystywanymi są static oraz dhcp, w przypadku pętli zwrotnej w tym miejscu używane jest słowo loopback. W przypadku drugiego (właściwego) interfejsu adres przypisywany będzie statycznie. To tak samo jakbyśmy przypisywali go ręcznie, jest to czynność automatyczna wykonywana podczas rozruchu. Po uruchomieniu ponownym komputera interfejs powinien zostać automatycznie podniesiony (załączony) i skonfigurowany przy użyciu podanych w pliku parametrów.

Obecnie systemy służą użytkownikom końcowym, którzy zapewne mają uprawnienia administratora, ale nie posiadają odpowiedniej wiedzy aby móc konfigurować odpowiednie warstwy. Proste rozwiązania, takie jak to opisane powyżej daje oczywiście taką mozliwośc, jednak nie jest ono zbyt przystępne dla użytkownika, dlatego też w dystrybucjach takich Red Hat, stosowane są managery, których zadaniem jest ułatwienie konfiguracji.

9.9. Menedżery konfiguracji sieciowe

Dla dystrybucji Linuksa dostępnych jest wiele programów będących menedżerami konfiguracji sieciowe. Część z nich przeznaczona jest na systemy wbudowane. Dlatego godnymi uwagi pozostają tylko dwa. Jeden będący częścią programu typu init systemd-networkd nadaje się on wykonania podstawowej konfiguracji sieciowej. Jest wystarczający dla serwerów, ale nie dla użytkonika końcowego. Wówczas pozostaje jeden - NetworkManager i to na nim się skupimy.

NetworkManager jest demonem takim samym jak pozostałe więc jego zadaniem jest nasłuchiwanie zdarzeń generowanych zarówno przez system jak i przez samych użytkowników, dodatkowo NetworkManager zajmuje się konfiguracją sieciową zgodnie z założonymi regułami. Program ten operuje na informacjach o dostępnych urządzenia uzyskanch od jądra oraz na połączeniach, które są wskazaniem konkretnych urządzeń wraz z parametrami warstw sieciowych. Konfigurując sieć w danej dystrybucji, NetworkManager korzysta z określonego dodatku aby dostosować konfigurację do standardu używanego przez dystrybucję, nie narzucacjąc przy tym swoich rozwiązań.

W momencie uruchomienia NetworkManager, zbiera informacje o dostępnych urządzeniach sieciowych, sprawdza listę połączeń i podejmuje decyzję o próbie połączenia. Proces decyzyjny na podstawie, którego menedżer decyduje, z którą z sieci chce się połączyć można rozpisać w trzech krokach.

  1. Jeśli dostępne jest połaczenie kablowe. To ono jest preferowane jako pierwsze.
  2. Jeśli połączenie kablowe nie jest dostępne, wówczas jeśli w systemie dostępny jest interfejs bezprzewodowy to skanowane jest otoczenie podzwględem występowania sieci bezprzewodowych. Jesli dostępne są sieci, z którymi system już kiedyś nawiązał połączenia. To jest ono nawiązywane ponownie. Jeśli nie to dostępna jest lista sieci bezprzewodowych osiągalnych na danym terenie.
  3. Jeśli dostępne są więcej niż dwie sieci, z którymi już wcześniej nawiązywano połączenie to wówczas wybierana jest, z którą łączono się jako z ostatnią.

Połączenie sieciowe zwłaszacza wśród sieci bezprzewodowych utrzymywane jest do momentu uzyskanie połączenia o lepszych parametrach. Na przykład, po przez wpięcie kabla.

9.9.1. Interakcja z Network Manager

Kontrolować NetworkManager możemy za pomocą apletu, przygotowanego dla środowiska graficznego. Jednak w tym materiale skupiamy się wyłącznie na rozwiązaniach opartych o wiersz polecenia. Więc przejdziemy od razu do tego typu interakcji z tym narzędziem. Do kontrolowania NetworkManager z poziomu powłoki, służy narzędzie nmcli. Za pomocą podpoleceń tego narzędzia możemy kontrolować połączenia sieciowe zarządzane przez omawiany menedżer.

Innym narzędziem związanym z NetworkManager-em, jest polecenie nm-online, które zwraca jedynie infomacje w zależności od tego czy posiadamy aktywne połączenie czy też nie.

Poniżej znajduje się przykładowe uruchomienie, nmcli bez żadnych podpoleceń.


      

Polecenie to agreguje informacje zwracane przez wiele poznanych do tej pory poleceń, takich jak ip addr lub ip route show. Dodatkowo wyświetlane są informacje o konfiguracji systemu DNS.

Dla przykładu możemy utworzyć ręczną konfigurację dla interfejsu tym razem wykorzystując do tego NetworkManager.

xf0r3m@immudex:~$ sudo nmcli con add con-name my-connection ifname enp4s0 \
type ethernet ipv4 192.168.8.150/24 gw4 192.168.8.1

Efekt działania tego polecenia możemy sprawdzić za pomocą polecenia ip a lub za pomocą nmcli

xf0r3m@immudex:~$ ip a

xf0r3m@immudex:~$ nmcli -p con show my-connection

Opcja -p formatuje wynik polecenia, aby był on bardziej czytelny dla człowieka. Więcej przykładowych poleceń wraz z opisami znajduje się na stronie podręcznika: man 7 nmcli-examples. NetworkManager to naprawdę rozbudowane narzędzie więc warto mieć z tyłu głowy to polecenie, chociażby dlatego, że znajduje się tam opis sposobu łączność z siecią bezprzewodową z poziomu wiersza poleceń. Sposób dużo bardziej przystępny niż samodzielne skanowanie i obcowanie z pakietem wpa_supplicant, gdzie często demon tego narzędzia może być już użwany przez jeden z komponentów systemu.

9.9.2. Konfiguracja NetworkManager

Konfiguracja NetworkManagera wymaga edycji pliku /etc/NetworkManager/NetworkManager.conf. Plik jest formatu XDG lub wśród osób zaznajomionych z systemami od Microsoftu formatu .ini. Rzadko będzie potrzeba zmiany czego kolwiek w tym pliku. Nie mniej jednak jest jedna funkcją warta omówienia związana z konfiguracją tego menedżera - wyłączenie zarządania interfejsu przez NetworkManager. Interfejs pętli zwrotnej jest izolowany od tego narzędzia i jeśli uznamy, że inny/inne interfejsy w systemie też powinny to możemy zadeklarować w tym pliku. W sekcji [keyfile] (jeśli nie ma takowej, to należy dopisać). Plik ma format XDG, więc opcje zapisywane są w postaci klucz=wartość. Tak więc do klucza unmanaged-devices przypisujemy adres MAC interfejsów, które mają być wyłączone z spod zarządzania przez NetworkManager w taki sposób jak przedstawio to poniżej.

...
[keyfile]
unmanaged-devices=mac:08:00:27:3d:1b:9c

Inną kwestią związaną z konfiguracją omawianego w tym rodziale menedżera jest propagacja informacji o zmianie status interfejsu sieciowego. Niektóre demony do swojego poprawnego działania muszą otrzymać takową informację o tego typu sytuacji, aby zakończyć lub rozpocząć nasłuchiwanie na danym interfejsie. NetworkManager realizuje to za pomoca sieciowych skrytów sterujących znajdujących się w katalogu /etc/NetworkManager/dispatcher.d. Jeśli status interfejsu sieciowego ulegnie zmianie menedżer uruchomi wszystko co zostało umieszczone w powyższym katalogu z odpowiednim dla stanu interfejsu (up lub down). Na przykład w dystrybucjach opartych na Debianie (w tym i Ubuntu) istnieje plik 01-ifupdown, który na podstawie otrzymanego komunikatu zdrzenia (jeśli przyjrzeć się plikowi, to dowiemy się że to nie tylko up oraz down) uruchomi wszystko znajduje się w odpowiednich podkatalogach w katalogu /etc/network.

Dla nas zarówno konfiguracja NetworkManager może być mało istotna, podobnie jest ze skryptami rozsyłającymi. Nie mniej jednak może się kiedyś zdarzyć, że będzię potrzeba zmiany czegoś w skrypcie tego typu lub wyłączenie interfejsu z menedżera.

9.10. Rozwiazywanie nazw hostów.

Jeśli chcemy nawiązać jakie kolwiek połączenie sieciowe musimy podać adres komputera, z którym chcemy to połączenie nawiązać. Ludzie rzadko posługują się adresami IP, chyba że są to specjaliści lub entuzajści nauk komputerowych i pracują oni w lokalnych środowiskach. Takie adresy trudno zapamiętąć, więc niezbędne jest zapewnienie systemu/usługi, która zmieni adresu domenowe (np. morketsmerke.org) na adres IP. Z systemem DNS mieliśmy już styczność, w momencie gdy omawialiśmy protokół ICMP. Konfiguracja adresów systemu DNS jest tak naprawdę czwartą ostatnią wartością konfigurowaną na interfejsie sieciowym, aby mógł on nawiązać połączenie z internetem. Jest to o tyle ciekawe, że system DNS należy do warstwy aplikacji. Większość aplikacji sieciowych w Linuksie ma możliwość odpytania systemu DNS o adres IP, dlatego możemy zarówno użyć adresów IP lub nazw domenowych (chyba, że wyszczególniono inaczej). Uzyskiwania takiego adresu od DNS zwykle przebiega w następujący sposób:

  1. Przy uzyciu systemowej biblioteki współużytkowanej aplikacja wywołuje funkcję wyszukiwania adresu IP.
  2. Określa się kolejność odpytywania źródeł na podstawie danych zapisanych w pliku /etc/nsswitch.conf. Jedną z takowych reguł może być wymuszenie kolejności odpytania na początku pliku /etc/hosts jeszcze przed odpytaniem systemu DNS.
  3. Jeśli funkcja jednak zdecyduje o skorzystaniu z systemu DNS, musi użyć zawartości pliku konfiguracyjnego, w którym zawarte są adres serwerów systemu DNS.
  4. Funkcja wysyła zapytanie z adresem domenowym serwera, prosząc o jego adres IP.
  5. Serwer DNS odsyła do funkcji adres IP serwera, który zwracany jest do aplikacji.

Często występującym scenariuszem jest to, że serwer systemu DNS nie zna adresu IP serwera, o który prosimy, wówczas serwer, do którego skierowaliśmy to zapytanie musi zapytać się innych serwerów. DNS ma budowę hierarchiczną, a cała hierarchia jest zapisana w adresie domenowym. Szerszy opis systemu DNS wykracza poza ramy meryteoryczne tego materiału.

9.10.1. Pliki biorące udział w działaniu systemu DNS

Jak możemy wywnioskować z wyżej wymienionych czynności system DNS nawet w roli klienta potrzebuje korzysta z dużej ilości plików. Pierwszym z nich jest /etc/nssswitch.conf. Ten plik nie jest związany stricte z systemem DNS, ale ze wszystkim co jest związane z nazwami w systemie. Jest to klasyczny interfejs określający pierwszeństwo w źródłach rozwiązywania nazw. Jeśli przeanalizujemy sobie zawartość tego pliku, możemy zauważyć skąd i na jakiej podstawie, niektóre z narzędzi czerpią wiedzę taką jak informacje o użytkownikach, usługach czy innych rodzajach sieci niż IP. Jednak na tym etapie interesuje nas wyłącznie linia rozpoczynająca się od hosts. Wskazuje ona pierwszeństwo pliku /etc/hosts nad system DNS, przez co korzystając z dystrybucji możemy dowolnie manipulować rozwiązywaniem nazw. Jest to szczególnie przydane podczas administracji i testowania usług, które później będziemy chcieli użyć.

Kolejnym plikiem jest wspomaniany już wcześniej /etc/hosts. Plik ten zawiera zestawienie adresów IP wraz z nazwami domenowymi. Kiedyś kiedy hosty w internecie można było policzyć na palcach obu rąk służył za centralną bazę danych odnosnie odwzrowania nazw. Był on swojego czasu protoplastą systemu DNS. Jednak ze względu na bum rozwoju internetu, zaprzestano korzystania z tej praktyki. Jednak obecnie wciąż można spotkać go w roli lokalnej bazy rekordów DNS w sieciach, gdzie centralnym urządzeniem (router/bramka core'owy) jest urządzenie oparte na jakimś uniksie.

Ostatnim plikiem jest /etc/resolv.conf. Zawartość tego pliku, ma wpływ na to z jakiego źródła systemu DNS korzysta nasz system. Otóż w tym pliku przechowywane są zazwyczaj dwie wartości pierwszą z nich są adresy serwerów systemu DNS, a drugim sufiks nazwy domenowej wykorzystywany wtedy gdy podamy samą nazwę hosta bez nazwy domeny, to znaczy jeśli na przykład podamy samo www zamiast www.morketsmerke.org, wówczas funkcja na podstawie sufiksu z tego pliku sama go doda i przekaże żądanie dalej na jeden z adresów widniejących po opcji nameserver.

9.10.2. Buforowanie oraz bezkonfiguracyjne systemy DNS

Buforowanie to czyność, którą głównie się kocha a czasem nienawidzi. Buforowanie odpowiedzi systemu DNS jest odpowiedzią na fakt, że komputery odpytujące system DNS nie przechowywały nigdzie uzyskanych od serwera odpowiedzi, zbyt często powtarazna tego typu czynność mogła przytkać sieć. Obecnie bardzo często adres serwera DNS jest również adres bramy. Ma to głównie na celu właśnie buforowanie, na naszym domowym routerze za pewne uruchomiony jest demon buforująco-przekazujący. Niestety w przypadku wielu urządzeń nie będzie my się wstanie tego dowiedzieć. Ale jeśli nasz sprzęt jest zmodyfikowany, lub naszym routerem jest jest oprogramowanie zainstalowane na komputerze z wieloma kartami sieciowymi, to raczej na 100% tak będzie. Innym sposóbem na wykrycie systemu buforującego, jest sprawdzenie zawartości pliku /etc/resolv.conf. Jeśli widnieje tam tylko jeden wpis nameserver to za pewne z adresem 127.0.0.53. Jest to adres z klasy pętli zwrotnej (o której będzie za chwilę), czyli wiemy, że to usługa uruchomiona lokalnie na tej maszynie. Jest to jeden z demonów programu typu init (systemd) - systemd-resolved. Często w tym wypadku sam plik jest dowiązaniem symboliczym. Użycie tego rodzaju systemu rozwiązuje inny problem związany z brakiem elastyczności statycznych konfiguracji, wówczas nie ma potrzeby wprowdzania zmian w komponentach systemu.

Wspomniany wcześniej demon posiada jedną ważną cechę. Potrafi łączyć wiele sposobów wyszukiwania nazw w sieci, co przekłada się na możliwość użycia bezkonfiguracyjnych systemów DNS. W przypadku takiej konfiguracji jeśli podłączymy nowe urządzenie do sieci będziemy mogli wywołać je za pomocą nazwy. Jeśli host istnieje udzieli odpowiedzi ze swoim adresem IP. Rozwiązaniami tego typu są mDNS (multicastDNS) oraz LLMNP (Link-Local Multicast Name Protocol). Rozwiązania te poza zwykłym odwzorowaniem nazw, mogą przedstawiać dostępność usług. Ich dostępność w systemie możemy sprawdzić za pomocą polecenia resolvectl status, warto jednak pamiętać, że jego działanie jest uzależnione od działania wcześniej wspomnianego demona.

9.11. Host lokalny

W wielu systemach operacyjnych, poznając ich możliwości sieciowe możemy natknąć się na dośc specyficzny interfejs, który w uniksach określany jako lo. Jest to jedyny interfejs, który będzie posiadać w plikach konfiguracyjnych statyczną konfigurację. Interfejs pętli zwrotnej bo tak jest określany przez swój sposób działnia służy głównie interakcji z programami sieciowymi bez potrzeby angażowania innych interfejsów. Na przykład jeśli aplikacja jest w fazie testów i póki co nikt po za naszym komputerem nie powinien się z nią łączyć. To wtedy taką aplikację ustawia się tak aby oczekiwała na połączenia na interfejsie pętli zwrotnej. Wówczas pakiety będą przesyłane przez ten interfejs w obu kierunkach i opuszczając tym samym jednej fizycznej maszyny. Można powiedzieć również że adres pętli zwrotnej to połączenie z samym sobą.

Jeśli wyświetlimy sobie konfigurację interfejsu pętli zwrotnej to zwrócimy uwagę na to, jaki adres został mu przypisany:

xf0r3m@immudex:~$ ip addr show lo
1: lo: <LOOPBACK,UP> mtu 1500 group default qlen 1
    link/loopback 00:00:00:00:00:00
    inet 127.0.0.1/8 brd 127.255.255.255 scope global dynamic
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host dynamic
       valid_lft forever preferred_lft forever

Jest to bardzo duża klasa adresowa. Jeśli przypisany do usługi adres będzie rozpoczynął się od 127 i posiadał 8 bitową maskę, będzie wówczas przypisany do pętli zwrotnej.

9.12. Warstwa transportowa oraz usługi

Przedostatnią warstwą modelu TCP/IP jest warstwa transportowa. Jak sobie wcześniej omówilismy definiuje ona sposób transmisji w sieciach IP. Wewnątrz niej możemy znajdują się takie protokoły jak TCP oraz UDP, ona także definiuje pojęcie portu.

9.12.1. TCP - Transmission Control Protocol

TCP jest protokołem przepływu danych w sieciach IP. Jest to protokół, który przed właściwą transmisją danych zestawia coś w rodzaju połączenia. Za pomocą specjalnych pakietów kontrolnych strona odległa informowana jest o nadchodzącej transmisji, i jeśli faktycznie jest ona w stanie odebrać dane (port na który, strona lokalna chce wysłać informacje jest otwarty) odsyła informacje zwrotną o tym, że może wysyłać. Wówczas po wymianie pakietów kontrolnych, dochodzi do wysłania danych z komputera lokalnego do komputera zdalnego. Protokół TCP czuwa nad przebiegiem transmisji, każdy pakiet ma swój numer i strona zdalna po jego otrzymaniu wysyła pakiet kontrolny z potwierdzeniem jeśli odpowiedź nie przychodzi w określonym czasie wówczas dochodzi do retransmisji zagubionych pakietów. TCP jest protokołem wolniejszym od innego protokołu z tej samej warstwy, zapewnia jednak niezawdność transmisji, przez co strona odległa może mieć pewność, że uzyska poprawne dane. Ten protokół wykorzystywany jest w wiekszości usług w internecie, w których przenoszone przez jego łącza dane mają znaczenie użytkowników końcowych. Dane są po prostu istotne.

9.12.2. UDP - User Datagram Protocol

UDP jest trochę innym protokołem niż TCP, ale jego główna rola jest bardzo podobna, z tą różnicą, że nie zawiera on żadnych mechanizmów kontroli. Ma to swoje plusy i minusy. Protokół jest na tyle szybki, że wykorzystywany jest przez różnego rodzaju aplikacji służące do przesyłania strumieniowego. W tym wypadku możemy osobiście doświadczyć błędów w transmisji objawiających się spadkiem jakość streamowanych materiałów. Inną ważnym wykorzystaniem tego, protokołu, właśnie ze względu na zalety protokołu TCP jest użycie UDP do budowania sieci prywatnych VPN. Faktem, dla którego nie korzysta się protokołu TCP jest piekło retransmisji. Załóżmy, że dochodzi do błedów w zewnętrznej transmisji, czyli między serwerami VPN i jeśli byłaby to transmisja TCP doszło by do retransmisji, system VPN będzie musiał zatrzymać się wówczas host docelowy transmisji wewnętrznej zauważy zagubienie pakietu i rozpocznie swoją retransmisję co doprowadzi do zapchania łącza i wstrzymania trasnsmisji w ogóle. Dlatego też stosuje się protokół UDP dla transmisji zewnętrznej, wówczas nad poprawnością danych przenoszonych przez tunel VPN czuwać będą hosty transmisji wewnętrznej. Jak możemy zauważyć wszystko co może być zaletą w konkretnym przypadku może okazać się wadą w innym.

9.12.3. Porty

Z wyżej wymienionych metod transmisji przy uzyciu portów korzystają różne aplikacje. Port to nic innego jak numer wystąpienia aplikacji w obszarze warstwy transportowej. Na ten numer kierowane są pakiety i w ten sposób aplikacja otrzymuje dane z sieci. Aplikacje sieciowe posidaja przypisane te numery przez swoich twórców, ich lista wstępuje w każdym uniksie, który ma możliwośc podłaczenia do sieci. Lista znajduje się w pliku, /etc/services. Większość zebranych tam aplikacja powstawa wraz z internetem, więc lista może być długa i już nie zbyt aktualna, ale jednak jest pewnym zbiorem informacji. Poniżej znajduje się kilka protokołów wraz z informacją z której pochodzi źródło.

# Updated from https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml .
http            80/tcp          www             # WorldWideWeb HTTP
https           443/tcp                         # http protocol over TLS/SSL
https           443/udp                         # HTTP/3
http-alt        8080/tcp        webcache        # WWW caching service

Obok portu wyświetlony jest również rodzaj transmisji. Ciekawostką jest fakt, że trzecia generacja protokołu HTTPS będzie oparta na protokole UDP, a nie TCP.

9.12.4. Wyświetlanie połączeń sieciowych w systemie

Omawiając warstwę transportową to nie sposób jest nie wspomnieć o wyświetlaniu połączeń. Obecnie w dystrybucjach Linuksa używa się nowego polecenia jakim jest ss, aby wyświetlić połączenia TCP w systemie należy wydać poniższe polecenie. Polecenie to dla przykładu zostało wydane na jednym z moich serwerów VPS

root@searx:~# ss -tr
State       Recv-Q  Send-Q          Local Address:Port                                     Peer Address:Port   Process
CLOSE-WAIT  32      0        ip109.ip-51-178-2.eu:57026                                   146.75.74.208:https
CLOSE-WAIT  64      0        ip109.ip-51-178-2.eu:32808                     text-lb.drmrs.wikimedia.org:https
CLOSE-WAIT  64      0        ip109.ip-51-178-2.eu:43164                     text-lb.drmrs.wikimedia.org:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:57466                                    52.84.150.51:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:35092     server-143-204-68-27.lhr61.r.cloudfront.net:https
CLOSE-WAIT  25      0        ip109.ip-51-178-2.eu:45492                   upload-lb.drmrs.wikimedia.org:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:41928     server-143-204-68-21.lhr61.r.cloudfront.net:https
CLOSE-WAIT  32      0        ip109.ip-51-178-2.eu:36336              qwantbot-115-168-187-194.qwant.com:https
ESTAB       0       52       ip109.ip-51-178-2.eu:2022                                     213.25.29.68:63950
CLOSE-WAIT  130     0        ip109.ip-51-178-2.eu:60580                        waw02s07-in-f4.1e100.net:https
CLOSE-WAIT  47      0        ip109.ip-51-178-2.eu:33310                                    104.16.55.16:https
CLOSE-WAIT  130     0        ip109.ip-51-178-2.eu:43572                        waw02s07-in-f4.1e100.net:https
CLOSE-WAIT  32      0        ip109.ip-51-178-2.eu:36666                                   140.177.50.13:https
CLOSE-WAIT  1       0        ip109.ip-51-178-2.eu:33408                       par21s17-in-f14.1e100.net:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:53446                                    172.67.22.17:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:57454                                    52.84.150.51:https
ESTAB       0       0        ip109.ip-51-178-2.eu:2022                                     213.25.29.68:61628
CLOSE-WAIT  32      0        ip109.ip-51-178-2.eu:53654                            www.wolframalpha.com:https
CLOSE-WAIT  32      0        ip109.ip-51-178-2.eu:58752              qwantbot-109-168-187-194.qwant.com:https
CLOSE-WAIT  130     0        ip109.ip-51-178-2.eu:58880                        waw02s07-in-f4.1e100.net:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:37804              qwantbot-106-168-187-194.qwant.com:https
CLOSE-WAIT  25      0        ip109.ip-51-178-2.eu:50924     server-18-244-117-84.lhr50.r.cloudfront.net:https
CLOSE-WAIT  64      0        ip109.ip-51-178-2.eu:43148                     text-lb.drmrs.wikimedia.org:https
CLOSE-WAIT  90      0        ip109.ip-51-178-2.eu:41176                                  151.101.65.181:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:58504    server-18-244-153-127.lhr50.r.cloudfront.net:https
CLOSE-WAIT  40      0        ip109.ip-51-178-2.eu:58216      server-13-224-132-41.lhr3.r.cloudfront.net:https

W pierwszej kolumnie znajduje się stan połaczenia, jak już wcześniej wspominałem protokół TCP ustanawia połączenia i ta kolumna przedstawia ich stan. Estab skrót od ang. established oznacza że połączenie zostało nawiązane i dalej trwa. Dalej przesyłane są jakieś pakiety. Reszta połączeń pozostaje w stanie CLOSE-WAIT jest to stan oczekiwania na zamknięcie połączenia przez użytkownika. Przeważnie nie musimy się przejmować tego rodzaju połączeniami, one zostaną zakończone z czasem. Druga i trzecia kolumna przechowują informacje odnośnie pamięci podręcznej wysyłania i odbioru. Najlepiej aby te wartości były jak najbliżej zera. Wysokie wartości w kolumnie Recv-Q mogą świadczyć o tym, że pakiety osiągneły komputer docelowy, ale nie ma kto ich odebrać, na natomiast w wysokie wartości w kolumnie Send-Q mogą najczęściej oznaczać, że pakiety zostały wysłane ale do hosta, który na nie oczekuje i nie potwierdza ich odebrania lub pakiety nie zostały wysłane w ogóle. Czwarta oraz piąta kolumna zawierają gniazda lokalne oraz zadalne. Za pomocą gniazd określamy połączenie adresu IP lub nazwy domenowej (tak, jak w tym przypadku) z portem. O gniazadach w kontekście Uniksów więcej informacji przedstawię w następnym rozdziale.

Wydając powyższe polecenie użyłem opcji -t określającej rodzaj transmisji (-t - TCP) oraz opcji -r, która zamienia adres IP oraz numery portów na nazwy domenowe i nazwy usług. Warto zapoznać się zawartością strony podręcznika narzędzia ss. Można odnależć opcje pozwalającą na zamykanie dowolnych połączeń. Połączenia (może, raczej rejestr transmisji) dla protokołu UDP możemy wyświetlić za pomocą opcji -u.

9.13. Protokół dynamicznej konfiguracji hosta - DHCP

Ciężko sobie wyobrazić współczesną sieć bez tak ważnego składnika jakim jest protokół DHCP. Jest on odpowiedzialny za konfigurację interfejsów sieciowych oraz za utrzymywanie informacji o przekazanych parameterach, aby nie dochodziło do absurdalnych zdarzeń, np. żeby dwa komputery w sieci nie dostały tego samego adresu. Poza adresami IP serwer DHCP, konfigurje interfejsy w taki sposób, aby komputer był podłączony do internetu. W sieciach korporacyjnych mogą być konfigurowane dodatkowe paramety lub domyślna pula może nie istnieć, a nowe urządzenia trzeba dodawać do listy rezerwacji, w tym wypadku komputery uzyskują statyczny adres IP ale nie jest on przypisywany przez administratora, tylko przez DHCP właśnie.

Metoda uzyskiwania adresu jest dość niezwykła. Otóż jak komputer, który nie posiada adresu IP ma skomunikować z innym komputerem i jakimi. Otóż z każdym. Klient DHCP rozsyła na pakiet z poszukiwaniem serwera DHCP, na adres rozgłoszeniowy sieci (255.255.255.255) w tym pakiecie może być zawarty adres, jaki klient kiedyś posiadał. W odpowiedzi od serwera, klient dostanie ofertę, z którą może się zgodzić lub żądać innych, bądź dodatkowych parametrów, wówczas zostaje wysłane żądanie. Po uznaniu żądania, serwer wysyła potwierdzenie. Kiedy klient otrzyma potwierdzenie rozpoczyna konfigurację interfejsu sieciowego i na tym etapie rola DHCP kończy się. Jednak należy pamiętać, że adresy są dzierżawione od serwera, czyli czas konfiguracji jest określony i od czasu do czasu trzeba się z tym serwerem skomunikować, aby utrzymać obecną kofigurację lub prosić o nową. Jednak to pozostaje już w gestii klienta.

Obsługa protokołu DHCP na uniksach występuje w oryginalnej implementacji. Do dyspozycji mamy pakiet klienta dhclient, wśród serwerów możemy wyróżnić co najmniej dwie godne polecenie implementacje. Jedną z nich wykorzystamy tworząc z komputera z dystrybucją Linuksa domową bramkę. Aby pobrać konfigurację z serwera DHCP (nie które dystrybucje, nie posiadają domyślnie żadnej konfiguracji sieciowej) należy wydać poniższe polecenie.

xf0r3m@immudex:~$ dhclient enp4s0

Podanie interfejsu jako argument spowoduje skonfigurowanie tylko tego konkretnego interfejsu, jeśli go pominiemy skonfigurowane zostaną wszystkie aktywe (w których warstwy poniżej internetowej już działają).

Jeśli chcelibyśmy poznać np. czas dzierżawy to informacje uzyskane od serwera znajdują sie w pliku dzierżawy /var/lib/dhcp/dhclient.leases

Do tej pory omawiając DHCP, skupialiśmy się głównie na protokole IPv4. W przypadku protokołu IPv6 również istnieje możliwość zastosowania DHCP, nawet został przygotowany ku temu standard protokołu DHCPv6 to proponowane jest innego rodzaju rozwiązanie. Ze względu na długość adresów IPv6, organizacja zarządzająca wykorzystała częśc adresów na potrzeby nowej metody konfiguracji. Konfiguracja bezstanowa nie wymaga punktów centralnych w postaci serwerów do konfiguracji interfejsów dla IPv6.

Metoda działania tej konfiguracji opiera się o adresy sieci lokalnej łącza, ze względu długość adresu host może wygenerować sobie adres, który może z mały prawdopodobnieństwem powtórzyć się w sieci. Z resztą mając ustalony prefiks (fe80::/64) można wysłać zapytania do innych hostów w sieci czy adres, który host wygenerował jest wykorzystywany przez inne hosty w sieci. Jest to mało prawdopodobne gdyż do wygenerowania tego adresu bierze udział adres MAC. Po uzyskaniu łącza lokalnego, host może określić adres globalny z pomocą komunikatów RA (Router Advertisement) wysłanego co jakiś czas przez router w naszej sieci lokalnej. Komunikat zawiera prefiks sieci globalnej, adres routera oraz adresy serwerów DNS. Po otrzymaniu tych informacji może podjąć próbę wygenerowania części adresów odpowiadającej za identyfikator interfejsu. W ten sposób interfejs otrzymuje dostęp do internetu za pośrednictwem protokołu IP w wersji 6.

9.14. Konfiguracja systemu Linux jako router

Ten podrozdział będzie dość obszerny, ponieważ postanowiłem zebrać w nim część zagadnień sieciowych dotyczących konfiguracji dystrybucji Linuksa w celu wykorzystania jako router.

Chcąc wykorzystać komputer z dystrybucją Linuksa jako bramę czy router (w zależności ile sieci będzie łączyć) musimy zaopatrzyć nasz komputer w odpowiednią ilość interfejsów. Jak możemy się domyślić interfejsy to nic innego jak karty sieciowe. Jeśli jest to zwykły PC to dołożenie kart nie powino być problemem (zakładając, że nasza sieć jest oparta na miedzianej skrętce) a najzwyklejsze karty (gigabitowe) mogą kosztować w okolicach 50 zł jeśli nie mamy możliwości rozbudowania komputera o więcej niż jedną kartę to możemy użyć kart serwerowych, one zazwyczaj posiadają od 2 do 4 portów a zajmują jeden port PCI-E x16 (rzadko, kto będzie mieć w komputerze przeznaczonym na router, złącza PCI-E x8 lub x4, bo takie złącze będą mieć karty tego typu).

9.14.1. Oprogramownie główne

Wiele urządzeń dostępnych na rynku jako router posiada przyjazny, z którym możemy połączyć się za pośrednictwem przeglądarki internetowej, a obecnie cześć producentów posiada również aplikacje mobline do ich kontrolowania. Tego typu rozwiązania zapewniają nam łatwość konfiguracji, która często sprowadza sie to wpisania kilku rzeczy i klikania Dalej, dalej, dalej... i na koniec zrestartowania urządzenia. Na tym zadanie się, kończy za pośrednictwem tego urządzenia komputery w naszej sieci lokalnej będą posiadać połączenie z internetem. Mimo to na tych sprzętach w dużej mierze przeważa Linux. Ten system w warunkach wbudowanych jest wstanie uruchomić się z każdego urządzenia. Czytając te słowa, zabrneliśmy tak daleko w głąb tego materiału, że możemy z pewnością stwierdzić, że domyślnie na Linuksie próżno szukać takich udogodnień.

Każda dystrybucja posiada wiele cech specyficznych, z racji tego iż nie narzucałem żadnej konkretej (chociaż, Debian tutaj przoduje). To warto wybrać tę której używaliśmy do tej pory jeśli jest to wiodąca dystrybucja to możemy zainstalować takie oprogramowanie jako cockpit, jest to środowisko do zarządania serwerem z poziomu WWW, przez przeglądarkę. Dostępne jest ono w wiekszości dystrybucji. Niestety nie daje ono zbyt rozbudowanych możliwości. Cechą dominującą przy wyborze powinna być stabliność dystrybucji, dlatego takie dystrybucje jak Debian lub dystrybucje klasy Enterprise (pomijając te płatne) jak Rocky czy Alma Linux sprawdzą się tutaj znakomicie. Problemem z tymi dystrybucjami jest trochę inna konfiguracją zapory sieciowej, ale ona opisana jest w innym moim materiale odnośnie RHCSA. Zatem wybierając taką dystrybucję mamy nieco więcej pracy, ale możemy mieć pewność, że nie spotkamy się z różnymi, nie zawsze przemyślanymi rozwiązaniami dostawców różnych nakładek czy dystrybucji skierowanych na routery. Sam osobiście wole takie rozwiązanie.

Innym, dużo prostszym podejściem do tego typu zadania, może być użycie dystrybucji przeznaczonej do zamieniania PC-tów z kilkoma kartami sieciowymi w router, takimi jak np. dystrybucja IPFire. Takich dystrybucji nie tylko opartych o Linux jest wiele więcej. Część z nich jest już niewspierana lub wątpliwej jakość (zeroshell, może dlatego upadła). Cała lista dostępna jest pod tym adresem.

Możemy również zmienić całkowicie podejście do tematu. Jeśli nie mamy wolnego komputera, którego możemy użyć w roli routera. To możemy użyć domowego routera, jako mikroserwer z Linux. Za pomocą dystrybucji takiej jak DD-WRT, a szczególnie OpenWRT możemy uzyskać dostęp dystrybucji Linuksowej na tym nie pozornym urządzeniu. Lista kompatybilnego sprzętu, na którym możemy zainstalować OpenWRT jest dość obszerna. Więc może znajdziemy system pasujący do naszego sprzętu. Dodam również, że obsługa tego typu dystrybucji może może opierać na konkretnym narzędziu i żadne z przedstawionych poniżej rozwiązań, skierowanych raczej do dystrybucji ogólnych, takich jak Debian, może nie działać. Wówczas zachęcam do zapoznania się z dokumentacją.

Poniższe rozwiązania bedą dotyczyć głównie wiodących dystrybucji. Więc jeśli zdecydujemy się inne rozwiązania wykonanie zadania możemy oprzeć o dostępną dokumnetację.

9.14.2. Oprogramowanie dodatkowe

Poza system operacyjnym i jego wewnętrznymi komponentami sieciowymi do zbudowania pełno prawnego routera lub bramy potrzebujemy kilku pakietów:

Jeśli jesteśmy w posiadaniu odpowiedniego sprzętu nasz router może rozgłaszać sieć bezprzewodwą. Jeśli chcielibyśmy coś takiego zrealizować to warto zapoznać się z tematem tworzenia mostów. Jeśli taki most miedzy LAN-em a WLAN-em zostanie zestawiony to oba interfejsy zostaną połączone jednym wirtualnym, który będzie obsługiwał połączenia z zupełnie różnych dwóch warstw fizycznych.

Dostępność oraz sposób instalacji tych pakietów dla wybranych przez nas dystrybucji (ze względu na ich róznorodność) należy ustalić we własnym zakresie. Poniżej znajduje się polecenie dla dystrybucji Debian oraz pokrewnych.

xf0r3m@immudex:~$ sudo apt install dnsmasq

To polecenie zainstaluje tylko jeden pakiet. Debian 11 jest już rozprowadzany zainstalowanym pakietem zapory.

9.14.3. Konfiguracja dystrybucji jako router

Po złożeniu sprzętu oraz zainstalowaniu oprogramowania. Przyszedł czas na jego konfigrację. Najlepiej rozpocząć ją (a wręcz trzeba) od konfiguracji interfejsów. Musimy ustalić wykorzystywany przez nas sposób łączenia z Internetem, czy posiadamy statyczny adres IP lub interfejs jest konfigurowany dynamicznie przez naszego usługodawcę. Te informacje musimy zapisać w charkterystyczny dla naszej dystrybucji sposób konfiguracji interfejsów. Jeśli chcemy możemy wykorzystać do tego celu NetworkManager. Pamietając, że w przypadku Debiana interfejs wykorzystywany podczas instalacji będzie kontrolowany przez system ifupdown, trzeba go stamtąd usunąć. Po skonfigurowaniu interfejsu łączącego z internetem musimy skonfigurować interfejsy sieci LAN. Tutaj wystarczy sam adres oraz maska podsieci. Adres ten musi być jednym z adresów sieci prywatnych IPv4.

Sieci prywatne IPv4

Sieci prywatne to nic innego jak wycięte klasy adresowe sieci IP w wersji 4. Te adresy mają zastosowanie jedynie w sieciach lokalnych, ale i usługodawcy również mogą się nimi posługiwać, ponieważ to co wpinamy w port WAN routera jest umowne i tak naprawdę naszym WAN-em, może być sieć osiedlowa, która jest nieco wiekszym LAN-em lub będąc bardziej szczegółowym siecią kampusową. Adresy sieci prywatnych powstały ze względu na wyczerpywanie się adresów publicznych. Można by się zastanowić nad tym w jaki sposób zabranie prawie 18 milionów adresów, przyczniło się do tego, że sieci IPv4 jest na wyczerpaniu to nadal są przydzielane konkretne adresy. Otóż adresy klas prywatnych nie mogą funkcjonowac w internecie, są tylko do użytku wewnetrznego. Największa klasa A (posidająca grubo ponad 16 miliownów adresów) może być widoczna w internecie jako jeden adres. Dzięki technologii translacji adresów (bardziej szczegółów informacje na ten temat znajdują się poniżej). Stąd taka optymalizacja. Dostępne mamy trzy klasy adresowe:

Gdzieś wśród młodych adeptów informatyki, wzięło się przeswiadczenie, że klasy B oraz C są dużo mniejsze. Klasie B przypisywało się wówczas maskę 16-bitową, odcinając wszystkie adresy powyżej 172.16.255.255. A klasie C przypisywano maskę 24-bitową, przez co zmniejszano ją do 254 adresów. Ten sposób adresacji wziął się z nie wiedzy osób konfigurjących sprzęt oraz z narzucenia przez producentów domyślnie takich wartości. Nie jest to błędem, ponieważ możemy alokować te adresy odpowiednio do swoich potrzeb. Dlatego jeśli nie potrzebujemy tak dużej sieci, to możemy ją zmniejszyć dzieląc te sieci na podsieci i wykorzystując tylko jedną z nich.

Po konfiguracji interfejsów możemy przjeść do konfiguracji serwera DHCP, który jest również serwerem buforująco-przekazujący systemu DNS. Przyczym konfiguracja DNS-u nie wymaga od nas żadnej uwagi dnsmasq zaraz po uruchomieniu zaczyna nasłuchiwać na porcie 53/UDP. Naszym zadaniem będzie jedynie ograniczenie tego nasłuchiwania do portów LAN. Dla przykładu żałożymy, że naszym interfejsem LAN-u jest klasyczny port eth0.

interface=eth0
dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,12h
dhcp-option=3,192.168.4.1
dhcp-option=6,192.168.4.1

Pierwsza opcja interface=eth0 ogranicza działanie demona tylko i wyłącznie do interfejsu eth0. Dzięki tej opcji nasłuchiwanie na porcie 53/udp (DNS) zostanie ograniczone do wskazane interfejsu. Pozostałe opcje konfigurją nam już serwer DHCP. Pierwsza opcja dhcp-range definiuje nam zakres przydzielanych adresów, maskę podsieci oraz czas dzierżawy. Następnie zaczynają się typowe opcje protokołu DHCP, są one opisane w standardzie, ale dla zachowania kontekstu przytocze je również tutaj opcja 3 odpowiada za przypisanie adresu domyślnej bramy, natomiast opcja 6 odpowiada za przypisanie adresów serwerów DNS. Adres bramy oraz adres serwera DNS wskazują na adres portu LAN. Te cztery linie wystarczą aby skonfigurować serwer DNS i DHCP dla naszej sieci. Teraz po podłączeniu komputera do routera powinniśmy uzyskać adres IP wraz z pozostałymi paramerami pozwalającymi na połączenie z internet za pośrednictwem naszego systemu.

Aby zrealizować połączenie z internetem hostów podłączanych do sieci LAN musimy wykonać jescze dwie czynności. Umożliwić w systemie przsyłanie pakietów między interfejsami, domyślnie jest ono wyłączone oraz włączyć funckję NAT-u.

NAT - Translacja adresów sieciowych

Translacja adresów potocznie zwana NAT-em, jest funkcja zapór sieciowych, której zadaniem jest najczęściej zamiana adresów sieci LAN, na adres sieci publicznej. Tak aby komputery mogły wysyłać oraz odbierać dane z sieci rozległej nie będąc do niej bezpośrednio podłączone. Istnieje kilka rodzajów translacji adresów nas będą interesować tylko dwa. Przeznaczone dla ruchu wychodzącego (danych generowanych przez komputery z sieci LAN). Do wybory wówczas mamy SNAT - ang. Source Network Address Translation lub MASQUERADE. Użycie SNAT-u będzie wymagać podania adresu IP portu WAN, teoretycznie nic w tym złego, ale jeśli nasz usługodawca automatycznie przypisuje swoim klientom adresy IP, za każdą zmianą adresu na tym interfejsie połączenie z internetem zostało by zerwane. Zatem SNAT jest szybszą metodą NAT-u, ale wymaga stałego adresu IP. Jeśli takim nie władamy, pozostało nam tylko użycie MASQUERADE, który pozwala na podanie interfejsu. Działanie NAT-u opiera się na podstawieniu w miejscu adresu źródłowego pakietu albo adresu podanego w regule (SNAT) albo adres interfejsu (MASQUERADE).

Za przekazywanie pakietów między interfejsami odpowiada jądro. Aby je włączyć musimy skorzystać z interfejsu ukrytego za poleceniem sysctl. Do konkretnych wartości prowadzi ścieżka, której elementy są odzielone od siebie kropkami. Poniżej znajduje sie polecenie, które odpowiada za przekazywanie pakietów miedzy interfejsami.

xf0r3m@immudex:~$ sudo sysctl net.ipv4.ip_forward=1

Teraz pakiety będą swobodnie przekazywane między interfejsami. Tak jak wspomniałem NAT jest funkcją zapory sieciowej. Tak więc konfiguracja dystrybucji Linuksa jako router będzie wymagać jej konfiguracji. Co prawda będzie to jedno polecenie, ale żeby nie pozostawiać użytkownika w niewiedzy trzeba trochę przybliżyć temat działania zapory jaką jest iptables.

Zapora sieciowa w systemie Linux

Zapora sieciowa w dystrybucjach Linux realizowana jest przez pakiet iptables, jego zasada działania opiera się o łańcuchy zasad pod kątem których sprawdzany może być pakiet przychodzący, wychodzący a nawet dodatkowo przekazywany dalej. Łańcuchy łączą się w tablice, które mogą modyfikować moment konfrontacji pakiety z łańcuchem, ale ogólna zasada pozostaje bez zmian. Każdy łańcuch posiada domyślną politykę, która jest stosowana dla pakietów niepasujących do żadnej z reguł. Domyślnie jest to polityka przepuszczaj, oznacza to swobodną transmisję. Wśród najważniejszych tablic oraz łańcuchów możemy wyróżnić:

Iptables konfrontując pakiet z łańcuchem zastosuję akcję dla pierwszej reguły do której będzie pasować przetwarzany pakiet. Natomiast możemy wyróżnić trzy rodzaje akcji podstawowych takich jak:

Istnieją także dodatkowe akcje specyficzne dla tablicy. Przeważnie po podaniu akcji w tablicy filter reguła się kończy. W przypadku akcji SNAT oraz DNAT tablicy nat wymagane jest podanie docelowego adresu IP. Akcją bez argumentów dla tej tablicy jest MASQUERADE uruchamiające maskaradę.

Z powyższego wywodu możemy wywnioskować, że aby uruchomić NAT na naszym routerze musimy zapisać regułę tablicy nat w łańcuchu POSTROUTING. Więc tak, tablicę wskazujemy z pomoca opcji -t, teraz w zależności od tego czy chcemy wstawić w konkretną linią naszą regułę czy ją dopisać musimy użyć innych opcji. Opcja -I odpowiedzialną za wstawianie (INSERT) wymaga po podaniu nazwy łańcucha numeru linii, w którą ma wstawić daną regułem, natomiast opcja -A odpowiedzialna za dopisanie (APPEND). Ze względu na to, że jest to łańcuch POSTROUTING, to raczej będzie on pusty, więc nie będzie to miało znaczenia czy użyjemy dopisania czy wstawienia. Drugą rzeczą jest wskazanie interfejsu, na którym interfejsy będą wymagały NAT-u, aby transmisja miała sens. W przypadku wyjściowych łańcuchów takich jak POSTROUTING czy OUTPUT interfejs podwany jest po opcji -o (nazywane jest kierunkien od in/out, jednak łańcuchy mają już zdefiniowane możliwe kierunki transmisji) następnie po opcji -j podawana akcja, zawsze wieliki literami. Więc nasza reguła uruchamiająca NAT prezentuje się następująco. W przypadku tego oraz poniższych przykładów dotyczących zapory sieciowej interfejsem przeznaczonym do połączenia z siecią WAN będzie eth1.

xf0r3m@immudex:~$ sudo iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

Teraz o ile komputery podłaczone do interfejsu LAN posiadają prawidłowe adresy IP, powinny mieć już połączenie z Internetem. Mimo, że wszystko funkcjonuje prawidłowo to pozostawienie tak podłączonego routera do Internetu pozostawia wiele do życzenia. Pierwszym rażącym zaniedbanie jest fakt iż router będzie przyjmował połączenia z internetu, z każdym dowolnym portem, w ramach lepszego poznania pakietu firewall-a na Linuksie możemy nieco bardziej zabezpieczyć naszą transmisję.

Jak zapewne pamiętamy, każdy łańcuch posiada swoją politykę, która jest stosowana dla każdego pakietu, które nie zostanie przypasowana żadna reguła. Moglibyśmy ustawić ją na odrzucanie i dopuścić tylko potrzebny ruch. To dobry pomysł szczególnie jeśli myślimy o interfejsie WAN, ale zły jeśli pomyślimy interfejsie LAN. Ponieważ możemy zdefiniować polityki per interfejs, ten pomysł odpada, ponieważ dla interfejsu LAN musiała by powstać długa lista reguł dopuszczająca ruch do wszystkie bardziej oraz mniej znanych usług. Dlatego też pozostawimy politykę bez zmian i dopuścimy tylko niezbędny ruch. Na pewno z internetu musimy przyjąć dane od serwera DNS, aby rozwiązywać dotąd nieznane nazwy oraz w zależności konfiguracji interfejsu WAN dane z serwera DHCP usługodawcy. Łańcuch INPUT będziemy konfigurować dodają na początku reguły zezwalające na ruch, żeby następnie zablokować pozostały nieokreślony regułami.

xf0r3m@immudex:~$ sudo iptables -A INPUT -i eth1 -p udp --sport 53 -j ACCEPT
#Opcjonalnie:
xf0r3m@immudex:~$ sudo iptables -A INPUT -i eth1 -p udp --sport 67 -j ACCEPT
#Zablokowanie pozostałego ruchu
xf0r3m@immudex:~$ sudo iptables -A INPUT -i eth1 -p udp -j DROP;
xf0r3m@immudex:~$ sudo iptables -A INPUT -i eth1 -p tcp --syn -j DROP;

Dwie pierwsze reguły dopusczają ruch z sieci WAN (interfejs eth1) na porcie 53/udp (serwer systemu DNS) oraz 67/udp (serwer DHCP). Pozostały ruch protokołu UDP został zablokowany. Podobnie jest z całym ruchem na protokole TCP, tutaj dodano także modyfikator --syn. Jak wspominałem omawiając protokół TCP, musi on zestawić połączenie, aby rozpocząć transmisje co wymaga wysłania kilku pakietów sterujących. Pakiet wysyłany aby zainicjować zestawienie połączenia TCP posiada własnie ustawioną flagę SYN (pakiety TCP posiadają specjalne miejsce na flagi). I tego typu pakiety są właśnie blokowane. To rozwiązanie skutecznie zablokuje próbę połączenia, ale prawdopodbnie nie uchroni przed metodą rozpoznawczą jaką jest skanowanie portów. Należało by tutaj zamiast flagi, stanów połączenia.

Ze względu na to, iż zapora na Linuksie to temat rzeka. Małe ćwiczenie. Usprawnienie tej ostatniej reguły, takby aby opierała się na stanach TCP. (Podpowiedź: Zablokowanie ma służyć nawiązywaniu nowych połączeń z naszym routerem. Transmisja przez hosty w sieci, ktore łączy nasz routera powina przebiegać bez zarzutu).

Iptables nie jest bez wad jedną z nich jest utrzymanie reguł w łańuchach do momentu wyłączenia systemu. Dlatego systemy, w których instalowany jest ten pakiet dostępny jest też pakiet odpowiadający za zapisanie reguł w pliku i przywrócenie ich w momencie startu systemu. Pakiet nazywa się iptables-persistent dostępny jest w repozytoriach Debiana.

xf0r3m@immudex:~$ sudo apt install iptables-persistent

Zapisanie reguł odbywać się będzie za wydaniem poniższego polecenia.

xf0r3m@immudex:~$ sudo iptables-persistent save

Teraz router jest skonfigurowany i gotowy do działania. Routery programowe mają to do siebie, że ich funkcjonalność można rozszerzyć np. o serwer plików. Konfiguracje takiego serwera plików będziemy jeszcze omawiać w tym materiale. Także będziemy jeszcze wracać do naszego routera.

Kolejną kwestią jest komunikacja między sieciami. Jeśli nasz router łączy ze sobą więcej niż jedną sieć lokalną i ich komputery mają się ze sobą komunikowac to należy utworzyć statyczne trasy, wskazujące przez, który interfejs należy komunikować się z daną siecią.

xf0r3m@immudex:~$ sudo ip route add 192.168.1.0/24 via 192.168.1.1 dev eth2
xf0r3m@immudex:~$ sudo ip route add 192.168.4.0/24 via 192.168.4.1 dev eth0

Trasy te mogą zostać utworzone automatycznie jeśli np. interfejs sieci WAN zostanie podniesiony podczas automatycznej konfiguracji. Jeśli takie zachowanie nie jest przez nas porządane to w najlepszym wypadku jest zablokować transmisję między sieciami na firewall-u. Ninejszym temat konfigruacji routera uważam za zakończony. Nie mniej jednak to nie koniec rodziału. Zostały jeszcze dwa tematy do omówienia.

9.15. Sieci IP oparte na Ethernet. Protokół ARP i NDP

Mając na uwadzę to, że chcąc wysłać jakieś dane musimy podać adres hosta docelowego. Zazwyczaj takim adresem jest albo adres IP albo nazwa domenowa. Te piewsze wykorzystywane są głównie w sieciach lokalnych przez specjalistów. Zatem podając nazwę domenową aplikacja musi rozwiązać ją na adres IP i dokonuje tego za pomoca systemu DNS. To dzieje się w warstwie aplikacji. Warstwa transportowa definiuje port oraz rodzaj transmisji dla wysłanych danych. Najczęściej są to znane protokoły jak HTTPS, protokoły przesyłania strumieniowego oraz protokoły poczty. Warstwa sieciowa posiada już adres IP hosta docelowego i bez względu na to czy host docelowy znajduje się w tej samej sieci czy nie, pakiet przekazywany jest dalej i tutaj pozostaje nam pewna nie wiadoma, skąd host źródłowy wie jakim adresem MAC ma zaadresować ramkę ethernet?

Tutaj pojawia się protokoły ARP oraz RARP. Ten drugi jest może mniej wykorzystywany. Ich zadaniem jest zamiana adresów IP na MAC i odwrotnie. Głownie chodzi tu o protokół ARP i to nanim się się skupimy. Informacje na temat adresów MAC hostów w sieci przechowywane są w tablicy lub w buforze ARP. Jednak co jeśli tablica jest pusta? Wówczas protokół ARP dla docelowego adresu IP tworzy ramkę z żądaniem ARP. Taka ramka rozsyłana jest w całej sieci. Jeśli jeden ze znajdujących się w niej hostów posiada adres MAC dla adresu IP z żądania wówczas odsyłana jest odpowiedź. Nasz host na podstawie odpowiedzi uzpełnia swoją tablicę o ten wpis wówczas może on rozpocząć transmisję. Dostęp do tablicy w dystrybucjach Linuksa odbywa się z pomocą:

xf0r3m@immudex:~$  sudo ip -4 neigh
172.16.2.20 dev enp4s0 lladdr 74:27:ea:e9:cc:14 STALE
172.16.7.6 dev enp4s0 lladdr 70:85:c2:a4:bf:b9 STALE
172.16.8.2 dev enp4s0 lladdr 6c:4b:90:be:91:74 STALE
172.16.6.1 dev enp4s0 lladdr 78:45:c4:12:6a:5d STALE
172.16.2.100 dev enp4s0 lladdr 3c:84:6a:44:d7:a4 STALE
172.16.2.46 dev enp4s0 lladdr 74:27:ea:ea:98:8a STALE
172.16.17.15 dev enp4s0 lladdr 78:45:c4:09:23:4c STALE
172.16.2.146 dev enp4s0 lladdr 1c:69:7a:cd:f4:cb STALE
172.16.2.119 dev enp4s0 lladdr 74:27:ea:ea:63:56 STALE
172.16.8.13 dev enp4s0 lladdr 6c:4b:90:be:88:16 STALE
172.16.0.1 dev enp4s0 lladdr 90:e2:ba:45:4f:8c REACHABLE
...

Wpisy w tablicy same ulegją usunięciu jeśli nie nastąpiła aktywność przez pewien czas. Nie mniej jednak istnieje możliwość ręcznego usunięcia wpisów za pomocą polecenia:

xf0r3m@immudex:~$ sudo ip neigh del 172.16.8.13 dev enp4s0

Efektem działania tego polecenia będzie usunięcie wpisu dotyczączego host 172.16.8.13.

Protokołem o działaniu udwrotnym jest RARP, jego zadaniem była zamiana adresów MAC na adresy IP, przez co wykrzystywany był do automatycznej konfiguracji hosta, przed upowszechnieniem się protokołu DHCP. Obecnie jest już rzadko spotykany.

Za pomocą podpolecenia neigh polecenia ip mogliśmy sprawdzić tablicę ARP w systemie. Kiedyś używano do tego polecenia arp. Obecnie doszło do unifikacji przez narzędzie ip obsługi protokołu ARP dla IPv4 oraz protokołu NDP dla IPv6. NDP jest podobny rozwiązaniem dla IPv6. Jego działanie opiera na dwóch rodzajach komunikatów:

9.16. Sieć bezprzewodowa

Logiczna konstrukcja sieci bezprzewodowej, nieco rózni się od połączeń kablowych kilkoma charakterystycznymi elementami w warstwie fizycznej. Sieci bezprzewodowe definiowane są przy użyciu standardu 802.11 i jego poszczególnych odmian (np. 802.11a lub 802.11n) Poniżej opiszemy sobie cechy charakterystyczne, z którymi będziemy mieć styczność podczas konfiguracji sieci bezprzewodowej.

Oczywiście cech charakterystycznych jest jescze więcej. Nie opisywałem tutaj siły sygnału. To chyba każdy rozumie. My tutaj skupimy się głownie na podłączaniu się do sieci i to najlepiej za pomocą terminala. Opcji jest kilka.

Na konfiguracji narzędzia wpa_suplicant zakończymy temat konfiguracji sieci w Linuks. Jednak to nie koniec tematów sieciowych ponieważ następne dwa rozdziały będą na niej bazować.

10. Warstwa aplikacji - usługi sieciowe

W poprzednim rozdziale dowiedzieliśmy się czym są sieci komputerowe i jak wygląda ich konfiguracja w dystrybucjach Linuksa. Sieci służą do przesyłania danych, a te musimy dostarczyć za pomocą uzupełnienia formularza na stronie internetowej lub w postaci zwykłego pliku tekstowego. Za obie te formy odpowiadają różne ustandaryzowane zbiory programów składające się na jeden konkrent protokół. Protokołów nie należy stawiać obok programów, które możemy kojarzyć z daną formą sposóbu przesyłania informacji przez sieć. Te programy najczęściej zajmują się ich obsługą, zawierają tym samym ich implementacje oraz dostosowanie aby korzystanie z protokołu było najcześciej niezauważalne a końcowy użytkownik utrzymał tylko i wyłącznie żądaną stronę internetową lub dostęp do zasobu dyskowego udostępnionego gdzieś na serwerze.

Aby pomiędzy dwoma stronami mogła zajść komunikacja potrzebny jest nadawca i odbiorca. Z punktu widzenia sieci komputerowej, każdy host jest jednocześnie jednym i drugim. Jednak patrząc na to ze strony obecnie omiawianego tematu, różnica między stronami jest już dużo bardziej podkreślona. Mamy sztywny podział między aplikacji klienckimi danego protokołu, które żądają dostępu do konkretnego zasobu a serwera, które te dane udostępniają. Wiele serwerów określa się mianem usług sieciowych. Często mówiąc o jakiś serwerze do słowa serwer dodaje się protokół, który obsługuje. Jak możemy pamiętać np. z rozdziału 7, niektóre serwery domyślnie nie są usługami sieciowymi (chociaż mogą, wystarczy odpowienio je skonfigurować).

Usługi sieciowe mają za zadanie udostępniać dane poprzez wybrane protokoły. Na przykład coś co nazywamy przeglądarką internetową opiera swoje działanie na protokole HTTP bądź jego bezpieczeniejszej wersji w postacji protokołu HTTPS. Programy pocztowe wykorzystują protokoły SMTPS oraz POP3S lub IMAPS. Wiele różnych aplikacji sieciowych może wykorzystywać różne protokoły lub samodzielnie implementować format danych przesyłanych przez sieć. Nie mniej jednak wszystkie te protokoły rezydują w warstwie aplikacji, czwartej warstwie modelu TCP/IP.

W tym rozdziale nadal pozostaniemy w sieciach. Przeanalizujemy działanie protokołu HTTP. Omówimy sobie usługę sieciową na podstawie SSH. Dowiemy się jak uruchamiane były, niektóre specyficzne usługi i jak są uruchamiane teraz. Sprawdzimy sobie narzędzia diagnostyczne, które pozwolą nam na sprawdzenie dostępności usługi ale również monitorowanie przesłanych przez nia i do niej danych. Na koniec zajmiemy się zdalnym wywołaniem procedur (RPC) oraz omówimy sobie podstawowe zagadnienia związane z bezpieczeństwem. Tematem zaawansowamym będą gniazda.

10.1. Analiza HTTP

Na wstępie omówilśmy sobie co tak naprawdę znajduje się w warstwie aplikacji. Aby była możliwość zapewnienia bezbłędnego działania wielu znanych protokołów, które są podstawą komunikacji w internecie, zespoły odpowiedzialne za implementacje tych standardów często umieszczają w swoich programach klienckich możliwość analizy przesyłanych danych. W tym podrozdziale skupimy się głównie na jednym protokole i jednym narzędziu umożliwiającym śledzenie danych przesyłanych za pomocą protokołu. Jednak w podrozdziale poświęconym narzędziom dianostycznym dowiemy się, że możemy sprawdzić większość usług, za pomocą kilku narzędzi.

W większości dystrybucji dostępne jest narzędzie curl jest ono nagminnie wykorzystywane do pobierania plików za pomocą protokołu HTTP w terminalu. Program ten jako, że wykorzystuje protokół HTTP/S posiada również możliwość prześledzenia transmisji. Jako przykład wykorzystamy serwis pogodowy, który przesyła obecną sytuację pogodową (lub dwu-dniową prognozę) za ASCII. Dla przykładu:

xf0r3m@immudex:~$ curl wttr.in/Warszawa?0\&lang=pl
Pogoda w: Warszawa

      \   /     Słonecznie
       .-.      21 °C
    ― (   ) ―   → 9 km/h
       `-’      10 km
      /   \     0.0 mm

Na podstawie wyścia tego polecenia, ciężko jest na prierwszy rzzut oka dostrzec, że jest to strona internetowa, ale tak właśnie jest. Chcąc przeanalizować jak wygląda komunikacja za pomocą protokołu HTTP należy wydać następujące polecenie:

xf0r3m@immudex:~$ curl --trace-ascii curl.log wttr.in/Warszawa?0\&lang=pl

Opcja --trace-ascii mówi programowi curl aby zapisywał on komunikaty diagnostyczne. Dodatkowo ta opcja wymaga podania pliku przeznaczonego na dane wyjściowe. W moim przypadku jest curl.log. Poniżej znajduje się jego zawartość, pominąłem na poniższym przykładzie dane zwracane przez polecenie (są one normalnie dostępne w terminalu po wydaniu polecenia).

== Info:   Trying 5.9.243.187:80...
== Info: Connected to wttr.in (5.9.243.187) port 80 (#0)
=> Send header, 89 bytes (0x59)
0000: GET /Warszawa?0&lang=pl HTTP/1.1
0022: Host: wttr.in
0031: User-Agent: curl/7.74.0
004a: Accept: */*
0057:
== Info: Mark bundle as not supporting multiuse
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 32 bytes (0x20)
0000: Access-Control-Allow-Origin: *
<= Recv header, 21 bytes (0x15)
0000: Content-Length: 314
<= Recv header, 41 bytes (0x29)
0000: Content-Type: text/plain; charset=utf-8
<= Recv header, 37 bytes (0x25)
0000: Date: Wed, 23 Aug 2023 08:23:51 GMT
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 314 bytes (0x13a)
0080:  .  .[38;5;226m  ... (   ) ...  .[0m .[1m....[0m .[38;5;154m9.[0
00c0: m km/h.[0m       .  .[38;5;226m     `-...     .[0m 10 km.[0m
0100:       .  .[38;5;226m    /   \    .[0m 0.0 mm.[0m         .
== Info: Connection #0 to host wttr.in left intact

Pierwsze dwie linie, są to linie informujące nas o uzyskanym adresie IP oraz o tym czy połącznie powiodło się. Jeśli tak jest, to wówczas wysyłany jest nagłówek - w nagłówkach HTTP przeważnie znajdują się informacje kontrolne dla programów, które będą zajmować się interpretowaniem treści przesłanej przez serwer. No właśnie. Logiczne było by to, że przeglądarka powinna pobrać żądaną stronę WWW. W rzeczywistości to klient przesyła jedynie nagłówek z żądaniem. Przyglądając się pierwszej linii nagłówka rozpoczynajcego się liczbą: 0000 (Ta liczba to bajt rozpoczęcią tych danych w ładunku/bloku danych przesyłanych w pakietach sieciowych). Zawiera ona słowo kluczowe GET Następnie występuje wartość żądania (czego sobie życzysz), na podstawie tych informacji skrypt jest wstanie zwrócić odpowiedni stan pogodowy, w przypadku kiedy poprostu odwiedzamy jakąś witrynę to najczęściej po słowie GET występuje ukośnik (/). Serwery wówczas interpretują to, że żądanie dotyczy głównie pliku index.html zapisanego w katalogu przypisanym na serwerze do danej witryny.

Uwaga! Jeden serwer WWW, może utrzymać kilka jak nie kilkanaście lub kilkadziesiąt różnych stron internetowych. Wszystko zależy od jego ustawień oraz konfiguracji sprzętowej, która go utrzymuje. Dlatego też wiele stron może kierowanych pod jeden adres IP. Taka funkcjonalność nazywa się hostingiem.

W naszym przypadku również żądamy głównego pliku tego hostingu. W nagłówku natomiast przekazujemy dane, które pomogą w uzyskaniu żądanych przez nas danych. Jest to jedna z cech protokołu HTTP. Dane (głównie do aplikacji, ponieważ statyczne strony rzadko interpretują jakieś dane) mogą być przekazywane jako rozwinięcie adresów jest to tak zwana metoda GET lub w postaci odrębnego pakietu danych poza widocznością dla zwykłego użytkownika - metoda POST. Przy użyciu metody POST najczęściej przesyłane są dane uzupełnionych formularzy na stronach WWW. Po danych przekazywanych aplikacji (w tym przypadku) wstepują już informacje kontrolne dla samego protokołu HTTP w linii 0022 (będę używał takich oznaczeń) występuje pole nagłówka Host: zawiera ono nazwę hosta do, którego kierujemy nasze żądanie. Następne pole nagłówka to User Agent:, to pole zawiera informacje jakiego programu klienckiego WWW używamy. Ta informacja jest zapisywana w plikach dziennika serwera wraz z adresem IP. Narzędzia takie jak curl lub podobny mu i bardziej powszechny wget pozwalają na zmianę tej wartości, za pomocą jednej opcji możemy podać się za np. program indeksujący jednej z wyszukiwarek. Trzecim polem jest rodzaje danych jakie może zinterpretować, polecenie curl to służy głównie pobieraniu treści więc rzadko kiedy je interpretuje. Wyjątkiem może być omawiany przez nas przykład. Ostatnim pole jest znak nowego wiersza, który kończy nagłówek żądania. Teraz następuje jego przesłanie i oczekiwnaie na odpowiedź serwera.

W przypadku odpowiedzi jak możemy zauważyć przesyłanych jest wiele nagłówków przedstawiających pojedyncze pola. Nie będe ich tutaj wszystkich opisywał, jednak na uwagę zasługuję jedna informacja, która została wcześniej pominięta. Otóż, w polu metody żądania lub w pierwszym naglówku odpowiedzi znajduje się wykorzystywana w transmisji wersja protokołu HTTP. Wersja 1.1 jest obecnie standardem, jednak obecnie w stanie proponowanych standardów są wersje 2 i 3. Seria nagłówków odpowiedzi kończy się w taki sam sposób jak w przypadku nagłówka żądania, znakiem nowego wiersza. Następnie odebrany zostaje pakiet danych a dane w nim zawarte trafiają do użytkownika.

Tak moglibyś to wywnioskować na podstawie danych diagnostycznych zwróconych przez program curl. Wygląda to nieco inaczej, ale różnice występują tylko w przedstawieniu danych. Otóż otrzymując pakiet protokołu HTTP, nie ma w nim podziału na sekcje nagłówka i danych. Nie ma także dwóch odrębnych pakietów. Wszystko spakowane jest w jednej paczce, a curl na podstawie znaku nowego wiersza jest jedynie oddzielić te dane.

Innym sposobem na wejście interakcje np. z protokołem HTTP jest wykorzystanie zapomnianego już programu Telnet. Telnet kiedyś służył do tego do czego służy teraz protokół SSH. Do zdalnego połączenia się z powłoką. Telnet sam w sobie również jest protokołem, obecnie rzadko jest spotykany w swojej pierwotnej formie, chyba że w postaci lokalnej konsoli w systemach wbudowanych.

xf0r3m@immudex:~$ telnet example.org 80 > telnet.log
Trying 93.184.216.34...
Connected to example.org.
Escape character is '^]'
GET / HTTP/1.1
Host: example.org

HTTP/1.1 200 OK
Age: 126394
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Wed, 23 Aug 2023 11:28:50 GMT
Etag: "3147526947+ident"
Expires: Wed, 30 Aug 2023 11:28:50 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (dcb/7F7F)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
...

Po wielokropku występuje już tylko kod HTML strony. Polecenie telnet wymaga portu, pod który należy się podłączyć. Aby otrzymać odpowiedź musimy metodę (GET), ewentualne dane/żądany plik (/), wersje protokołu (HTTP/1.1) w drugiej lini nagłówka należy podać nazwę hosta (Host: example.org), ze względu na to aby serwer WWW wiedział z jakiej witryny przesłać odpowiedź.

Protokół jest może trochę mało interaktwny przy takim połączeniu, ale np. usługi pocztowe są już bardziej skore do rozmowy.

10.2. Serwery sieciowe

W dystrybucjach Linuksa możemy napodkać wiele serwerów, niektóre z nich działają w oparciu o sieć i oczekują zdarzeń pochodzących od zdalnych użytkowników. Serwery wewnątrz systemów to dość niszowa grupa, do których zalicza się tylko kilka demonów np. cron. Dlatego w mowie potocznej przyjęło się, że mówiąc o serwerach mamy na myśli serwery sieciowe. Poniżej znajduje się lista rodzajów serwerów wraz kilkoma przykładmi oprogramowania.

Aby bardziej zaznajomić się z serwerami sieciowymi, omówimy sobie po krótce jeden z nich. Z powyższej listy najciekawszym z nich jest znana implementacja protokołu SSH - openssh. Implementacji protokołu SSH jest kilka, jednak ta jest w najpowszechniejszym użytku obecna jest nawet w najnowszych wersjach MS Windows 10.

10.3. Serwer bezpiecznej powłoki - implementacja openssh

Demony sieciowe zawyczaj skupiają się na czynnościach wokół protokołu, który obsługują. Implementacja openssh ma za zadanie zapewnienie solidnego poziomu bezpieczeństwa zarówno dla zdalnej powłoki, transferu plików jak i innych połączeń za pomocą proxy czy tunelowania.

10.3.1. Aplikacja klienta - polecenie ssh

Za pomocą polecenia ssh mamy dostęp do większości cudów jakie oferuje ta implementacja. Najprostszym jej wywołaniem jest podanie nazwy użytkownika następnie adresu zdalnego hosta, z którym chcemy się połączyć. Obie te wartości połączone są ze sobą za pomocą małpy (@). Po poprawnym znalezieniu hosta i połączeniu z jego demonem SSH (będzie o nim za chwile), zostaniemy poproszeni o hasło. Jeśli łączymy się z serwerem po raz pierwszy, Zostanie nam wyświetlona informacja na temat, że nie można zweryfikować autentyczności hosta.

xf0r3m@immudex:~$ ssh -p 2022 searx.morketsmerke.org
The authenticity of host '[searx.morketsmerke.org]:2022 ([82.117.231.222]:2022)' can't be established.
ECDSA key fingerprint is SHA256:ghhvjaz6T/qcsX9TiWN4UEV4fuiv6oqHxsD1bGB+40c.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[searx.morketsmerke.org]:2022,[82.117.231.222]:2022' (ECDSA) to the list of known hosts.

W przypadku protokołu SSH, serwer musi się przedstawić za każdym razem gdy nawiązujemy z nim połączenie. Jeśli jest pierwsze połączenie z tym serwerem, to na komputerze klienta nie ma jeszcze danych autentykacji serwera (bez wdawania sie w zawiłe, kryptograficzne szczegóły) wówczas musimy się upewnić we własnym zakresie czy jest rzeczywiście ten serwer, z którym chcemy się połączyć, a nie np. próba wyłudzenia hasła. Po wyświetleniu się tego komunikatu zostaniemy zapytaniu czy chcemy kontynuować, jeśli odpowiedź jest twierdząca to dane autentykacyjne serwera zostaną zapisane w plikach na naszym komputerze i przy kolejnej próbie logowania poproszeni zostaniem wyłącznie o hasło.

xf0r3m@immudex:~$ ssh -p 2022 searx.morketsmerke.org
xf0r3m@searx.morketsmerke.org's password:

Na powyższym przykładzie podałem opcję -p, która pozwala na wkazanie nie standardowego portu (standardowy port to 22/TCP). Na tym przykładzie pominąłem, użytkownika. Jeśli go pominiemy ssh jako użytkownika przyjmie tego, który wydał polecenie.

Domyślnie podczas logowania używa się haseł, ale jeśli mamy pod sobą kilka maszyn i wpisywanie do każdej hasła może być męczące. SSH daje nam możliwość uwierzytelnienia za pomocą kluczy kryptograficznych. Nie będę zagłębiał się charkterystykę kluczy kryptograficznych, ale istnieje możliwość wygenerowania pary plików (klucza publicznego oraz prywatnego), następnie załadowanie jednego z nich do maszyn, którymi administrujemy. Podczas generowanie pary kluczy, będzie można podać hasło chroniące klucz prywatny. Najlepiej, aby dodać to hasło. Wówczas będziemy mieć jedno hasło do wielu maszyn (niezależne, bo nawet jeśli główny administrator serwera zmieni nam hasło, to jeśli klucz nadal będzie w systemie, to nadal będziemy mieli możliwość zalogowania się do systemu.).

Aby wygenerować klucze wydajemy następujące polecenie. Dodam tylko, że obecnie będziemy generować klucze algorytmu kryptograficznego RSA, są one domyślne dla SSH. Choć, nie które z systemów mogą ich nie przyjmować i wymagać innych rozdzajów. Więcej na temat generowanie kluczy znajduje się na stronie podręcznika polecenia ssh-keygen.

xf0r3m@immudex:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/xf0r3m/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/xf0r3m/.ssh/id_rsa
Your public key has been saved in /home/xf0r3m/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:7N+WSF5OuOXiGh0hWr3hjMClfssGrn7Ofh8igPYpqcw xf0r3m@immudex
The key's randomart image is:
+---[RSA 3072]----+
|        .        |
|     . o .       |
|      + o +      |
|   . . = = +     |
|  o . + S =.     |
| . o + = oo.+    |
|  o o o BooO .   |
|o. . o.o.+=o=    |
|.E .o++..++o.    |
+----[SHA256]-----+

Polecenie wydajemy bez żadnych opcji chcąc wygenerować parę kluczy RSA. Ścieżkę zapisu klucza równięż pozostawiamy bez zmian, oczywiście jeśli mamy w planach ustawienie hasła klucz prywatnego. Jeśli nie to może warto było by wskażać jakie bezpieczne miejsce np. szyfrowaną partycję lub dysk. Posiadając parę kluczy możemy je załadować do docelowych systemów.

xf0r3m@immudex:~$ ssh-copy-id xf0r3m@172.26.38.226
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/xf0r3m/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
xf0r3m@172.26.38.226's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'xf0r3m@172.26.38.226'"
and check to make sure that only the key(s) you wanted were added

Klucze możemy załadować do systemu za pomocą polecenia ssh-copy-id. Działanie tego polecenia widzimy na powyższym przykładzie. To polecenie przyjmuje również te same opcje jak w przypadku polecenia ssh. Jeśli podaliśmy inną ścieżkę podczas generowania klucza to za pomoca opcji -i możemy skazać plik klucza publicznego (plik z rozszerzeniem .pub). Natomiast jeśli chcemy się uwierzytelnić za pomocą klucza to przy opcji -i polecenia ssh podajemy plik klucza prywatnego (bez rozszerzenia).

10.3.2. Demon serwera - sshd

Jak możemy zdawać sobie z tego sprawę z demona nie korzysta się jak z aplikacji klienckiej. Nie wydajemy poleceń, żeby go uruchomić. Oczywiście możemy to zrobić, ale normalnie tego się nie robi. Demony uruchamiane są za pomocą plików jednostek (systemd). Dlatego, że obsługa demona różni się od obsługi klienta, w tym podrozdziale skupimy się głównie zmianie konfiguracji demona openssh, jednak nie będziemy się skupiać na wszystkich możliwych opcjach poniżej przedstawię tylko te, które mogą nam się przydać na obecnym poziomie wtajemniczenia. Opisy tych opcji przybliżą nam również nie co bardziej działanie samego SSH.

Konfiguracja demona openssh znajduje się w pliku: /etc/ssh/sshd_config. W tym pliku wiele opcji jest wyłaczonych (ujęte w komentarz), oznacza to, że przechowują one domyślne wartości. Jeśli więc chcemy coś zmienić w konfiguracji daemona SSH, musimy usunąć początkowy symbol komentarz i zmienić wartość opcji.

Oczywiście opcje z omawianego przez nas pliku są opisane na stronie podręcznika man 5 sshd_config. Po zapisaniu zmian w pliku konfiguracyjnym demona SSH, należy uruchomić go ponownie aby nasze zmiany zaczęły działać.

xf0r3m@immudex:~$ sudo systemctl restart ssh.service

10.3.3. Zabezpieczenie fail2ban

Wystawiając otwarty port 22/TCP czyli SSH, możemy zauwać w plikach dzienników systemowych (plik /var/log/btmp - polecenie lastb - nieudane próby logowania) wiele prób logowania się, odgadywania nazwy użytkownika oraz hasła. Możemy wpaść na pomysł wyłączenia wyłaczenia logowania za pomocą hasła. Jest to pewne rozwiązanie, jednak:

Co jeśli serwer ma być podpięty do domeny, i umożliwiać logowania dużej ilości użytkowników? Wtenczas administrator będzie musiał spędzić trochę kariery na dodaniu tych wszystkich kluczy. A to nie koniec, ponieważ rotacje pracowników zdarzają się dość często. W tym wypadku nie możemy zablokować logowania za pomocą hasła.

Istnieje kompromis między logowaniem za pomocą hasła a ograniczeniem odgadywania dostępu przez różnego rodzaju automaty. Jest nim fail2ban. Jest to usługa monitorująca próby uwierzytelnienia się i po przekroczeniu nie udanych prób odcinająca dostęp do usługi na jakiś czas. Usługa ta opiera swoje działanie na monitorowaniu komunikatów diagnostycznych oraz dynamicznym tworzeniu i usuwaniu reguł zapory sieciowej.

Uwaga! Na Debianie fail2ban nie posiada w swoich zależnościach pakietu zapory, dlatego też należy pamiętać, żeby dopisać go podczas instalacji programu. Pakiet fail2ban znajduje się w repozytoriach.

Aby banowanie miało skutek konfiguracja narzędzia musi znaleźć się w pliku /etc/fail2ban/jail.conf. Domyślnie ten plik zawiera bardzo dużo różnych opcji, sekcji oraz wartości. Poniżej umieściłem minimalną działającą konfigurację narzędzia dla SSH. Fail2ban może rownież działać z innymi usługami.

[INCLUDES]
before = paths-debian.conf

[DEFAULT]
protocol = tcp
chain = <known/chain>
port = 0:65535

banaction = iptables-multiport
action = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]

[sshd]
enabled = true
port = 2022
filter = sshd
maxretry = 3
findtime = 5m
bantime = 30m
backend = systemd

W piewszej sekcji ([INCLUDES]) znajduje się jedna opcja wskazująca plik z konfiguracją scieżek przeznaczoną dla wybranej dystrybucji. Każda dystrybucja może zawierać w różnych miejscach komponenty potrzebne mu do działania stąd ten plik. Sama sekcja zawiera może zawierać dodatkowe konfiguracje, które mogą być interpretowane przez program przed interpretacją sekcji przeznaczonej dla wybranej usługi (opcja before). Sekcja [DEFAULT] rozpoczyna się od określenia domyślnej akcji podejmowanej podczas nadużycia oraz jej konfiguracji. Jak możemy się domyśleć po wartości opcji banaction, wstrzymanie możliwości logowania będzie opierać się reguły iptables. Sama wartość tej opcji jest wskazaniem pliku w katalogu /etc/fail2ban/action.d. W tym pliku możemy zobaczyć jak na podstawie wykonywanych przez fail2ban czynności układane są reguły iptables. Sekcja [DEFAULT] zawiera podsekcje dedykowane dla konkretnych usług. W naszym przypadku jest to demon SSH. W sekcji usługi znajdują się opcje definujące kryteria dotyczące blokowania dostępu podczas nadużycia. Zatem kolejno:

Tyle informacji wystarczy demonowi fail2ban do monitorowania i także przeciw działania próbą uzyskania dostępu do powłoki. Jeśli zmieniamy coś w plikach konfiguracyjnych to wówczas należy pamiętać o restartcie usługi. Po zastosowaniu tego rozwiązania będzie zauważymy znaczny spadek nieudanych prób logowania. Każdy ma 3 próby jeśli wszystkie się nie powiodą adres IP, z którego nasz świadek jehowy puka zostanie zbanowany na 30 minut. Pozostaje również możliwość logowania się za pomocą hasła.

Nie ma metody, która jednoznacznie rozprawiwła by się z próbami uzyskania dostępu do SSH. Można za pomocą informacji w internecie ustawić czas blokady na stałe lub zwiększać go jeśli trafi się naprawdę natrętny osobnik, albo odciąć osiągalność demona SSH z internetu i wymagać do tego połączeń VPN.

10.3.4. Pozostałe metody użycia SSH

Najpopularniejsza implementacja protokołu bezpiecznej powłoki, to nie tylko jakby sama nazwa wskazywała dostęp do powłoki systemu w bezpieczny sposób, ale także bezpieczne przesyłanie plików za pomocą SSH.

Pierwszym z nich jest SCP, który tak jakby zwykłym poleceniem cp, ale przesyłającym plik za pomocą szyfrowanago kanału i rozszerzającym scieżki o dowolne Uniksy w internecie. Istotnym czynnikiem korzystania z SCP jest fakt, że musimy niestety znać zdalną ściezke dostępu do pliku.

xf0r3m@immudex:~$ scp test.txt user@server:/usr/share/doc/test/test.txt

Wraz z SCP dostępne jest bardziej interaktywne polecenie, które jest też protokołem - SFTP. W informatyce jest wiele jest rozwiązań tego skrótu, nas będzie interesować wyłącznie SSH File Transfer Protocol. Połaczenie SFTP realizowane jest na takiej same zasadzie jak SSH. Podajemy nazwę polecenia użytkownika oraz host, po jego zgłoszeniu się podajemy hasło. Wówczas zostaniem na zwrócony prompt sftp>.

xf0r3m@vm-3eb3a0e:~$ sftp -P 2022 xf0r3m@192.168.122.76 
xf0r3m@192.168.122.76's password: 
Connected to 192.168.122.76
sftp>

Na powyższym przykładzie użyłem opcji -P, która w przypadku SCP (to polecenie formalnie korzysta SFTP) oraz SFTP służą do określania portu. Opcja -p, służy do zachowania praw własności przesyłanych plików. Przy czym -p przy polecenie ssh służy określania portu zdalnego demona SSH. Bywa to czasami bardzo irytujące.

Po zwróceniu znaku zachęty do dyspozycji mamym dostęp do części podstawowych poleceń takich jak: ls, cd, mkdir. Dodatkowo te wymienione i kilka dodatkowych mają swój lokalny odpowiednik poprzedzony literą l za pomocą tych poleceń możemy poruszać się po lokalnym systemie plików, bez przerywania połączenia na potrzeby na przykład, zmiany katalogu.

Wymiana danych w przypadku polecenia SFTP, odbywa się poprzez polecenia takie jak: get (pobierz) lub put (wyślij), każde z tych poleceń zawiera opcję -r lub -R, co oznacza rekurencję. Niektóre implementacje tego protokołu wymagają, aby katalog istniał przed rekurencyjnym przesłaniem danych. Inne dostępne polecenia są opisane na stronie podręcznika: man sftp.

Poza bezpieczym przesyłaniem plików pozostało jeszcze umieszczenie innego połączenia sieciowego wewnątrz połączenia SSH. Takie działanie nazywane jest tunelowaniem. Połączenie pierwotne pozwala na przenoszenie wewnątrz swoich pakietów danych wewnątrz innego połączenia. Mozliwości tuneli tworzonych za pomocą SSH w implementacji openssh znajdują się w innym moim materiale: Laboratorium sieci VPN. Dodam tylko, że połaczenie SSH jest dość wrażliwe (jak każde zapewniające jakiś standard bezpieczeństwa) na jakość połączenia jeśli połączenie jest niewykorzystywane może zostać łatwo zerwane. Należy mieć na uwadze, że tunele SSH otwierają sesje powłoki, więc musimy posiadać dostęp do użytkownika, który może uruchomić dowolną powłokę, może być to równiez program, który będzie działać po zalogowaniu do momentu zakończenia połączenia, jeśli jest to zwykła powłoka np. BASH, to wówczas trzeba pomyśleć o prostym skrypcie, którego zadaniem będzie nieskończone przesyłanie danych wypisywanie czegoś w połowce (Pisanie skryptów powłoki, nie będzie obowiąkowym rodziałem tego materiału, dlatego umieszczę go na samym końcu. Możemy przejść do niego nawet teraz po skończeniu tego podrozdziału) lub użycie programu tmux, który jest multiplekserem terminala dodatkowo pozwala na podtrzymania połączenia SSH.

10.4. Demony internetowe - inetd, xinetd

Niektóre małe usługi sieciowe takie jak np. telnetd - demon usługi telnet czy klasyczna usługa FTP oraz wiele innych posiadają te same lub podobne wymagania dotyczące obsługi sieci, zatem żeby nie implementować tego samego kodu. Twórcy tych usług stworzyli superdemona on nazwie inetd i najprościej rzecz ujmując jego zadaniem jest obsługa połączeń: otwieranie portów oraz przekierowywanie połączeń już do właściwych usług.

Na przestrzenii lat działania tego rozwiązania powstała nowsza implementacja xinetd posiadająca lepsze mechanizmy obsługi oraz kontroli połączeń.

Obecnie rzadko spotyka się działający demon inet lub xinetd, został on wyparty przez jednego z demonów systemd, a demony które obsługiwał obecnie posiadają własne implementacje obsługi połączeń, z biegiem lat rozszerzały swoje możliwości i finalnie stały się bardziej samodzielne. Wiele życzenia pozostawiaja również kwestie bezpieczeństwa, chociaż wiele rzeczy zostało poprawionych między oryginalną implementacją a xinetd.

Inną równie archaiczną rzeczą jest wrapper TCP w postaci demona tcpd. Jego działanie przypomina nieco działanie filtra sieciowego. Był on wykorzystywany jeszcze zanim takie rozwiązania jak iptables zostały na stałe wdrożone jako swoisty standard i podstawowy poziom ochrony. Zanim ruch z inetd trafił do właściwego demona przechodził przez tą usługę. Tcpd rejestrował połączenie następnie konfrontował je z listami dostępu /etc/hosts.allow lub /etc/hosts.deny, na podstawie informacji ustalonych przez wrapper ruch był dopuszczany do właściwego procesu lub nie.

Plikowe listy kontroli dostępu mogą być wykorzystywane w dystrybucjach klasy enterprise, do ustalenia dostępu do wybranych komponentów systemu. Takim przykładem może być dostęp do cron (harmonogramu zadań). Więcej informacji znajduje się pod tym adresem: https://morketsmerke.github.io/articles/terminallog/RedHat_-_RHCSA.html#8.2.1.scheduleaccesscontrol

10.5. Narzędzia diagnostyczne

Podczas analizownia działania protokołu HTTP, na samym końcu wspomniałem o innym sposobie połączenia się z serwerem WWW przy użyciu programu telnet. Program ten może być programem diagnostycznym w podczas sprawdzania uruchomienia i dostępności usługi. W większości dystrybucji program telnet trzeba będzie zainstalować z repozytorium. Jednak możemy w systemie naleźć co najmniej jedno narzędzie, które jest nam wstanie zwrócić jakieś informacje na temat usług sieciowych takim program jest już omawiany ss.

Wynik działania programu ss, był już omawiany podczas omawiania transmisji TCP w warstwie transportowej. Ale poniżej znajdują się najważniejsze (dla celów diagnostycznych) opcje.

10.5.1. Polecenie lsof

Jesli pamiętamy z rozdziału 8, polecenie lsof służy do wyświetlania otwartych plików w systemie. Z jego pomocą możemy również wyświetlić połączenia realizowane w systemie. Rola tego polecenia może przypominać ss, jednak polecenie lsof do działania w obrębie całego systemu wymagają uprawnień administratora, w przeciwnym razie polecenie wyświetli tylko i wyłącznie połączenia utworzne przez użytkownika, który wydał to polecenie. Aby móc wyświetlić połączenia sieciowe przy użyciu polecenia lsof należy użyć opcji -i, tak jak przedstawiono to na poniższym poleceniu.

xf0r3m@vm-3d6b184:/media/xf0r3m/immudex_crypt0$ sudo lsof -i
COMMAND     PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
systemd       1    root   35u  IPv4  11469      0t0  TCP *:sunrpc (LISTEN)
systemd       1    root   36u  IPv4  11472      0t0  UDP *:sunrpc 
systemd       1    root   37u  IPv6  11475      0t0  TCP *:sunrpc (LISTEN)
systemd       1    root   38u  IPv6  11478      0t0  UDP *:sunrpc 
rpcbind     427    _rpc    4u  IPv4  11469      0t0  TCP *:sunrpc (LISTEN)
rpcbind     427    _rpc    5u  IPv4  11472      0t0  UDP *:sunrpc 
rpcbind     427    _rpc    6u  IPv6  11475      0t0  TCP *:sunrpc (LISTEN)
rpcbind     427    _rpc    7u  IPv6  11478      0t0  UDP *:sunrpc 
avahi-dae   476   avahi   12u  IPv4  13560      0t0  UDP *:mdns 
avahi-dae   476   avahi   13u  IPv6  13561      0t0  UDP *:mdns 
avahi-dae   476   avahi   14u  IPv4  13562      0t0  UDP *:33600 
avahi-dae   476   avahi   15u  IPv6  13563      0t0  UDP *:34773 
NetworkMa   675    root   26u  IPv4  14371      0t0  UDP vm-3d6b184.morketsmerke.org:bootpc->
DESKTOP-TMP2137.mshome.net:bootps 
cupsd       790    root    6u  IPv6  14463      0t0  TCP localhost:ipp (LISTEN)
cupsd       790    root    7u  IPv4  14464      0t0  TCP localhost:ipp (LISTEN)
cups-brow   860    root    7u  IPv4  15147      0t0  UDP *:631 
chronyd     883 _chrony    5u  IPv4  14538      0t0  UDP localhost:323 
chronyd     883 _chrony    6u  IPv6  14539      0t0  UDP localhost:323 
ssh       60722  xf0r3m    3u  IPv4 187934      0t0  TCP vm-3d6b184.morketsmerke.org:36440->
ip82-117-231-222.static.awhost.cloud:2022 (ESTABLISHED)

Na powyższym przykładzie możemy zauważyć, że wyjście tego polecenia jest trochę bardziej podrasowanym wyjściem polecenia ss. Poza tym lsof daje nam możliwość przefiltrowania listy dostępnych połączeń. Ogólny format filtra wygląda następująco:

$ sudo lsof -i[wersja_IP][protokół]@[komputer]:[port]

Oczywiście istnieje pełna dowolność w stosowaniu tych elementów, aby to pokazać przedstawię kilka użytecznych filtrów:

xf0r3m@vm-3d6b184:/media/xf0r3m/immudex_crypt0$ sudo lsof -i4 -s TCP:LISTEN
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
systemd   1 root   35u  IPv4  11469      0t0  TCP *:sunrpc (LISTEN)
rpcbind 427 _rpc    4u  IPv4  11469      0t0  TCP *:sunrpc (LISTEN)
cupsd   790 root    7u  IPv4  14464      0t0  TCP localhost:ipp (LISTEN)

Powyższy przykład przestawia wyłącznie otwarte porty dla transmisji TCP. W tym przypadku uzyskanie stanu połączenia (LISTEN) wymaga podania dodatkowej opcji (-s) a ona ma swoją składnię dla wartości: PROTOKOŁ:STAN.

xf0r3m@vm-3d6b184:/media/xf0r3m/immudex_crypt0$ sudo lsof -iTCP@searx.morketsmerke.org
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
ssh     60722 xf0r3m    3u  IPv4 187934      0t0  TCP vm-3d6b184.mshome.net:36440->
ip82-117-231-222.static.awhost.cloud:2022 (ESTABLISHED)

Ciekawy przypadek mamy tutaj. Ponieważ wśród połączeń TCP wyszukujemy hosta o konkretnej nazwie. Nie dostajemy informacji zwrotnej, że coś jest nie tak, wręcz przeciwnie uzyskujemy odpowiedź, na której nie widnieje nasza nazwa. Ta odpowiedź jest jak najbardziej prawidłowa. Adres searx.morketsmerke.org jest odwzrowywany do 82.117.231.222, ale jeśli skorzystamy z zamiany adresu IP na nazwę domenową wówczas otrzymamy taki oto wynik: ip82-117-231-222.static.awhost.cloud Więc jeśli adres searx.morketsmerke.org jest tożsamy z ip82-117-231-222.static.awhost.cloud, co oznacza, że filtr jest jak najbardziej poprawny. Poniżej znajduje się ostatni już przykład:

xf0r3m@vm-3d6b184:/media/xf0r3m/immudex_crypt0$ sudo lsof -i6UDP:323
COMMAND PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
chronyd 883 _chrony    6u  IPv6  14539      0t0  UDP localhost:323 

10.5.2. tcpdump

Innym narzędziem, dzięki któremu możemy śledzić ruch sieciowy jest tcpdump. Program ten działa na takiej zasadzie, że rejestruje każdy pakiet, który będzie pojawiać się na kartach sieciowych naszego komputera. Zakres działania urządzenia jest bardzo szeroki gdyż zbiera on zarówno pakiety aplikacji takie jak DNS czy inne jak komunikaty protokołu ARP.

xf0r3m@vm-f48088c:/media/xf0r3m/immudex_crypt0$ sudo tcpdump 
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:27:01.136396 IP DESKTOP-TMP2137.mshome.net.60948 > 239.255.255.250.1900: UDP, length 175
07:27:01.238437 IP vm-f48088c.mshome.net.46759 > DESKTOP-TMP2137.mshome.net.domain: 9527+ PTR? 250.255.255.239.in-addr.arpa.
(46)
07:27:01.244853 IP DESKTOP-TMP2137.mshome.net.mdns > 224.0.0.251.mdns: 0 PTR (QM)? 250.255.255.239.in-addr.arpa.local.
(52)
07:27:01.247220 IP DESKTOP-TMP2137.mshome.net.mdns > 224.0.0.251.mdns: 0 PTR (QM)? 250.255.255.239.in-addr.arpa.local.
(52)
07:27:01.250774 IP6 DESKTOP-TMP2137.morketsmerke.org.mdns > ff02::fb.mdns: 0 PTR (QM)? 250.255.255.239.in-addr.arpa.local.
(52)
07:27:01.252918 IP6 DESKTOP-TMP2137.morketsmerke.org.mdns > ff02::fb.mdns: 0 PTR (QM)? 250.255.255.239.in-addr.arpa.local.
(52)
07:27:02.142559 IP DESKTOP-TMP2137.mshome.net.60948 > 239.255.255.250.1900: UDP, length 175
07:27:02.241843 IP DESKTOP-TMP2137.mshome.net.mdns > 224.0.0.251.mdns: 0 PTR (QM)? 250.255.255.239.in-addr.arpa.local.
(52)
07:27:02.242557 IP6 DESKTOP-TMP2137.morketsmerke.org.mdns > ff02::fb.mdns: 0 PTR (QM)? 250.255.255.239.in-addr.arpa.local.
(52)

W moim przypadku zostało tylko złapanych kilka zapytań w przód (PTR) do usługi Multicast-DNS do serwera na którym hostuje maszynę wirtualną. Uruchamiając ten program od ręki, nie dokonując żadnych zmian w systemie wyłapiemy tylko pakiety adresowane do nas lub wysyłane przez nas. Teoretycznie powinniśmy przełączyć nasz interfejs w tryb nasłuchiwania (promiscous mode) możemy to zrobić za pomocą poniższego polecenia:

xf0r3m@immudex:~$ sudo ip link set <interfejs> promisc on

Niestety w obecnych konfiguracjach sieci to narzędzie może sprawdzić się głównie w sieciach bezprzewodowych do przechwycenia komunikatów warstwy łącza danych, chyba że sieć jest w pełni otwarta. Sieci kablowe są obecnie zbudowane na przełącznikach, a te powodują bezpośrednią komunikację w sieci, więc teoretycznie nie ma możliwości aby pakiety niezaadresowane do naszej karty do niej trafiły. Oczywście, tylko teoretycznie, bowiem inteligentne przełączniki (większość przełączników zarządzalnych) pozwalają na tzw. port mirror. Ustawienie portu w ten tryb oraz wybranie portu docelowego spowoduje, że to coś się pojawi na porcie docelowym trafi również to portu z ustawioną tą funkcją. Wówczas możemy monitorować ruch z jednego komputera, chyba że będzie kopiować ruch z portu, który łączy nasz przełącznik z pozostałą częścią sieci. Jednak wówczas będziemy monitorować jedynie ruch wychodzący i przychodzący do tego segmentu. Natomiast komunikacja między komputerami tego samego segmentu może pozostać dalej nie osiągalna.

Nie mniej jednak jeśli już przyjdzie nam pracować z tym programem to musimy wiedzieć jego działania odbywa się na zasadzie filtrów. Filtry tcpdump składaja się (oczywiscię te proste) z elementów pierwotnych (z ang. primitives) oraz z operatorów takich jak and, or lub wykrzyknik (!) powodujący negacje całego filtru. Poniżej znajduje się lista elementów pierwotnych:

Tworząc filtr oparty na sieci jako wartość elementu net podajemy adres podsieci np. 192.168.8.0. Poniższe polecenie wyświetli nam pakiety UDP, połączenia HTTPS oraz połączenia SSH.

xf0r3m@immudex:~$ sudo tcpdump udp or port 443 or port 22

To co zostało przedstawione powyżej to tylko mała część możliwosci filtrowania ruchu zbieranego przez tcpdump. Więcej informacji na temat filtrów możemy znaleźć na stronie podręcznika: man pcap-filter (pcap to biblioteka odpowidzialna zbieranie pakietów).

tcpdump jest tekstowym odpowiednikiem podobnego narzędzia działającego w oknie - programu Wireshark.

10.5.3. Program netcat

Jest to program sieciowy o ogromnych możliwościach. Zwany często szwajcarskim scyzorykiem. Za jego pomoca możemy sprawdzić dostępność usługi, czy otworzyć port a następnie przekierować dane z polecenia na standardowe wyjście. Jesli ktoś się podłączy do tego i przekieruje standardowe wejście, na przykład za pomoca netcata. Wówczas klient będzie mógł wysyłać do serwera komunikaty tekstowe, które będą się wyświetlać w terminalu serwera. Serwer również będzie mógł wysyłać komunikaty wpisując bezpośrednio w to samo okno terminala, w którym działa proces. Na nasze szczęście program nc sam dokonuje takich przekierowań i wystarczy, że po jednej stronie uruchomimy serwer a po drugiej stronie podłączymy się do niego, wygląda to mniej więcej tak:

xf0r3m@server:~$ nc -l 9595
test
test2
Server: To jest wiadomość testowa. Proszę o odpowiedź.
Klient: Odpowiedź na wiadomość testową.
xf0r3m@client:~$ nc -N 172.24.171.253 9595
test
test2
Server: To jest wiadomość testowa. Proszę o odpowiedź.
Klient: Odpowiedź na wiadomość testową.

Na powyższym przykładzie zmieniłem nazwy hostów w znakach zachęty, aby było wiadomo kto jest kim w tej komunikacji. Do uruchomienia serwera na prawdę nie potrzeba wiele wystarczy podanie opcji -l, która powoduje nasłuchiwanie następnie ja podałem port, z tego co można wyczytać nas stronie podręcznika tego narzędzia, to że możemy pominąć opcję odpowiedzialną za wskazanie portu i podać sam numer portu oraz dodać do opcji adres IP interfejsu, na którym serwer ma nasłuchiwać w tym przypadku opcję opowiedzialną za ten funkcję też możemy pominąć.

Opcja -N podana przez klienta powoduje, że jeśli wyślemy znak EOF (End of File, Ctrl+d), to połaczenie zostanie zakończone i serwerów również zakończy swoją pracę.

Domyślnie otwieranymi portami są porty TCP, jeśli jednak chcemy użyć transmisji UDP należy podać -u.

W przypadku programu netcat naprawdę warto zapoznać się z stroną podręcznika, po opisie wszystkich obsługiwanych opcji znajdują się przykłady zastosowania tego narzędzia.

10.5.4. Program nmap - skanowanie portów

Program nmap jest dość ważnym narzędzie w rękach większości administratorów, ma on masę możliwości jednak nas na tym etapię będzie interesować możliwość odpytania hosta na różnych portach i określenie czy możliwe jest połączenie z tymi portami. Ta czynność nosi nazwę skanowania portów i w niektórych sieciach bywa nielegalna (Więcej o tym w podrozdziale dotyczącym bezpieczeństwa). Istnieje wiele metod skanowania. A samo skanowanie może zostać rozszerzone np. o próbę zmuszenia usługi do powiedzenia czegoś więcej o sobie przez co można poznać jaki system operacyjny jest zainstalowany na hoście, którego porty skanujemy.

Rodzajów skanowania jest kilka. Jednym z prostszych i szybszych jest SYN. Polega ono na wysłaniu pakietu inicjalizacji połączenia i kiedy uzyskamy odpowiedź na ten pakiet, możemy uznać (oczywiście tylko wedle tej metody), że port jest otwarty.

xf0r3m@searx:~$ sudo nmap -sS ftp.morketsmerke.org
Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-31 08:39 CEST
Nmap scan report for ftp.morketsmerke.org (195.164.150.92)
Host is up (0.040s latency).
Not shown: 993 filtered ports
PORT     STATE  SERVICE
80/tcp   open   http
113/tcp  closed ident
443/tcp  open   https
2000/tcp open   cisco-sccp
2022/tcp open   down
5060/tcp open   sip
9090/tcp open   zeus-admin

Nmap done: 1 IP address (1 host up) scanned in 4.43 seconds

Na powyższym przykładzie przeskanowałem za pomocą metody SYN jeden ze swoich serwerów. Program podczas skanowania może działać znacznie dłużej niż tym przypadku w zależności od tego ile hostów ma do przeskanowania lub jaki ma zakres portów. Zatem po uruchomieniu skanowania zostanie nam przedstawiona tylko pierwsza linia. Po zakończeniu skanowania zostanie nam zwrócony raport. Pierwsza linia raportu zawienie nazwę hosta oraz jego adres IP. (Nmap scan report for ftp.morketsmerke.org (195.164.150.92)) Następnie informacje o tym, że host jest dostępny opóźnieniach jakie mogą wystąpić podczas komunikacji. (Host is up (0.040s latency).) Trzecia linia zawiera informacje o niepokazywanych portach/usługach, ze względu na to, że nie udało się uzyskać od nich żadnej odpowiedzi. Nmap rozpoznaje trzy stany portu:

Za pomocą skanowania portów możemy zauważyć różnicę miedzy akcjami drop oraz reject, pakietu zapory iptables. W przypadku akcji drop, stan portu będzie określony jako filtered, a w przypadku reject closed.

Polecenie nmap poza wykrywanie dostępnych na hostach usług, może zostać wykorzystane do odkrywania hostów w sieci, za pomocą opcji -sP jako argument podając adres sieci w notacji CIDR.

Opcji skanowania jest masa, funkcjonalność programu Nmap można rozszerzyć za pomoca skryptów Lua. Więc jeśli nie mielśmy styczności z tym programem warto się zapoznać stroną podręcznika.

10.6. Zdalne wywołanie procedury - RPC

RPC (ang. Remote Procedure Call), czyli zdalne wywołanie procedury to system, ktory ma umożliwić aplikacjom sieciowym uruchomienie funkcji znajdujących się na serwerze. Każdemu z tych programów przypisywany jest numer, dzięki któremu będzie można go wywołać. System ten znajduje się w niższych partiach warsty aplikacji.

Aplikacje korzystające z RPC używają klasycznych portów transmisji warstwy transportowej takich jak: TCP czy UDP. Wymagają one natomiast jeszcze jednej usługi pośredniczącej mianowicie rpcbind. Jej zadaniem jest mapowanie portów TCP/UDP na numery programów RPC. Usługa rpcbind może być widoczna podczas skanowania. Za pomocą poniższego polecenie możemy sprawdzić czy jakiś program możemy uruchomić i za pomocą jakiej usługi.

xf0r3m@vm-76925c3:~$ rpcinfo -p localhost
   program vers proto   port  service
    100000    4   tcp    111  portmapper
    100000    3   tcp    111  portmapper
    100000    2   tcp    111  portmapper
    100000    4   udp    111  portmapper
    100000    3   udp    111  portmapper
    100000    2   udp    111  portmapper

Podobny wynik polecenia możemy uzyskać posiadając obsługę NFS w swoim systemie. Nie będę tutaj rozpisywał się na temat wyników tego polecenia. RPC to póki co nie nasza liga, nie mniej jednak warto wiedzieć że istnieje coś takiego. Poza NFS z RPC korzysta także usługa NIS (Network Information Service) oraz program monitorujący FAM (File Access Monitor) środowiska GNOME.

10.7. Zabezpieczenie sieci

Zabezpieczanie sieci to temat rzeka. Polega on na takim skonfigurowaniu elementów/warstw bezpieczeństwa hostów w sieci, aby zniechęcić potencjalnych atakujących do brania za cel naszych komputerów. Dystrybucje Linuksa sprawdzaja się głównie w środowiskach serwerowych i to one stają się najczęściej ofiarami ataków. Obecnie procesy przetwarzania informacji opieraja się w mniejszym bądź wiekszym stopniu na bazach danych, a te przechowują różne informacje. Dane wrażliwe firmy współdzelone na są na serwerach udostępniających pliki czy strony internetowe wszystkie te usługi opierają swoje działanie na dystrybucjach Linuksa. Udział innych systemów jest tutaj niszowy, chociaż to powoli zaczyna ulegać zmianie. Oczywiście nie ma zabezpieczeń idealnych i jedynym bezpiczecznym sposobem przechowywania danych jest przechowywanie ich poza siecią. To jednak w mniejszym lub większym stopniu stanie się uciążliwe. Dlatego też poniżej znajduje się kilka zasasd, które pozwolą na podniesienie poziomu bezpieczeństwa naszych systemów.

Systemy uniksopodobne w tym i dystrybucje Linuksa mają to do siebie, że ich poziom bezpieczeństwa zależy od tego jaki jest nasz własny, nas jako ludzi. Jak bardzo mamy rozbudowaną wiedzę i umiejętności w tym temacie. Tak jest w przypadku wielu dystrybucji, jest to podyktowane wolnością użytkowników i nie chęcią do narzucania komukolwiek czegokolwiek. Trochę inaczej sprawa ma się w przypadku systemów klasy enterprise, jak sama nazwa wskazuje często są one wyposażone w dodatkowe mechanizmy, aby mogły działać jak podstawa systemów produkcyjnych w przedsiębiorstwach, a kierowanie się powyższymi zasadami powinno ograniczyć ryzyko uzyskania pełnego dostępu do systemu.

10.7.1. Typowe zagrożenia

Wśród ataków jakie można przeprowadzić na serwery z dystrybucją Linuksa możemy wyróżnić między innymi:

Omawiając zabezpieczanie sieci, moglismy się nie spodziewać informacji tego typu tutaj. W podrozdziale 10.5 zostały przedstawione dwa narzędzia netcat oraz nmap. Ich wykorzystywanie, w niektórych sieciach, może podnieść alarm o potencjalnej próbie włamania. Dlatego lepiej nie sprawdzać sieci którymi nie zarządzamy, ponieważ konsekwencje mogą być spore. Wydanie nawet pojedynczego polecenia ze skanowaniem, może zakończyć się oskarżeniem o włamanie.

Jakby nie patrzeć najwieksze podatności systemów informatycznych siędzą przed ekranami urządzeń, które się z nimi łączą. W firmach mimo wysokiego poziomu zabezpieczeń kluczowych systemów, to za pomocą inżynierii społecznej (sztuki manipulowania ludźmi) można uzyskać dostęp systemów w nawet tak dobrze zabezpieczonej organizacji i to z minimalną wiedzą techniczną. Dlatego też mimo zabezpieczeń technicznych trzeba mieć się na baczności, np. kiedy czytamy wiadomość e-mail z prośbą otwarcia załącznika, albo że nasz komputer jest zarażony złośliwym oprogramowanie i możesz połączyć się z konsulatantem, który je usunie za darmo.

10.8. Gniazda sieciowe

Omawiając warstwę aplikacji trzeba również wspomnieć o tym w jaki sposób procesy są wstanie brać udział w komunikacji sieciowej. W zależności od tego czy połączenie jest już zestawione to sprowadza się to skorzystania z dwóch wywołań systemowych recv oraz send. Proces chcąc skorzystać z tych wywołań musi odwołać się do połączenia za pomocą takie tworu jak gniazdo. Gniazda są wykorzystywane do uzyskania dostępu do sieci przy użyciu jądra. Proces wykorzystuje do określenia tego jak i kiedy komunikuje się on z siecią. Gniazda również biorą udział w komunikacji międzyprocesowej (IPC).

W zależności od tego w jaki sposób proces chce uzyskac dostęp do sieci wówczas musi skorzystać z określonego rodzaju gniazda. Dla protokołu TCP używane jest gniazdo strumienia (SOCK_STREAM) a dla UDP wykorzystywane jest gniazdo datagramu (SOCK_DGRAM). Konfigracja gniazda wymaga ustalenia rodzaju gniazda, adresów IP, portów i protokołu warstwy transportowej. Gniazda zazwyczaj są konfigurowane przez procesów serwerów. Po ustaleniu tych informacji proces wykorzystuje standardowe metody obsługi sieci.

Typowy serwer podczas swojego działania wykorzystuje dwa gniazda a nie jedno. Jeśli tak by było to wówczas serwery mogły świadczyć usługi tylko punktowo. Jedne klient się podłączył to drugi musi czekać na zakończenie połączenia. Na szczęście jedno z gniazd służy do nasłuchiwania, jeśli proces nadrzędny wykryje na nim ruch to za pomocą wywołania systemowego accept akceptuje to połączenie i tworzy dla tego połączenia dedykowane gniazdo zapisu i odczytu. Po tej czynności za pomocą wywołania systemowego fork tworzy proces podrzędny do obsługi tego połączenia. Gniazdo nasłuchujące dalej będzie prowadzić nasłuch dla procesu nadrzędnego.

Po skonfigurowaniu gniazda proces może prowadzić z nim interakcje zgodnie z rodzajem użytego gniazda.

10.9. Gniazda uniksowe

Gniazda uniksowe rodzaje gniazd szczególnie wykorzystywanym w komunikacji międzyprocesowej (IPC), wiele aplikacji bowiem jest projektowanych w architekturze klient-serwer (nawet te, które nie są sieciowe), a tego typu gniazda mogą się zachowywać jak gniazda sieciowe, mimo że nimi nie są i nie jest powiązana z nim żadana sieć. Możliwe jest również takie skonfigurowanie gniazd zachowywały się podobnie do protokołów TCP/UDP. Przykładem wykorzystania tego typu gniazd jest komunikacja z systemem D-Bus (odpowiedzialnym za monitorowanie i reagowania na zdarzenia w systemie).

Powodem popularności gniazd uniksowych jest możliwość utworzenia specjalnego pliku gniazda, które jak każdy plik w systemie będzie podlegać kontroli na podstawie klasycznych uniksowych uprawnień przez co inne procesy nie będą mieć dostępu do niego. Przykładem takiego pliku może być na przykład:

xf0r3m@vm-eba25a2:~$ ls -al /run/rpcbind.sock
srw-rw-rw- 1 root root 0 09-01 07:04 /run/rpcbind.sock

Inną sprawą jest wydajność, korzystając z gniazd uniksowych jądro nie musi używać podsystemu sieciowego. Sam proces programowania nie różni się zbytnio od gniazd sieciowych, dlatego też wiele aplikacji sieciowych również umożliwia połączenia za pomocą gniazd uniksowych. Przykładem takiej aplikacji może być przedstawiony na przykładzie rpcbind.

Za pomocą polecenia lsof z opcją -U możemy wyświetlić wszystkie uniksowe gniazda dostępne w systemie.

xf0r3m@vm-eba25a2:/media/xf0r3m/immudex_crypt0$ sudo lsof -U
COMMAND     PID       USER   FD   TYPE             DEVICE SIZE/OFF   NODE NAME
systemd       1       root   10u  unix 0x00000000c0eae923      0t0  15927 /run/systemd/journal/stdout 
type=STREAM (CONNECTED)
systemd       1       root   17u  unix 0x00000000a1f6a748      0t0  11408 /run/systemd/notify type=DGRAM (CONNECTED)
systemd       1       root   18u  unix 0x0000000062a0c5b9      0t0  11409 type=DGRAM (CONNECTED)
systemd       1       root   19u  unix 0x00000000962057e3      0t0  11410 type=DGRAM (CONNECTED)
systemd       1       root   20u  unix 0x000000001183727b      0t0  11411 /run/systemd/private type=STREAM (LISTEN)
systemd       1       root   21u  unix 0x000000007042f6df      0t0  11413 /run/systemd/userdb/io.systemd.DynamicUser 
type=STREAM (LISTEN)
systemd       1       root   22u  unix 0x000000008bad806c      0t0  11414 /run/systemd/io.system.ManagedOOM
type=STREAM (LISTEN)
systemd       1       root   23u  unix 0x0000000096526742      0t0  20935 /run/systemd/journal/stdout
type=STREAM (CONNECTED)
systemd       1       root   24u  unix 0x00000000f0a1522b      0t0 182291 /run/systemd/journal/stdout
type=STREAM (CONNECTED)

11. Udostępnianie plików w sieci

Do tej pory zajmowaliśmy się siecią jako komponentem systemu pora skorzystać z niej jako z środka komunikacji. W wiekszości przypadków, ludzie wykorzystują sieć do dzielenia się informacjami a te moga przybrać formę pliku dowolnego formatu, a my poznamy metody w jaki sposób możemy przenieść pliki z jednego komputera na drugi za pośrednictwem sieci. Pominiemy jednak takie narzędzia jak SCP oraz SFTP, ponieważ były już one omawiane w poprzednim rodziale.

11.1. Proste udostępnienie z pośrednictwem WWW

Rozważmy taki scenariusz, że musimy udostępnić plik wielu osobom w tym samym laboratorium. Plik jest za duży, żeby przesłać go mailem, ale z użyciem poczty elektronicznej możemy przesłać link. Wówczas przeglądarki połaczą się z naszym komputerem i pobiorą udostępniony plik. Dla maksimum bezpieczeństwa możemy utworzyć specjalny folder dla tego pliku, przenieść/skopiować go to tam i będą wewnątrz go wydać następujące polecenie:

xf0r3m@vm-d67b064:~$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [08/Sep/2023 12:35:06] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [08/Sep/2023 12:35:06] code 404, message File not found
127.0.0.1 - - [08/Sep/2023 12:35:06] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [08/Sep/2023 12:35:25] "GET /newsfeed HTTP/1.1" 200 -

Za pomocą wydanego przez nas polecenia z użyciem modułu języka programowania Python, uruchomiliśmy prosty serwer WWW. Serwer działa na wysokim porcie ze względu na uprawnienia. Zwykli użytkownicy nie mogą otwierać portów poniżej numeru 1023. Domyślnie serwer startuje na porcie 8000, jeśli z jakiś przyczyn nie możemy użyć tego portu, to wówczas możemy podać mu inny wysoki port na końcu polecenia. Adres 0.0.0.0 oznacza każdy adres przypisany do tego komputera, oznacza to że mogę połączyć z tym serwerem przez pętlę zwrotną co własśnie uczyniłem, koncowo pobrałem plik newsfeed. Działanie serwera kończymy prostym Ctrl+c.

11.2. Synchronizacja katalogu zdalnego - program rsync

Kolejny przykład udostępniania plików, wyłamuje się poza konwencję tego rozdziału, nie mniej jednak warto o nim wspomnieć. Może zostać po traktowany jako narzędzie do przesłania plików na serwer służący do ich udostępniania. Mowa tutaj o programie rsync. Jest on powszechnie wykorzystywany przez administratorów do przesyłania danych między Uniksami. Program wymaga istnienia na obu stronach komunikacji oraz dostępu do powłoki (rsync domyślnie wykorzystuje SSH). Program jeśli ma działać na zdalnym systemie zachowuje się podobnie do SCP, program może również działać na lokalnym systemie zastępując klasyczne polecenie cp i tak też to polecenie działa w prosty niemodyfikowany opcjami sposób. Opcje programu są w stanie dość mocno wpłynąc na jego zachowanie, że te dwa powyższe polecenie mogą stać się zbędne. Polecenie rsync może być również wykorzystywane to kopiowania/przenoszenia w obrębie jednego komputera.

Najprostsza składnia polecenia rsync wygląd następująco:

xf0r3m@immudex:~$ rsync -r katalog_lokalny user@host:/sciezka/do/katalogu/zdalnego

Polecenie to prześle cały katalog katalog_lokalny do katalogu zdalnego na serwerze. Na powyższym poleceniu użyłem opcji -r (rekurencja), w przeciwnym razie nic nie zostanie przesłane.

Podczas przekazywania ścieżek do polecenia (głównie ścieżki lokalnej) istnieje pewna zależność. Jeśli definiując scieżkę lokalną podamy na jej końcu ukośnik, to wówczas do katalogu zdalnego zostanie przekopiowana sama zawartość bez tworzenia początkowego katalogu.

#Bez kończącego ukośnika:
xf0r3m@vm-331503c:/ic0$ rsync -r Repos2 /ic0/Dokumenty
# Katalog docelowy
xf0r3m@vm-331503c:~/Dokumenty$ ls -al
razem 12
drwxr-xr-x  3 xf0r3m xf0r3m 4096 09-18 09:52 .
drwxr-xr-x 11 xf0r3m xf0r3m 4096 09-18 09:12 ..
drwxr-xr-x  4 xf0r3m xf0r3m 4096 09-18 09:52 Repos2
xf0r3m@vm-331503c:~/Dokumenty$ ls -al Repos2/
razem 16
drwxr-xr-x 4 xf0r3m xf0r3m 4096 09-18 09:52 .
drwxr-xr-x 3 xf0r3m xf0r3m 4096 09-18 09:52 ..
drwxr-xr-x 5 xf0r3m xf0r3m 4096 09-18 09:52 Repos
drwxr-xr-x 2 xf0r3m xf0r3m 4096 09-18 09:52 .test

#Z kończącym ukośnikiem.
xf0r3m@vm-331503c:/ic0$ rsync -r Repos2/ /ic0/Dokumenty
xf0r3m@vm-331503c:~/Dokumenty$ ls -al
razem 16
drwxr-xr-x  4 xf0r3m xf0r3m 4096 09-18 09:58 .
drwxr-xr-x 11 xf0r3m xf0r3m 4096 09-18 09:12 ..
drwxr-xr-x  5 xf0r3m xf0r3m 4096 09-18 09:58 Repos
drwxr-xr-x  2 xf0r3m xf0r3m 4096 09-18 09:58 .test

Na tę zależność należy uważać podczas kopiowania danych, ponieważ sam utworzony przez polecenie katalog może być istotny. Na powyższych przykładach użyłem polecenia rsync wewnątrz tego samego systemu.

Jak wcześniej wspominiałem dużą rolę w polecenie rsync odgrywają jego opcje. Poniżej znajduja się najważniejsze z nich.

W tym materiale przedstawiono tylko, kilka opcji powszechnie wykorzysywanych podczas korzystania z polecenia rsync. Obszerną wiedzę na ten temat zwiera strona podręcznika polecenia, zawierająca opisy wszystkich opcji oraz inne zagadnienia związane z programem.

Ostatnią rzeczą związana z poleceniem jest przekazywanie do rsync poleceń związanych z SSH, w końcu ten program domyślnie korzysta z tego protokołu. Przekazanie opcji SSH wymaga użycia zmiennej powłoki.

export RSYNC_RSH='-p 2022 -i id_rsa';

W powyższym poleceniu użyto polecenia wbudowane export w celu przeniesienia zmiennej do obszaru pamięci odpowiedzialnego za zmienne środowiskowe (będzie o tym w ostatnim rodziale materiału). Zmiennej nadano wartość składającą się z dwóch opcji polecenia SSH. Rsync będzie korzystać z tych informacji podczas zestawiania połączenia ze stroną zdalną.

11.3. Wprowadzenie do udostępniania plików

Operując w sieci lokalnej maszynami z dystrybucjami Linuksa, przyjdzie kiedyś pora, na to aby wykorzystać je do współdzielenia plików między użytkownikami, nie zależnie od tego jakiego systemu używają. Tutaj warto się zastanowić na tym między jakimi systemami będą one wymieniane oraz w jaki sposób użytkownicy łączą się z naszymi serwerami. Czy łączą się bezpośrednio z siedziby firmy gdzie też znajdują się serwery czy też pracują zdalnie.

Dlaczego o tym mówię, otóż istnieją metody lepiej i gorzej przystosowane do współdzielenia plików między różnymi systemami. Jedne są prostsze w obsłudze, ale np. nie posiadają żadnych metod uwierzytelniania, albo zostosowanie ich wymaga dodatkowych środków i nakładu pracy. Inne posiadają już pewne zabezpieczenia przed nieuprawnionym dostępem, ale nie są aż tak wydajne. Trzecią opcją są metody bardziej skupiające się na bezpieczeństwie, ale ich wydajność dość mocno kuleje. Oczywiście przy większym zangażowaniu można użyć tych metod w pozostałych środowiskach, a nie tylko w tych wymienionych, jednak warto mieć powyższe na uwadze oraz przyjąć do wiadomości fakt, że jeśli coś jest bardziej skomplikowane, to więcej składników tego czegoś może zawieść. Tym optymistycznym akcentem przjdziemy do pierwszej metody.

11.4. Współdzielenie plików między wszystkimi platformami

Jedną z metod współdzielenia plików między większością dostępnych platform na świecie jest pakiet Samba. Pakiet ten zawiera wszelkie niezbędne oprogramowanie do obsługi protokołu SMB (ang. Server Message Block) firmy Microsoft. Protokół ten w systemach MS Windows służy do udostępniania folderów w sieci. Samba jest natomiast implementacją protokołu SMB dla Uniksów (bo nie tylko dla samych dystrybucji Linuksa). Opis zagadnienia będzie składać sie głównie z opisu konfiguracji serwera. W niej zajmiemy się ogólną konfiguracją serwera, konfiguracją uwierzytelniania i użytkowników, udostępnianiem udziałów (katalogów) oraz udostępnianie drukarek. Zatem nie przedłużając:

11.4.1. Ogólna konfiguracja serwera Samba

Samba to potężny demon, posiadający wiele zastosowań, na przykład po za konfiguracją udostępniania folderów oraz drukarek Samba może świadczyć usługi katalogowe (domenowe) dla systemów w sieci jeśli chcelibyć zarządzać wszystkimi komputerami w sieci. O konfiguracji Samby jako serwera domenowego Active Directory (usługi katalogowe w implementacji Microsoft-u) zrobiłem odrębny materiał i znajduje się pod tym linkiem: https://morketsmerke.github.io/articles/linux/samba_AD_DC_-_Instalacja.html Pakiet Samby składa się głównie z dwóch programów. Głównego demona protokołu SMB smbd oraz demona protokołu NetBIOS, który jest protokołem pomocniczym dla SMB i zapewnia interfejs pozwalający na łączenie się aplikacji w innym komputerami w sieci lokalnej. Istotną cechą jaką odgrywa NetBIOS jest używanie przyjaznej nazwy zamiast adresu. A odpowiadającym za niego demonem jest nmbd. Te informacje mogą być nam potrzebne podczas zarządzania Sambą, bowiem czasami trzeba zrestartować oba demony, aby pewne zmiany mogły wejść w życie.

Plik konfiguracyjny na Debianie znajduje się w katalogu /etc/samba. Plik jest formatu XDG, czyli jest podzielny na sekcję (np. [global]) w sekcjach zawarte są opcje w formacie klucz-wartość (np. workgroup = WORKGROUP). Sama konfiguracja jest dość rozwleczona przez duża ilość opisów, pomocne rozwiazanie kiedy nad lub pod opcją mamy wyjaśnienie co dokładnie ona robi. Sama sekcja [global] podzielona jest za pomocą komentarzy na podsekcje, w których znajdują się opcje np. dotyczące sieci czy uwierzytelniania. Zawartość tego pliku jest zależna od dystrybucji. Dlatego też na Debianie mamy w podsekcji Browsing/Identification mamy tylko opcję definiującą grupę roboczą.

[global]

## Browsing/Identification ###
workgroup = WORKGROUP

Jest to wskazanie grupy roboczej, której częścią ma być serwer Samby, najczęściej jest to WORKGROUP, który jest wartością domyślną. Po tej opcji następuje Podsekcja sieciowa. W niej występują następujące opcje:

#### Networking ####
;   interfaces = 127.0.0.0/8 eth0

;   bind interfaces only = yes

Tutaj znajdują się tylko dwie opcje służą one kształtowaniu ruchu jaki może docierać do Samby. Za pomocą opcji interfaces podajemy adresy sieci w notacji CIDR wraz z nazwami interfejsów, które będą mogły korzystać z serwera. Druga opcja bind interfaces only powoduje, że ruch do Samby będzie dopuszczony tylko ze zdefiniowanych za pomocą opcji interfaces sieci.

#### Debugging/Accounting ####
   log file = /var/log/samba/log.%m
   max log size = 1000
   logging = file
   panic action = /usr/share/samba/panic-action %d

W podsekcji Debugging/Accounting znajdują się opcje odwiedzialne za konfiguracje rejestrowania komunikatów diagnostycznych i tak kolejno:

W ten sposób omówilismy sobie podstawowe opcje konfiguracji serwera Samba.

11.4.2. Konfiguracja uwierzytelniania użytkowników na serwerach Samba

Poniżej podsekcji dotyczącej komunikatów diagnostycznych znajduje się fragment pliku konfiguracyjneg dotyczący ustawień uwierzytelniania - Authentication, ta podsekcja zawiera ustawienia dostępu użytkowników do serwera. Samba może mieć odrębne hasła, niż te wykorzystywane przez użytkowników do dostępu do powłoki. Poniżej znajduje domyślna podsekcja uwierzytelniania dołączona pakietu Samby dostępnego na Debianie.

####### Authentication #######
   server role = standalone server
   obey pam restrictions = yes
   unix password sync = yes
   passwd program = /usr/bin/passwd %u
   passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
   pam password change = yes
   map to guest = bad user

Pierwsza z opcji jest rola serwera. Jeśli nasza Samba ma być serwerem plików to zostaje wartość wskazująca na samodzielny serwer (standalone server), kolejna opcja decyduje czy dostęp musi być zgodny z wymogami systemu PAM (obey pam restrictions). Trzecia opcja jest dość istotna (unix password sync). Dodając użytkownika do Samby, należy przypisać mu hasło. Te hasła mogą być w odrębnej bazie, ale mogą być również synchonizowane z hasłami systemowymi. Ta opcja właśnie o tym decyduje. Czy hasła powinny być synchronizowane z systemowymi czy też nie. Nie ma jednoznacznej odpowiedzi czy tak czy nie. Wszystko zależy od potrzeb, tak jak wspomniałem o tym podczas wprowadzenia do współdzielenie plików. Warto natomiast znać konsekwencje takiego działania, włączenie synchonizacji haseł, zmieni hasło dostępu do systemu i najbezpieczniejszym rozwiązaniem będzie zablokowanie dostępu do powłoki dla użytkowników wyłącznie Samby, ponieważ hasła zapisane aby mieć łatwiejszy dostęp do udziałów Samby z platform nie Linuksowych można odzyskać. Dziś w dystrybucjach takich jak Debian, mimo iż ta opcja jest domyślnie włączona to i tak nie działa, ponieważ element podsystemu PAM, który kontrolował i obsługiwał cały ten proces został usunięty w z Samby w okolicach 2015 roku. Opcje poprzedzone frazą passwd, służą wskazaniu programu służącemu ustawianiu hasła (passwd program) oraz zdefiniowaniu komunikatów wyświelanych użytkownikowi podczas procesu zmiany hasła (passwd chat). Obecnie te opcje są puste (przynajmniej na Debianie), nie mają żadnego wpływu na działanie pakietu, ponieważ na dzień dziejszy synchronizacja haseł nie działa i nie zapowiada się aby kiedy kolwiek przywórcono tę funkcjonalność. W wspomniano o tych opcjach dla spójności opisu konfiguracji. Przedostatnia opcja (pam password change) służy kontroli wykorzystania PAM podczas zmiany hasła za pomocą klienta SMB niż w przypadku użycia programu zdefiniowanego w opcji passwd program. Ostatnia opcja definiuje kiedy należy zrzucić uprawnienia do połaczenia anonimowego. Domyślnie jest to stosowane, kiedy nie uda się pomyślnie uwierzytelnić użytkownika.

Opcje uwierzytelniania otrzymały osobny podrozdział ze względu na to, że pełnią dość istotną rolę we współdzieleniu plików i zapewniają jakiś minimalny stopień bezpieczeństwa. Warto zwrócić uwagę na to, iż przedstawione tutaj są domyślymi ustawieniami zapożyczonymi bezpośrednio z pakietu Samby rozprowadzanego wraz z Debianem.

11.4.3. Współdzielenie katalogów - udziały Samby

Eksport katalogów domowych użytkowników

Jedną z najważniejszych cech, dla których ludzie korzystają z pakietu Samba jest współdzielenie katalogów, w których użytkownicy mogą dzielić się plikami z innymi w sieci. Innym zagadnieniem może być centralizacja katalogów domowych między użytkownikami wielu systemów uniksowych.

Pozostawiamy resztę opcji i przechodzimy do podsekcji Share Definitions. Tutaj kończy się sekcja [global]. Domyślnie w pliku konfiguracyjnym nie znajdują się żadne predefiniowane udziały więc na początek zajmiemy się exportem katalogów domowych.

Za udostępnienie katalogów domowych odpowiada sekcja [homes]. Poniżej znajduje się sekcja eksportu katalogów domowych zapożyczona z domyślnego pliku konfiguracyjnego Debiana:

[homes]
   comment = Home Directories
   browseable = no
   read only = yes
   create mask = 0700
   directory mask = 0700
   valid users = %S

Sekcję rozpoczyna opcja comment, najzwyczajniej w swiecie jest opis. W nim możemy opisać zawartość czy wskazać właściciela, nie które programy (lub składniki programów) zajmujące się obsługą protokołu SMB w systemie mogą wykorzystać to pole do tworzenia opisów znalezionych udziałów. A odnośnie programów obsługujących protokół SMB to za widoczność udziału w nim odpowiedzialna jest opcja browseable. Tutaj (domyślnie) ustawiona jest na no, przez co udziału nie będzie widać w składnikach typu "Otoczenie sieciowe" (obecnie "Sieć") w eksploratorach plików. Wpływ następnej opcji na udział możemy się domyślić po samej nazwie (read only) oraz ustawionej jej wartości (yes). Opcje zawierające frazę mask zawierają domyślne uprawnienia dla tworzonych plików (create mask) oraz katalogów (directory mask) i jak możemy zauważyć tylko właściciel będzie mieć jakie kolwiek uprawnienia. Ostatnią opcją jest (valid users), która określa jacy użytkownicy mają mieć dostęp do danego udziału. W przypadku eksportu katalogów domowych zamiast listy użytkowników używa się wartości %S, co powoduje, że nie musimy podawać listy wszystkich użytkowników, a wymusza to na użytkowniku stosowanie \\SERVER\username w momecie odwołania się do serwera. W przypadku Uniksów stosuje odwrotne ukośniki: //SERVER/username. W ten sposób wygląda eksportowanie katalogów domowych użytkowników. Dostęp katalogu domowego użytkownika wymaga podmontowania udziału lub skorzystania z klienta Samby. O czym będzie jeszcze w tym podrozdziale.

Tworzenie udziałów Samba

W pliku konfiguracyjnym Samby każdy udział jest odrębną sekcją. Nazwa udziału jest zarazem nazwą sekcji. Poniżej znajduje sie przykładowa sekcja definiująca udział.

[nazwa_udziału]
path = sciezka/do/katalogu
comment = opis
guest ok = no
writeable = yes
printable = no

Jak możemy zauważyć udostępnienie katalogu w sieci za pomocą pakietu Samba nie wymaga za dużo pracy. Na przykładzie pierwszym parametrem jest path, określa on ścieżkę do katalogu, który mamy zamiar udostępnić. Na temat opcji comment wspominałem w podczas opisu eksportu katalogów domowych. Opcja guest ok umożliwia udzielenie dostępu anonimowego do udziału. W tym przypadku dostęp w ten sposób jest zablokowany. Rzadko się zdarza generalnie aby dostęp anonimowy był wymagany, jeśli tak jest to najczęściej udział jest tylko odczytu. Chciaż spotkałem się z taką prośbą aby skonfigurować udział tak, aby każdy mógł w nim zapisać w nim swój plik. Opcja writeable konfiguruje czy na udziale będzie można zapisywać jakie kolwiek dane. Ostatnia opcja wprawdzie ma jedno głowne zadanie, wskazać Sambie, że ta sekcja to współdzielony katalog, a nie udostępiona drukarka dlatego, też ustawiono drukowalne jako nie. Nie ma potrzeby ustawiania opcji browsable, ponieważ domyślnie jest ona włączona. Chcąc zmienić ten stan, należy zapisać jej wystąpienie definicji udziału i ustawić wartość no. Oczywiście jeśli jest taka potrzeba możemy dodawać do definicji naszego udziału wiele innych poznanych tutaj opcji takich jak domyślne uprawnienia, zezwolenie na dostęp do tego udziału tylko dla wybranych użytkowników. Strona podręcznika jest pełna opcji, jedną z ciekawszych może być veto files, gdzie na podstawie wzorca możem zablokować udostępnianie konkretnych rodzajów plików. Na stronie podręcznika: man 5 smb.conf znajdziemy więcej informacji na temat tej opcji oraz wielu innych.

11.4.4. Współdzielenie drukarek przy użyciu pakietu Samba

Istnieje możliwość, aby za pomocą pakietu Samba można dać możliwość drukowania użytkownikom innych platform na drukarkach podłączonych do systemów z dystrybucjami Linuksa. Samo drukowanie na dystrybucjach odbywa się za pomocą system CUPS, ale to nie temat na teraz. Zajmiemy się nim w poźniejszych rodziałach. Teraz zakładamy, że drukarki są podpiętę do naszego komputera, na którym hostowana jest Samba i z tego komputera możemy drukować.

Dlaczego przyjąłem takie założenia, otóż drukowanie to jeden z cięższych tematów, ponieważ większość konsumenckich drukarek nie ma dobrego wsparcia dla dystrybucji Linuksa i korzystanie z nich to swojego rodzju loteria. Dlatego może warto zajrzeć na stronę https://www.openprinting.org/printers przed zakupem drukarki i jeśli ma działać pod którąś z dystrybucji to można rozważyć zakup jedenego z dostępnych tam modeli. Chociaż może to niebyć w cale takie prostę, poniważ wiekszość tych urządzeń lata świetności ma już za sobą. Szkoda, bo za pomocą komputerów jednopłytkowych (takich jak Raspberry Pi) można by drukarki, które nie są sieciowe podłączyć do sieci.

Nie mniej jednak. W domyślnej konfiguracji Debiana jest zdefiniowane współdzielenie drukarek. Obecne wersje Samby może nie zajmują się udostępnianiem drukarek, ale ich eksportem podobnie jak w przypadku katalogów domowych, przez co użytkownicy będą mieć dostęp do wszystkich drukarek podłączonych do naszego serwera Samby. Poniżej znajduje się wycinek konfiguracji dotyczącej eksportu drukarek w Debianie:

[printers]
   comment = All Printers
   browseable = no
   path = /var/tmp
   printable = yes
   guest ok = no
   read only = yes
   create mask = 0700

Wszystkie występujące tutaj opcje, dobrze już znamy. Zastanawiająca może być wartość opcji path, która wskazuje na ścieżkę /var/tmp. Samba wymaga katalogu z możliwością zapisu przez pozostałych użytkowników wraz z ustawionym bitem sticky, a to jedyny obok, drugiego /tmp taki katalog. Problem z ogólnym katalogiem /tmp jest taki, że znajduje się na ramdysku. Posiada raczej małą objetość, a spływające od wielu użytkowników pliki do drukarek mogą wyczerpać szybko to miejsce.

11.4.5. Dodawanie użytkowników Samby

Samba prowadzi odrębną bazę użytkowników wykorzystując do tego inne rodzaj szyfrowania. Dlatego też podczas "synchronizacji" (obecnie niedziałającej), wymaga wpisania hasła do /etc/shadow. Aby uzyskać dostęp do udziałów użytkownicy innych platform muszą mieć założone odrębne konta. Nazwy użytkowników kont Samby muszą odpowiadać tym Uniksowym, więc nie możemy utworzyć użytkownika mającego mieć dostęp tylko do udziałów, bez konta w systemie gdzie oprograwowanie do współdzielenia zasobów w systemie. Takie konto oczywiście możemy zabezpieczyć zmieniając domyślny program odpowiedzialny za uruchomienie zaraz po zalogowaniu. Użytkowników do Samby dodajemy za pomocą dostarczanego przez ten pakiet polecenia, musimy użyć opcji -a wskazującej na dodanie. Pominięcie tej opcji spowoduje, że będziemy zmieniać jedynie hasło. Do obsługi użytkowników Samby wymagane są uprawnienia administratora.

xf0r3m@debian:/var$ sudo smbpasswd -a debian
[sudo] hasło użytkownika xf0r3m: 
New SMB password:
Retype new SMB password:
Added user debian.

Tak naprawdę to tyle, konto użytkownika jest gotowe. Polecenie smbpasswd posiada o wiele wiecej funkcji niż tylko samo dodanie poniżej znajduje się lista opcji, które mogą pomóc w administrowaniu użytkownikami Samby.

Warto pamietać o tym, że użytkownicy służą jedynie uwierzytelnianiu, natomiast autoryzacji do zapisu na jednym z udziałów należy dokonać z użyciem użytkowników uniksowych. Nie warto szukać przyczyn problemów z dostępem do elementów udziału w użytkownikach Samby. Na początku warto w ogóle sprawdzić czy udział jest dobrze ustawiony (read only = no oraz writeable = yes) oraz czy uprawnienia do udziału umożliwiają na zapis.

11.4.6. Użycie protokołu samby

Kiedy nasz serwer SMB jest już skonfigurowany możemy przejść do konfiguracji klienta, a w zasadzie klientów. Ponieważ istnieje kilka metod na skorzystania z serwera Samby.

Pierwszym z nich jest skorzyststanie z przeglądarki sieciowej wbudowanej w menedżer plików, jeśli korzystamy z jednego z popularniejszych środowisk graficznych. Przycisk uruchamiający te funkcję znajduje się zazwycznaj po lewej stronie, najczęsciej nosi nazwę Przeglądanie sieci lub Sieć. Tutaj musimy przypomnieć sobie opcję browseable, która powodowała, że udziały były widoczne w tego typu komponentach. Domyślnie wyeksportowane katalogi domowe nie są w nich widoczne.

Takie ukryte dla przeglądarek udziały są osiągalne na dwa sposoby. Pierwszym z nich jest zamontowanie udziału jak dysku wymienego, czy partycji. Montowanie udziałów SMB wygląda podobnie, ale wymaga kilku dodatkowych opcji. Pierwszą z nich dość istotną jest wymagana w systemie obsługa protokołu SMB jako systemu plików. Taki system plików nosi nazwę CIFS i pakiety go obsługujący jest gotowy do zainstalowania w repozytorium dystrybucji (przynajmniej na GNU/Linux Debian - cifs-utils) Po zainstalowaniu pakietu, możemy wykorzystać polecenie mount do zamontowania udziału w systemie. Jak pamietamy montowanie systemów plików może wymagać uprawnień administratora.

xf0r3m@vm-9585173:~$ sudo mount -t cifs //DEBIAN/xf0r3m homes_on_debian -o user=xf0r3m,uid=1001,gid=1001
Password for xf0r3m@//DEBIAN/xf0r3m: 
xf0r3m@vm-9585173:~$ sudo mount | grep '//DEBIAN/xf0r3m'
//DEBIAN/xf0r3m on /media/xf0r3m/immudex-crypt0/homes_on_debian type cifs 
(rw,relatime,vers=3.1.1,cache=strict,username=xf0r3m,uid=1001,noforceuid,
gid=1001,noforcegid,addr=172.18.227.14,file_mode=0755,dir_mode=0755,soft,
nounix,serverino,mapposix,rsize=4194304,wsize=4194304,bsize=1048576,
echo_interval=60,actimeo=1,closetimeo=1)

Chcąc wykorzystać polecnie mount. Należy wskazać za pomocą opcji -t montowany system plików, następnie występuje adres udziału w przypadku dystrybucji Linuksa, ukosniki mają zwrot zgodny z tymi wykorzystywanymi w systemie (//DEBIAN/xf0r3m). Następnie występuje punkt montowania (homes_on_debian). Na samym końcu występuje specyficzne dla systemu CIFS opcje montowania, więc kolejno:

Po wydaniu takiego polecenia, zostaniemy zapytani o hasło. Hasło również możemy w komponować w powyższe polecenie, za pomocą opcji pass. Jest to przydane gdy chcemy montować udziały samby podczas uruchamiania systemu (dodać wpis to pliku /etc/fstab). Jeśli po podaniu hasła zostanie nam zwrócony znak zachęty, wówczas oznacza to, że udział został poprawnie zamontowany. W przypadku problemów zostaniemy o tym poinformowani stosownym komunikatem. Dowód zamontowania możemy również otrzymać kiedy przefiltrujemy wyjście polecenia mount pod kątem występowania adresu udziału. Co uczyniono w drugim polecenie.

Odłączenia udziału od systemu dokonujemy w ten sam sposób, co z każdym innym system plików, za pomocą polecenia umount.

xf0r3m@vm-9585173:~$ sudo umount homes_on_debian

Po wydaniu tego polecenia udział zostanie odłączony od systemu.

Innym sposobem jest dostęp do udziału SMB poza jego montowanie jest skorzystanie z dedykowanego klienta - smbclient. Korzystanie z niego przypomina trochę SFTP. Prawdopodobnie nie znajdziemy go domyślnie w systemie i trzeba będzie go zainstalować. Za pewne znajduje się on w repozytorium (na GNU/Linux Debian: smbclient). Po instalacji wydajemu poniższe polecenie.

xf0r3m@vm-9585173:~$ smbclient -U xf0r3m //DEBIAN/xf0r3m
Password for [WORKGROUP\xf0r3m]:

Jesli jesteśmy się w stanie połączyć z serwerem Samby to to zostanie nam zwrócony prompt z prośbą o hasło. Jeśli hasło jest poprawne otrzymamy o taki znak zachęty.

Try "help" to get a list of possible commands.
smb: \> 

Lista dostępnych poleceń uzyskamy po przez wpisania polecenia help. Ta opcja może być przydatna, kiedy musimy skorzytać udziału Samby a nie możemy montować (np. wewnątrz kontenerów) lub nie mamy wystarczających uprawnień do podmontowania udziału.

Oczywiście przedstawione poniżej dwa sposoby tyczą się głównie Uniksów, chciaż metoda z użyciem menedżera plików, również moze znaleźć zastosowanie oczywiście jesli posiadamy opowiednie komponenty w swoim systemie. Jednak inne platformy jak głównie MS Windows będą wykorzystywać wyłącznie ją. Rzadko używa się Samby do wymiany danych między dwoma Uniksami są do tego... No właśnie, nie można jednoznacznie powiedzieć, że lepsze metody. Zatem będzie metoda bezpieczeniejsza ale mało wydajna i metoda mniej bezpieczna (można by powiedzieć, że domyślnie nie posiadająca żadnych mechanizmów bezpiczeństwa), ale za to bardziej wydajna.

11.5. Bezpieczenie współdzielenie plików - SSHFS

Na podstawie informacji z poprzedniego rozdziału wiemy już, że przy użyciu protokołu SSH możemy przesyłac pliki służy do tego SFTP. Ale to nie wszystkie możliwości związane z obsługą plików przez ten protokół. Otóż możemy w lokalnym systemie zamontować katalog zdalny z wykorzystaniem SSH. Służy do tego SSHFS. Ta metody współdzielenia plików jest bezpieczna i łatwa w konfiguracji, SSHFS wymaga jedynie dostępu do protokołu SFTP. Ten system plików wykorzystuje ciekawą mechnikę o nazwię FUSE, która pozwala na zamontowanie udziału przez zwykłego użytkownika i nie wymaga żadnych dodatkowych uprawnień. Oczywiście jeśli coś ma tyle zalet, to albo musimieć równie dużo wad lub jedną dużą wadę, która nieco przekreśla popularność tej metody udostępniania plików. Jest nią wydajność, która jest wręcz mierna. Nie mniej jednak jest to chyba jeden z najbezpieczniejszych metod montowania katalogów zdalnych. Obsługa wymaga aby na komputerze klienta znajdowało się polecenie sshfs oraz fusermount są one dostarczane za pomoca pakietów (przynajmniej na Debianie): fuse3 oraz sshfs. Montowanie katalogu odbywa się za pomocą poniższego polecenia.

xf0r3m@vm-c1b9654:~$ sshfs xf0r3m@debian:/home/xf0r3m homes_on_debian -o reconnect
xf0r3m@debian's password: 
xf0r3m@vm-c1b9654:~$ mount | grep 'xf0r3m@debian'
xf0r3m@debian:/home/xf0r3m on /media/xf0r3m/immudex-crypt0/homes_on_debian type fuse.sshfs
(rw,nosuid,nodev,relatime,user_id=1001,group_id=1001)

Tak zamontowany udział jest normalnie widocznym katalogiem w systemie. Jak byśmy montowali np. udział Samby. Jeśli katalog zdalny znajduje się w tej samej sieci lokalnej co klient to nie powinniśmy dostrzec żadnych problemów z wydajnością. Problem może okazać się gdy klient korzysta z niepewnego łącza jak jest np. transmisja danych w sieci komórkowej. Mimo wolnego transferu, możemy doświadczyć także zerwania połącznia co spowoduje, że nasz katalog stanie się nieosiągalny, zaradzić temu może opcja reconnect dołączona na końcu polecenia montowania.

Po zakończeniu prac katalog należy odmontować z systemu. Jednak nie robi się tego za pomocą znanego nam polecenie umount. To polecenie jest zarezerwowane do obsługi klasycznego montowania z udziałem jądra. Tutaj montowaliśmy ograniczając się tylko do przestrzeni użytkownika. Do tego celu wykorzystamy drugie polecenie jakim jest fusermount.

xf0r3m@vm-c1b9654:~$ fusermount -u /media/xf0r3m/immudex-crypt0/homes_on_debian 
xf0r3m@vm-c1b9654:~$ mount | grep 'xf0r3m@debian'

Po wyniku działania drugiego polecenia, możemy żauważyć że montowany udział nie istnieje już w systemie.

11.6. Współdzielenie plików między dwoma Uniksami

Ostatnią metodą udostępniania plików, jest współdzielenie zasobów dyskowych między dwoma systemami uniksopodobnym. W systemach Microsoftu, również istnieje możliwość wykorzystania tego protokołu, jednak ze względów bezpieczeństwa jest on wykorzystywany głównie między systemami godnymi zaufania. A wykorzystywanym do tego protokołem jest NFS (Network File System). Wymiana danych między hostami za pomocą NFS nie jest szyfrowana więc zaleca się wykorzystanie VPN jeśli jest konieczność użycia tej metody między dwoma odległymi serwerami. Ten protokół również nieposiada, żadnych wbudowanych metod uwierzytelniania, poza jednym przypisaniem dostępu do udziału konkretnemu adresowi IP, ale również taki udział może być udostępniony całej sieci. Zatem najlepszym środowiskiem jest dla NFS była sieć SAN-owska (Storage Area Network) odseparowana fizycznie i logicznie od sieci LAN.

W tym materiale ze względu na to, że maja to być wyłącznie podstawy obsługi dystrybucji linuksowych skupimy się tylko i wyłącznie na montowaniu udziału NFS w systemie. Jeśli chcialibyśmy skonfigurować serwer NFS, to możemy skorzystać z innego materiału, gdzie zostało to opisane pod tym adresem: https://morketsmerke.github.io/articles/terminallog/RedHat_-_RHCSA.html#17.nfs

Wykorzystanie NFS w naszym systemie wymaga oprogramowania, które trzeba zainstalować. Na Debianie pakiet nosi nazwę nfs-common. Po zainstalowaniu pakietu możemy przejść do montowania naszego udziału w systemie. W tym przypadku nie musimy podawać żadnych dodatkowych informacji poleceniu mount, polecenie samo na podstawie wprowadzonego adresu serwera spróbuje połączyć się z wykorzystaniem protokołu NFS.

xf0r3m@vm-c1b9654:~$ sudo mount debian:/home/xf0r3m homes_on_debian/
xf0r3m@vm-c1b9654:~$ mount | grep "debian:/home/xf0r3m"
debian:/home/xf0r3m on /media/xf0r3m/immudex-crypt0/homes_on_debian type nfs4
(rw,relatime,vers=4.2,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,
timeo=600,retrans=2,sec=sys,clientaddr=172.19.87.103,local_lock=none,
addr=172.19.82.231)

Zwróćmy uwagę na prostę z jaką dokonuje się podłączenia udziału NFS na Uniksach. Dowód znajduje się w informacji zwróconej przez polecenie mount. Podobnie wygląda odmontowywanie.

xf0r3m@vm-c1b9654:~$ sudo umount homes_on_debian 
xf0r3m@vm-c1b9654:~$ mount | grep "debian:/home/xf0r3m"

Sieciowy system plików został odłączony od naszego systemu.

11.7. Jak wygląda współdzielenie plików dzisiaj?

Przedstawione tutaj metody nie są archaiczne. Są one wykorzystywane głównie w sieciach lokalnych. Są one wykorzystywane głównie wewnątrz organizacji co może mieć mało wspólnego ze zwykłym konsumenckim współdzieleniem plików. W dzisiejszych czas wszystko zależy od dostępności zasobów na żądanie. Chcąc mieć te same pliki na laptopie z dystrybucją Linuksa oraz np. na telefonie z system Android to wystarczy że skorzystamy z usług przechowywania danych jakie serwuje na Google w postaci Google Drive. Jednak ilość miejsca jest ograniczona do 15GB dla zwykłych użytkowników, oczywiście można poszukać innego dostawcy tego typu usług, bo i tak nalepszym narzędziem do korzystania z tego typu rozwiązań jest przeglądarka internetowa. Ceny są do siebie zbliżone. Jedyną taka usługą ze strony trzeciej godną uwagi wydaję się Internxt oferując np. plan dożywotni. Nie mniej jednak na uwagę zasługuje przchowywanie danych, które opiera się na szyfrowaniu oraz na polityce zero-knowledge, co oznacza, że nawet sami pracownicy nie są wstanie dowiedzieć się co tak naprawdę tam jest przechowywane. Inną metodą może być samodzielne hostowanie takich rozwiązań jak Google drive chociażby w postaci Nextcloud. W takim bądź przypadku spada na nas rola zorganizowania oraz utrzymania niezbędnej infrastruktury. Na koniec warto dodać, że najlepszą metodę współdzielenia plików określa poziom zaawansowania technicznego udostępniących pliki. Jedni potrzebują specjalnych rozwiązań i specjalnych aplikacji innych zadowoli SFTP oraz SSHFS.

12. Konfiguracja środowiska użytkownika

Chcąc przedstawić użytkownikom dostępnę środowiska często przedstawia się podział, na środowisko tekstowe (TUI/CLI) lub środowisko graficzne. Dla uproszczenia też zastosowuje tutaj ten podział. Ze względu na to, że istnieje bardzo duża liczba dostępnych środowisk graficznych oraz fakt, że jego obecność do normalnego działania systemu jest zbędna, w tym rodziale skupimy się głównie na środowisku tesktowym, a tym jest głównie powłoka.

12.1. Rodzaje powłok

W Obrębie konfiguracji środowiska tekstowego użytkownika, pojęcie rodzaje powłok, nie tyczy się programów powłoki takich jak BASH czy CSH. Tutaj zakładamy, że BASH jest naszą domyślną powłoką oraz że to jej używamy. Powłoka może interaktywna (korzystamy z niej domyślnie np. logując się do systemu.) lub nieinteraktywna (taki rodzaj powłoki uruchamiają skrypty [będzie o nich w ostatnim rozdziale]). Innym rodzajem podziału działania powłoki może być powłoka logowania oraz powłoka bez logowania

Powłoka logowania uruchamiana jest za każdym razem kiedy, (najprościej rzecz ujmując) wymagane jest uprzednio uwierzytelnienie się aby uruchomić sesję powłoki. Logując się jedeną z konsol na serwerze uruchamiamy interaktywną połokę logowania podobnie jest w przypadku połączeń zdalnych z wykorzystaniem protokołu SSH.

Drugim rodzajem jest powłoka bez logowania, tyczy się ona głównie środowisk graficznych, bo tego rodzaju działanie powłoki jest zapewnianie programy typu emulator terminala oraz multiplekser terminala jak GNU Screen lub tmux.

Określenie tego czy powłoką, którą używany jest powłoką logowania czy też bez logowania następuje poprzez analizę wartości zwróconej przez polecenie echo $0.

xf0r3m@debian:~$ echo $0
-bash
xf0r3m@vm-6bee73e:~$ echo $0
bash

Pierwsza powłoka została zinicjowana przez połączenie SSH na maszynie wirtualnej. W przypadku wartości zwróconej przez powyższe polecenie na tym hoście, nazwę powłoki poprzedza myślnik (-), co oznacza, że jest to powłoka logowania, wartość zmiennej bez myślnika, oznacza powłokę bez logowania. Druga powłoka została uruchomiona na drugiej karcie tego samego emulatora terminala, na którym pisze ten tekst.

Istnieje mozliwość uruchomienia wielu kombinacji rodzajów powłoki, nawet możliwość uruchomienia nieinteraktywnej powłoki logowania.

12.2. Pliki środowiska użytkownika

Dlaczego o tym wspominam, otóż różne rodzaje powłok mogą korzystać z różnych plików uruchomieniowych, z ktorych składa się środowisko użytkownika. Tych plików jest kilka, głównego podziału możemy doknac tylko i wyłącznie na podstawie logowania zatem:

Jak możemy wnioskować po obecności tyldy (~), te pliki są plikami użytkownika, chociaż w systemie może znaleźć się także wersja globalną. Tak jest w przypadku dysrybucji Debian, pliki znajdują się w katalogu /etc: /etc/profile lub /etc/bash.bashrc. W każdym systemie znajdują się te pliki. Pliki globalne posiadają kilka różnic od tych użytkowników. Ale my w ramach tego materiału skupiamy sie głównie środowisku użytkownika więc i na plikach użytkownika poprzestaniemy. Tego typu pliki posiada każdy w swoim systemie więc nie mam sensu ich tutaj przytaczać w przykładach.

Jeśli przyjrzymy się plikowi ~/.profile to zauważymy, że wewnątrz ładowany jest plik ~/.bashrc. Tak więc powłoka logowania zostanie zainicjowana również zgodnie z plikiem ~/.bashrc. Na koniec do zmiennej zawierającej ścieżkę wyszukiwania poleceń dodane są lokalne katalogi, w których to użytkownik powinien przechowywać swoje pliki binarne. I to cały plik konfigurjący powłokę logowania. Znacznie bogatszym plikiem jest plik konfigurujący powłokę bez logowania. Wewnątrz niego znajdują się miedzy innymi:

Generalnie plik ~/.bashrc jest plikiem, który przechowuje informacje na temat:

Jeśli potrzebujemy zmienić coś z powyższych zaganień to należy umieścić je w tym pliku.

Generalnie jeśli chodzi o zmiany dokonywane w tych plikach, to raczej możemy pominąć plik ~/.profile, ponieważ on sam sobie zawiera załączenie (polecenie source, będzie o nim w ostatnim rozdziale) pliku ~/.bashrc. Więc wszystkie zmiany możemy zawrzeć w tym pliku. Nie należy tworzyć pliku na nowo został on przygotowany przez twórców dystrybucji i jest najlepiej dostosowany do tego co się w niej znajduje. Dodając jakie kolwiek zmiany do tych plików, musimy mieć na uwadzę wykonanie czynności, które chcemy umieści. Dłuższe wykonywanie poleceń w pliku ~/.bashrc to dłuższe oczekiwanie na znak zachęty po zalogowaniu się lub uruchomieniu terminala. Dlatego też, za nim wdrożymy zmiany w plikach osobistych, warto przetestować je z użyciem dodatkowego użytkownika. Jeśli uznamy, że działanie zmian jest przez nas akceptowalne, to możemy je dodać do naszych plików.

12.3. Rozszerzenie domyślnego środowiska użytkownika

Korzystanie z włącznie konsol wirtualnych, może niebyć za bardzo ekscytującym doświadczeniem pod kątem wizualnym. Poza takim rzeczami jak przeglądanie zdjęć czy oglądanie filmów jesteśmy wstanie normalnie korzystać z takiego systemu. Ma to swoje wady i zalety. Jedną z nich możebyć wykorzystanie naprawdę archaicznego sprzętu, np. laptopów z początku wieku. Zrezygnowanie z środowiska graficznego może pomóc w wyrobieniu sobie przyzwyczajeń odnośnie pracy w terminalu, nauke jednego z edytorów takich jak Vim, emacs lub inne. W teminalu możemy przeglądać strony internetowe pozbawione wszelkich pstrokatych dodatków, sam tekst, sama treść. Możemy przeglądać pocztę czy słuchać muzyki. Także wielu użytkownikom uniksów korzystanie z terminala wystarczy. Sam należę do takich osób, u których terminal to podstawowe narzędzie pracy i śmiało mogę obejść się bez środowiska graficznego. Jeśli już zdecydujemy się na to aby porzucić środowisko, to warto zaznajomić się z jednym z takich narzędzi jak tmux lub GNU Screen. Osobiście wolę tmux, jednak potrafi on sprawiać problemy czasami. W starszych wersjach zmiana katalogu głównego w sesji powłoki uruchomionej przez tmux, może powodować jej dziwne zachowanie, np. brak efektu działania klawisza backspace, mimo tego że znaki teoretycznie zostały usunięte. Jak wiemy mamy do dyspozycji kilka konsol. Jeśli zaś uruchomimy tmux to może zdefiniować sobie bardzo dużą ilość okien (tak jakby odrębna konsola), a je można jeszcze podzielić na mniejsze części. Tmux powzwala na kopiowanie i wklejanie przy użyciu innej metody dostępnej bez środowiska graficznego, co czyni go dość użytecznym programem.

13. Interfejs użytkownika i drukowanie

Jeśli kiedyś przyjdzie nam korzystać z dystrybucji Linuksa poza, serwera, systemami wbudowanymi oraz innymi rozwiązaniami, w których do interakcji z systemem wykorzystujemy wyłącznie powłokę, spotkamy się z interfejsem graficznym użytkownika. Najprościej rzecz ujmując jest tak jakby klasyczny pulpit z wyświetlającymi się w okienkach programami. Tak jakby ponieważ na interfejs graficzny składa się bardzo duża ilość różnych elementów, które mogą się między sobą diametralnie różnić.

Krótko scharakteryzujemy elementy, z których składa sie interfejs użytkownika. Ten rodział będzie dość mocno teoretyczny, więc jeśli nie interesuje nas czym jest Wayland czy X Window System lub magistrala D-Bus można przejść kolejnego rodziału.

13.1. Elementy składowe interfejsu graficznego

Jak już wcześniej wspomniałem na interfejs graficzny użytkownika składa się duża ilość elementów. Te elementy moga być zostać pogrupowane na podstawie czym tak na prawdę są. Jesli powiem, że środowisko graficzne uzupełniają widget-y, to tymi widget-ami są po prostu aplikacje dostarczane jako zależności zadania instalacji wybranego środowiska. Środowiska oczywiście mogą się bez nich obejść jednak przyjemnosć korzystania z takiego pulpitu powoduje mieszane uczucia.

13.1.1. Bufory ramki

Na samym dole intefejsu użytkownika znajduje się zazwyczaj bufor ramki jest to obszar pamięci odczytywany przez układy graficzne i następnie przekazywany do wyświetlenia na ekranie. Kilka bajtów reprezentuje jeden piksel. Podczas wyświetlania okien procesy na bierząco aktualizują zawartość bufora lub buforów (może ich być kilka, o czym się zaraz przekonamy) pamiętając przy tym aby nie nadpisać elementów innych okien (procesy programów wyświetlających się w oknie).

13.1.2. Mechnizm wyświetlania

Ciężko jest sklasyfikować przedstawione poniżej elementy. Niby służą temu samemu to jednak posiadają wiele zasadniczych różnic, że trzeba się na chwilę zatrzymać i zastanowić na tym jak je sklasyfikować.

X Window System

System X Window bazuje na architekturze klient-serwer, serwer wyświetlania, nazywany również serwerem X jest tak jakby jądrem całego interfejsu użytkownika. Serwer X zarządza wszystkimi elementami związanymi z pulpitem przy czym nie narzucając jak coś powinno działać lub wyglądać. Klientem w tej releacji pozostaja wszelkie aplikacje, które chcą wyświetlić okna w systemie X. Po nawiązaniu połączenia z nim, aplikacja żąda wyświetlenia okna. W odpowiedzi uzyskamy informacje o docelowym położeniu okna oraz zostanie mu wskazany obszar pamięci przeznaczony na bufor ramki. Czasmi serwer X sam zajmuje się renderowaniem elementów graficznycznych.

Ze względu na to, że serwer X bierze udział w tak wielu czynnościach, może stać się wąskim gardłem w pewnym momencie, chociaż X Window System jest aktywnie wykorzystywany od lat 80-tych, okazał się dość elastyczny aby móc obsługiwać elementy współczesnych interfejsów.

Wayland

Protokół Wayland w przeciwieństwie do X Window jest nastawiony na decentralizację. Żadna centralny serwer nie bierze udziału w renderowaniu elementów graficznych. Każdy klient otrzymuje swój bufor ramki oraz składnik kompozycji łączący bufory ramki klienta do postacji akceptowanej przez bufor ramki ekranu, przy czym to zadanie jest wspierane sprzętowo co może podnieść wydajność.

Różnice między Wayland oraz X Window System

Generalnie to nie ma zbyt wielkich różnic między protokołem Wayland a system X Window. Aplikacje w obecnych środowiskach wykorzystując X Window nie oczekują na wspracie od serwera i same renderują bitmapę (zapisuja elementy graficzne w buforze ramki w postaci bajtów) i przesyłają ja serwerowi X. Serwer łaczy je ze sobą z wykorzystaniem rozszerzenia kompozycji, które jest dostępne w nim od kilku lat. Jedną z faktycznych różnic jest wymaganie biblioteki libinput służącej do kierowania danych wejściowych do klientów. Protokół nie wymaga tej biblioteki. Jednak jest ona w każdym dostęp środowisku. O mówimy ją sobie przy okzaji głębszego zapoznania się z system X Window. Kolejną rzeczą jest rola menedżera okien w wyświetlaniu interfejsu użytkownika.

13.1.3. Mendżery okien

Menedżer okna w interfejsie użytkownika zajmuje się renederowanie elementów dekoracyjnych, obsługuje kierowane do tych elementów zdarzenia wejściowe oraz informuje serwer o położeniu okien. Te zadania są wykonywane kiedy korzystamy z systemu X Window. Inaczej jest w przypadku protokołu Wayland tutaj menedżer okien pełni rolę serwera, składa bufory ramek, aby były zgodne z buforem przeznaczonym do wyświetlania oraz obsługuje przekazywanie urządzeń wejścia na kanale zdarzeń. Jest to jedna z różnic miedzy opisanymi wcześniej mechanizmami wyświetlania. Każdy z dostępnych interfejsów może mieć swój własny menedżer okien. Niektóre menedżery okien są traktowane jako pełne interfejsy użytkownika. Oczywiście jest błędne założenie, ponieważ takim gołym menedżerom okien, brakuje kilku elementów. Zazwyczaj jest nazwa potoczna dla zestawu programów składających się na środowiska użytkownika, lecz nie jest to pełno prawne środowisko graficzne jak GNOME czy KDE.

13.1.4. Biblioteki interfejsu graficznego

Elementy interfejsów graficznych są często oparte o jedną z bibliotek, która wspiera tworznie różnego rodzaju przycisków, paneli, elementów dekoracyjnych czy projektowania okien. Wśród środowisk interfejsu użytkownika prym wiodą biblioteki standardu GTK+ oraz standardu Qt. Przyczym GTK+ jest wykorzystywany przez większą ilość środowisk.

13.1.5. Graficzny interfejs użytkownika

GUI obecnie są domyślnie określane jako całe środowisko użytkownika. Fakt, spinają one wiele cześci razem, np. są oparte o standard GTK+ lub Qt, często posiadają swój menedżer okien oraz menedżer wyświetlania (będzie o nim w dalszej części tego rozdziału) GUI zazwyczaj decydują o tym w jaki sposób wyświetlić elementy, decydują o wyglądzie wielu programów, dostaczaja swoje narzędzia np. takie jak emulator terminal, będąc jednym produktem składającym się z wielu składników. Warto poruszyć kwestię tego, że wielu użytkowników nie korzytsta z typowych GUI takich jak GNOME, KDE czy XFCE. Często wybierają oni menedżer okien, np. Fluxbox czy IceWM następnie dodają do tego menedżer wyświetlania, biblioteki oraz potrzebny im zestaw aplikacji. Tego typu rozwiązania powodują, mniejsze zużycie zasobów niż w przypadku użycia gotowych GUI, a to z kolei przekłada się to na konfort naszej pracy, niektóre czynności, które w przypadku gotowych GUI są automatyczne to w przypadku innych rozwiązań mogą wymagać dodatkowej konfiguracji. Oczywiście rozwiązania tego typu są pracochłonne, ale mogą przyspieszyć działanie systemu oraz umożliwić uruchomienie tak przygotowanego środowiska na słabszym sprzęcie, dzieki czemu możemy zaoszczędzic pieniądze. GUI mimo iż może być opartę o którąś z powyższych bibliotek, to potrafi być niezależne w domyśle od mechanizmu wyświetlania, wszystko zależy od twórców dystrybucji jak został skonfigurowany pakiet instalujący oraz konfigurujący domyślnie GUI.

13.1.6. Aplikacje

Do tej pory korzystaliśmy z programów, które były uruchamien z poziomu konsoli/terminala. Posiadając środowisko graficzne mamy możliwość skorzystania z przeglądarek WWW, programów pocztowych i wielu innych aplikacji. Oczywiście istnieją programy, dzięki którym możemy przeglądać strony internetowe (co prawda w postaci, czystego tekstu) lub czytać pocztę w terminalu. To jednak większa część tych programów jest projektowana z myślą o użytkownikach GUI upraszczając interakcje z nimi. Warto pamiętać jednak o tym, jednej z złotych zasad korzystania z jakiego kolwiek Uniksa. Nie zawsze to GUI może być dostępne, a powłoka będzie zawsze w mniej lub bardziej rozbudowanej formie

13.2. Określenie mechanizmu wyświetlania

Wiele wiodących dystrybucji może automatycznie instalować środowisko użytkownika, bazując na zawartości obrazu płyty, z którego uruchomiliśmy nasz komputer. Wykorzystując ten system do testowania przykładów z tej książki, i czytając wyżej wymieniony podrozdział możemy zacząć się zastanawiać, z jakiego mechanizmu wyświetlania korzystamy w naszym w systemie. Do ustalenia informacji na temat wykorzystywanego przez nas serwera wyświetlania (tak wiem Wayland nie jest serwerem, ale tak będzie prościej do zrozumienia i tak pozostanie do końca tego materiału), wykorzystamy zmienną $WAYLAND_DISPLAY. Jeśli wyświetlenie jej zawartości zwróci jakieś informacje to oznacza, że możemy być nie mal pewni, że korzystamy z protokołu Wayland. Jeśli wyświetlenie jej zawrtości nie zwróci nic, to najpewniej nie jest ona w ogóle zainicjowana, co oznacza że korzystamy z serwera X Window.

[xf0r3m@fedora39-sway ~]$ echo $WAYLAND_DISPLAY 
wayland-1

13.3. Protokół Wayland

Wayland jest protokołem pośredniczącym między menedżerem kompozycji a klientem graficznym. Ten system nie posiada jednego dużego pakietu, ale bibliotekę protokołu, z której korzystają klienci. Poza tym w skład protokołu wchodzi referencyjny menedżer kompozycji Weston, kilka klientów oraz programów narzędziowych.

Referencyjność Weston polega na tym, że nie powinien być on wykorzystywany samodzielnie, natomiast posiada on wszelkie składniki składniki kompozycji i jego zadaniem jest zapewnienie podstawowego interfejsu, który projektancji własnych menedżerów kompozycji mogą analizować i na podstawie tego poprawnie implementować kluczowe funkcje.

13.3.1. Menedżer kompozycji

Korzystając z Wayland, możemy nie dokońca być świadomi tego z jakiego menedżera kompozycji korzystamy, każde wieksze środowisko wykorzystuje swoje rozwiązanie. Jednak zdobycie tych informacji z systemu jest dośc proste do realizacji. Menedżer kompozycji do komunikacji z klietami wykorzystuje gniazdo uniksowe o nazwie wayland-X. Tę nazwę może kojarzyć z wartości z zwracanej przez zmienną WAYLAND_DISPLAY, bo tym tak dokładnie jest - nazwą ekranu. Przy użyciu polecenia ss wraz z opcjami -xlp wyświetlającymi gniazda uniksowe na których prowadzony jest nasłuch wraz procesami, odpowiedzialnymi za ten nasłuch.

[xf0r3m@fedora39-sway ~]$ ss -xlp | grep 'wayland'
u_str LISTEN 0 1   /tmp/.X11-unix/X0        22765  * 0    users:(("Xwayland",pid=1153,fd=23))                                    
u_str LISTEN 0 128 /run/user/1000/wayland-1 22759  * 0                                                                           
u_str LISTEN 0 1   @/tmp/.X11-unix/X0 22764        * 0    users:(("Xwayland",pid=1153,fd=21)) 

13.3.2. Biblioteka libinput

Biblioteka libinput jest odpowiedzialna z uzyskanie od jądra danych wejściowych z urządzeń, np. znaków wprowadzanych na klawiaturze oraz ustandaryzowanie ich do danych protokołu. Mimo, iż te informacje mogą wydawać się nie ciekawe do omówienia to przy użyciu tej biblioteki możemy śledzić zdarzenia wejściowe:

Zanim przjedziemy do śledzenia zdarzeń możemy za pomoca polecenia libinput, które jest powiązane z biblioteka libinput wyświetlić listę dostępnych w systemie urządzeń wejściowych.

[xf0r3m@fedora39-sway ~]$ sudo libinput list-devices
...
Device:           AT Translated Set 2 keyboard
Kernel:           /dev/input/event1
Group:            3
Seat:             seat0, default
Capabilities:     keyboard 
Tap-to-click:     n/a
Tap-and-drag:     n/a
Tap drag lock:    n/a
Left-handed:      n/a
Nat.scrolling:    n/a
Middle emulation: n/a
Calibration:      n/a
Scroll methods:   none
Click methods:    none
Disable-w-typing: n/a
Disable-w-trackpointing: n/a
Accel profiles:   n/a
Rotation:         0.0
...

Teraz kiedy orientujemy się jakie urządzenia są podłączone oraz w jaki sposób są widziane przez protokoł Wayland możemy przjeść do śledzenia zdarzeń.

[xf0r3m@fedora39-sway ~]$ sudo libinput debug-events --show-keycodes
...
-event1   KEYBOARD_KEY            +8.267s	KEY_SPACE (57) pressed
  event1   KEYBOARD_KEY            +8.387s	KEY_SPACE (57) released
 event1   KEYBOARD_KEY            +12.993s	KEY_K (37) pressed
k event1   KEYBOARD_KEY            +13.113s	KEY_K (37) released
 event1   KEYBOARD_KEY            +15.175s	KEY_M (50) pressed
m event1   KEYBOARD_KEY            +15.283s	KEY_M (50) released
-event5   POINTER_MOTION_ABSOLUTE +18.768s	18.95/ 39.58
 event5   POINTER_MOTION_ABSOLUTE +18.770s	18.95/ 39.45
 event5   POINTER_MOTION_ABSOLUTE +18.782s	18.95/ 39.32
...
-event1   KEYBOARD_KEY            +20.305s	KEY_LEFTCTRL (29) pressed
 event1   KEYBOARD_KEY            +20.308s	KEY_C (46) pressed

Po uruchomieniu mozemy poprzemieszczać kurs myszy lub poklikać kilka klawiszy na klawiaturze, w na wyjściu polecenia zobaczymy takie informacje jak te przedstawione na przykładzie.

13.3.3. Zgodność Wayland z system X Window

Protokół Wayland jest w miarę świerzym (jak na postrzeganie czasu przez uniksy) podejściem jesli chodzi o wyświetlanie. Do tej pory większość okienkowych, mających uruchamiać się na dystrybucjach Linuksa, ale i nie tylko - system X Window jest również używany na innych uniksach, było projektowanych z myślą o system Xorg. Zgodność natomiast między tymi dowoma systemami polega na dwóch podejściach.

Podejście pierwsze polega na dostosowaniu aplikacji natywnie przygotowanej dla X. Aplikacje przygotowane z myślą o dystrybujach Linuksa często wykorzystują zestawy narzędzi dużych środowisk graficznych takich jak GNOME lub KDE. Te zestawy są przygotowane do działania z protokołem Wayland. Wiele podstawowych aplikacji zostało już dostosowanych w ten sposób. Różnice tutaj mogą polegąć na obsłudze elementów dekoracyjnych czy konfiguracji urządzeń wejściowych. Pozostaja jescze zależności wobec bibliotek związanych z systemem X.

Innym rozwiązaniem jest uruchomienie aplikacji przeznaczonych dla X po przez warstwę zgodności, polegającą na uruchomieniu jako klienta Wayland całego serwera X Window. Nazwywane jest to serwerem Xwayland i jest domyślne działanie podczas sekwencji ładowania menedżera kompozycji. Wykorzystanie takiej warstwy wymaga przetłumaczenia zdarzeń wejściowych oraz utrzymania osbono buforów ramek okien. Metoda ta jest równiez nieco wolniejsza, ale często nie jest brane w ogóle pod uwagę.

Oczywście odwrotnie to nie zadziała nie można uruchmaiać aplikacji dla Wayland pod system X, choć teoretycznie jest to możliwe. Możemy uruchomić menedżer kompozycji w oknie system X, jednak nie jest zalecane ze wzgledu na efekty uboczne, takie jak uruchomienie aplikacji w sesji X Window, a jej rzeczywiste pojawienie się w uruchomionej w oknie sesji Wayland.

13.4. System X Window

W dość odgłegłych czasach jedną z cech systemu X window był dość duży gabaryt, bowiem pakiet ten zawierał poza serwerem wyświetlania biblioteki oraz kilka klientów. Te czasy na szczęście minęły dzięki upowszechnieniu się niezależnych środowisk graficznych takich jak GNOME czy KDE, wówczas można było skupić się na samym serwerze. Uproszczeniu uległa również biblioteka klientów.

Działanie serwera X Window jest proste do namierzenia, wystarczy spojrzeć na listę procesów:

xf0r3m@vm-65cd1fb:~$ ps -aux | grep 'Xorg'
root        1001  1.1  6.1 406772 119620 tty7    Ssl+ 10:59   0:32 /usr/lib/xorg/Xorg :0 
-seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch

Istotną wartością w linii polecenia uruchamiającego jest :0. Jest to oznaczenie wyświetlacza X. Wyświetlaczem X możemy nazwać całość obrazu jaki jest generowany przez serwer wyświetlania. Na to składają się wyświetlacze podłączone do karty graficznej lub do komputera. Najczęściej jednak wyświetlacz X odpowiada podłączonemu monitorowi. Jeśli już chcielibyśmy użyć podobnych metod do ustalanie mechanizmu wyświetlania jak w przypadku Wayland, to możemy skorzystać ze zmiennej DISPLAY. Wyświetli ona zapewne oznaczenie wyświetlacza oraz oznaczenie ekranu. Jeśli mamy podłączony do systemu tylko jeden fizyczny ekran, to zapewne wartość, tej zmiennej będzie wyglądać następująco.

xf0r3m@vm-65cd1fb:~$ echo $DISPLAY
:0.0

13.4.1. Menedżery wyświetlaczy

Korzystając z dystrybucji przeznacznonych na komputery biurkowe, raczej nię będziemy sami uruchamiać serwera wyświetlania w jednej z wirtualnych konsoli. Po załadowaniu się systemu naszym oczom ukaże się okienko służące do logowania z jakimś obrazkiem w tle oraz umiesczonym w jednym z rogów przycisku wyboru sesji lub ustawień ułatwień dostępu. Menedżery wyświetlaczy mogą być częścią środowisk graficznych i to nie koniecznie tych dużych. Ponieważ środowisko LXDE, uważane za jedno z leżejszych posiada swój menedżer wyświeltacza. Jednak posiadanie dedykowanego menedżera jest raczej domeną, największych środowisk takich jak GNOME czy KDE. Istnieją również menedżery niezależne pozwalające uruchomić najróżniejsze środowiska czy menedżery okien. Jednym z nich jest lightdm, który nawet jest uruchomiony na komputerze na którym piszę ten materiał. Możemy to wywnioskować z widniejącej w poleceniu uruchomienia serwera X nazwy menedżera.

Głównym zadaniem menedżerów wyświetlaczy jest uruchomienie serwera wyświetlania. Po uwierzytelnieniu, bądź od razu w przypadku ustawionego automatycznego logowania. Jednak samo uruchomienie serwera X Window nie jest jedynym zadaniem tego programu. Jeśli by tak było, to po zalogowaniu/włączeniu systemu dostalibyśmy czarny ekran z x zamiast kursora na środku ekranu. Jednym z zadań jest zainicjowanie sesji użytkownika, poprzez np. odpowiednie skonfigurowanie składników tego środowiska (np. włączenie wygaszacza ekranu, ochrony wzroku czy ustawienie tapety [nie które mendżery okien, funkcjonują w ten sposób]) i uruchomienie odpowiedniego menedżera okien będącego jednym z najważniejszych programów sesji użytkownika. Menedżery wyświetlania odpowiadają za szereg ciekawych funkcji, których możemy doświadczyć i dostosowac je do swoich potrzeb.

13.4.2. Właściwości sieciowe serwera X Window

Serwer X Window oferuje zdalny dostęp poprzez sieć. Rozwiązanie to jest podobne do, możliwe że znanych nam usług zdalnego pulpitu. Jednak ta usługa w obecnych czasach jest wyłaczona ze względów bezpieczeństwa - nie zapewnia poza uwierzytlenieniem żadnych zabepieczeń. Warto rozejrzeć się za jakimś innym dostępem, większość konfiguracji na Uniksach wykonamy z poziomu powłoki więc dostęp przez secure shell powinien wystarczyć. Mimo to jeśli na przykład potrzebujemy:

Ze pomocą wyżej wymienionych sztuczek, możemych wchodzić w interakcje z serwerem wyświetlania X window na odległość. A jeśli naprawdę potrzebujemy zdalnego pulpitu, to warto użyć do tego np. takich protokół jak RDP lub VNC.

Przegląd klientów X Window

Mogliśmy spodziewać się tego, że omawianie środowiska graficznego użytkownika może nie mieć nic wspólnego z wierszem polecenia, ale system X Window wychodzi nam na przekór, udostępniając kilka narzędzi, które pozwalają wyświetlenie informacji czy też diagnostykę. Jednym z takich narzędzi jest polecenie xwininfo. Polecenie to zwraca informacje na temat wybranego przez nas okna. Po uruchomieniu poprosi nas o wybranie okna za pomocą myszy o po kliknięciu na nie w terminalu zostaną nam zwrócone informacje o tym oknie.

xf0r3m@vm-cffa62e:~$ xwininfo

xwininfo: Please select the window about which you
          would like information by clicking the
          mouse in that window.

xwininfo: Window id: 0x3400003 "Terminal - xf0r3m@vm-cffa62e: ~"

  Absolute upper-left X:  5
  Absolute upper-left Y:  158
  Relative upper-left X:  5
  Relative upper-left Y:  29
  Width: 817
  Height: 578
  Depth: 32
  Visual: 0x50c
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x3400002 (not installed)
  Bit Gravity State: NorthWestGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +5+158  -202+158  -202-32  +5-32
  -geometry 100x30+0-27

Innym ciekawym narzędziem może być polecenie xlclients wraz z opcją -l. Jego zadaniem jest wyświetlenie wszystkich klientów serwera X Window.

13.4.4. Zdarzenia serwera X

Okna programów otrzymują dane wejściowe oraz informacje o stanie serwera X za pośrednictwem zdarzeń. Serwer X odbiera zdarzenia ze źródła następnie kieruje do dowolnego zainteresowanego klienta. System X Window daje nam możliwość eksperymentowania z zdarzeniami przy użyciu polecenia xev. Uruchomienie tego polecenia spowoduje otworzenie okna, po którym możemy jeździć kursorem i na nim klikać, natomiast w terminalu polecenie zajmie się obróbką tych zdarzeń i ich przedstawieniem użytkownikowi.

xf0r3m@vm-cffa62e:~$ xev
ButtonPress event, serial 38, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 21524866, (41,103), root:(464,396),
    state 0x0, button 1, same_screen YES
...
MotionNotify event, serial 38, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 21526915, (49,104), root:(472,397),
    state 0x100, is_hint 0, same_screen YES

MotionNotify event, serial 38, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 21526998, (51,104), root:(474,397),
    state 0x100, is_hint 0, same_screen YES

MotionNotify event, serial 38, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 21527185, (52,104), root:(475,397),
    state 0x100, is_hint 0, same_screen YES

ButtonRelease event, serial 38, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 21527854, (52,104), root:(475,397),
    state 0x100, button 1, same_screen YES

MotionNotify event, serial 37, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 19437248, (99,87), root:(522,380),
    state 0x0, is_hint 0, same_screen YES
...
KeyPress event, serial 37, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 19448798, (194,226), root:(617,519),
    state 0x0, keycode 39 (keysym 0x73, s), same_screen YES,
    XLookupString gives 1 bytes: (73) "s"
    XmbLookupString gives 1 bytes: (73) "s"
    XFilterEvent returns: False

KeyRelease event, serial 37, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 19448918, (194,226), root:(617,519),
    state 0x0, keycode 39 (keysym 0x73, s), same_screen YES,
    XLookupString gives 1 bytes: (73) "s"
    XFilterEvent returns: False

KeyPress event, serial 37, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 19449093, (194,226), root:(617,519),
    state 0x0, keycode 40 (keysym 0x64, d), same_screen YES,
    XLookupString gives 1 bytes: (64) "d"
    XmbLookupString gives 1 bytes: (64) "d"
    XFilterEvent returns: False

KeyRelease event, serial 37, synthetic NO, window 0x3800001,
    root 0x529, subw 0x0, time 19449206, (194,226), root:(617,519),
    state 0x0, keycode 40 (keysym 0x64, d), same_screen YES,
    XLookupString gives 1 bytes: (64) "d"
    XFilterEvent returns: False

Na przykładzie pokazano w przyciśniecie klawisza myszy, przesunięcie kursora z przytrzymanym klawiszem, następnie zwolnienie klawisza i przesunięcie kursora. Następnie naciśnięto dwa klawisze na klawiaturze. W polu root otrzymamy współrzędne kursora myszy na ekranie. Natomiast przed tym polem znajdują się współrzędne względem okna otwartego przez polecenie xev. W przypadku naciśniętych na klawiszy istotną informacją zwracaną przez to polecenie jest keycode, który pozwoli nam na zmianę mapowania klawiatury, co może być przydatne przy komputerach firmy Apple, zamieniając na ich klawiaturach klawisze Command z Option. W tych klawiaturach klawisz Option pełni rolę klawisza Alt, zatem po zmianie mapowania, klawiatura funkcjonalnie będzie przypinać każdą inną.

Ciekawą opcją polecenia xev jest -id identyfikator. Pozwala ona na podanie identyfikatora okna (możną go uzyskać, za pomocą polecenia xwininfo), z którego polecenie xev będzie odczytywać zdarzenia.

13.4.5. Ustawianie preferencji i dane wejściowe serwera X

System wyświetlania Xorg oraz jego serwer zapewniają kilka możliwych metod na zmianę preferencji, a niektóre z nich mogą nawet nie zadziałać. Jedną ze zmian jakie możemym chceć przeprowadzić jest wspomniana wcześniej zmiana mapowania, które można przeprowadzić na dwa sposoby, albo zmiany są małe i wprowadzane na obecnym mapowaniu przy użyciu polecenia xmodmap albo utworzenie nowego mapowania, skompilowania go za pomocą polecenia xkbcomp oraz załadowania i aktywacji przy użyciu polecenia setxkbmap. Za pomocą tych poleceń możemy zmieniać mapowania większości klawiszy, jak i mamy możliwość definicji mapować dla każdej podłączonej klawiatury.

Innym ustawieniem, które możemy chceć zmienić jest kolejność klawiszy przy myszy. W przypadku osób leworęcznych podstawowy przypisk (LPM, dla osób praworęcznych mieści skrajnie po lewej stronie) powinien znajdować sie po drugiej stronie urządzenia (skrajnie po prawej). Takiej zamiany możemy dokonać z pomocą xinput. Na poniższym przykładzie pokazano w jaki sposób dostosować myszkę dla osób leworęcznych.

xf0r3m@vm-cffa62e:~$ xinput --set-button-map dev 3 2 1

Gdzie dev jest identyfikatorem urządzenia. Po podaniu informacji o urządzeniu wskazujemy kolejność klawiszy. Jednak skąd można wziąć taki identyfikator?

Aby poznać urządzenia wejsciowe podłączone do komputera z dystrybucją linuksa, na której wykorzystywany jest serwer wyświetlania X, należy użyć wyżej wymienionego polecenia xinput.

xf0r3m@vm-cffa62e:~$ xinput
⎡ Virtual core pointer                    	id=2	[master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer              	id=4	[slave  pointer  (2)]
⎜   ↳ JUCHEN USB Keyboard                     	id=11	[slave  pointer  (2)]
⎜   ↳ JUCHEN USB Keyboard                     	id=12	[slave  pointer  (2)]
⎜   ↳ Logitech G502 HERO Gaming Mouse         	id=13	[slave  pointer  (2)]
⎜   ↳ Logitech G502 HERO Gaming Mouse Keyboard	id=14	[slave  pointer  (2)]
⎜   ↳ AlpsPS/2 ALPS GlidePoint                	id=17	[slave  pointer  (2)]
⎣ Virtual core keyboard                   	id=3	[master keyboard (2)]
    ↳ Virtual core XTEST keyboard             	id=5	[slave  keyboard (3)]
    ↳ Power Button                            	id=6	[slave  keyboard (3)]
    ↳ Video Bus                               	id=7	[slave  keyboard (3)]
    ↳ Power Button                            	id=8	[slave  keyboard (3)]
    ↳ Sleep Button                            	id=9	[slave  keyboard (3)]
    ↳ JUCHEN USB Keyboard                     	id=10	[slave  keyboard (3)]
    ↳ Dell WMI hotkeys                        	id=15	[slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard            	id=16	[slave  keyboard (3)]
    ↳ DELL Wireless hotkeys                   	id=18	[slave  keyboard (3)]
    ↳ JUCHEN USB Keyboard                     	id=19	[slave  keyboard (3)]
    ↳ Logitech G502 HERO Gaming Mouse Keyboard	id=20	[slave  keyboard (3)]

W przypadku mojego komputera, klawiatury posiadają identyfikatory 10 lub 19 oraz 16, w przypadku myszy (wskaźnika) jest to 13 oraz 17.

Nie ma takiej potrzeby aby klienci mieli by nasłuchiwać zdarzeń na (w tym przypadku) na trzech lub większej ilości urządzeń. Wykorzystują oni natomiast pojedyncze urządzenia rdzenia wirtualnego, dzięki temu możemy podłączyć i korzystać z więcej niż jednej klawiatury i więcej niż jednej myszy. Nie mniej jednak, klienci mogą korzystać z rozszerzenia X Input Extension w celu skupienia nasłuchiwania na jednym z nich, najczęsciej wiekszość z nich nie posiada w ogóle informacji o tym rozszerzeniu.

Pozostałymi preferencjami to ustawienia może być tło ekranu nazywane potocznie tłem pulpitu, w tym przypadku możemy skorzystać z polecenia xsetroot, jednak nie działa ono we wszystkich przypadkach. Lepszym rozwiązaniem może być użycie zewnętrznego programu feh, które de facto jest przeglądarką plików graficznych; czy ustawienia wygaszacza ekranu lub funkcji DPMS (ang. Display Power Management Signaling) za pomocą polecenia xset.

13.5. Usługa D-Bus

Usługa D-Bus jest mechanizm przekazywania komunikatów, umożliwia ona wymianę informacji między aplikacjmi klienta, wykorzystując do tego komunikację między procesową. Jednak aplikację wykorzystują ją do powiadamiania o zdarzeniach systemowych takich jaki np. podłącznie pendrive-a do komputera. Proces chcąc wykorzystać ją do nasłuchiwania zdarzeń muszą połączyć się na początku z centralnym koncentratorem - demonem dbus-daemon i zarejestrować w celu odbierania konkretych rodzajów komunikatów. Dla przykładku inny monitor udisks-daemon sprawdza stan systemu plików udev pod kątem zdarzeń dyskowych i następnie wysyła je do dbus-daemona, a on przesyła je dalej do aplikacji zaintersowanych zdarzeniami tego typu.

13.5.1. Instancja systemowa oraz instancja sessji

System D-Bus obecnie jest jedną z ważniejszych części dystrybucji Linuksa - kanałami komunikacji D-Bus dysponuje systemd, a ze względu na to, że jest on ściśle powiązany ze środowiskiem graficznym powoduje to konflikt natury projektowej. Rozwiązaniem okazało się podzielenie centralnego koncentratora na dwa osobne procesy - instancję systemową, która uruchamiana jest podczas rozruchu przez program typu init. Proces ten jest uruchamiany wówczas w raz z opcją --system i działa z uprawnieniami użytkowanika usługi D-Bus, a procesy mogą łączyć się z nią za pomocą uniksowego gniazda.

Nie zależnie od instacji systemowej występuje instancja sesji która jest uruchamiana wyłącznie w momencie startu środowiska graficznego i to z nią łączą się aplikacje.

13.5.2. Monitorowanie komunikatów usługi D-Bus

W przypadku instancji systemowej, może dziać się nie wiele w zależności od sprzętu na jakim pracujemy. Nie mniej jednak do obserwacji komunikatów usługi D-Bus możemy wykorzystać dostępne polecenie takie jak dbus-monitor Wybóru instancji dokonujemy za pomocą odpowiedniej opcji: instancja systemowa - --system oraz instancja sesji - --session.

Osobiście na swoim sprzęcie za pomocą monitorowania komunikatów D-Bus zauważyłem informacje odnośnie baterii oraz skanowania sieci bezprzewodowych przez mój komputer.

xf0r3m@laptop-0b3697e:~$ dbus-monitor --system
...
signal time=1712415292.734476 sender=:1.39 -> destination=(null destination) 
serial=3626 path=/org/freedesktop/UPower/devices/battery_BAT0; 
interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
   string "org.freedesktop.UPower.Device"
   array [
      dict entry(
         string "UpdateTime"
         variant             uint64 1712415292
      )
      dict entry(
         string "Voltage"
         variant             double 8.388
      )
   ]
   array [
   ]
signal time=1712415307.335665 sender=:1.8 -> destination=(null destination) 
serial=14104 path=/org/freedesktop/NetworkManager/Devices/3; 
interface=org.freedesktop.NetworkManager.Device.Wireless; member=AccessPointAdded
   object path "/org/freedesktop/NetworkManager/AccessPoint/794"
signal time=1712415307.335727 sender=:1.8 -> destination=(null destination) 
serial=14105 path=/org/freedesktop/NetworkManager/Devices/3; 
interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
   string "org.freedesktop.NetworkManager.Device.Wireless"
   array [
      dict entry(
         string "AccessPoints"
         variant             array [
               object path "/org/freedesktop/NetworkManager/AccessPoint/348"
               object path "/org/freedesktop/NetworkManager/AccessPoint/349"
               object path "/org/freedesktop/NetworkManager/AccessPoint/766"
               object path "/org/freedesktop/NetworkManager/AccessPoint/787"
               object path "/org/freedesktop/NetworkManager/AccessPoint/788"
               object path "/org/freedesktop/NetworkManager/AccessPoint/792"
               object path "/org/freedesktop/NetworkManager/AccessPoint/793"
               object path "/org/freedesktop/NetworkManager/AccessPoint/794"
            ]
      )
   ]
   array [
   ]

W przypadku instancji sesji informacji jest znacznie więcej, wystarczy że zmienimy aktywne okno lub nacisniemy klawisz kontrol przy aktywnym terminalu.

xf0r3m@laptop-0b3697e:~$ dbus-monitor --session
...
error time=1712415718.701034 sender=:1.119 -> destination=:1.106 
error_name=org.xfce.Xfconf.Error.PropertyNotFound reply_serial=25086
   string "Właściwość „/shortcuts-no-menukey” nie istnieje na kanale „xfce4-terminal”"
method call time=1712415718.925428 sender=:1.106 -> destination=:1.119 
serial=25087 path=/org/xfce/Xfconf; interface=org.xfce.Xfconf; member=GetProperty
   string "xfce4-terminal"
   string "/shortcuts-no-menukey"
error time=1712415718.926183 sender=:1.119 -> destination=:1.106 
error_name=org.xfce.Xfconf.Error.PropertyNotFound reply_serial=25087
   string "Właściwość „/shortcuts-no-menukey” nie istnieje na kanale „xfce4-terminal”"

13.6. Drukowanie

Drukowanie dokumentów w dystrybucjach linuksa, to wieloetapowy proces:

Program odpowiedzialny za drukowanie może przekszatałcić drukowany dokument na format PostScript. Ten krok jest opcjonalny. W przypadku braku konwersji program przesyła dokument do serwera wydruku, dokument wówczas zostaje umieszczony w kolejce wydruku. W momencie rozpoczęcia procesu drukowania serwer wydruku przesła dokument do filtra wydruku. W przypadku gdy dokument nie ma formatu PostScript filtr wydruku może dokonać konwersji. Jeśli drukarka nie obsługuje formatu PostScript to przy użyciu sterownika dokument zostaje skonwertowany do postaci odpowiedniej dla urządzenia drukującego. Ze sterownika drukarki zostają pobrane takie informacje jak źródło papieru czy ewentualny dupleks - zostają one dodane do dokumentu. Na koniec serwer wydruku wykorzystuje postprocesor aby wysłać dokument do drukarki.

Sam proces jest dość skomplikowany, ponieważ wykorzystuje się on w bardzo dużym stopniu na języku programowania PostScript. Język ten pełni rolę standardu drukowania w dystrybucjach Linuksa.

13.6.1. CUPS

System wydruku w dystrybucjach jest CUPS, produkt firmy Apple, więc jest on także stosowany systemach macOS. Demonem tego system jest cupsd. W raz system drukowania mamy dostępne polecenie lpr, które może pełnić rolę prostego klienta pozwalającego na wysłanie plików do demona.

Istotną funkcją systemu CUPS jest implementacja protokołu IPP (ang. Internet Print Protocol), wykorzystuje on port TCP/631 i jego działanie opiera się o transakcje podobne do protokołu HTTP. System CUPS można konfigurować za pomocą przeglądarki łącząc się z adresem http://localhost:631. Użytkownikiem administracyjnym jest root i hasło jest takie same jak do zalogowania się na tego użytkownika w systemie. Większość serwerów wydruku obsługuje protokół IPP, w tym również system MS Windows. Co umożliwia nam tworznie drukarek sieciowych, które nie są domyślnie sieciowe. To zadanie jednak wymaga nieco ingerencji, ponieważ domyślna konfiguracja nie jest zbyt bezpieczna.

Konfiguracja drukarek w dystrybucjach Linuksa, jeśli mówimy tu o komputerze desktopowym zazwyczaj sprowadza się dodania drukarki przy użyciu specjalnego apletu w sekcji ustawień dla danego środowiska graficznego. Oczywiście, jak w przypadku większości zwykłego sprzętu dostępnego dla ogółu problemem mogą być sterowniki lub brak pliku PPD (wyjaśnienie czym jest plik PPD znajduje się w podrozdziale 13.6.2). Z własnego doświadczenia wiem, że z dystrybucjami Linuksa (przynajmniej z Debianem) dobrz działają drukarki firmy Kyocera (dawniej Kyocera-Mita) jeśli nie działają plug and play to zapewne dostępny jest sterownik własnościowy - w przypadku tych urządzeń problemem może być koszt tonera (są to urządzenia laserowe). Urządzeniem domowym, z które rzadko korzystam jest Canon Pixma TR4550 działa z Debianem plug and play dużym plusem jest tego urządzenia jest to, że te tusze nie zasychają (przynajmniej oryginalne). Z racji tego że to urządzenie wielofunkcyjne różnież jest możliwość skanowania przez pakiet Sane.

13.6.2. Konwersja formatów i filtry wydruku

Większość tanich drukarek nie rozpoznaje formatów PostScript ani PDF, aby móc znich korzystać na dystrybucjach Linuksa niezbędna jest konwersja formatu do tego określanego przez drukarkę.

System CUPS wysyła dokument do procesora RIP (ang. Raster Image Processor), aby wygenerować bitmapę. Procesor RIP zawsze stosuje program Ghostscript (gs) do realizowanie większości zadań. Czynność jest dość skomplikowana, gdyż bitmapa musi być dostowana do formatu drukarki. Sterowniki drukarki używane przez CUPS sprawdzają plik PPD (ang. PostScript Printer Definition) konkretnego urządzenia, aby określić takie ustawienia jak rozmiar papieru czy rozdzielczość.

14. Narzędzia programistyczne

Dystrybucje Linuksa, nie wymagają do podstawowej obsługi posiadania jakich kolwiek umiejętności programistycznych. Nie mniej jednak ten rodzaj systemów operacyjnych jest niezwykle często wybierany przez programistów, ze względu na swoją przejrzystość oraz obszerne udokumnetowanie.

Odwiedzając tę stronę czy też czytając ten materiał, za pewne oczekujemy od naszych komputerów czegoś więcej niż wyświetlenie żądanej strony internetowej czy odtworzenie wybranego pliku wideo. Może nadejść taka sytuacja, w której będziemy mieli styczność z kodem źródłowym i to nie tylko języków interpretowanych (zostaną one omówione również) ale także języków takich jak C, C++ czy Java. Warto zapoznać się z narzędziami programistycznymi dostępnymi w dystrybucjach, aby czytając pliki README dostarczone do paczek z kodem czy innej jego dokumentacji wiedzieć co się robi i być może dostosować proces tworzenia plików wykonywalnych do własnych wymagań.

14.1. Kompilator języka C

Za pomocą języka C, stworzono cały ten tematyczny obszar wokół, które oscyluje ten materiał. Wszystkie obecnie wykorzystywane Uniksy korzystają z języka C. Dlatego też warto wiedzieć na tym etapie, w jaki sposób możemy uruchomić program, który dostaniem w postaci zrozumiałej dla człowieka - w postaci kodu źródłowego (zwykłego tekstu).

W uproszczniu zamiana kodu źródłowego na postać wykonywalną przez komputer nosi nazwę kompilacji. Jednak jak się za chwilę okaże jest tylko połowa sukcesu (w większości przypadków). Obecnie w dystrybucjach Linuksa dostępne są dwa kompilatory GNU C Compiler (gcc) oraz Clang/LLVM. W tym materiale skupimy się na klasycznym kompilatorze gcc.

Kompilator oraz inne narzędzia programistyczne mogą domyślnie nie występować w dystrybucjach. W przypadku Debiana czy Ubuntu wystarczy zainstalować za pomocą domyślnego menedżera pakietów pakiet build-essential, w przypadku rodziny dystrybucji opartych o Red Hat należy użyć grupy instalacyjnej Development Tools.

W celu zobrazowania będę umieszczać w przykładach bardzo proste proste programy zapisane w języku C. Tak jak wspominałem, nie jest wymagana umiejętność programowania w żadnym z języków.

#include <stdio.h>

int main() {
  printf("Hello, World!\n");
}

Zapisany powyżej bardzo prosty program, zapisałem w pliku hello.c. Pliki kodu źródłowego języka C powinny mieć rozszerzenie .c. Chociaż wiemy, że w przypadku Uniksów nie to znaczenia, to dla utrzymania porządku w plikach, warto je pokrótce opisać za pomocą zwykłego rozszerzenia plików.

Aby zamienić kod źródłowy na postać niskopoziomową wykonywalną przez nasz komputer musimy skompilować kod przy użyciu kompilatora. Program ten może być dostępny za pomocą poleceń cc lub gcc

xf0r3m@vm-cac72df:~/C$ cc hello.c

Polecenie to powinno zakończyć się, bez oznajmiania jakiego kolwiek działania na standardowym wyjściu. Wynikiem jego pracy jest pojawienie się tuż obok pliku a.out, który ma odpowiednii rodzaj uprawnień, aby móc go odrazu uruchomić - jest to plik wykonywalny, efekt działania kompilatora na powierzonym mu kodzie. Przekonać się możemy o tym wydając poniższe polecenie:

xf0r3m@vm-cac72df:~/C$ ./a.out
Hello, World!

Wcześnie uruchamiając kompilator nie podaliśmy mu poza kodem żadnych innych informacji. Jeśli chcemy, aby nasz plik wykonywalny miał bardziej odpowienią nazwę możemy ją podać jako wartość opcji -o.

xf0r3m@vm-cac72df:~/C$ cc -o hello hello.c
xf0r3m@vm-cac72df:~/C$ ls
a.out  hello  hello.c
xf0r3m@vm-cac72df:~/C$ ./hello
Hello, World!

14.1.1. Kompilowanie wielu plików źródłowych

W przypadku pojednyczych programów takie działanie może wystarczyć, jednak rzadko się zdarza, aby programy były pojedynczymi plikami kodu źródłowego, zazwyczaj zawierają go znacznie, znacznie więcej. Sama praca z pojedynczym plikiem, może być niepożądana przez programistów jak i kompilatory mogą mieć problem z ich przetworzeniem.

Główne składowe programów są najczęściej grupowane a w pojedynczych plikach umieszcza się ich poszczególne elementy. Kompilacja również wygląda nieco inaczej. Wymagane jest użycie opcji -c w celu utworzenia dla każdego ze składowych plików obiektowych - zawierających kod obiektowy, który finalnie przyjmie formę pliku wykonywalnego. Załóżmy, że mamy tylko dwa pliki.

main.c:

void hello_call();
int main() {
  hello_call();
}

aux.c:

#include <stdio.h>

void hello_call() {
  printf("Hello, World!\n");
}

Teraz dla każdego z plików źródłowych, musimy wygenerować pliki obiektowe. Pliki obiektowe są teoretycznie plikami wykonywalnymi ale brakuje w nich informacji pozwalających na dokończenie kompilacji. Sam system nie wie jak ma uruchomić pliki obiektowe, zazwyczaj na jeden program składa się wiele plików obiektowych.

xf0r3m@vm-cac72df:~/C$ cc -c main.c
xf0r3m@vm-cac72df:~/C$ cc -c aux.c
xf0r3m@vm-cac72df:~/C$ ls
aux.c  aux.o  main.c  main.o

W celu połaczenia plików obiektowych w plik wykonywalny użyjemy innego narzędzia, jakim jest konsolidator. Na systemach uniksowych nosi on nazwę ld i rzadko jest uruchamiany przez programistów samodzielnie. Kompilator wie w jaki sposób należy go uruchomić, dlatego też skorzystamy z jego pomocy. Mimo tego, że będziemy używać innego narzędzia to polecenie pozostaje to samo. Konsolidator może również występować pod nazwą linker.

xf0r3m@vm-cac72df:~/C$ cc -o myprog main.o aux.o
xf0r3m@vm-cac72df:~/C$ ls
aux.c  aux.o  main.c  main.o  myprog
xf0r3m@vm-cac72df:~/C$ ./myprog
Hello, World!

Dla celów dydaktycznych stworzyliśmy program składający z dwóch plików kodu źródłowego, zwykle jednak jest tego o wiele wiele więcej. Tutaj poszczególne czynności wykonaliśmy ręcznie. Jeśli w przypadku normalnego programu mielibyś to robić w ten sposób, zajęło by to masę czasu. Na szczęście nie trzeba robić tego ręcznie. Wykorzystamy do tego narzędzie GNU make, które omówimy sobie za chwilę.

14.1.2. Konsolidacja z bibliotekami

Obecne systemy do utworzenia pełnoprawnego programu z plików obiektowych wymagają zbiorów wcześniej skompilowanych komponentów. Pliki obiektowe zawierające te zestawy nie są niczym innym jak bibliotekami, a w ich skład poza wspomnianymi już plikami wchodzą również pliki nagłówkowego, których używaliśmy już w kodzie programów przy użyciu dyrektywy include. Biblioteki dodawane są na etapie konsolidacji, ich użycie w trakcie tej czynności nazywane jest konsolidacją na bazie biblioteki. W przypadku kiedy podczas konsolidacji zapomnimy wspomnieć o bibliotece konsolidatorowi to zostaniem nam zwrócony błąd. Chociaż błedy związane z niewłaściwym odwołaniem się do biblioteki, może nie wynikać z naszych ustawień konsolidatora. Może to również być wynikiem braku zainstalownych bibliotek w systemie.

Do wskazania bibliotek służy opcja -l kompilatora, oczywiście jeśli znajdują się one w domyślnej lokalizacji tj. /lib lub /usr/lib (chociaż w przypadku większości dystrybucji jest to ten sam katalog - kiedy program typu init to systemd), wystarczy podać jej nazwę. W przeciwnym razie musimy na początku wskazać ścieżkę, na której można znaleźć tą bibliotekę - za pomocą opcji -L, a następnie podać jej nazwę za pomocą wspomanianej już wcześniej opcji -l.

Przekazując do konsolidatora (kompilatora) informacje o bibliotekach wartości opcji wraz z opcjami piszemy łącznie.

$ cc -o badobject badobject.o -lcurses -L/usr/junk/lib -lcrud

Podczas wyszukiwania bibliotek, przydatne może okazać się polecenie locate, niestety może nie być domyślne zainstalowane w systemie. Dodatkowo wywmaga ono aktualizacji bazy przy użyciu polecenia updatedb.

Nasze proste programiki z przykładów, nie są pozbawione bibliotek. Tak jak już wcześnie wspominałem kompilator nie jest wstanie utworzyć pełnoprawnego pliku wykonywalnego. Konsolidator zawsze dołącza do utworzonego pliku obiektowego standardową bibliotekę języka C - libc.a - zawiera ona podstawowe komponenty języka C, jest ona zawsze dołączana chyba, że świadomie ją wykluczymy.

14.1.3. Biblioteki współużytkowane

Jak wiemy konsolidator zawsze dodaje do programów standardową bibliotekę języka C - libc.a. Ten plik jest biblioteką statyczną. Oznacza to, że konsolidator podczas tworzenia właściwego pliku wykonywalnego będzie kopiować do naszego pliku fragmenty kodu maszynowego z biblioteki, wówczas plik biblioteki nie będzie nam potrzeby do uruchomienia.

Tego typu rozwiązanie ma dwie dość znaczące wady. Pierwszą z nich są rozmiary bibliotek statycznych, a co za tym idzie rozmiary naszych programów oraz zajętosć pamięci operacyjnej podczas ich pracy. Drugą wadą, ale i nie kiedy zaletą, jest fakt przechowywanie kopii kodu biblioteki (oczywiście, określonych fragmentów), wewnątrz gotowego pliku wykonywalnego. Może być to wadą w momencie gdy, niektóre używane przez nas funkcje mogą okazać się niebezpieczenie i potrzebne będzie rekompilacja, a zaletą gdy twórcy przy okazji kolejnej wersji biblioteki zmieną coś, co nie dokońca będzie nam pasować lub zmiany będą zawierać błędy. Wówczas funkcje używane w naszym programie dalej pozostaną takie jakie powinny być według naszego projektu.

Rozwiązaniem wyżej wymienionych są obecnie stoswane biblioteki współdzielone. Podczas konsolidacji z wykorzystaniem bibliotek współdzielonych, nie są kopiowane całe fragmenty kodu, a tylko odwołania do nazw w bibliotece. W tym przypadku biblioteka jest ładowana do pamięci w momencie gdy jest potrzebna. Dodatkową zaletą tego rozwiązania jest fakt, że procesy mogą współdzielić ze sobą obszar pamięci, w którym znajdują się biblioteki, aby nie ładować ich za każdym razem, gdy uruchamiamy jakiś program.

Czasami po aktualizacji pakietów naszej dystrybucji, nie które otwarte programy mogą zachowywać się dziwnie lub przestać odpowiadać, może dziać się to, ze względu na to iż manadżer pakietów zaktualizował wraz z oprogramowaniem pakiety bibliotek współdzielonych. Wówczas wystarczy ponowne uruchomienie systemu (niektóre menedżery pakietów mogą same to zasugerować) i wszystko powinno wrócić do normy. Ta sama reguła tyczy się również jądra. Tutaj warto wspomnieć o różnicy między Uniksami a innymi systemami. W przypadku Uniksów, aktualizacja jest świadomym działaniem administracyjnym, wymagającym rozwagi oraz przemyślenia idących za nimi konsekwencji.

Rozwiązanie bibliotek współdzielonych nie jest rozwiązaniem bez wad. Biblioteki współużytkowane mają nieco bardziej złożony proces obsługi, a i sam proces kompilacji staje się nieco bardziej skomplikowanym zadaniem. Chcąc wykorzystywać biblioteki w swoich programach musimy odpowiedzieć sobie na kilka pytań:

Wypisywanie zależności od bibliotek współużytkowanych

Omawiajac kwestię bibliotek wspominalśmy miejsce ich położenia w systemie. We współczesnych dystrybucjach katalog /lib jest dowiązaniem symbolicznym do /usr/lib. Nie uświadczymy również zbyt wielu bibliotek statycznych. Większość znich to do biblioteki współdzielone, których pliki mają rozszerzenie .so. Tego typu plików nawet w małym systemie, może być klikadziesiąt. Aby ustalić z jakich bibliotek korzysta konkretny program możemy użyć polecnia ldd.

xf0r3m@vm-2339495:~/C$ ls
a.out  aux.c  aux.o  hello  hello.c  main.c  main.o  myprog
xf0r3m@vm-2339495:~/C$ ldd myprog
        linux-vdso.so.1 (0x00007fffc13dd000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1a32d99000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f1a32f94000)

Polecenie ldd wymaga argumentu w postaci wskazania pliku wykonywalnego. Ten program zwróci nam wszystkie zależność od bibliotek podanego programu. Jak możemy zauważyć na powyższym przykładzie mimo tak prostego programu, linker dołacza standardową bibliotekę języka C, konsolidator dynamiczny (ld-linux-x86-64.so.2, opisany poniżej) oraz bibliotekę ułatwiającą wykonywanie wywołań systemowych (linux-vdso.so.1).

Podczas konsolidacji nie podaje się pełnych ścieżek do bibliotek, aby zachować elastyczność. Zazwyczaj dostępne będą jedynie nazwy. Za odnalezienie bibliotek w systemie odpowiada właśnie konsolidator dynamiczny - ld.so (/lib64/ld-linux-x86-64.so.2). Przyjrzmy się linii standardowej biblioteki języka C. To co znajduje się po lewej stronie operatora (=>) zostało odczytane z programu, natomiast to co znajduje się po po prawej stronie operatora, jest wynikiem pracy właśnie konsolidatora dynamicznego.

Odnajdywanie przez ld.so bibliotek współużytkowanych

Program ld.so posiada wewnątrz wstępnie skonfigurowaną ścieżkę wyszukiwania bibliotek. Do utworzenia tej wykorzystywana jest tzw. składnica systemowa - w pliku /etc/ld.so.cache. Zawiera ona informacje o bibliotekach współdzielonych i ich lokalizacji. Przyczym aby uwzględnić taką lokalizację należy podać ścieżkę do katalogu z bibliotekami w pliku konfiguracjnym składnicy - /etc/ld.so.conf lub w plikach wewnątrz katalogu /etc/ld.so.conf.d. Zawartość tego pliku zazwyczaj składa się ze ścieżek wskazujących na katalogi jakie należy dodać do składnicy. Poniżej znajduje się jeden z plików w katalogu /etc/ld.so.conf.d, zawierający listę ścieżek

xf0r3m@vm-2339495:~/C$ cat /etc/ld.so.conf.d/x86_64-linux-gnu.conf
# Multiarch support
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu

Standardowe lokalizacje takie /usr/lib czy /lib, są domyślne i nie ma potrzeby ich dodawania do składnicy. Każda zmiana w pliku konfiguracyjnym wymaga przebudowania pliku samej składnicy, a dokonać tego możemy za pomocą poniższego polecenia:

xf0r3m@vm-2339495:~/C$ sudo ldconfig -v
[sudo] hasło użytkownika xf0r3m:
ldconfig: Nie można wykonać stat na /usr/local/lib/x86_64-linux-gnu: Nie ma takiego pliku ani katalogu
ldconfig: Ścieżka `/usr/lib/x86_64-linux-gnu' podana więcej niż raz
(od /etc/ld.so.conf.d/x86_64-linux-gnu.conf:4 i /etc/ld.so.conf.d/x86_64-linux-gnu.conf:3)
ldconfig: Ścieżka `/lib/x86_64-linux-gnu' podana więcej niż raz
(od :0 i /etc/ld.so.conf.d/x86_64-linux-gnu.conf:3)
ldconfig: Ścieżka `/usr/lib/x86_64-linux-gnu' podana więcej niż raz
(od :0 i /etc/ld.so.conf.d/x86_64-linux-gnu.conf:3)
ldconfig: Ścieżka `/usr/lib' podana więcej niż raz
(od :0 i :0)
/usr/lib/x86_64-linux-gnu/libfakeroot: (od /etc/ld.so.conf.d/fakeroot-x86_64-linux-gnu.conf:1)
        libfakeroot-0.so -> libfakeroot-tcp.so
/usr/local/lib: (od /etc/ld.so.conf.d/libc.conf:2)
/lib/x86_64-linux-gnu: (od /etc/ld.so.conf.d/x86_64-linux-gnu.conf:3)
        libzxcvbn.so.0 -> libzxcvbn.so.0.0.0
        libzvbi.so.0 -> libzvbi.so.0.13.2
        libzvbi-chains.so.0 -> libzvbi-chains.so.0.0.0
        libzstd.so.1 -> libzstd.so.1.5.5
        libzmq.so.5 -> libzmq.so.5.2.5
        libzmf-0.0.so.0 -> libzmf-0.0.so.0.0.2
        libzix-0.so.0 -> libzix-0.so.0.4.2
        libzimg.so.2 -> libzimg.so.2.0.0
        libzbar.so.0 -> libzbar.so.0.3.0
        libz3.so.4 -> libz3.so.4

Opcja -v powoduje wyświetlenie bardziej szczegółowych komunikatów.

Program ld.so może wykorzystać jeszcze jedno miejsce w celu znalezienia informacji na temat gdzie mogą znajdować się biblioteki - zmienną LD_LIBRARY_PATH. Nie we wszystkich systemach jest ona zdefiniowana i może zostać wykorzystana do wskazania niestandardowych bibliotek tuż przed uruchomieniem programu.

Nie ma co za bardzo skupiać się na dodawaniu ścieżek do składnicy. Wprowadzone przez nas zmiany mogą doprowadzić do chaosu w systemie i powodować konflikty. Jeśli już musimy uzyć jakiejś niestandardowej biblioteki to należy już podczas kompilacji zadeklarować ją oraz jej lokalizację (w podrozdziale poniżej, pokazano jak to zrobić).

Konsolidacja programów z bibliotekami współużytkowanymi

Jeśli w kompilowanym programie używaliśmy jakiś niestandardowych bibliotek współużytkowanych, których próżno szukać w zasobach systemowych musimy podczas konsolidacji wskazać scieżkę oraz nazwę biblioteki podczas uruchamiania kompilatora (tak jak robiliśmy to wcześniej - w przypadku bibliotek statycznych). Tutaj niezbędne będzie również wskazanie katalogu w celu dodania go do ścieżki wyszukiwania bibliotek. W tego typu przypadkach nie należy posługiwać się ustawieniami konsolidatora dynamicznego.

Opcja, o której mowa to: -Wl,-rpath. Przykład poniżej może to zobrazować:

xf0r3m@laptop-5cfe659:~$ cc -o myprog myprog.o -Wl,rpath=/opt/pt/lib -L/opt/pt/lib -lnetstats

Domyślny standardem dla plików wykonywalnych oraz bibliotek w dystrybucjach Linuksa jest ELF - Executable and Linkable Format, dzięki czemu możemy zmienić ścieżkę wyszukiwania bibliotek dla środowiska wykonawczego. Możemy tego dokonać za pomocą polecenia patchelf.

Problemy związane z bibliotekami współdzielonymi

Najczęściej występującym problemem związanym z bibliotekami współdzielonymi jest ich potencjalny brak w naszym systemie. Wówczas musimy szukać pakietów z końcówką -dev w nazwie i ześmiecać system gigabajtami niepotrzebnych plików.

Kolejną rzeczą może być, za długa wartość zmiennej LD_LIBRARY_PATH. Zawartość tej zmiennej jest odczytywana przez konsolidator, który następnie przeszukuje podane ścieżki pod kątem występowania bibliotek. Ta zmiana ma pierwszeństwo przez pozostałymi lokalizacjami, w których mogą wystąpić biblioteki w systemie. Dlatego też ustawienie jej globalnie gdzieś w systemie (np. w plikach konfiguracyjnych powłoki) może spowodować znaczy spadek wydajności systemu. Jeśli już musimy wskazać inną lokalizację bibliotek dla programów a nie chcemy ponownie kompilować (lub nie mamy takiej możliwości, brak kodu źródłowego), możemy utworzyć tzw. skrypt opakowywujący - manipulujący zawartością zmiennej LD_LIBRARY_PATH oraz uruchamiający żądany program. Przy czym zmienna zostanie zmieniona wyłącznie w obrębie procesu podpowłoki uruchomionego na potrzeby skryptu (Więcej o skryptach znajduje się w dodatku A).

Nie mniej jednak najlepszą ucieczką przez problemami związanymi z bibliotekami jest ich wskazanie w procesie konsolidacji lub użycie bibliotek statycznych.

14.1.4. Pliki nagłówkowe

Aby programiści mogi skorzystać z bibliotek w swoich programach muszą się do nich w jakiś sposób odwołać. W języku C mogą wykorzystać do tego pliki nagłówkowe zawierający deklaracje typów oraz funkcji bibliotecznych. Jednym z takich plików, był stdio.h przedstawiany we wcześniejszych przykładach - ten plik nagłówkowy udostępniał nam funkcję printf().

Do wskazywania plików nagłówkowych służy dyrektywa #include. Czasami zdarza się, że programiści dodają omyłkowo jakiś plik nagłówkowy, który nie istnieje. Tego typu błędy zostają wyłapane już na poziomie kompilacji gdzie kod jest wstępnie analizowany.

Oczywiście, może tak być, że wykorzystywane przez programistów pliki nagłówkowe nie znajdują się w standardowej lokalizacji dla tego rozdzaju plików w systemie (/usr/include). Jeśli tak jest to, możemy skorzystać z opcji kompliatora pozwalającą dodać podany katalog do ścieżki wyszukiwania plików nagłówkowych.

xf0r3m@laptop-5cfe659:~$ cc -c -I/opt/pt/include netstats.c

Istnieją dwie metody na dołączenie plików nagłówkowych do kodu programu. Różnice te są dość subtelne jednak mają dość duże znaczenie.

#include <stdio.h>

Klasyczne dołączenie pliku nagłówkowego. Mówi on nam, że kompilator będzie przeszukiwać zasoby systemowego, aby użyć tego pliku na potrzeby kompilacji.

#include "ip.h"

Użycie podwójnych apostrofofów zamiast ostych nawiasów, spowoduje że kompilator przeszuka katalog z podanym kodem źródłowym w poszukiwaniu tak załączonego pliku nagłówkowego. Tak załączone pliki nagłówkowe muszą występować w tym samym folderze do kompilowany kod źródłowy. Jest to przydatne w przypadku, gdy korzystamy z własnych plików tego rodzaju.

Preprocesor języka C

Wcześnie mówiliśmy o kompilatorze w kontekście odnajdywania plików nagłówkowych, jednak nie jest do dokońca prawdą. Preprocesor w przypadku języka C, zajmuje się dostoswaniem kodu pisanego przez człowiek dla kompilatora. Wykonuje on swoje działania na podstawie dyrektyw zapisywanych przez ludzi w kodzie dodając do niego kilka uproszczeń oraz skrótów.

Dyrektywy to nic innego jak instrukcje. Jednak, aby odróżniały się do instrukcji właściwego języka w C, poprzedza się znakiem krzyżyka (#). Taką dyrektywę poznaliśmy już, a jest nią dyrektywa #include. W języku C dostępne są trzy rodzaje dyrektyw:

Istotnym czynnikiem w wykorzystaniu makr a co za tym idzie kompilacji warunkowej jest możliwość przekazania makra do preprocesora za pomocą opcji -D kompilatora. Taka możliwość interakcji, na pewno bardziej wyjaśnia sens istnienia dyrektyw oraz samego preprocesora.

Odnośnie samego preprocesora, to nie zna on żadnych elementów języka C. Jest on skupiony wyłącznie na makrach oraz pozostałych dyrektywach.

14.2. Narzędzie make

Do tej pory kompilacja składała się maksymalnie z dwóch plików i nie obejmowała żadnych bibliotek. Przy takich projektach kompilacją była szybka i przyjmna. Jednak w 99% przypadków z jakim możemy spotkać się raczej tak nie będzie folder będą zawierać podkatalogi a w nich sterty plików ręczna kompilacja, przy nie których mogła by zająć tygodnie jak nie lata. Dlatego też powstało narzędzie typu make. Narzędzie make ma za zadanie zarządzać procesem kompilacji. Mimo, że jest to potężny program to jest on dość prosty w działaniu. Jeśli gdzieś w paczkach z kodem znajdziemy plik makefile lub Makefile oznacza to możemy użyć programu make do kompilacji projektu.

Działanie make polega na celu jaki chcemy osiągnąć, gdy je uruchamiamy. Takim celem najczęściej jest pliki wykonywalny, ale mogą to być również inne byty programistczne takie jaki pliki obiektowe. Same cele mogą się rozgałęziać być uzależnione od wyników działania innych celów. Cele tego rodzaju nazwyamy zależnościami.

W czasie wykonywania celów, narzędzie postępuje zgodnie z regułą (ang. rule), która może np. określać sposób w jaki kod źródłowy ma zostać zmieniony na plik obiektowy. Sam make posiada już zdefiniowane reguły, ale możemy je dostosowywać do własnych potrzeb jak i również tworzyć własne.

14.2.1. Przykładowy plik Makefile

Na podstawie dwóch plików z poprzedniego podrozdziału utworzyłem przykładowy plik Makefile. Plik ten tworzy znany nam rownież plik myprog, który nie robi nic więcej poza wyświetleniem napisu Hello, World!.

xf0r3m@laptop-7bf2993:~/prog/C$ cat Makefile 

OBJS=aux.o main.o
all: myprog
myprog: $(OBJS)
	      $(CC) -o myprog $(OBJS)

W pierwszej linii znajduje się definicja makra, która w tym przypadku wskazuje na dwie nazwy plików obiektowych (OBJS=aux.o main.o). W następnej linii znajduje się bowiem reguła (all:) reguły jak już wcześniej wspomniano określają sposób w jaki ma zostać zbudowany cel. W tym przypadku reguła all: wskazuje mogłoby się wydawać, że na nasz plik końcowy. Jest to poczęści prawda, ale zwróćmy uwagę na to, że poza myprog nie znajduje się nic innego. W jaki sposób narzędzie make ma wiedzieć jak zbudować ten ten program. Odpowiedź kryje się w linii niżej, bowiem myprog jest celem, a co za tym idzie zależnością dla all:. W przypadku celu myprog: widzimy już jakieś konkrety: pierwszym jest odwołanie się do makra, jest ono rozwiązywane do nazw zapisanych na początku pliku Makefile, w pliku nie znajduje się żadne inne elementy systemu Make o tej nazwie tak więc ten cel jest od tych plików uzależniony. Warto dodać, że Make zakłada, że plki źródłowe znajdują się w tym samym katalogu co pliki Makefile. Uruchomienie polecenia make w tym katalogu prezentuje się następująco.

xf0r3m@laptop-7bf2993:~/prog/C$ make
cc    -c -o aux.o aux.c
cc    -c -o main.o main.c
cc -o myprog aux.o main.o

Poza tym co sami zapisaliśmy w pliku Makefile to nie wszystkie czynności jakie są wykonywane przez system Make. W Makefile użyliśmy plików obiektowych, zatem skąd narzędzie ma wiedzieć o tym, że ma wykorzystać pliki kodu źródłowego (.c), aby utworzyć pliki obiektowe (.o). Za tego typu czynności odpowiadają reguły wbudowane. Przez co osoby zajmujące się utworzeniem plików Makefile mogą od razu operować na właściwych plikach.

Ostatnia linia pliku Makefile jest odpowiedzialna za zbudowanie właściwego pliku wykonywalnego naszego programu. Po wcześniejszym przygotowaniu plików obiektowych z plików źródłowych odwołanie się do makra $(OBJS) staje się zwykłym podstawieniem argumentów do polecenia. Ta linia jest zwykłym poleceniem utworzonym na podstawie makr pliku Makefile. Każde polecenie w pliku Makefile musi zostać wprowadzone w nowej linii i poprzedzone znakiem tabulacji równym czterem znakom spacji. Makro $(CC) przechowywuje nazwę programu kompilatora C, w wiekszości przypadku jej wartość będzie ustawiona na cc.

Jednym z najczęstszych błędów, jakie możemy doświadczyć na początku tworzenia plików Makefile jest:

xf0r3m@laptop-7bf2993:~/prog/C$ make
Makefile:5: *** brakujący separator. Stop.

Wynika to najczęściej ze złego ustawienia szerokości tabulacji w naszym edytorze. Ja korzystam z 2 spacji na 1 tab, ze względu na wytyczne Makefile musiałem zmienić swoje ustawienia.

14.2.2. Aktualizacjia zależności

Program make został zaprojektowany w taki sposób aby do realizacji określonego celu była wymagana jak najmniejsza ilość kroków. Tak więc nie uświadczymy ponownego budowania programu w momencie ponownego wydania polecenia make. Aczkolwiek jedną z zasad systemu Make jest to, że cele zawsze powinny być budowane wraz ze swoimi zależnościami. Taki komunikat otrzymamy gdy wydamy polecenie make jeszcze raz.

xf0r3m@laptop-7bf2993:~/prog/C$ make
make: Nie ma nic do zrobienia w 'all'.

Natomiast jeśli chociażby zmienimy czas modyfikacji, któregoś z plików kodu źródłowego za pomocą prostego polecenia touch. Wówczas plik obiektowy będzie starszy niż plik źródłowy i Make dokona budowy naszego programu ponownie.

xf0r3m@laptop-7bf2993:~/prog/C$ touch aux.c
xf0r3m@laptop-7bf2993:~/prog/C$ make
cc    -c -o aux.o aux.c
cc -o myprog aux.o main.o

Z tą róźnicą, że zaktualizowany zostanie wyłącznie ten plik, który został zmieniony oraz ponownie zostanie zbudowany końcowy plik wykonywalny.

14.2.3. Argumenty i opcje wiersza poleceń programu make

Uzupełniając wywołanie programu make o różne opcje oraz argumenty możemy zmusić go do wykonani kilku przydatnych czynności. Najprostszym przykładem może być zmiana wartości makra CC, wskazując inny niż domyślny kompilator.

$ make CC=clang

Tak utworzone makra mogą być przydatne w trakcie testów. Szczególnie takie jak CFLAGS oraz LDFLAGS. Innym przypadkiem może być użycie programu make dla najprostrzych programów wówczas podajemy nazwe pliku bez rozszerzenia jako argument, a program zbuduje nam końcowy plik wynikowy o podanej nazwe. Zwróćmy uwagę, że nie ma potrzeby przygotowania pliku Makefile.

$ make foo
cc  foo.o   -o foo

Tego typu uruchmianie narzędzia make bedzie sprawdzać się w przypadku programów zapisanych w jednym pliku lub programów, w których jezykiem nie jest C. Jest to dobre rozwiązanie gdy dopiero rozpoczynamy pracę z danym językiem i nie wiem jeszcze jak działa kompilator czy inne narzędzia z nim związane. Jeśli nawet zbudowanie celu się niepowiedzie to i tak otrzymamy informację zwrotną co jest nie tak.

Po za zmianami makr, możemy wykorzystać kilka przydatnych opcji takich jak:

14.2.4. Standardowe makra i zmienne

System make posiada wiele predefiniowanych zmiennych oraz makr. Sama różnica między makrami i zmiennymi jest trudna do określenia. Dlatego też przyjęło się, że makra są niezmienne przez cały okres trwania jakiegoś procesu. W tym przypadku nazwiemy tak wszystko co nie zmieni się od momemntu rozpoczęcia procesu budowania celów zawartych w pliku Makefile.

Najczęsciej wykorzystywane makra to między innymi:

Poza makrami w narzędziu make możemy korzystać ze zmiennych. Zmienne mogą zmienić się podczas budowania zdefiowanych celów w Makefile. Najczęściej jednak będziemy się spotykać ze zmiennymi zdefiniowanymi automatycznie w obrębie reguł celów. Oto trzy z nich:

14.2.5. Typowe cele kompilacji

W plikach makefile odpowiedzialnych za pokierowanie systemu make faktycznych programów użytkowych możemy spotkać wiele innych celów realizujących zadania niekoniecznie związane z kompilacją. Są one predefiniowane przez make i oto kilka z nich:

14.2.6. Organizowanie pliku Makefile

Istnieje wiele róznych stylów tworzenia plików Makefile, mimo tego stosowanych jest kilka ogólnych zasad. W pierwszej częsci pliku, najcześciej spotykane są (jako definiecje makr) opcje bibliotek oraz plików nagłówkowych po grupowanych wg. określonych pakietów.

MYPACKAGE_INCLUDES=-I/usr/local/include/mypackage
MYPACKAGE_LIB=-L/usr/local/lib/mypackage -lmypackage
PNG_INCLUDES=-I/usr/local/include
PNG_LIB=-L/usr/local/lib -lpng

Następnie zapisywane są makra z opcjami kompilatora oraz opcje konsolidatora każde z opcji zapisywane jest w osobnym makrze.

CFLAGS=$(CFLAGS) $(X_INCLUDES) $(PNG_INCLUDES)
LDFLAGS=$(LDFLAGS) $(X_LIB) $(PNG_LIB)

Po tych opcjach zapisywana jest hierarchia makr opisujących pliki obiektowe. Hierarchia ze względu na zależności.

UTIL_OBJS=util.o
BORING_OBJS=$(UTIL_OBJS) boring.o
TRITE_OBJS=$(UTIL_OBJS) trite.o
PROGS=boring trite

W pozostałej częsci plików występują już cele, które składają główne pliki wykonywalne. Jeśli trzeba przygować regułę dla pliku obiektowego należy ją umieścić zaraz nad regułą programu pliku wykonywalnego. Jeśli z tego pliku obiektowego korzysta więcej plików wykonywalnych niż ten jednen to trzeba je przenieść nad nie wszystkie.

14.3. Lex i Yacc

Programy odczytujące pliki konfiguracyjne lub polecenia korzystają z takich narzędzi jak Lex oraz Yacc. Także możemy spotkać się z nimi podczas budowania niektórych projektów.

14.4. Języki skryptowe

Kiedyś poza obsługą powłoki oraz programu awk użytkownik nie musiał znać żadanych innych skryptowych języków programowania, zmieniło się to wraz z rozpowszechnieniem się takich języków jak Perl a w poźniejszych latach Python. Niektóre z narzędzi systemowych zostały przepisane z klasycznego C do np. Pythona - na przykład narzędziem whois. Języki skryptowe obecnie przechylają szalę na swoją korzyść względem języków kompilowanych, mimo to zalety takich języków jak C oraz wpisanie się tego języka w rdzeń nauk komputerowych nie sposowoduje zaprzestania jego używania nawet w ciągu kilku następnych dziesięcioleci. Język ten jest językiem niskopoziomowym, wymagającym abstrakcyjnego myślenia i dość trudnym w jego bardziej zaawansowanym zastosowaniu, dlatego też większość młodszych programistów skupia się na językach skryptowych, w których można osiągnać podobne rezultaty mniejszym kosztem.

W większości przypadków skrypty rozpoczynają się od wskazania interpretera poniższego skryptu. Takie wskaznie rozpoczyna się od od znaków #! (każdy plik tekstowy rozpoczynający się tych znaków uznawany jest za skrypt), po których bez przerwy występuje ścieżka wskazująca na właściwy program interpretera danego języka. Dla języka Perl, wskazanie interpretera prezentuje się w następujący sposób:

#!/usr/bin/perl -w

Natomiast dla języka Python wygląda to następująco:

#!/usr/bin/env python

W systemie może być dostępnych kilka wersji Pythona. Za pomocą tego rozdzaju wskazania wybieramy pierwszą wersję, która znajduje się na ścieżce wyszukiwania poleceń. Inną metodą na wskazanie interpretera Pythona jest: #!/usr/bin/python3, wskazująca konkretnie na jedną z 3 wersji Pythona, obecnie 3.12.

Opiszemy sobie teraz kilka ważniejszych języków skryptowych.

Innym wartym wspomnienia językiem jest m4 odpowiedzialny z przetwarzanie makr. Dostępny jest on w systemie GNU autotools. Bierze udział w automatycznym generowaniu pliku Makefile (Wiecej w 15 rozdziale).

14.5. Język Java

Java podobnie do języka C jest językiem kompilowanym. Zapewnia on prostszą składnie oraz duże możliwości progrowamowania obiektowego. Dawniej służył on jako środowisko dla aplikacji internetowych, jednak obecnie zmieniło się na rzecz interpretowanego przez przeglądarki JavaScriptu. Nie mniej jednak możemy spotkać się z aplikacją Javy na na dystrybucjach Linuksa lub z potrzebą uruchomienia takowej. Dlatego też warto się zapoznać, chociaż w podstawowym stopniu z zapoznać się z obsługą projektów w tworzonych w tym języku.

Java może być kompilowana w dwojaki sposób. Kompilator może przekształcić kod źródłowy do kodu maszynowego zgodnego z naszym systemem (tak samo, jak w przypadku języka C) lub może przetworzyć kod źródłowy na tzw. kod bajtowy interpretowany poźniej przez wirtualną maszynę Javy (interpreter). W Uniksach najczęściej będziemy się spotykać z kodem bajtowym - pliki z rozszerzeniem .class. Wszystkie niezbędne uruchomienia narzędzia znajdują się w pakiecie zwanym środowiskiem uruchomieniowym Javy (JRE). Jeśli mamy go zainstalowanego w systemie to do uruchomienia wystarczy wydać poniższe polecenie:

$ java file.class

Kod bajtowy może również występować w postaci skompresowanej, wówczas będziemy mieć doczynienia z plikami z rozszerzeniem .jar. Je również możemy uruchomić w podobny sposób jak zwykły kod bajtowy wystarczy użyć opcji -jar, tak jak pokazano to na poniższym przykładzie.

$ java -jar file.jar

Czasami do wykorzystania programów Java będzie potrzebne zdefiniowanie dwóch zmiennych JAVA_HOME zawierającej ścieżkę do katalogu z instalacją JRE. Drugą zmienną jest CLASSPATH, zmienna ta zawiera nazwy katalogów przechowywujących pliki kodu bajtowego, których dany program może wymagać.

Jesli zajdzie taka potrzeba, aby skompilować kod Javy to w zestaw narzędzi programistycznych JDK wchodzi w skład kompilator javac. Uruchmiamy go podobnie do interpretera. W skład JDK wchodzi również polecenie jar, które może kompresować kod bajtowy Javy.

$ javac file.java

15. Wprowadzenie do kompilacji oprogramowania kodu źródłowego C

Jedną z wielu zalet dystrybucji Linuksa jako systemu operacyjnego jest fakt, że większość dostępnego na niego oprogramowania jest nie tyle darmowa co rozprowadzana poprzez archiwa zawierające kod źródłowy danego programu. Każdy może sobie go pobrać, użyć, dostosować, a nawet redystrybuować z uznaniem pierwotnego autora, jednak ten aspekt jest już kwestią licencji na jakiej dostarczany jest on do użytkowników. W ten sposób prezentuje się idea open source - otwartych źródeł.

W zamierzchłych czasach gdy komputery znajdowały się w zasięgu uczelni wyższych, nawet oprogramowanie własnościowe było rozprowadzane przy użyciu kodu źródłowego. Dawniej chcać skorzystać z programu trzeba było wyposażyć się w zestaw narzędzi programistycznych aby móc w ogóle go uruchomić. Czy te czasy minęły? I tak i nie. Obecnie w wielu dystrybucjach częściej stosuje się paczki przygotowywane osoby związane z dystrybucją aniżeli archiwa kodu niezależnie rozprowadzane przez autora programu. Ma to oczywiście swoje wady oraz zalety. Tym rozdziale spróbujemy sobie przybliżyć w jaki sposób możemy zainstalować aplikację dostępną w postaci kodu źródłowego w naszym systemie.

15.1. Systemy do budowania oprogramowania

Jak pamietamy z poprzedniego rozdziału do automatycznego stworzenia pliku wykonywalnego dla naszej przykładowej aplikacji wykorzystaliśmy narzędzie make, narzędzie to wymaga pliku Makefile, w którym to zapisywane były cele, zależności czy różnego rodzaju makra. Jednym z systemów budowania oprogramowania dostępnym na Uniksy jest GNU autoconf. Zadaniem skryptów tego narzędzia jest przygotowanie plików Makefile na podstawie analizy docelowego systemu. System ten korzysta z istniejących już narzędzi takich jak make. Natomiast jeśli mieli byśmy opisać proces instalacji programów w C w Uniksach to wyglądałby on następująco:

  1. Rozpakowanie archiwum z kodem źródłowym.
  2. Konfigurowanie pakietu.
  3. Uruchomienie polecenie make lub innego polecenia służącego do kompilacji programów.
  4. Uruchomienie polecenie make install i instalacja programów.

15.2. Rozpakowywanie kodu źródłowego.

Oczywiście zdaje sobie sprawę, że chyba nie trzeba opisywać procesu rozpakowywania archiwów w Uniksach. Ten temat był już wałkowany na samym początku. Nie mniej jednak w przypadku kodu źródłowego, warto zachować pewną dozę niepewności. Otóż zadajmy sobie proste pytanie. Co odróżnia program przydatny od szkodliwego oprogramowania? Odpowiedź jest prosta. Czynności jakie on wykonuje. Na pewnej płaszczyźnie jedno i drugie to programy, które mogą być rozporowadzane w postaci kodu źrodłowego, który zostanie być może zostanie przez nas pobrany, skompilowany i zainstalowany w systemie. Najczęstszym tego typu oprogramowaniem są konie trojańskie. Dlatego też przed rozpakowaniem takiej paczki z kodem należy upewnić się co jest w środku. Opcja -t polecenia tar, pozwoli nam wyświetlić zawartość archiwum bez jego wcześniejszego rozpakowywania. Na co powinno się zwrócić uwagę? Otóż na taki drobiazg jak scieżka bezwzględna, przez co wypakowanie może nadpisać ważne systemowe pliki.

W samych archiwach możemy natknąć się takie pliki jak README czy INSTALL. Te pliki mogą zawierać ważne informacje dotyczące ustawień narzędzi programistycznych, a nawet cała instrukcję krok po kroku jak skompilować i zainstalować ten program w naszym systemie.

15.3. GNU autoconf

Jeśli kiedyś uczyliśmy się jezyka C, to wiemy że jest on przenośny między systemami, jednak między systemami operacyjnymi czy platformami sprzętowymi instnieje tak wiele różnic, że najczęściej użycie pojedynczego pliku Makefile nie wykonana poprawnie budowania programu. Rozważano opcję utworzenia dla każdej platformy osobny plik Makefile jednak. To wspomniany wcześniej GNU autoconf stał się rozwiązaniem tego problemu.

Projekty programistyczne przygotowane do współpracy z GNU autoconf zazwyczaj są dostarczane z plikami configure, Makefile.in oraz config.h.in. Pliki z rozszeniami .in są szablonami.

Natomiast sama koncepcja jest dość prosta. Polega ona na uruchomieniu pliku configure, który sprawdzi system i dokona odpowiednich podstawień w pliku Makefile.in, tworząc odpowiedni plik Makefile dla uruchomionego systemu oraz platformy sprzętowej. Uruchomienie skryptu następuje w bardzo prosty sposób.

$ ./configure

W czasie działania skryptu zostanie nam wyświetlona duża ilość komunikatów diagonstycznych. Jeśli działanie skryptu zakończy się powodzeniem, to wówczas zostanie wygenerowany co najmniej jeden pliki Makefile, config.h oraz config.cache. Dzięki czemu nie ma potrzeby przeprowadzania wszystkich testów ponownie w przypadku kolejnego uruchomienia skryptu.

15.3.1. Przykładowe użycie systemu GNU autoconf

Dla przykładu uruchomimy sobie testowe budowanie jakieś małego projektu, aby nabrać nieco praktyki w systemie GNU autoconf zanim przejdziemy do jego ustawień. Dla przykładu wybrałem pakiet GNU coreutils, zawierający narzędzia poznane w drugim rozdziale. Paczkę z kodem źródłowym możemy pobrać z adresu: http://ftp.gnu.org/gnu/coreutils/ Kod należy pobrać, rozpakować a następnie skonfigurować. Żeby nie robić bałaganu w używanym systemie ustawimy jako katalog docelowy jeden z podkatalogów w naszym katalogu domowym.

󱩊 xf0r3m@laptop-d71f06f/ coreutils-9.5/󰯆 ./configure --prefix=$HOME/mycoreutils
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a race-free mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking whether make supports nested variables... (cached) yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
...
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile

Po zakończeniu działania skryptu configure, zostaje wygenerowany na podstawie pliku Makefile.in właściwy dla naszego systemu plik Makefile. W tym wypadku nie pozostaje nam nic innego jak wydanie polecenia: make

󱩊 xf0r3m@laptop-d71f06f/ coreutils-9.5/󰯆 make
  GEN      lib/alloca.h
  GEN      lib/arpa/inet.h
  GEN      lib/configmake.h
  GEN      lib/ctype.h
  GEN      lib/dirent.h
  GEN      lib/error.h
  GEN      lib/fcntl.h
...
  CC       lib/libcoreutils_a-copy-acl.o
  CC       lib/libcoreutils_a-set-acl.o
  CC       lib/libcoreutils_a-acl-errno-valid.o
  CC       lib/libcoreutils_a-acl-internal.o
  CC       lib/libcoreutils_a-get-permissions.o
  CC       lib/libcoreutils_a-set-permissions.o
...
  CCLD     src/chroot
  CC       src/hostid.o
  CCLD     src/hostid
  CC       src/timeout.o
  CC       src/operand2sig.o
  CCLD     src/timeout
  CC       src/nice.o
  CCLD     src/nice
  CC       src/who.o
  CCLD     src/who
  CC       src/users.o
  CCLD     src/users
  CC       src/pinky.o
  CCLD     src/pinky
...
  GEN      man/chroot.1
  GEN      man/hostid.1
  GEN      man/timeout.1
  GEN      man/nice.1
  GEN      man/who.1
  GEN      man/users.1
  GEN      man/pinky.1
  GEN      man/stty.1
...
make[2]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5'
Making all in gnulib-tests
make[2]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
## ---------------------------------------------------- ##
## ------------------- Gnulib tests ------------------- ##
## You can ignore compiler warnings in this directory.  ##
## ---------------------------------------------------- ##
make  all-recursive
make[3]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
Making all in .
make[4]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
  CC       bench_md5-bench-md5.o
  CC       c-strcasestr.o
  CC       c32tob.o
  CC       ioctl.o
  CC       localename.o
...
make[4]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[3]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[2]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[1]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5'

Powyższy przykład może być nieco bardziej obszerny, ponieważ zamieściłem w nim fragmenty kolejnych etapów jakie wykonuje narzędzie make. Teoretycznie możemy już wydadwać polecenie: make install, ale na początku zobaczymy sobie jakie pliki będą chciały się zainstalować (uruchamiając tym samym polecenie make w trybie dry-run).

󱩊 xf0r3m@laptop-d71f06f/ coreutils-9.5/󰯆 make -n install
make  install-recursive
make[1]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5'
...
dot_seen=no; \
target=`echo install-recursive | sed s/-recursive//`; \
case "install-recursive" in \
  distclean-* | maintainer-clean-*) list='po . gnulib-tests' ;; \
  *) list='po . gnulib-tests' ;; \
esac; \
for subdir in $list; do \
  echo "Making $target in $subdir"; \
  if test "$subdir" = "."; then \
    dot_seen=yes; \
    local_target="$target-am"; \
  else \
    local_target="$target"; \
  fi; \
  (CDPATH="${ZSH_VERSION+.}:" && cd $subdir && make  $local_target) \
  || eval $failcom; \
done; \
if test "$dot_seen" = "no"; then \
  make  "$target-am" || exit 1; \
fi; test -z "$fail"
Making install in po
make[2]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/po'
...

Dodanie opcji -n do polecenia make spowoduje wyświetlenie poleceń wykonywanych przez narzędzie make do zrealizowania celu install. Po sprawdzeniu poleceń, możemy wydać polecenie make install.

󱩊 xf0r3m@laptop-d71f06f/ coreutils-9.5/󰯆 make install
make  install-recursive
make[1]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5'
Making install in po
make[2]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/po'
installing af.gmo as /home/xf0r3m/mycoreutils/share/locale/af/LC_MESSAGES/coreutils.mo
installing af.gmo link as /home/xf0r3m/mycoreutils/share/locale/af/LC_TIME/coreutils.mo
installing be.gmo as /home/xf0r3m/mycoreutils/share/locale/be/LC_MESSAGES/coreutils.mo
installing be.gmo link as /home/xf0r3m/mycoreutils/share/locale/be/LC_TIME/coreutils.mo
installing bg.gmo as /home/xf0r3m/mycoreutils/share/locale/bg/LC_MESSAGES/coreutils.mo
...
make[2]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/po'
Making install in .
make[2]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5'
make[3]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5'
 /usr/bin/mkdir -p '/home/xf0r3m/mycoreutils/bin'
  src/ginstall -c src/ginstall '/home/xf0r3m/mycoreutils/bin/./install'
  src/ginstall -c src/chroot src/hostid src/timeout src/nice src/who src/users src/pinky src/stty
...
make[4]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[5]: Entering directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[5]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[4]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[3]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[2]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5/gnulib-tests'
make[1]: Leaving directory '/home/xf0r3m/Pobrane/coreutils-9.5'

Po podaniu za pomocą opcji --prefix miejsca docelowego dla programów, narzędzie make zainstalowało zbudowane pliki wykonywalne we wskazanym w opcji katalogu. A tak prezentuje się zawartość tego katalogu:

󱩊 xf0r3m@laptop-d71f06f/ coreutils-9.5/󰯆 ls -l ~/mycoreutils/
razem 0
drwxr-xr-x 2 xf0r3m xf0r3m 2160 04-30 18:49 bin
drwxr-xr-x 3 xf0r3m xf0r3m   60 04-30 18:49 libexec
drwxr-xr-x 5 xf0r3m xf0r3m  100 04-30 18:49 share

15.3.2. Instalacja za pomocą narzędzia do tworzenia pakietów

Istnieje możliwość instalacji kompilowanego oprogramowania w taki sposób, aby można było je kontrolować za pomocą mendżera paketów dystrybucji. Dystrybucjami, w których najprościej jest wykonać to zadanie są te oparte na Debianie, ze względu na prostotę tworzenia pakietów .deb. W tym celu posłużymy się narzędziem checkinstall. Domyślnie może nie występować w naszym systemie, ale możliwe jest ono do zainstalowania z repozytorium. Na chwilę obecną to program zawiera błąd i aby móc zbudować pakiet należy podać opcję: --fstrans=0. Jeśli nie chcemy aby pakiet automatycznie instalował się w naszym systemie podajemy opcje: --install=no. Podczas tworzenia pakietu wymagane będzie podanie opisu naszęgo pakietu oraz sprawdzenie czy skrypt dobrze odczytał takie informacje jak nazwa pakietu, jego wersja czy docelowa architektura. Te informacje można poprawić. Po zatwierdzeniu tych informacji pakiet zostaje zbudowany. Po zakończeniu budowania będzie go można zainstalować za pomocą polecenia dpkg.

󱩊 xf0r3m@vm-83a53d4/ coreutils-9.5/󰯆 checkinstall --install=no --fstrans=0

15.3.3. Opcje skyptu configure

Po za zaprezentowaną opcją --prefix, która zmienia katalog docelowy dla instalacji oprogramowania w systemie, skrypt configure posiada wiele innych przydatnych opcji. Sam skrypt posiada opcję --help, jednak informacji jest na tyle dużo, że cięzko znaleźć tę jedną potrzebną, dlatego poniżej zamieszczam kilka najważniejszych z nich:

15.3.4. Zmienne środowiskowe

Za pomocą skryptu configure możemy przekazać do narzędzia make makra dla kompilatora, konsolidatora czy preprocesora języka C. Skonfigurowanie zmiennych środowiskowych przed uruchomieniem skryptu daje nam możliwość dostosowywania kompilacji do własnych potrzeb.

Zmienne środowiskowe możemy podawać w tym samy wierszu polecenia przed uruchomieniem skryptu:

$ CPPFLAGS=-DDEBUG ./configure

Lub jako jego opcje:

$ ./configure CPPFLAGS=-DDEBUG

Na powyższych przykładach przedstawiłem zmienną środowiskową preprocesora, ale również dostępna jest LDFLAGS (zmienna środowiskowa konsolidatora) oraz CFLAGS (zmienna środowiskowa kompilatora). Ich wartości są identyczne jak opcje, które przekazywaliśmy do wyżej wymienionych programu - o czym warto pamiętać.

15.3.5. Cele tworzone przez GNU autoconf

Po za standardowymi celami takimi jak all czy install, skrypt configure w pliku Makefile tworzy kilka innych celów. Oto, niektóre z nich, część z nim została już opisana w poprzednim rozdziale.

15.3.6. Plik dziennika systemu GNU autoconf

Jeśli podczas pracy skryptu configure zdarzy się coś nieprzewidzianego jakiś błąd lub problem, to wówczas możemy posłużyć się plikiem dziennika tworzonym podczas pracy skryptu. Plik zowie się config.log i zewzględu na to, że zawierać bedzie on wiele danych to najlepiej jest go przeglądać od końca (po otwarciu pliku za pomocą narzędzia less naciskamy G (Shift+g), przeniesie nas to na koniec pliku.).

15.3.7. Narzędzie pkg-config

Narzędzie pkg-config zostało zaprojektowane w celach rozprowadzania informacji o lokalizacji bibliotek, plików nagłówkowych, a także opcji wymaganych do poprawnego skompilowania i skonsolidowania programu. Składnia tego polecenie prezentuje się w następujący sposób:

$ pkg-config opcje pakiet1 pakiet2 ... pakietN

Przykładowo jeśli do konsolidacji naszego programu potrzebujemy dodać bibliotekę kompresującącą możemy wydać poniższe zapytanie. W odpowiedzi otrzymamy opcję, którą należy dodać do argumentów konsolidatora.

󱩊 xf0r3m@laptop-69924a2/ ~/󰯆 pkg-config --libs zlib
-lz 

Za pomocą opcji --list-all możemy wyświetlić informacje na temat wszystkich bibliotek, o których informacje mogą być dostępne przez pkg-config, co już może sugerować nam, że nie wszystkie biblioteki są dostępne w jego zasobach.

󱩊 xf0r3m@laptop-69924a2/ ~/󰯆 pkg-config --list-all
blkid                          blkid - Block device id library
dbus-1                         dbus - Free desktop message bus
egl                            EGL - EGL library and headers
expat                          expat - expat XML parser
form                           form - ncurses 6.4 add-on library
formw                          formw - ncurses 6.4 add-on library
geoclue-2.0                    Geoclue - The Geoinformation Service
...

Działanie programu pkg-config opiera sie o pliki .pc, które mogą być rozprowadzane w pakietach naszej dystrybucji w momencie instalacji bibilioteki, oczywiście zarządca pakietu musi utworzyć taki plik. Polecenie przeszukuje katalog lib/pkg-config swojego przedrostka instalacji (prefiksu), jeśli np. jego prefiksem jest /usr, to będzie on przeszukiwać katalog /usr/lib/pkg-config.

Jeśli chcemy dodać do zasobów pkg-config bibliotekę o niestandardowym miejscu instalacji, to możemy wykorzystać do tego dwie metody. Pierwsza z nich jest zwykłą kopią pliku .pc do domyślnego katalogu narzędzia lub utworzenie dowiązania symbolicznego. Drugą opcją jest uzupełnienie zmiennej PKG_CONFIG_PATH, o dodatkowy katalog. Jednak to rozwiązanie niesprawdza się w obrębie całego systemu.

15.4. Zalety i wady ręcznej instalacji

Na poziomie podstawowym obsługi systemów Linuksowych, nikt nie pownien od was wymagać umiejętności ręcznej instalacji oprogramowania w systemie. Raczej będziemy polegać na tym co oferują nam repozytoria dystrybucji. Obecnie dostawcy różnych programów często przygotowują pakiety systemu zarządzania oprogramowaniem dla wielu wiodących dystrybucji, dlatego też ręczna instalacja spada do rangi pewnego rodzaju niszy. Budowanie może zająć naprawdę wiele czasu i nie zawsze mozemy osiągnąć żądany skutek. Chcemy skompilować jakiś program, okazuje się że brakuje nam bibliotek w takiej wersji, więc musimy przeprowadzić kompilację bibliotek w takiej wersji w ten sposób nasza praca związana tym zagadnieniem się nawarstwia. Weźmy pod uwagę też sprzęt. Nie każdy dysponuje potężną maszyną, która będzie wstanie kompilować duże programy w ciągu kilku minut. Większość użytkowników dystrybucji Linuksa, do których adresowany jest ten materiał, raczej korzysta z budżetowego laptopa z marketu. Ucieczka do MS Windows, w celu polepszenia swoich doświadczeń z komputerem, bez reklam czy najważniejszej rzeczy w tym systemie czyli aktualizacji. Tak więc kompilacja na takim sprzęcie może zająć dłuższą chwilę, co sprawia, że budowanie od podstaw oprogramowania traktowane jest raczej jako ostateczność.

Nie mniej jednak taka instalacja oprogramowania w systemie nie jest bez zalet. Najważniejszymi z nich jest dostarczenie do naszych systemów najnowszych wersji programów, dostosowanie ich możliwości oraz funkcjonalności przy użyciu opcjonalnych bibliotek czy samych opcji kompilacji do naszych potrzeb. Nawet lepiej, istnieją całe dystrybucje, które opierają się tylko i wyłącznie o ręczną instalację lub zawierają takie programy do zarządzania oprogramowaniem, które i tak koniec końców sprowadzania się do ręcznej instalacjacji przeprowadzonej w nieco bardziej bezobsługowy sposób.

Oczywiście zachęcam do ręcznej instalacji, tylu programów ilu się da. Dzięki temu uczymy się, poznajemy te programy. Domyślnym miejscem przeznaczonym na ręczną instalację jest katalog /usr/local.

15.5. Stosowanie poprawek

Poprawki są nieodłącznym elementem życia oprogramowania. Są one dystrybuowane za pomocą takich narzędzi jak system kontroli wersji Git. Mogą one wprowadzać szereg zmian do kodu źródłowego, poprawiać błedy lub uzupełaniać oprogramowanie o nowa funkcjonalność. Poprawki tworzone są zapomocą polecenia diff. Poniżej znajduje się nagłówek pliku poprawki wprowadzający zamiany, które obecnie czytasz do tego materiału.

diff --git a/articles/terminallog/Linux.Podstawy.html b/articles/terminallog/Linux.Podstawy.html
index 464a32d..09c06a7 100644
--- a/articles/terminallog/Linux.Podstawy.html
+++ b/articles/terminallog/Linux.Podstawy.html
@@ -12597,6 +12597,212 @@ 

Wszysktie linie, które są dodatkowo wprowadzena oznaczne symbolem plusa (+) najczęściej koloru zielonego. Jeśli nasz terminal obsługuje kolory. Natomiast usunięcia oznaczane są za pomocą symbolu minusa (-) najczęściej koloru czerwonego. Linie z minusem (linie na czerwono) zawierają również treść, która występowała w pliku przed wprowadzeniem zmian. Warto mieć to na uwadze.

Pliki poprawek są tworzone za pomocą polecenia diff, natomiast instalowane są za pomocą polecenia patch. Przyczym podczas instalacji poprawek istotne jest dobre ustawie katalogu roboczego w folderze z kodem źródłowym. Załóżmy, że mamy plik poprawki, który obejmuje plik src/plik.c. Wówczas taki plik poprawki należy nałożyć w katalogu nadrzędnym katalogu src, wydając poniższe polecenie:

$ patch -p0 < plik_poprawki

Opcja -p0 usunie dokładnie 0 poprzedzających ukośników ze ścieżki odczytanej w poprawce aby zlokalizować plik. Jest to tyle istotne, ponieważ mogą występować różnice w lokalizacji instalacji na komputerze osoby tworzącej taką poprawkę a jej stosującej. Rozważmy taki przykład, że pliku poprawki widnieje taka ścieżka jak: patches-3.42/src/plik.c. W naszej instalacji nie ma tego katalogu patches-3.42, to aby zastosować poprawkę musimy przejść do katalogu zawierającego katalog src i wydać następujące polecenie:

$ patch -p1 < plik_poprawki;

16. Wirtualizacja

Same pojęcie tego, że coś jest wirtualne odnosi się do translacji warstwy bazowej do postaci uproszczonego interfejsu, z którego może korzystać wielu odbiorców. Z pojęciem wirtualizacji zapoznaliśmy się pośrednio przy poznawaniu zagadnienia pamięci wirtualnej, oferującej procesom duży odizolowany obszar pamięci operacyjnej. W tym przpadku skupimy się nieco szerszym aspekcie możliwości wirtualizacyjnych pozwalających na tworzenie izolowanych środowisk umożliwiających uruchomienie wielu systemów operacyjnych bez powstawania konfliktu miedzy nimi.

16.1. Maszyny wirtualne

Maszyny wirtualne obejmują całość sprzętu komputerowego za pomocą oprogramowania. Wydzielane są zasoby fizycznego hosta oraz tworzone nowe urządzenia aby powstała zupełnie nowa maszyna. Z terminu maszyna wirtualna, korzysta się od wielu lat, dlatego też w celach rozróżnienia (przecież spotkaliśmy się z maszyną wirtualną Javy) dla maszyn tworzących izolowane środowiska zdolne do uruchamiania systemów operacyjnych, które podwzględem programowym nie różnią się niczym od fizycznych komputerów używa się sformuowania systemowa maszyna wirtualna.

Żeby tego było mało, to możemy wróżnić jeszcze maszyny oparte całkowicie na oprogramowaniu, czyli emulatory. Przy użyciu tego rozwiązania możemy uruchamiać np. programy nieprzeznaczone domyślnie na wykorzystywaną przez nas architekturę. Dla przykładu mamy obecnie dostępne emulatory konsol do gier czy smartfonów.

16.1.1 Hipernadzorcy

Programy pozwalające nam na kontrolowanie maszyn wirtualnych i wchodzenie w interakcje z nimi (chociażby po to, aby zainstalować system operacyjny) nazywane jest hipernadzorcą. Istnieją dwa typy oprogramowania hipernadzorcy. Z typem 2, każdy mógł mieć styczność są to takie środowiska virtualizacji jak Oracle VirtualBox czy VMware Player lub Workstation. Hipernadzorcami drugiego typu jest najczęściej oprogramowanie instalowane zawierające pełen pakiet wirtualizacji. Inaczej jest w przypadku typu 1, gdzie instalujemy na fizycznym komputerze specjalny system operacyjny pełniący rolę hipernadzorcy. Pozostają również rozwiązania zawieszone pomiędzy pierwszym a drugim typem, np. rozwiązanie KVM z dystrybucji Linuksa. Pozwala ono zamienić normalną dystrybucję w hipernadzorcę typu 1.

Po omówieniu roli hipernadzorcy, możemy wprowadzić odpowiednią nomenklaturę dla zagadnienia maszyn wirtualnych. Mianowicie komputer, który udostępnia maszyny wirtualne lub ma możliwość ich tworzenia nazywany jest hostem, natomiast maszyna wirtualna gościem.

16.1.2. Sprzęt maszyny wirtualnej

Istnieje wiel różnic między sprzętem fizycznym a tym z którego korzystają maszyny - wirtualny. Wynikają one najczęściej z umożliwienia gościom bardziej bezpośredniego dostępu do zasobów hosta. Wykorzystywany jest mechnizm parawirtualizacji, który pozwala na pomięcie sprzetu wirtualnego między składnikami wirtualizacji (hostem a gościem). Tego typu rozwiązanie najczęściej stosowane jest w przypadku urządzeń blokowych (pamięci masowych) oraz interfejsów sieciowych.

Niezależnie od wykorzystywanych mechanizmów, celem wirtualizacji zawsze będzie takie dostosowanie zasobów, a żeby gośćie mogli trakotować te urządzenia jak swóje własne. Zapewni to stabilność działania uruchmianego na maszynach oprogramowanie, to dzięki tego typu rozwiązaniom możemy np. normalnie alokować przestrzeń pamięci masowych, czy formatować partycje.

Innym ważnym zagadnieniem związanym z maszynami wirtualnymi jest dostęp do procesora. Jak pamiętamy w systemie operacyjnym mamy dwa trybu dostępu do procesora. Tryb jądra - pozwalający na wykonanie dowolnych czynności oraz tryb użytkownika - najprościej rzecz ujmując - ograniczony. Jeśli mamy uruchmiać na maszynach systemy operacyjne, to hipernadzorcy powinni być uruchamiani w trybie jądra. I tu jest mały problem, tryb jądra jest zarezerwowany tylko dla jądra. Hipernadzorca działa w trybie użytkownika (jak każdy inny proces, po za procesami jądra). Jedną z koncepcji, która działa przez jakiś czas było wychwytywanie przez hipernadzorcę wszystkich ograniczonych instrukcji i emulowanie ich działania. To rozwiązanie zostało wyparte przez rozszerzenia procesorów VT-x (Intel) oraz AMD-V (AMD), które udostępniają odpowiednie zestawy instrukcji, które zapewniają gościom bardziej swobodny dostęp do procesora.

16.1.3. Użycie maszyn wirtualnych

Jak wielkrotnie było wspomniane w tym rozdziale maszyny wirtualne tworzą odizolowanie środowisko, zatem ich zastosowaniami mogą być wszelkiego rodzaju testy. Np. jeśli korzystamy z dystrybucji Linuksa na naszych komputerach codziennego użytku, to mogliśmy wykorzystać maszyny wirtualne to testowania przykładów z tego materiału, lub np. dostosowania konfiguracji systemowych do własnych potrzeb. Wiele systemów wirtualizacji posiada mechnizm migawek, które umożliwiają zapisanie stanu maszyny i poźniej ewentualnego cofnięcia się do tego zapisu w razie ewentualnych problemów.

Drugim zastosowaniem maszyn może byc sztywne wydzielenie zasobów dla konkretnych środowisk. Przydzielenie takiej a takiej ilośći pamięci operacyjnej, tyle i tyle rdzeni procesora oraz takiej wielkości dysk twardy znajdujący się np. na macierzy.

16.2. Kontenery

Kontenery w przeciwieństwie do maszyn wirtualnych nie są tak odizolowane od systemu hosta. Często są wykorzystywane jako prostsza alternatywa. Kontenery korzystają z rozwiązań dostępnych już w systemie, aby zapewnić swój rodzaj wirtualizacji. Najczęściej korzystają z możliwości zmiany katalogu głównego (chroot) oraz określenia limitów jądra (rlimit). Kontener można określić jako ograniczone środowisko uruchomieniowe przeznaczone dla zbioru procesów. Oznacza to, że mogą one nie mieć dostępu do niczego poza obrębem swojego środowiska. Tego typu wirtualizację określa się mianem wirtualizacji na poziomie systemu operacyjnego.

Kontenery uruchomione w systemie hosta, będą wspołdzielić ze sobą jądro jego bazowego systemu, bez znaczenia jaka dystrybucja jest uruchomiona w kontenerze. Procesy przestrzeni użytkownika mogą pochodzić z wielu różnych dystrybucji w przypadku kontenerów.

Kontenery są ograniczne za pomocą kilku opcji jądra, przez co one:

Mimo iż jest to możliwe, rzadko kto zadaje sobie tyle trudu aby poustawiać te wszystkie aspekty ręcznie. Dlatego też korzysta się z uproszczonych systemów konteneryzacji takich jak Docker. Nie mniej jednak za pomocą takie systemu LXC mamy możliwość własnoręcznego skonfigurowania tych zagadanień. W tym rozdziale nacisk położono na Docker, ale system LXC również po krótce omowiono.

16.2.1. Docker i Podman

Docker jest jednym z podstawowych narzędzi kontenerowych, jest on bardzo łatwy do instalacji z strony projektu. Wymaga do korzystania z kontenerów procesu serwera oraz przywilejów superużytkownika aby uzyskać dostęp do opcji jądra.

Alternatywą dla Dockera jest Podman. Nie wymaga on do swojego działania procesu serwera. Podman może zostać uruchomiony z uprawnieniami zwykłego użytkownika (ang. rootless), wówczas stosowane są inne mechanizmy do uzyskania izolacji. Jeśli uruchomimy Podman z uprawnieniami administratora to użyje on rozwiązań stosowanych przez Docker. Podman jest kompatybilny pod względem wiersza polecenia z Dockerem. Oczywiście występują różnice w implementacji, szczególnie gdy Podman działa z uprawnieniami zwykłego użytkownika. Jeśli gdzieś zajdzie taka potrzeba rożnice między tymi systemami zostaną one wyraźnie zaznaczone.

W tym materiale skupie się na Docker-ze. Podman-a z resztą już omawiałem w tym rozdziale materiału o RHCSA: https://morketsmerke.github.io/articles/terminallog/RedHat_-_RHCSA.html#23.containers

16.2.2. Użycie Dockera

Zamieszczony w tym podrozdziale przykład użycia Docker-a przestawia podstawowe elementy ukazujące działanie kontenerów. Na początku niezbędne może być utworzenie obrazu składającego się z systemu plików oraz innych elementów definiujących kontener. Obraz ten będzie bazować na obrazie pobranym z repozytorium. Jest to standardowe zachowanie. Przez sam obraz można rozumieć jako system plików kontenera. Obraz naszego kontenera przygotujemy do uruchomienia za pomocą pliku Dockerfile. Oczywiście nie zawsze takie działanie jest wymagane, ponieważ moglibyśmy utworzyć kontener ręcznie, zalogować się do jego powłoki i zainstalować powłokę BASH. Za pomocą pliku Dockerfile utworzymy obraz, z którego będziemy mogli uruchamiać wiele kontenerów.

xf0r3m@debian:~/lp_test$ cat Dockerfile 
FROM alpine:latest
RUN apk add bash
CMD ["/bin/bash"]

Po zapisaniu zmian w pliku Docker-a, wydajemy poniższe polecenie. Opcja -t lp_test wskazuje na identyfikator obrazu. Później łatwiej będzie go zlokalizować, ewentualnie wykorzystać do utworzenia nowego kontenera.

xf0r3m@debian:~/lp_test$ docker build -t lp_test .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM alpine:latest
latest: Pulling from library/alpine
4abcf2066143: Pull complete 
Digest: sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
Status: Downloaded newer image for alpine:latest
 ---> 05455a08881e
Step 2/3 : RUN apk add bash
 ---> Running in ad8a95239176
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
(1/4) Installing ncurses-terminfo-base (6.4_p20231125-r0)
(2/4) Installing libncursesw (6.4_p20231125-r0)
(3/4) Installing readline (8.2.1-r2)
(4/4) Installing bash (5.2.21-r0)
Executing bash-5.2.21-r0.post-install
Executing busybox-1.36.1-r15.trigger
OK: 10 MiB in 19 packages
Removing intermediate container ad8a95239176
 ---> 72784ac891aa
Step 3/3 : CMD ["/bin/bash"]
 ---> Running in 5571f05ede12
Removing intermediate container 5571f05ede12
 ---> 9abc76076917
Successfully built 9abc76076917
Successfully tagged lp_test:latest

Zwróćmy uwagę na informacje zwracane przez Docker podczas przetwarzania pliku Dockerfile. Na początku pobierany jest oficjalny obraz kontenera dystrybucji Alpine Linux (id: 05455a08881e) na jego podstawie tworzony jest kontener (id: ad8a95239176) w którym to uruchamiany jest proces menedżera pakietów, które zdaniem jest zainstalowanie powłoki BASH (RUN apk add bash), jest to druga linia pliku Dockerfile. Na podstawie tego kontenera tworzony jest obraz (id: 72784ac891aa). Następnie z tego obrazu tworzony jest kontener, który uruchamia proces powłoki (id: 5571f05ede12). Ten kontener finalnie posłuży nam za wzorzec dla naszego obrazu - wyniku pliku Dockerfile. Po między kolejnymi czynnościamy odczytanymi z pliku, pośrednie kontenery są usuwane. Wynik działania polecenia możemy również zobaczyć na liście dostępnych obrazów. Dostęp do niej możemy uzyskać za pomocą polecenia: docker images.

xf0r3m@debian:~/lp_test$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
lp_test       latest    9abc76076917   5 minutes ago   11.6MB
alpine        latest    05455a08881e   3 months ago    7.38MB

Na uruchomienie naszego kontenera mamy dwa sposoby: nieinteraktywny, który dobry byłby gdybyśmy mieli uruchomić jakiś skrypt powłoki. Próba uruchomienia naszego kontenera w tym trybie wygląda w następujący sposób:

xf0r3m@debian:~/lp_test$ docker run lp_test
xf0r3m@debian:~/lp_test$

Ze względu na to, że nie było żadnego polecenia dla powłoki, proces zamknął się natychmiastowo. Na potwierdzenie możemy wyświetlić listę wszystkich procesów (kontenerów), nawet wykonanych za pomocą polecenia docker ps -a, polecenie bez opcji -a spowoduje wyświetlenie działających kontenerów.

xf0r3m@debian:~/lp_test$ docker ps -a
CONTAINER ID   IMAGE         COMMAND       CREATED              STATUS                          PORTS     NAMES
d4e8739ee569   lp_test       "/bin/bash"   2 seconds ago        Exited (0) 2 seconds ago                  modest_ardinghelli
6f5067c97eb9   lp_test       "/bin/bash"   About a minute ago   Exited (0) About a minute ago             great_tharp

Jak możemy wywnioskować po pierwszym wpisie, to coś się działo. Jednak nie wiele z tego użyliśmy. Jeśli chcelibyśmy uruchomić kontener w drugim trybie interaktywnym to wówczas musimy do podpolecenia run dodać opcje -it, czyli -i - praca interaktywna oraz -t - podłączenien terminala.

xf0r3m@debian:~/lp_test$ docker run -it lp_test
1d87aa6e43e7:/#

Dopóki pozostawimy tę sesje powłoki uruchomioną, doputy kontener pozostanie uruchiomy. A w ten sposób prezentuje się lista procesów, które są uruchomione na kontenerze:

1d87aa6e43e7:/# ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/bash
  203 root      0:00 ps

Proces uruchomionej powłoki (a co za tym idzie, kontenera) możemy rownież zobaczyć wyświetlając listę procesów hosta:

root        2369  0.0  0.1   2612  2360 pts/0    Ss+  14:42   0:00 /bin/bash

Za tego typu zachowanie odpowiedzialna jest jedna z opcji jądra wykorzystywana na potrzeby kontenerów - przestrzenie nazw Dzięki tej funkcji proces może utworzy zupełnie nowy zestaw identyfikatorów dla siebie oraz swoich procesów potomnych zaczynając do PID-u 1. Procesy te będą wówczas miały dostęp tylko do tego identyfikatora.

Jeśli mamy na to ochotę to po skończonej zabawie możemy usunąć nieaktywne już kontenery za pomocą polecenia docker rm identyfikator:

xf0r3m@debian:~/lp_test$ docker ps -a
CONTAINER ID   IMAGE         COMMAND       CREATED       STATUS                      PORTS     NAMES
1d87aa6e43e7   lp_test       "/bin/bash"   2 hours ago   Exited (0) 19 minutes ago             mystifying_gauss
31808ed62e2a   hello-world   "/hello"      3 hours ago   Exited (0) 3 hours ago                elegant_davinci
xf0r3m@debian:~/lp_test$ docker rm 1d87aa6e43e7
1d87aa6e43e7
xf0r3m@debian:~/lp_test$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED       STATUS                   PORTS     NAMES
31808ed62e2a   hello-world   "/hello"   3 hours ago   Exited (0) 3 hours ago             elegant_davinci

Nakładkowy system plików

Inną funkcją jądra wykorzystywaną przez kontenery jest nakładkowy system plików. Opcja ta pozwala na utworzenie systemu plików po przez połącznie istniejących katalogów jako warstw, wówczas zmiany przechowywane są w jednym miejscu. Ten mechanizm składa się z trzech warstw:

W przypadku Podmana bez uprawnień, wykorzystywany nakładkowy system plików w wersji FUSE.

Obsługa sieci

W przypadku Docker-a istnieje kilka metod na zapewnienie łączności kontenerom. Najczęściej stosowanym rozwiązaniem jest sieć z mostem wykorzystującym siecią przestrzeń nazw (netns). Serwer Docker-a tworzy w systemie hosta interfejs sieciowy zazwyczaj o nazwie docker0 przypisanym pierwszym adresie z podsieci prywatnej 172.17.0.0/16 - 172.17.0.1/16. Ten interfejs wykorzystywany do komunikacji miedzy hostem a kontenerami. Docker wykorzystuje dodatkowe interfejsy w sieciowej przestrzeni nazw oraz mechnizm NAT aby zapewnić kontenerom dostęp do sieci zewnętrznych (w tym sieci lokalnej) hosta, w tym do Internetu.

Podman bez uprawnień wykorzystuje natomiast interfejsy TAP oraz mechanizmy przekazujące w postaci daemona slirp4netns, aby uzyskać dostęp do sieci zewnętrznych. To rozwiązanie ma jeden minus, kontenery niestety nie mogą łączyć się ze sobą.

16.2.3. LXC

LXC jest to jeden z najstarszych systemów konteneryzacji dostępnych na dystrybucje Linuksa. Pierwsze wersje Docker-a operały się o ten pakiet. Niekiedy termin ten jest używany do odniesienia się do mechanizmów jądra pozwalających na obsługę kontenerów, jak i biblioteki oraz pakietu narzędzi wykorzystywanych do ich tworzenia oraz modyfikowania.

Pakiet LXC nie nadaje się dla osób, które nie czują się biegłe w administracji kontenerami oraz nie posiadają odpowiedniej wiedzy jak działają poszczególe mechanizmy systemów konteneryzacji. LXC wymaga dość sporego zakresu ręcznej konfiguracji. Wymagane jest utworzonie np. dla kontenerów interfejsów sieciowych czy zapewnienie mapowania identyfikatorów użytkowników. Wartym wspomnienia faktem odnośnie kontenerów LXC, jest fakt że zawierają one najwięcej elementów z dystrybucji, np. program typu init.

Kontenery LXC są bardziej elastyczne pod kątem dostosowania do różnych wymagań. Jeśli ktoś będzie mieć ochotę pobawić się LXC, To zapraszam na stronę wiki projektu Debian, gdzie jest opisane w jaki sposób zainstalować LXC. Obecnie jednak nie musimy się tym przejmować ponieważ w wersji stablinej Debiana możliwy do zainstalowania jest pakiet LXD, który podczas inicjalizacji wykona większość tej konfiguracji pakietu LXC za nas Trzeba tylko odpowiedzieć na kilka pytań instalatora. Pakiet LXD został odłączony od projektu linuxcontainers.org i stał się produktem firmy Canonical Inc. w repozytorium backports wersji stabilnej znajduje się pakiet Incus, który jest forkiem LXD dalej rozwijanym przez społeczność.

16.3. Wirtualizacja oparta na środowisku uruchomieniowym

Inny rodzaj wirtualizacji może bazować na typie środowiska aplikacji. W tym przypadku nie wykorzystuje się innych mechanik niż izolacja i to tylko określonej aplikacji. Celem takie działania jest zapewnie spójności między środowiskiem języka programowania wykorzystywanym przez ważne kompenenty systemu operacjnego, a zapewnieniem możliwości uruchomienia żądanej przez nas aplikacji. Głównie ma to zastosowanie przy Pythonie, i to jego wykorzystamy do przedstawienia tego rodzaju wirtualizacji.

Przed uruchomieniem takiego środowiska, należy upewnić się, że mamy zainstalowanym pakiet (w Debianie): python3-venv jeśli tak to możemy przjeść do stworzenia środwiska wirtualnego.

xf0r3m@vm-f99031d:~$ python3 -m venv test-env

Polecenie zakończy swoje działanie bez żadnego komunikatu, jeśli faktycznie utworzył to środwisko wirtualne, to powiniśmy zobaczyć katalog o nazwie - w moim przypadku - test-env, a wewnątrz niego powinniśmy znaleźc kilka katalogów systemowych:

xf0r3m@vm-f99031d:~/test-env$ ls
bin  include  lib  lib64  pyvenv.cfg

Aby środowisko mogło zadziałać należy je aktywować za pomocą poniższego polecenia:

xf0r3m@vm-f99031d:~$ . test-env/bin/activate
(test-env) xf0r3m@vm-f99031d:~$

O fakcie, że znajdujemyu się wewnątrz środowiska może świadczyć zmieniony znak zachęty. W ten sposób uzyskaliśmy czyste środowsko uruchomieniowe dla aplikacji w Pythonie. Możemy je smiało również wykorzystać do programowania, czy testowania modułów z repozytorium narzędzia pip. W celu opuszczenia do dyspozycji mamy polecenie będące funkcją powłoki - deactivate Polecenie to przywrócić zmienne środowiskowe oraz ustawienie powłoki do stanu sprzed uruchomienia skryptu deactivate. Na podobnej zasadzie działa polecenie activate.

(test-env) xf0r3m@vm-f99031d:~$ deactivate 
xf0r3m@vm-f99031d:~$ 

Oczywiście nie wszystkie języki programowania, będą miały możliwość utworzenia takie środowiska wirtualnego. W przypadku Pythona jest najprostsze do osiągniecia.

Podsumowanie

Rozdziałem o wirtualizacji zakończyliśmy ten materiał. Nosi on tytuł Linux. Podstawy., jednak czytelnik po jego lekturze może dojść do wniosku, że wiele informacji zostało opisanych nadwyraz szczegołowo, za brakło przykładów faktycznie przezentujących podstawy. Jednak ciężko jest sklasyfikować podstawy. Gdzie kończą się podstawy a zaczyna się wiedza bardziej zaawansowana? W tym materiale, takie faktyczne podstawy znajdują się w pierwszych dwóch rozdziałach, a te tematy zaawansowane zostały omówione w dość powierzchownie, aby wyłącznie nakreślić użytkownikowi jak pewne mechanizmy funkcjonują w dystrybucjach Linuksa i wrazie potrzeby pogłebienia informacji na dany temat, móc napisać odpowiednie zapytanie do wyszukiwarki lub wiedzieć o czym się pisze tworząc wątek na jakimś tematycznym forum z prośbą o pomoc. Tematy te również mogą być traktowe, jako wyłącznie przedstawie danego zagadnienia, aby czytelnik mógł stwierdzić czy ten akurat temat go interesuje. Ze względu na to, że materiał był tworzony na przestrzeni kilku lat. A tak dokładnie to 3. I w trakcie materiał źródłowy, z którego korzystałem został zaktualizowany, można zauważyć rozbierzności w sposobie oraz ilości przedstawianych treści między różnymi rozdziałami.

17.1. Co dalej?

Dobry rozszerzeniem tego materiału, ale już od bardziej usystematyzowanej, bardziej praktycznej formy jest materiał o RHCSA. Mimo iż tutaj używałem wyłącznie GNU/Linux Debian, nie mniej jednak mechnizmy zawarte w Red Hat, mają swoje odpowiedniki w takich systemach jak właśnie Debian. Poniżej pozostał jeszcze dodatek zawierający podstawy programowania w powłoce BASH. O nieco bardziej zaawansowanym wykorzystaniu tej powłoki powstawał osobny materiał

18. Dodatek A. Podstawy programowania skryptów w powłoce BASH

Jak już wspomniałem w 14 rozdziale do zrozumienia tego materiału nie są potrzebne żadne umiejętności programistyczne. Dlatego też przeniosłem rozdział o tworzeniu skrytów powłoki na sam jego koniec do roli dodatku.

Wśród Uniksów mamy dostępne kilka rodzajów powłok. Najczęściej będą to powłoki BASH oraz KSH (wykorzystywana w środowiskach BSD). Obecnie w wielu dystrybucjach możemy spotkać się z ewolucją kwestii domyślnej powłoki a przynajmniej dla naszego domyślnego użytkownika. Co raz częściej możemy spotkać się z takimi powłokami jak ZSH oraz FISH są one następcami powłoki BASH, zawierającymi wiele udogodnień pozwalających na łatwiejsze tworzenie poleceń. Obecnie na popularności zyskuje projekt o nazwie nu-shell jest to nowy rodzaj połoki zmieniający dotychczasowe doświadczenia z nią związane. Nie mniej jednak informacje przedstawione w tym rozdziale będą działać w powłoce BASH oraz z pewnymi ograniczeniami i potrzebą dostosowania w powłoce KSH. Dostosowanie tych informacji do innych programów powłoki pozostawiam wam.

18.1. Podstawy tworzenia skryptów

Podczas tworzenia skryptu powłoki, warto miec uwadze to że ma się do dyspozycji powłokę, na drugiej karcie lub drugim oknie. Przed utworzeniem skryptu można przetestować, niektóre pomysły zanim zostaną one zapisane w skrypcie. Może być to szczególnie przydatne, gdy działanie skryptu może byc destrukcyjne - np. skrypt coś usuwa. Sprawdzenie tego czy nasze pomysły maja w ogóle sens, pozwala zaoszczędzić czas podczas debugowania skryptu. Może nam się wydawać, że pewne konstrukcje powłoki są cieżkie do zapisania w postaci polecenia. Tak może faktycznie być, ale weźmy pod uwagę to, że te konstrukcje mają szczególnie zaznaczony początek i koniec. Jeśli powłoka nie napotka odpowiedniego słowa kluczowego w wprowadzonym wierszu polecenie wówczas przejdzie w tryb wielolinii, w którym to będzie można kontynuować wprowadzanie polecenia.

18.1.1. Początek skryptu

Jak może pamiętamy z podrozdziału o językach skryptowych na początku każdego skryptu umieszczamy linię wskazującą jednoznacznie na program interpretujący zawartość skryptu. W przypadku skryptów powłoki ta linia wygląda następująco:

#!/bin/bash

W dobrym tonie jest pozostawienie jednej linii odstępu do wskazania interpretera a treści skryptu. Nie raz możemy spotkać się takim zapisem jak:

#!/bin/sh

W tym przypadku możemy to dwojako interpretować, albo interpreterem skryptu jest klasyczna powłoka Bourne-a, albo autor zakłada, że domyślnym programem dostarczającym powłokę jest BASH, a plik /bin/sh jest tylko dowiązaniem symboliczym do /bin/bash. W przypadku Debiana, /bin/sh jest dowiązaniem do powłoki DASH bedąca tworem podobnym do KSH. Takie skrypty zazwyczaj wykorzysują proste polecenie oraz instrukcje bez specyficznych dla BASH mechanizmów.

18.1.2. Prosty skrypt.

Na potrzeby tego podrozdziału stworzymy pierwszy skrypt składający się z jednego polecenia echo. Polecenie to służy do wyświetlania ciągów znaków podanych jako argumenty. Polecenie to posiada kilka przydatnych opcji, których opisy znajdują się na stronie podręcznika.

#!/bin/bash

echo "Hello, World!";

Te dwie linie nalezy zapisać w pliku. Możemy dopisać rozszerzenie .sh, wówczas nasz edytor będzie kolorować składnię skryptu. Obecnie edytor mogą uruchamiać odpowiednie kolorowanie składni na podstawie zawartości pliku. Vim działa na takiej zasadzie. Plik należy zapisać i zamknąć następnie otworzyć go ponownie.

18.1.3. Uruchomienie skryptu

Uruchomienie skryptu powłoki BASH może odbyć się na dwa sposóby: bezpośrednio z wiersza polecenia oraz pośrednio podając ścieżkę do skryptu poleceniu bash. Uruchomienie bezpośrednie wymaga nadania plikowi uprawnień do wykonywania. Wówczas takie uruchomienie wygląda następująco:

$ ls -al test.sh
-rw-r--r-- 1 xf0r3m xf0r3m 35 05-10 12:08 test.sh
$ ./test.sh
-bash: ./test.sh: Brak dostępu
$
$ chmod +x test.sh
$ ./test.sh
Hello, World!

Teraz jeśli odbiorę uprawnienia wykonywania dla tego pliku to i tak będę wstanie go uruchomić, poprzez uruchomienie pośrednie.

$ chmod -x test.sh
$ ls -al test.sh
-rw-r--r-- 1 xf0r3m xf0r3m 35 05-10 12:08 test.sh
$ bash test.sh
Hello, World!

Oczywiście te uruchomienia poza poleceniami się niczym innym nie różnią. W momencie uruchomienia skryptu, dochodzi to startu procesu podpowłoki, który wykonuje kolejno jedno po drugim polecenie zapisane w pliku skryptu. Efekty uruchomienia tych sposóbów są identyczne.

18.1.4. Debugowanie skryptów.

W przypadku skryptów użycie słowa debugowanie jest określeniem nadwyrost, nie mniej jednak możliwe jest śledzenie tego co robi skrypt, poprzez wywołanie procesu powłoki wraz z opcją -x, mozna to zarówno zrealizować przez uruchomienie pośrednie jak i bezpośrednie. Jednak raczej stosuje się tylko uruchomienie pośrednie. Nikt nie chce aby jego standardowe wyjście zalała fala informacji z np. każdą wartością zmiennej zadeklarowanej w skrypcie.

#!/bin/bash -x
...
$ bash -x test.sh
+ echo 'Hello, World!'
Hello, World!

Każdy znak plusa (+), oznacza że dane polecenie zostało uruchomione w podpowłoce. Więcej niż jeden taki symbol może mówić nam, że mamy doczynienia z wyrażeniem okrągłych nawiasów lub z podstawieniem polecenia. Obie te konstrukcje zostaną omówione w tym rozdziale.

Teraz kiedy wyjaśniliśmy sobie w jaki sposób utworzyć, uruchomić oraz w wrazie problemów wyświetlić więcej szczegółów w celu namierzenia błedu. Możemy poznać podstawowe instrukcje.

18.2. Podstawowe instrukcje powłoki.

Głównym celem jaki przyświeca tworzeniu skryptów powłoki jest automatyzacja powtarzalnych zadań wykonywanych w powłoce dystrybucji Linuksa. Zatem jej najprostszą oraz najbardziej złożoną instrukcją będzie najzwyklejszy uruchamiany przez nią program. Możemy dla przykładu umieścić w skrypcie z poprzedniego podrozdziału polecenie ls.

$ cat test.sh
#!/bin/bash -x

echo "Hello, World!";
ls

Same polecenie echo jest instrukcją powłoki, co za tym idzie poleceniem skryptu.

Jak możemy zauwazyć na powyższym przykładzie linia z instrukcją echo, kończy się średnikiem (;)a linia z ls nie. Generalnie średnik nie jest wymagany na końcu linii. W ten sposób BASH interpretując skrypt wie gdzie się kończy polecenie. Jeśli zaś linia musi zawierać dwa polecenia, to należy umieszczać między poleceniami średnik. Jest jeszcze kilka przypadków gdy należy stosować średniki, ale przypomnę o tym w sposobnym momencie. Jeśli poza BASH-em programujemy w C lub w PHP, gdzie te średniki są wymagane to nie ma przeszkód aby je stosować.

18.2.1. Zmienne

Instrukcje programowania dostarczane w wraz powłoka BASH pozwalające na pisanie skrytów, są pełnoprawnym językiem programowania. Jak każdy inny język potrzebuje on konstrukcji, które pozwolą mu na pracę z danymi. Tak najprostszą z konstrukcji znaną także w innych językach są zmienne - kontenery na dane, ułatwiające zapisanie toku myślenia w postaci algorytmu. W programowaniu ma to swoje odzwierciedlenie na pamięci, pozwalajac przypisać do niewielkich obszarów pamięci, identyfikatory aby móc odwołać się, w kodzie źródłowym do ich zawartości.

W BASH-u (będę używać tego określenia, na programowanie skryptów powłoki BASH. Myślę że upowszechniło się na tyle, że śmiało możemy powiedzieć o programowaniu w BASH-u.) zmienne są definiowane w prosty sposób. Podajemy nazwę zmiennej (identyfikator), następnie znak równości (=) po tym znaku podajemy wartość. Ważne jest aby nie robić przerw między tymi elementami, wówczas nazwa zmiennej zostanie potraktowana jako polecenie (nazwa programu do uruchomienia).

var=1;

Do tak zdefiniowanej zmiennej możemy się odwołać poprzedzając nazwę zmiennej znakiem dolara ($).

echo "$var";

Polecenie echo wyświetli zawartość zmiennej var na standardowym wyjściu.

18.2.2. Komentarze

Ciągi znaków oznaczone jako komentarze po przez umieszczenie na ich początku krzyżyka (#) są całkowicie ignorowane przez powłokę w trakcie wykonywania skryptu.

# To jest komentarz
#Tresc komenentarza może przylegać do krzyzyka
# lub może być miedzy nimi odstep

Komentarze mają służyć wyłącznie programiście. Ich zadaniem jest możliwość dodawania notatek do kodu, w celu np. wyjaśnienia zawiłego bloku kodu.

18.2.3. Potoki

Potoki czy inaczej polecenia potokowe są to ciągi poleceń połączonych ze sobą za pomocą mechnizmów wejścia-wyjścia.

cat /etc/passwd | grep 'xf0r3m' | sed 's,/bin/bash,/bin/sh,'

W powyższym poleceniu, dane wejściowe programu cat pochodzą z pliku, ale dane wejściowe polecenia grep pochodzą już ze standardowego wyjścia polecenia cat. Zawartość pliku zostaje przekazana na standardowej wejscie drugiego polecenia, które wyszukuje odpowiedniego wzorca. Ta sama sytuacja tyczy się ostatniego polecenia (sed). Potoki konstrułuje się przy użyciu znaku potoku lub znaku kreski pionowej (|).

Potoki są przydatne gdy musimy przetworzyć w jakiś sposób dane, wówczas sprowadzamy je do postaci strumienia. Każde z poleceń na potoku wykonuje swoją działkę i przekazuje je dalej, aż trafiają one do zmiennej lub na standardowej wyjście powłoki. Z prostej analogii wygląda to trochę jak praca na linii produkcyjnej.

Powyższy przykład ma duży potencjał optymalizacyjny swoją drogą, ponieważ pierwsze polecenie można pominąć i podać pliki od razu poleceniu grep.

18.2.4. Podstawienie polecenia

Mając omówione zmienne oraz potoki, to warto może połączyć ze sobą te zagadnienia i zadać pytanie: w jaki sposób można zapisać w zmiennej dane przetwarzane przez potok?

Chcąc zapisać wynik działania potoku do zmiennej, należy wykorzystać mechanizm zwany podstawieniem polecenia. Działa on na takiej zasadzie, że w momencie napotkania tego mechnizmu przez podpowłokę uruchamiana jest kolejna podpowłoka wykonująca polecenia zapisane w tym przypadku w potoku w podstawieniu polecenia. Po zakończeniu wartość zwracana jest przekazywana do zmiennej. Podstawienie polecenia może mieć dwie postacie, można wykorzystać lewe cudzysłowy (``) lub parę okrągłych nawiasów poprzezdzonych znakiem dolara ($()). Obie te formy są poprawne. Do niedawna istniała jedynie wersja z cudzysłowami.

var=$(grep 'xf0r3m' | sed 's,/bin/bash,/bin/sh,');
# lub:
var=`grep 'xf0r3m' | sed 's,/bin/bash,/bin/sh,'`;

18.2.5. Wyrażenie nawiasów okrągłych

Wyrażenie nawiasów okrągłych lub podstawienie podpowłoki, umożliwia nam oddzielenie wykonania pewnych poleceń od głównego procesu podpowłoki wykonującego skrypt. Użycie tego mechinizmu sprawdza się do zapisania, poleceń w zwykłym nawiasie okrągłym.

(cd ytfzf && make install doc)

Jest to szczególnie przydane, gdy musimy trzymać kurczowo jakiś lokalizacji w systemie plików, a niektóre czynności tak jak instalacja projektu pokazana na powyższym przykładzie wymaga zmiany tej ścieżki. Ścieżka może zostać zmieniona w podpowłoce i wykonanie czynności również. Nie będzie to miało wpływu na główny nurt działania skryptu.

18.2.6. Znaki cytowania

Jak było prawdopodobnie wspomniane o znakach cytowania w drugim rozdziale. W BASH-u do dyspozycji mamy 3 rodzaje cudzysłowów:

Należy o tym pamiętać! Że cudzysłowy w powłoce są rozróżniane i mają różne funkcje. Szczególnie może mieć to znaczenie przy takich poleceniach jak sed czy grep.

18.2.7. Parametry pozycyjne

Przekazanie do skryptu informacji jest jak najbardziej możlwe, oczywiście możemy zrobić to jak przypadku innych języków - podczas działania programu poprosić o podanie danych wejściowych. Możemy również użyć mechanizmy parametrów pozycyjnych, które pozwolą nam na przekazanie danych wejściowych podczas uruchamiania skryptu.

$ ./test.sh foo bar

Dostęp do tak przekazanych danych można uzyskać poprzez odwłanie się do numeru parametru:

#!/bin/bash

echo "1: $1";
echo "2: $2";
#

$ ./test.sh foo bar
1: foo
2: bar

Parametry pozycyjne są czesto wykorzysywane do implementowania opcji skryptów. Paramentrów jest maksymalnie 10 (chociaż jest mozliwe jest uzyskanie dostępu do parametrów powyżej 10 za pomocą mechanizmu wyrażenia parametru.

Dostęp do całej listy parametrów pozycyjnych możemy uzyskać na dwa sposoby. Oba są warte poznania. Pierwszym z nich jest lista parametrów, w której każdy jest osobnym elementem. Dostęp do niej uzyskujemy poprzez - $@ - tego typu odwołanie się. Innym sposobem dostęp do parametrów jako pojedynczego ciagu znaków, wówczas wszysktie paramety są jednym elementem. Dostęp do parametrów w ten sposób osiągamy poprzez - $*.

18.2.8. Wyrażenie parametru

Wyrażenie parametru posiada w BASH-u kilka funkcji. Jednak chyba najważniejszą oraz tą, która wymaga zapamiętania jest separacja nazwy zmiennej od pozostałych elementów, jest to szczególnie przydatne gdy scieżki w instrukcjach zawierają zmienne. Wyrażenie parametru jest przedstawiane za pomocą pary nawiasów klamrowych poprzezdzonych znakiem dolara (${}) pomiędzy nawiasami zapisuje się nazwę zmiennej, już bez znaku dolara.

wget https://ftp.server.com/example/${ARCH}/${VERSION}/file.tgz

Oczywiście poza funkcją separacyjną wyrażenie parametru kilka innych zastosowań, jedno już zostało tutaj wymienione, jest to dostęp do parametrów pozycyjnych powyżej 10-tej pozycji. Pozostałe są dostępne na stronie podręcznika powłoki BASH.

18.2.7. Exitcode - wartość zwracana przez program

Niektóre narzędzie wykorzystywane przez nas do pracy w dystrybucjach Linuksa, zostają przez nas uruchomione i zaraz po tym zostaje nam zwrócony znak zachęty. I tak naprawdę to nie wiemy czy program wykonał swoje zadanie pomyślnie czy też nie. Nie mniej jednak większość programów działających na Uniksach zwraca takzwany kod wyjścia.

W przypadku wartości nie zerowych programiści mają cały wachlarz możliwości na opisanie co poszło nie tak, zwracając kod wyjścia inny niż 0. Do dyspozycji mają od 1 - 255 możliwości. Kod wyjścia 1 jest najczęściej wykorzystywany jako błąd ogólny i ta wartość jest najczęsciej zwracana przez programy. Programy raczej sporadycznie korzystają z pozostałych wartości, oczywiście wszystko pozostaje w rękach programistów.

18.3. Wyrażenia warunkowe

Rozpoczynając omawianie wyrażeń warunkowych, warto sobie wyjaśnić, że w BASH-u nie operujemy na wartościach logicznych. Na wartościach logicznych operują polecenia wykonujące sprawdzenie danego wyrażenia warunkowego. Na podstawie wyniku tego wyrażnia narzędzia te generują kod wyjścia: 0 - dla prawdy i 1 - dla fałszu. Warto mieć to na uwadzę, aby podczas debugowania nie zastanawiać się dlaczego nasz warunek zwraca 0, a wykonywany jest blok kodu dla prawdy.

18.3.1. Instrukcje warunkowe

Instrukcjami warunkowymi możemy nazwać polecenia, które zwrócą nam odpowiedni kod wyjścia na podstawie testu logicznego podanego jako argument. Zazwyczaj zapisanie takie testu nie różni się niczym od zapisania warunku w innych językach.

W BASH-u do dyspozycji mamy takie instrukcje jak [, test, [[. Pierwsze dwie instrukcje są identyczne i najczęściej instrukcja [ jest przedstawiana jako alias instrukcji test, która występuje nie tylko BASH-u, ale w większości powłok Uniksowych. Inną instrukcją jest [[ jest klasyczny test jednak jego funkcjonalność została rozszerzona, jednym z takich rozszerzeń jest sprawdzenie czy np. podany ciąg znaków pasuje do nazwy wieloznacznej. Instrukcji [[ nie będziemy tutaj omawiać.

Instrukcje warunkowe do wykonania warunku logicznego potrzebujemy dwóch operandów oraz operatora lub jednego operandu oraz operatora, ponieważ wśród operacji warunkowych istnieją operacje jednoargumentowe. Zazwyczaj przeprowadzane są one ścieżkach wskazujących plik lub katalog. Takich jak na przykład:

[ -f $HOME/testfile.txt ]

Ten warunek sprawdzi czy plik $HOME/testfile.txt jest zwykłym plikiem.

Warunki, które wymagają dwóch operatorów, to zazwyczaj takie jakie znamy z lekcji matematyki: a > b, d <= e itd. Przyczym tutaj warto przypomnień, że zapis instrukcji warunkowych (warunków), jest jednocześnie zapisem polecenia, tak więc nie można użyć znaków mniejszości (<) czy większości (>), zamiast nich musimy używać specjalnie przygotowanych na tę okazję zamienników:

Nie będę tutaj wszysktich wypisywał, wszystkie operatory wraz tymi jednoargumentowymi znajdziemy na stronie podręcznika polecenia test lub na stronie podręcznika powłoki BASH w sekcji CONDITIONAL EXPRESSIONS. Mimo to warto zaznaczyć, że te wyżej wymienione operatory wymagają całkowitych (liczb całkowitych) operandów, a przyrównanie ciągów znaków wymaga pojednyczego znaku równości (=), a nie dwóch jak w przypadku innych języków. Warto mieć to uwadze.

Instukcje warunkowe na podstawie przekazanych przez programistę warunków, zwracaja kod wyjscia, który najczęściej interpretowany jest przez konstrukcje warunkowe.

18.3.2. Konstrukcje warunkowa - if

Konstrukcje warunkowe są elementem kontrolnym w programowaniu. Ich zadaniem jest wykonanie określonych instrukcji determinowanych na podstawie przekazanej instrukcji warunkowej. I najprostszym tego typu konstrukcją jest if-then-else.

Konstrukcja if-then-else, jest podstawową konstrukcją warunkową opartą o wynik jednej instrukcji warunkowej oraz zawierającą dwa bloki kodu, dla prawdy oraz fałszu.

if warunek; then
  #Blok kodu dla prawdy
else
  #Blok kodu dla fałszu
fi

Konstrukcja rozpoczyna się od słowa kluczowego if, oznacza on początek konstrukcji, następnie obok znajduje się warunek, warunek może być instrukcją warunkową, ale może być rownież poleceniem lub jego podstawieniem - czymś co jest nam wstanie zwrócić kod wyjścia. Po warunku obowiązkowo występuje średnik, następnie słowo kluczowe then otwierające blok kodu dla sytuacji, w której warunek zwrócił kod wyjścia równy 0 (potocznie nazywany prawdą). Po tym bloku występuje słowo kluczowe else otwierając tym samym blok kodu dla każdej innej wartości kodu wyjścia, która nie jest zerem. Na samym końcu znajduje się słowo kluczowe fi, zamykające blok else oraz całą konstrukcję.

Konstrukcję if-then-else można rozszerzyć o dodatkowy blok warunkowy elif. W momencie interpretacji takiej konstrukcji BASH sprawdzi na początek warunek przy if następnie przy elif i jeśli oba te warunki zwrócą niezerowy kod wyjścia, wtedy zostaną wykonane instrukcje zapisane w bloku else. Warto dodać, że nie ma limitu w ilości dodatkowych warunków (elif) tworząc przy tym całą kaskadę. Ponizej znajdue się przykład poglądowy przezentujący konstrukcję if-then-elif-else:

if warunek; then
  #Blok dla prawdy warunku;
elif warunek2; then
  #Blok dla prawdy warunku2;
else
  #Blok dla fałszu obu warunków;
fi

18.3.3. Konstrukcja warunkowa - case

Inny rodzajem konstrukcji warunkowej, jest konstrukcja case, działa ona trochę na innej zasadzie niż konstrukcja if-then-else. Nie mam tutaj klasycznego warunku, a jedynie zmienna, która jest porównywana z zapisanymi przez programistę wartościami (przypadkami). Przypadki definiują bloki kodu, w momencie gdy wartość zmiennej jest równa wartości danego przypadku wówczas BASH wykona instrukcje zapisane w bloku tego przypadku.

case $alphabet in
  'a') echo "A";;
  'b') echo "B";;
esac

Zmienna alphabet będzie kolejno porównywana z przypadkami 'a' 'b'. Jeśli zostanie odnaleziony odpowiedni przypadek, wówczas zostanie uruchomiony odpowiedni blok kodu. Zwróćmy uwagę na to, że bloki kodu poszczególnych przypadków zakończone są podwójnym średnikiem, jest to wymagane, a jego pominięcie spowoduje błąd powłoki.

Konstrukcja case posiada przypadek uniwersalny wykorzystywany gdy zmienna nie pasuje, do żadnego z przypadków. Wartość takiego przypadku zapisuje się symbolem gwiazdki (*).

case $alphabet in
  'a') echo "A";;
  'b') echo "B";;
   *) echo "To nie jest litera alfabetu";;
esac

W przypadku konstrukcji case, bloki kodu mogą nie być aż tak widoczne. Niemniej jednak blok rozpoczyna się od wskazania wartości przypadku i obejmuje wszystkie instrukcje, aż do napotkania podwójnego średnika. Samo wskazanie wartości przypadku nie musi się ograniczać do pojednczej wartości, ale może zawierać warianty.

case $alphabet in
  'a'|'A') echo "A";;
  'b') echo "B";;
   *) echo "To nie jest litera alfabetu";;
esac

Konstrukcja case, stosowana jest głównie po to aby nie tworzyć kaskady konstrukcji if-then-elif-else. Każda konstrukcja case, kończy się słowem kluczowym esac.

18.4. Pętle

Pętle w programowaniu służa wykonywaniu powtarzalnych czynności pod pewnym warunkiem. W bloku pętli element warunku (najprawdopodobniej) ulega zmianie, tak aby pętla zakończyła się i interpretacja programu wróciła na właściwy tor. Jeśli tak się nie dzieje, to wówczas mamy doczynienia z nieskończoną pętlą, tego typu konstrukcje nie zawsze muszą oznaczać błąd programisty, mogą być one pożądne, w zależności co znajduje się w ich bloku kodu. W BASH-u dość często wykorzystywane są dwa rodzaje pętli.

18.4.1. Pętla while

Pętla while jest podstawowm rodzajem pętli, który potrzebuje do działania spełnionego warunku, wówczas uruchamiana jest sekwencja poleceń znajdujące sie w bloku tek pętli. W przypadku tego rodzaju pętli istotne jest utworzenie licznika - zmiennej, która będzie kontrolować ilość powtórzeń pętli. Licznik zmieniany jest zazwyczaj, gdzieś na samym końcu pętli lub jego zmiana jest użależniona od określonego warunku. Ze względu na to, iż warunek pętli musi być prawdą, aby mogła ona w ogóle rozpocząć działanie - licznik musi zostać zdefiniowany przed pętlą. Poniżej znajduje się przykładowa konstrukcja pętli while wyświetlająca kwadraty pierwszych 10 dodatnich liczb.

count=1
while [ $count -le 10 ]; do
  pow=$(expr $count \* $count);
  echo "${count}: $pow";
  count=$(expr $count + 1);
done

Pętla while, rozpoczyna się od słowa kluczowego while, po którym następuję warunek. Warunek (jak sama nazwa pętli może wskazywać) musi być spełniony, aby pętla się uruchomiła, bez tego pętla nie wykona się ani razu. Warunek operuje na klasycznych instrukcjach warunkowych. Co pozwala stwierdzić, że pętla while działa na takiej samej zasadzie jak konstrukcja if. Po warunku obowiązkowo występuje średnik, blok kodu zawierającego instrukcje do wykonania w pętli otwiera słowo kluczowe do. Po tym słowie umiesczana jest sekwencja poleceń, która będzie wykonywana dopóki warunek będzię spełniony (będzie zwracać prawdę). Blok kodu pętli, kończy się słowem kluczowym done.

18.4.2. Pętla for

Pętla for działa na odmiennej zasadzie niż pętla while. W tym przypadku zamiast warunku, mamy listę oraz jej element (zmienną). Pętla wykonuje swoje działanie do momentu, aż skończą się elementy na liście. Elementy te są przypisywane do zmiennej i następnie uruchamiany jest blok kodu pętli. Za pomocą zmiennej możemy odwłać się do elementu ustawionego dla tego przebiegu pętli. Poniżej znajduje się przykładowa pętla for realizująca to samo zadanie co pętla while.

for i in 1 2 3 4 5 6 7 8 9 10; do
  echo "${i}: $(expr $i \* $i)";
done

W tym przypadku lista została zapisana z ręki (hardcoded). Jednak tutaj panuje duża elastyczność, listę możemy przekazać zmienną lub w postaci podstawienia polecenia. Wykonanie zadania zostało skompresowane do pojedynczej linii.

18.4.3. Inne pętle oraz instrukcje sterujące

Poza omówionymi wyżej pętlami, BASH oferuje jeszczę pętlę do-while, której cechą charakterystyczną jest fakt, iż wykona się co najmniej raz, ponieważ sprawdzanie warunku występuje poniżej bloku kodu pętli, kiedy zostaną wykonane jej instrukcje wówczas będzie można określić czy warunek zwraca kod wyjścia 0 czy też inny.

Kolejną z pętli jest select, która na podstawie listy wyświetla menu, oddając wykonanie programu pod interaktywną decyzję użytkownika. Użytkownik wybierając odpowiedni numer ustawia wartość przedstawioną w tym przypadku do zmiennej, do której można się odwołać w bloku pętli. Powtarzalność tej konstrukcji, polega na tym, że będzie ona ciągle pytać użytkownika o wybór do momentu, aż nie przerwiemy wykonania całego skryptu lub w kodzie pętli zaimplementujemy warunek, że jeśli została wybrana powiedzmy litera 'q', to zakończ pętle. Poniższy przykład wyświetla nazwy kolorów dostępnych do wyświetlenia w powłoce, po wybraniu koloru, zostanie wyświetlona jego próbka.

colors="black red green yellow blue purple cyan white";
select i in $colors; do
  case $i in
    'black') cc=0;;
    'red') cc=1;;
    'green') cc=2;;
    'yellow') cc=3;;
    'blue') cc=4;;
    'purple') cc=5;;
    'cyan') cc=6;;
    'white') cc=7;;
  esac
  echo -e "${i}: \e[1;4${cc}m      \e[0m";
done

Instrukcjami wpływającymi na działanie pętli w BASH-u są instrukcje: break oraz continue. Instrukcja break służy przerwaniu wykonania pętli i przejściu do instrukcji znajdującej się tuż za pętlą, inaczej jest w przypadku instrukcji continue. Instrukcja ta powoduje zresetowanie przebiegu pętli - wykonanie wraca do sprawdzenia warunku i rozpoczęcia wykonania bloku kodu od nowa.

18.5. Funkcje

Funkcje w BASH-u służą temu samemu celowi co w innych językach programowania - udostępnieniu fragmentów kodu realizującego powtarzalne zadania, aby nie trzeba było ich przepisywać. Dopasowuje się algorytm tak, aby mógł działać z podanymi z zewnątrz danymi, ponieważ funkcję uruchamia się w taki sam sposób jak polecenie.

18.5.1. Definicja oraz wywołanie funkcji

Definicja funkcji wymaga użycia specjalnej konstrukcji function. Przykładową definicję zamieszczono poniżej:

function square () {
  echo "$(expr $1 \* $1)";
}

Definicja funkcji wymaga słowa kluczowego function następnie nazwy funkcji oraz pary okrągłych nawiasów (()). Sekcję bloku kodu funkcji wskazują nawiasy klamrowe ({}). Dane z zewnątrz zostały zaimplementowane pod postacią parametrów pozycjnych. Oczywiście, może nam się wydawać, że definiowanie funkcji dla pojednczego polecenia może nie końca mieć sens, nie mniej jednak takie działanie może posłużyć jako ustawienie aliasu, dla czynności, które mozna zapisać w krótki i prosty sposób, nie tłumaczący jednak dokońca jaka operacja jest wykonywana.

Funkcje tak jak już wspominałem, są wywoływane jak polecenia. Tak więc chcąc wywołać wcześniej zdefiniowaną funkcję należy zapisać pod jej definicją następujące polecenie:

square 4;

18.5.3. Dołączanie plików skryptów

Korzystając z możliwości tworzenia funkcji może się okazać, że jest ich całkiem spora ilość i dużo lepszym sposobem na zarządzanie nimi jest przeniesienie do innego pliku. Wówczas aby móc dalej korzystać z tych funkcji muszą być one dostępne dla skryptu, w którym będą wywoływane. W BASH-u nie mamy takich poleceń jak include czy import. Tutaj musimy posłużyć się wywołaniem skryptu w tym samym proces powłoki, dzięki temu obszar pamięci będzie taki sam, więc dla intepretera wykonującego główny skrypt wczytane z innego pliku funkcję będą dostępne, przez co główny skrypt będzie wstanie z nich skorzystać.

Dołaczenie dodatkowego skryptu można zrealizować na dwa sposoby. Wykonują one tę samą operację i różnią się tylko zapisem. Do wyboru mamy polecenie source oraz zwykłą kropkę (.).

source functions.sh
#lub:
. functions.sh

Drugi sposób nie jest tożsamy z takim zapisem jak: ./functions.sh. Tym przypadku kropka została użyta jako element ścieżki. My będziemy wykorzystywać ją jako polecenie.

18.6. Inne przydatne narzędzia

Tworząc skryptu w BASH-u, za pewne będziemy pracować z tekstami. Takim zadaniem może być wyciągnięcie z pliku feed-u RSS adresu odnośnika do najnowszej wersji oprogramowania, którego używamy. Jest to jedno z kilku przykładów, do których będziemy potrzebowali narzędzi, które mogą być wykorzystywane w wielu skryptach.

18.6.1 Polecenie cut

Polecenie cut może być wykorzystywane do ekstrakcji podciągu znaków z innego ciągu. W tym przypadku jego działanie będzie opierać się na wskazaniu ogranicznika za pomocą opcji -d, przez co ciag znaków - najczęsciej przekazany na standardowej wejście (polecenie cut często jest umieszczane w potoku) - podzielony zostaje na pola, które możemy wybrać podając numer tego pola liczony od początku ciągu. Tutaj warto zazanaczyć, że część ciągu do wystąpienia pierwszego ogranicznika, będzie pierwszym polem. Dostęp do pól uzyskujemy za pomocą opcji -f

...| cut -d " " -f 2 |...

18.6.2. AWK

Samo narzędzie AWK jest języka programowania operującym na tekstach. Nie mniej jednak, nie będziemy skupiać się na szczegółach tego narzędzia. Wykorzystamy jedną z jego funkcjonalności. Wspomniane wcześniej rozwiązanie związane z poleceniem cut ma jedną zasadniczą wadę, możemy miec problem z określeniem żądanego pola, w momencie gdy znak ogranicznika występuje po sobie kilka razy. Najczęściej jest tak z białymi znakami (spacją oraz znakami tabulacji). Wówczas możemy skorzystać z pól prezentowanych przez narzędzie AWK, pola tego narzędzia są tak naprawdę kolumnami, więc należy zadbać o odseparowanie konkretnej linii. Tutaj podział jest prosty, każda kolumna rozpoczyna się od każdego innego znaku nie będącego tabulatorem lub spacją. Numer kolumny podajemy po znaku dolara, tak jak na poniższym przykładzie.

... | awk '{printf $1}' | ...

Narzędzie zbierze wszystkie ciągi występujące w tej kolumnie w jeden ciąg bez żadnych przerw. Dodając trzy znaki, możemy zamienić ten nieczytelny ciąg znaków, chociażby w listę dla pętli for.

... | awk '{printf $1" "}' | ...

18.6.3. sed

Sed, czyli edytor strumienia (ang. stream editor), jest nieinteraktywnym edytorem tekstu opierającym się na wydawaniu pojedynczych poleceń. Za jego pomocą możemy wstawiać do plików poszczególne linie lub je zmieniać stosując znaną metodę find and replace. Sed działa zarówno na plikach jak i strumieniu danych wejściowych.

W plikach może zajść taka potrzeba, aby zmienić pewien zapis tylko w jednej linii, to wskazanie tej wybranej linii może odbyć się na dwa sposoby: za pomocą wyrażenia regularnego lub podając konkretny numer linii. Wyrażenie regularne sprawdzi się przy kilku liniach, w przypadku jednej konkretnej, wystarczy podać jej numer.

Przy początkach naszej pracy ze skryptami, najczęściej będziemy wykorzystywać raczej opcje find and replace, nie mniej jednak specyficzna składania polecenia sed wymaga, aby chociażby pokazać jak wyglądają polecenia insert, append czy print.

Polecenie sed - insert

Polecenie insert wstawia podaną wartość w konkretną linię podaną przez wyrażenie regularne lub numer linii.

$ sed '21i Lorem ipsum dolor sit amet, consectetur adipiscing elit' plik.txt

Na powyższym przykładzie i oznacza właśnie polecenie insert. Przed poleceniem znajduje się numer linii, który w przypadku narzędzia sed pisany jest łącznie wraz ze wskazniem konkretnego polecenia. Po poleceniu występuje wartość, która ma zostać zapisana w pliku.

Polecenie to domyślnie zwrócić na standardowe wyjście całą zawartość pliku z podmienioną linią, ponieważ jest to edytor strumienia to musi on otworzyć plik i działać na nim w pamięci. Aby nasze zamiany faktycznie miały jakiś skutek musimy dodać opcję -i.

Podobnie jest poleceniem append - a, również wymaga ono opcji -i. Składania tego polecenia jest identyczna z insert, zmianie ulega tylko literka wskazująca polecenie, nie mniej jednak zasada działania jest trochę inna. Otóż to polecenie dopisze pod wskazaną linią, podaną wartość. W przypadku naszego przykładu, podana wartość znalazła by się w 22 linii.

Polecenie sed - print

Za polecenie print odpowiedzialna jest litera p nie ma w tym nic dziwnego, warto jednak zaznaczyć, że polecenie to przyjmuje zakres linii do wyświetlania np. jesli chcemy wyświetlić linie od 1 do 12, to wówczas możemy skorzystać z poniższego polecenia:

$ sed '1,12p' test.txt

Tutaj istotną rolę pełni cytowanie. Jeśli checielibyśmy wyświetlić linie od 100 do końca pliku, to polecenie sed mogło by wyglądać w ten sposób: '100,$p'. No dobrze, a jeśli początek zakresu jest zapisany w zmiennej to polecenie powinno wyglądać mniej więcej tak "$a,$p". Problem w tym, że dolna granica zakresu, rownież zostanie zinterpretowana jako zmienna. W tej sytuacji należy zacytować sam znak dolara.

$ sed "$a,\$p" test.txt

Natomiast jeśli w zmiennej znajduje się dolna granica ciągu, to aby oddzielić odwołanie się do zmienne od polecenia print należy użyć funkcji powłoki BASH, jaką jest wyrażenie parametru.

$ sed "1,${a}p" test.txt

Polecenie sed - find and replace

Do funkcji programu sed, find and replace przypisana jest litera s. Domyślna składnia jest następująca:

$ sed "s/ala/ola/" test.txt

Po poleceniu s występuje separator, następnie wartość poszukiwana, znów separator oraz nowa wartość, która zastąpi wartość poszukiwania, na końcu polecenia znów występuje separator. Polecenie zamieni pierwsze wystąpienie ciągu ala na ciąg ola.

W przypadku tego polecenia sed jest bardzo elastyczny. Ponieważ równie dobrze jako separator możemy użyć innych znaków. Znak ukośnika (/) wykorzystywany jest w ścieżkach, a one są jednymi z częściej zmienianych elementów konfiguracyjnych w Uniksach. Jako separatora możemy użyć zarówno: małpy (@) jak i przecinka (,).

Ostatni separator nie musi kończyć polecenia. Ponim zaś występują modyfikatory. Jednym z takich modyfikatorów jest g. Ten modyfikator powoduje, że wszystkie znalezione występienia szukanej frazy zostaną zastąpione.

Podobnie polecenie s nie musi rozpoczynać polecenia. Polecenie s zarówno jak i czy a może zostać sprecyzowane do konkretnej linii lub grupy elementów pasujących do wyrażenia regularnego.

18.6.4. wc

Polecenie wc (ang. word count), jest poleceniem, które zadaniem jest liczenie linii, znaków czy też słów, podanych za pomocą pliku czy też ze strumienia standardowego wejścia. Wykorzystanie polecenia wc, może być całkiem przydatne w momencie gdy musimy ustalić np. granicę dla pętli while. Najczęsciej stoswane jest liczenie linii (za pomoca opcji -l), czasami może się przydać liczenie znaków (za pomocą opcji -c) oraz słów (za pomocą opcji -w).

18.7. Podsumowanie

W tym rozdziale poznaliśmy podstawy tworzenia skryptów powłoki, które pomogą nam automatyzować zadania wykonywana w powłoce. Nie piszę tutaj, konkretnych nazw. Ponieważ jeśli zagłebimy się w temat, to BASH zebrał najlepsze funkcje oraz rozwiązania z innych powłok i zaimplementował u siebie. Oczywiście sam BASH nie jest nowym rozwiązaniem, ponieważ powstał już 1987 roku. Innymi stosowanymi rozwiązaniami są takie powłoki jak zsh czy fish. Inny ciekawy rozwiązaniem jest projekt nu shell, przestawiający troche inne podejście. Zachęcam do zapoznania się z projektem. Jeśli jednak twardo będziemy obstawać, przy BASH-u (ma to swoje zalety, większość dystrybucji wykorzystuje jednak BASH, więc lepiej mieć to samo środowisko w domu jak i na serwerze w pracy). To można zapoznać się z dodatkowymi materiałami: