Przejdź do treści

Część II

Użytkownicy i grupy

Linux od początku był systemem dla wielu osób pracujących jednocześnie na tym samym komputerze. Każda osoba ma własne konto użytkownika (ang. user). System rozpoznaje użytkownika po liczbie: UID (ang. user ID); login to tylko czytelna dla człowieka etykieta. Użytkowników można łączyć w grupy (ang. group), które również mają swoje numery: GID. Specjalny użytkownik root ma UID równy 0 i jest administratorem: system pozwala mu na wszystko.

Polecenie id pokazuje, kim jesteśmy, a groups wypisuje same nazwy naszych grup:

z1234567@student:~$ id
uid=20936(z1234567) gid=20936(z1234567) groups=20936(z1234567),100(users)
z1234567@student:~$ groups
z1234567 users

Na serwerze studenckim każdy ma własną grupę o nazwie loginu, a wszyscy studenci należą też do grupy users.

Informacje o kontach i grupach system przechowuje w plikach tekstowych:

  • /etc/passwd -- konta użytkowników (może czytać każdy),
  • /etc/group -- grupy i ich członkowie (może czytać każdy),
  • /etc/shadow -- skróty (hashe) haseł (może czytać tylko administrator).

Polecenie getent przeszukuje te bazy, także wtedy, gdy konta są zapisane gdzie indziej (np. w LDAP):

z1234567@student:~$ getent passwd z1234567
z1234567:x:20936:20936:Jan Kowalski,,,:/home/z1234567:/bin/bash
z1234567@student:~$ getent group users
users:x:100:banakh,bosek,brianski,duraj,...

Każda linijka /etc/passwd ma format

login:x:UID:GID:opis:katalog domowy:powłoka

x w drugim polu oznacza, że hasła tu nie ma: jest w /etc/shadow. GID to główna grupa użytkownika; pozostałe grupy wynikają z /etc/group. Każdy plik i każdy proces w systemie ma właściciela (użytkownika), a plik ma także grupę.

Zarządzanie kontami

Konta i grupy zakłada administrator. Żeby wykonać pojedyncze polecenie jako root, używa się polecenia sudo. Wolno to tylko użytkownikom wskazanym w konfiguracji sudo (zwykle członkom grupy sudo). Na serwerze studenckim nie mamy takich uprawnień, więc poniższe polecenia skończą się odmową; warto jednak wiedzieć, jak wyglądają:

sudo adduser anna                  # nowe konto z katalogiem domowym; pyta o hasło
sudo addgroup projekt              # nowa grupa
sudo usermod -aG projekt anna      # dopisz użytkownika anna do grupy projekt
sudo gpasswd -d anna projekt       # wypisz annę z grupy projekt
sudo deluser --remove-home anna    # usuń konto razem z katalogiem domowym
sudo delgroup projekt              # usuń grupę
sudo passwd anna                   # ustaw annie nowe hasło
  • W usermod -aG litera a (ang. append) oznacza „dopisz”. Samo -G projekt zastąpiłoby listę dodatkowych grup użytkownika.
  • Przynależność do nowej grupy zaczyna działać od następnego logowania.
  • Własne hasło każdy zmienia sam poleceniem passwd, bez sudo.
  • adduser, deluser, addgroup i delgroup to wygodne nakładki dostępne w Debianie i Ubuntu. W każdym Linuksie działają niskopoziomowe useradd (useradd -m anna), userdel (userdel -r anna), groupadd i groupdel.

Prawa dostępu

Odczytywanie praw

Kolumny wypisywane przez ls -l omówiliśmy w części I. Teraz przyjrzyjmy się pierwszej z nich:

z1234567@student:~$ ls -l /etc/passwd /etc/shadow
-rw-r--r-- 1 root root   23187 Oct 12 12:34 /etc/passwd
-rw-r----- 1 root shadow 10807 Oct  5 11:10 /etc/shadow
z1234567@student:~$ cat /etc/shadow
cat: /etc/shadow: Permission denied
-rw-r-----  root  shadow  /etc/shadow
│└┬┘└┬┘└┬┘   │     └ grupa pliku
│ u  g  o    └ właściciel pliku
└ typ: - zwykły plik, d katalog, l dowiązanie symboliczne

Po typie następują trzy trójki praw: u (ang. user) dla właściciela, g (ang. group) dla członków grupy pliku i o (ang. others) dla wszystkich pozostałych. Każda trójka to r (odczyt), w (zapis) i x (wykonanie); - oznacza brak prawa.

System wybiera jedną trójkę: jeśli jesteś właścicielem, liczy się u; jeśli nie, ale należysz do grupy pliku, liczy się g; w przeciwnym razie o. Prawa z różnych trójek się nie sumują: plik z prawami ----r--r-- mogą czytać wszyscy poza jego właścicielem.

Dla /etc/shadow jesteśmy „pozostałymi” (o), a ta trójka to ---, stąd odmowa.

Opcja -d pokazuje prawa samego katalogu zamiast jego zawartości:

z1234567@student:~$ ls -ld ~
drwx--x--- 8 z1234567 www-data 4096 Mar 24  2023 /home/z1234567

Do naszego katalogu domowego wchodzimy tylko my oraz serwer WWW (grupa www-data), który może przejść do katalogu public_html.

Znaczenie praw dla plików i katalogów

Prawo Zwykły plik Katalog
r odczyt zawartości odczyt listy nazw (ls)
w zmiana zawartości tworzenie, usuwanie i zmiana nazw wpisów (razem z x)
x uruchomienie jako program wejście (cd) i dostęp do pliku o znanej nazwie

Katalog można wyobrazić sobie jako szafkę z kartkami: r pozwala przeczytać spis kartek, x pozwala sięgnąć po kartkę, której nazwę znamy, a w pozwala dokładać i wyjmować kartki. Przykład:

z1234567@student:~$ mkdir szafka; echo tajne > szafka/kartka
z1234567@student:~$ chmod u-x szafka
z1234567@student:~$ ls szafka
kartka
z1234567@student:~$ cat szafka/kartka
cat: szafka/kartka: Permission denied
z1234567@student:~$ chmod u+x,u-r szafka
z1234567@student:~$ ls szafka
ls: cannot open directory 'szafka': Permission denied
z1234567@student:~$ cat szafka/kartka
tajne

Wnioski:

  • Bez x widzimy nazwy, ale nie dostaniemy się do plików.
  • Bez r nie wylistujemy katalogu, ale plik o znanej nazwie przeczytamy.
  • Dostęp do pliku wymaga prawa x we wszystkich katalogach na ścieżce.
  • Usunięcie pliku zmienia spis w katalogu, więc decyduje o nim prawo w katalogu, a nie samego pliku.
  • Kropka na początku nazwy tylko ukrywa plik przed ls; nie jest zabezpieczeniem.

Zmiana praw: chmod

Polecenie chmod zmienia prawa. W zapisie symbolicznym podajemy komu (u, g, o, a -- wszystkim), jak (+ dodaj, - zabierz, = ustaw dokładnie) i jakie prawa:

chmod u+x skrypt.sh       # właścicielowi dodaj x
chmod go-rwx plik         # grupie i pozostałym zabierz wszystko
chmod a+r plik            # wszystkim dodaj odczyt
chmod u+w,g+rx plik       # kilka zmian naraz, po przecinku

W zapisie ósemkowym każda trójka to suma r=4, w=2, x=1, a trzy cyfry to kolejno u, g, o:

chmod 640 plik   →   rw- r-- ---      (6 = 4+2, 4 = 4, 0)
Tryb Typowe zastosowanie
600 prywatny plik, np. klucz SSH
700 prywatny katalog, np. ~/.ssh
644 plik czytelny dla wszystkich
755 katalog lub program dostępny dla wszystkich
711 program, który inni mogą uruchomić, ale nie przeczytać

Liczba ustawia wszystkie trzy trójki naraz, zapis symboliczny zmienia tylko wymienione prawa. Prawa pliku może zmienić tylko jego właściciel (oraz root). Trybu 777 nie używamy jako „naprawy” problemów z dostępem: pozwala każdemu zmienić lub podmienić plik.

Właściciel i grupa pliku

chgrp zmienia grupę pliku. Właściciel może ustawić tylko grupę, do której sam należy:

z1234567@student:~$ chgrp users notatki.txt
z1234567@student:~$ ls -l notatki.txt
-rw-rw-r-- 1 z1234567 users 0 Oct 19 10:15 notatki.txt

chown zmienia właściciela. Wolno to tylko administratorowi (inaczej można by „podrzucić” komuś plik):

z1234567@student:~$ chown root notatki.txt
chown: changing ownership of 'notatki.txt': Operation not permitted (os error 1)
sudo chown anna plik             # nowy właściciel
sudo chown anna:projekt plik     # właściciel i grupa naraz
sudo chown -R anna katalog       # rekurencyjnie: katalog z całą zawartością

Opcja -R działa także w chmod i chgrp.

Prawa nowych plików: umask

Nowo tworzone pliki dostają prawa wynikające z maski umask, która zabiera wybrane bity. Programy zwykle proszą o 666 dla plików i 777 dla katalogów; maska je ogranicza:

z1234567@student:~$ umask
0002
z1234567@student:~$ touch nowy; mkdir nowyk; ls -ld nowy nowyk
-rw-rw-r-- 1 z1234567 z1234567    0 Oct 19 10:20 nowy
drwxrwxr-x 2 z1234567 z1234567 4096 Oct 19 10:20 nowyk

Maska nie zmienia istniejących plików, a nowy plik nigdy nie dostaje automatycznie prawa x.

Uruchamianie programów

Powłoka szuka poleceń w katalogach wymienionych w zmiennej PATH (o zmiennych niżej). Bieżącego katalogu tam nie ma, dlatego program z bieżącego katalogu uruchamiamy, poprzedzając jego nazwę ./:

z1234567@student:~$ echo 'echo Hej' > hej.sh
z1234567@student:~$ ./hej.sh
-bash: ./hej.sh: Permission denied
z1234567@student:~$ chmod u+x hej.sh
z1234567@student:~$ ./hej.sh
Hej
z1234567@student:~$ hej.sh
hej.sh: command not found

Plik hej.sh to skrypt: plik tekstowy z poleceniami dla powłoki. Pierwsza linijka skryptu może zaczynać się od #!, np. #!/bin/bash; wskazuje wtedy program, który ma ten skrypt wykonać. Skrypt jest czytany przez interpreter, więc potrzebuje praw r i x. Program skompilowany (np. z C) uruchamia bezpośrednio jądro systemu, więc wystarczy mu samo x.

Specjalne bity

  • Sticky bit (t na miejscu ostatniego x): w takim katalogu każdy może tworzyć pliki, ale usuwać tylko własne. Ma go /tmp: drwxrwxrwt.
  • Setuid (s na miejscu x właściciela): program działa z prawami swojego właściciela, a nie uruchamiającego. Tak działa passwd, który musi zmienić /etc/shadow: -rwsr-xr-x 1 root root ... /usr/bin/passwd.

Potoki

Znak | łączy dwa polecenia: standardowe wyjście pierwszego staje się standardowym wejściem drugiego, bez pliku pośredniego. Polecenia można łączyć w dłuższe łańcuchy:

z1234567@student:~$ printf 'kawa\nherbata\nkawa\nsok\nherbata\nkawa\n' > napoje
z1234567@student:~$ sort napoje | uniq -c | sort -n
      1 sok
      2 herbata
      3 kawa
z1234567@student:~$ sort napoje | uniq -u
sok
z1234567@student:~$ ls /etc | wc -l

(printf działa jak echo, ale \n w tekście oznacza nową linię.)

  • uniq porównuje tylko sąsiednie linijki, dlatego zwykle poprzedza go sort.
  • Program, któremu nie podamy pliku, czyta standardowe wejście; dlatego uniq -c po | nie potrzebuje nazwy pliku.
  • Potok przekazuje dane, nie argumenty. Komunikaty błędów (standardowe wyjście błędów) nadal trafiają na ekran.
  • tail -f plik wypisuje nowe linijki dopisywane do pliku na bieżąco; kończymy go Ctrl+C.

Zmienne

Powłoka pamięta zmienne: nazwy, pod którymi zapisany jest tekst.

z1234567@student:~$ x=kawa
z1234567@student:~$ echo $x
kawa
z1234567@student:~$ echo "Pije $x"
Pije kawa
z1234567@student:~$ echo 'Pije $x'
Pije $x
z1234567@student:~$ x = kawa
x: command not found
z1234567@student:~$ unset x
  • Przy przypisaniu nie może być spacji wokół =; inaczej powłoka uzna x za nazwę polecenia.
  • $x powłoka zamienia na wartość, zanim uruchomi polecenie. W cudzysłowie "..." zamiana działa, w apostrofach '...' nie.
  • Zmienną zawierającą ścieżkę piszemy w cudzysłowie ("$D"), bo wartość może zawierać spacje.
  • Niektóre zmienne są ustawione od początku: USER (login), HOME (katalog domowy), PATH (katalogi, w których powłoka szuka programów).
  • Zmienna istnieje tylko w powłoce, w której ją ustawiono: w drugim terminalu jej nie będzie. Żeby była ustawiana w każdej nowej powłoce, dopisujemy przypisanie do pliku ~/.bashrc. export x udostępnia zmienną także programom uruchamianym z tej powłoki.

Zmienne przydają się jako skróty: K=/tmp/kopernik/bin/kopernik, a potem "$K" status.

Procesy

Program to plik na dysku. Proces to uruchomiony program. Ten sam program może działać jednocześnie w wielu procesach. Każdy proces ma:

  • PID -- swój numer,
  • PPID -- numer rodzica, czyli procesu, który go uruchomił,
  • właściciela (UID),
  • stan: R działa, S śpi (czeka np. na dane), T wstrzymany, Z zombie (zakończony, ale rodzic nie odebrał jeszcze jego wyniku).

Po zakończeniu procesu jego PID może zostać kiedyś użyty ponownie, dlatego zanim coś zrobimy z procesem, sprawdzamy, czy to na pewno ten.

Przeglądanie procesów

Polecenie ps:

  • ps -- nasze procesy w bieżącym terminalu,
  • ps x -- wszystkie nasze procesy, także w innych terminalach,
  • ps ax -- procesy wszystkich użytkowników,
  • ps u, ps ux -- dodatkowo właściciel, zużycie procesora i pamięci, czas startu,
  • ps -o pid,ppid,stat,etime,args -- wybrane kolumny,
  • ps -u "$USER" -o pid,pcpu,comm --sort=-pcpu -- nasze procesy posortowane od największego zużycia procesora.

$$ to PID bieżącej powłoki, a pstree rysuje drzewo procesów:

z1234567@student:~$ sleep 100 &
[1] 31012
z1234567@student:~$ echo $$
30998
z1234567@student:~$ ps -o pid,ppid,stat,args
    PID    PPID STAT COMMAND
  30998   30997 Ss   -bash
  31012   30998 S    sleep 100
  31014   30998 R+   ps -o pid,ppid,stat,args
z1234567@student:~$ pstree -p $$
bash(30998)-+-pstree(31025)
            `-sleep(31012)

Polecenie pgrep wyszukuje procesy: -u login -- tylko danego użytkownika, -x -- dokładna nazwa, -a -- pokaż argumenty.

z1234567@student:~$ pgrep -a -u "$USER" -x sleep
31012 sleep 100

Argumenty wywołania procesów są widoczne dla wszystkich użytkowników serwera (ps ax -o user,args). Dlatego nie przekazujemy haseł w argumentach poleceń. Informacje o procesach system udostępnia w katalogu /proc: /proc/PID/ dla każdego procesu.

Tryb interaktywny: top i htop

top i htop pokazują procesy na żywo, odświeżając listę co kilka sekund. top -u "$USER" pokazuje tylko nasze procesy. W top: P sortuje po zużyciu procesora, M po pamięci, k wysyła sygnał (o sygnałach niżej), h pomoc, q wyjście. htop jest wygodniejszy: F6 sortowanie, F5 drzewo, F9 sygnał, F10 wyjście.

Kolumna %CPU to pomiar w czasie: żeby wskazać proces stale zajmujący procesor, trzeba poczekać kilka odświeżeń. Przydatny do eksperymentów jest program yes, który bez końca wypisuje y:

z1234567@student:~$ nice -n 19 yes > /dev/null &
z1234567@student:~$ top -u "$USER"

Po obejrzeniu kończymy go poleceniem kill %1 (zob. niżej).

Priorytet procesu (nice) to liczba od -20 (najwyższy) do 19 (najniższy), domyślnie 0. nice -n 19 polecenie uruchamia polecenie z najniższym priorytetem, a renice -n 19 -p PID zmienia priorytet działającego procesu. Zwykły użytkownik może priorytet tylko obniżać.

Zadania w powłoce

Polecenie uruchomione zwykle działa na pierwszym planie: zajmuje terminal, dopóki się nie skończy. Zakończone znakiem & działa w tle, a powłoka od razu wypisuje prompt. Powłoka nazywa takie procesy zadaniami (ang. jobs) i numeruje je osobno: %1, %2, ...

Polecenie Działanie
polecenie & uruchom w tle; powłoka wypisze [numer zadania] PID
Ctrl+Z wstrzymaj zadanie z pierwszego planu (proces żyje, stan T)
jobs -l lista zadań tej powłoki z PID-ami i stanem
bg %1 wznów zadanie 1 w tle
fg %1 wznów zadanie 1 na pierwszym planie
kill %1 poproś zadanie 1 o zakończenie
Ctrl+C przerwij zadanie z pierwszego planu
Ctrl+D koniec danych na wejściu (to nie przerwanie)
z1234567@student:~$ sleep 300
^Z
[1]+  Stopped                 sleep 300
z1234567@student:~$ bg %1
[1]+ sleep 300 &
z1234567@student:~$ jobs -l
[1]+ 31200 Running                 sleep 300 &
z1234567@student:~$ fg %1
sleep 300
^C

bg i fg nie uruchamiają nowej kopii: to ciągle ten sam proces z tym samym PID. Numer zadania istnieje tylko w powłoce, która je uruchomiła; w innym terminalu jobs go nie pokaże, a %1 oznacza coś innego. PID jest wspólny dla całego systemu.

Więcej: Job Control Basics.

Praca po rozłączeniu

Gdy zamykamy terminal lub zrywa się połączenie SSH, procesy uruchomione w tej sesji dostają sygnał HUP i zwykle się kończą, także te w tle.

  • nohup polecenie & -- polecenie przetrwa zamknięcie terminala; jego wyjście trafi do pliku nohup.out.
  • tmux -- sesja terminala działająca na serwerze, z oknami i podziałem ekranu. Ctrl+B D odłącza sesję (dalej działa), a tmux attach wraca do niej, także po ponownym zalogowaniu. Ctrl+B C tworzy nowe okno, Ctrl+B N przechodzi do następnego, Ctrl+B ++%++ dzieli ekran.
  • byobu -- nakładka na tmux z paskiem stanu: F2 nowe okno, F3/F4 przełączanie, F6 odłączenie; ponowne byobu wraca do sesji.

Sygnały

Jak działają Ctrl+C i Ctrl+Z? Terminal wysyła procesowi sygnał: krótki komunikat od systemu. Proces może na sygnał zareagować po swojemu, zignorować go albo zachować się domyślnie (zwykle zakończyć). Wyjątkami są KILL i STOP: tych program nie może przechwycić ani zignorować.

Sygnał Jak wysłać Znaczenie
TERM kill PID (domyślny) prośba o zakończenie; program może posprzątać
INT Ctrl+C przerwanie z klawiatury
TSTP Ctrl+Z prośba o wstrzymanie
STOP kill -STOP PID wstrzymanie, zawsze skuteczne
CONT kill -CONT PID, bg, fg wznowienie
KILL kill -KILL PID (kill -9) natychmiastowe zakończenie, bez sprzątania
HUP zamknięcie terminala terminal zniknął
USR1, USR2 kill -USR1 PID znaczenie ustala autor programu

Polecenie kill wysyła sygnał -- nazwa myli, bo nie każdy sygnał kończy proces. Zamiast PID można podać numer zadania (kill %1). kill -l wypisuje nazwy sygnałów, a pełny opis jest w man 7 signal.

W skrypcie reakcję na sygnał ustawia polecenie trap. Przykładowy skrypt ekspres.sh:

trap 'echo zapisuje; rm szkic.tmp; exit' TERM
trap 'echo odebrano wiadomosc' USR1
touch szkic.tmp
while true; do date >> dziennik; sleep 1; done

Skrypt tworzy plik szkic.tmp, a potem w nieskończonej pętli co sekundę dopisuje datę do pliku dziennik. Uruchommy go w tle i obserwujmy tail -f dziennik w drugim terminalu:

z1234567@student:~$ sh ekspres.sh &
[1] 4100
z1234567@student:~$ kill -STOP %1      # dziennik przestaje rosnąć
z1234567@student:~$ kill -CONT %1      # i rośnie dalej
z1234567@student:~$ kill -USR1 %1
odebrano wiadomosc
z1234567@student:~$ kill %1
zapisuje

Po TERM skrypt posprzątał: szkic.tmp zniknął. Gdyby zamiast tego dostał KILL, zakończyłby się natychmiast, a szkic.tmp zostałby na dysku. Dlatego najpierw wysyłamy TERM, a KILL tylko wtedy, gdy TERM nie działa. USR1 wysłany do programu, który go nie obsługuje (np. do sleep), zwykle go zakończy.

Kilka procesów naraz:

  • pkill -- jak pgrep, ale od razu wysyła sygnał wszystkim znalezionym procesom, np. pkill -TERM -u "$USER" -x sleep. Warto najpierw uruchomić pgrep z tymi samymi opcjami i sprawdzić, co zostanie wybrane.
  • killall nazwa -- wysyła sygnał wszystkim procesom o danej nazwie.

Takie polecenia łatwo trafiają w procesy, których nie chcieliśmy zakończyć. Na przykład pkill -u "$USER" bez nazwy zakończyłby wszystkie nasze procesy, w tym połączenie SSH. Zwykły użytkownik może wysyłać sygnały tylko do własnych procesów; próba wobec cudzego kończy się błędem Operation not permitted. Administrator (sudo kill PID) może wysłać sygnał do każdego procesu.

Dobry nawyk: obejrzyj (pgrep -a, ps) → wybierz swój PID → wyślij sygnał → sprawdź efekt.

Rodzic i sierota

Proces, którego rodzic się zakończył, staje się sierotą i zostaje przygarnięty przez innego rodzica: zwykle przez proces numer 1 (init/systemd), czasem przez proces, który zgłosił się jako opiekun swoich potomków (ang. subreaper).

z1234567@student:~$ sh -c 'sleep 333 & echo $!'
4650
z1234567@student:~$ ps -o pid,ppid,args -p 4650
    PID    PPID COMMAND
   4650       1 sleep 333
z1234567@student:~$ kill 4650

sh -c '...' uruchamia nową powłokę z podanymi poleceniami, a $! to PID ostatniego procesu uruchomionego w tle. Powłoka sh uruchomiła sleep i od razu się zakończyła, więc sleep dostał nowego rodzica.

Odwrotna sytuacja to zombie: proces już się zakończył, ale rodzic nie odebrał jeszcze informacji o tym, jak się zakończył. Zombie nie zużywa procesora i nie da się go zakończyć sygnałem, bo już nie działa; zniknie, gdy rodzic odbierze wynik albo sam się zakończy.

Przydatne skróty klawiaturowe

  • Up, Down -- historia poleceń,
  • Tab -- dopełnianie nazw poleceń i ścieżek,
  • Ctrl+R -- wyszukiwanie w historii poleceń,
  • Ctrl+L -- czyszczenie ekranu,
  • Ctrl+C -- przerwanie zadania z pierwszego planu,
  • Ctrl+Z -- wstrzymanie zadania z pierwszego planu,
  • Ctrl+D -- koniec danych na wejściu; w pustym wierszu powłoki kończy powłokę.

Więcej: Command Line Editing.