Naprej Nazaj Vsebina

5. Napredna vprašanja; radi jih vprašujejo ljudje, ki mislijo, da že poznajo vse odgovore

5.1 Kako berem znake s terminala, ne da bi bilo uporabniku treba pritisniti RETURN?

Subject: How do I read characters ... without requiring the user to hit RETURN?
Date: Thu Mar 18 17:16:55 EST 1993
Preverite način cbreak v BSD ali način ~ICANON v SysV.

Če se sami ne želite spoprijeti s terminalskimi parametri (z uporabo sistemskega klica ioctl(2)), lahko prepustite delo programu stty - a to je počasno in neučinkovito, in včasih boste morali spremeniti kodo, da bo delala pravilno:


#include <stdio.h>
main()
{
    int c;

    printf("Pritisnite katerikoli znak za nadaljevanje\n");
    /*
     * funkcija ioctl() bi bila tukaj boljša;
     * le leni programerji delajo takole:
     */
    system("/bin/stty cbreak");        /* ali "stty raw" */
    c = getchar();
    system("/bin/stty -cbreak");
    printf("Hvala, ker ste vnesli %c.\n", c);

    exit(0);
}

Nekaj ljudi mi je poslalo različne bolj pravilne rešitve tega problema. Žal mi je, da jih ne morem vključiti, saj zares presegajo namen tega seznama.

Preveriti boste želeli tudi dokumentacijo prenosljive knjižnice zaslonskih funkcij, imenovane ,,curses``. Če vas zanima V/I enega samega znaka, vas pogosto zanima tudi neka vrsta kontrole prikaza na zaslonu, in knjižnica curses priskrbi različne prenosljive rutine za obe funkciji.

5.2 Kako preverim, če je treba prebrati znak, ne da bi ga zares prebral?

Subject: How do I check to see if there are characters to be read ... ?
Date: Thu Mar 18 17:16:55 EST 1993

V nekaterih različicah Unixa je mogoče preveriti, ali je v danem datotečnem deskriptorju kaj neprebranih znakov. V BSD-ju lahko uporabite select(2). Uporabite lahko tudi FIONREAD ioctl, ki vrne število znakov, ki čakajo na prebranje, a to deluje le pri terminalih, cevovodih in vtičih. V System V Release 3 lahko uporabite poll(2), a to deluje le na tokovih. V Xenixu - in torej na Unixu SysV r3.2 in poznejših - sistemski klic rdchk() poroča o tem ali se bo klic read() na danem datotečnem deskriptorju blokiral.

Ni načina, da bi preverili, ali so znaki dostopni za branje s kazalca FILE. (Lahko gledate po notranjih podatkovnih strukturah stdio, da bi videli, če je vmesni pomnilnik vhoda neprazen, a to ne bo delovalo, saj ne veste, kaj se bo zgodilo naslednjič, ko boste hoteli napolniti vmesni pomnilnik.)

Včasih ljudje vprašujejo to vprašanje z namenom, da bi napisali

if (znaki dostopni iz fd)
        read(fd, buf, sizeof buf);
in dobili učinek neblokovnega branja z read. To ni najboljši način za izvedbo tega, saj je možno, da bodo znaki dostopni, ko preverite dostopnost, a ne bodo več dostopni, ko pokličete read. Namesto tega prižgite zastavico O_NDELAY (ki se pod BSD imenuje tudi FNDELAY) z izbiro F_SETFL funkcije fcntl(2). Starejši sistemi (Version 7, 4.1 BSD) nimajo O_NDELAY; na teh sistemih lahko najbližje neblokovnemu branju pridete z uporabo alarm(2), da branju poteče čas.

5.3 Kako ugotovim ime odprte datoteke?

Subject: How do I find the name of an open file?
Date: Thu Mar 18 17:16:55 EST 1993
V splošnem je to pretežko. Datotečni deskriptor je lahko obešen na cevovod ali pty, in v tem primeru nima imena. Lahko je obešen na datoteko, ki je bila odstranjena. Lahko ima več imen, zaradi pravih ali simboličnih povezav.

Če morate to res storiti, in prepričajte se, da ste o tem razmislili na dolgo in široko in se odločili, da nimate druge izbire, lahko uporabite find z izbiro -inum in morda še -xdev, ali uporabite ncheck, ali še enkrat oponašajte funkcionalnost enega od teh orodij v vašem programu. Zavedajte se, da preiskovanje 600 megabytnega datotečnega sistema za datoteko, ki je morda sploh ni, traja nekaj časa.

5.4 Kako lahko tekoči program ugotovi svojo lastno pot?

Subject: How can an executing program determine its own pathname?
Date: Thu Mar 18 17:16:55 EST 1993
Vaš program lahko pogleda argv[0]; če se začenja z ,,/``, je to verjetno ime absolutne poti do vašega programa, sicer lahko vaš program pogleda v vsak imenik, imenovan v okoljski spremenljivki PATH in poskuša najti prvi imenik, ki vsebuje izvedljivo datoteko, katere ime se ujema s programovim argv[0] (ki je po dogovoru ime programa, ki se izvaja). Če združite ta imenik in vrednost argv[0], imate verjetno pravo ime.

Ne morete pa biti prepričani, saj je povsem legalno, da en program izvede drugega z exec() s katerokoli vrednostjo argv[0], ki si jo zaželi. Da exec izvaja nove programe z imenom izvedljive datoteke v argv[0], je le dogovor.

Na primer, povsem hipotetični primer:

        
#include <stdio.h>
main()
{
    execl("/usr/games/rogue", "vi Disertacija", (char *)NULL);
}
Izvedeni program misli, da je njegovo ime (njegova vrednost argv[0]) ,,vi Disertacija``. (Tudi nekateri drugi programi lahko mislijo, da je ime programa, ki ga trenutno poganjate ,,vi Disertacija``, a seveda je to le hipotetični primer, zato tega ne poskušajte sami :-)

5.5 Kako uporabim popen() za odprtje procesa za branje in pisanje?

Subject: How do I use popen() to open a process for reading AND writing?
Date: Thu Mar 18 17:16:55 EST 1993

Problem, ko poskušate preusmeriti vhod in izhod poljubnemu suženjskemu procesu je, da lahko nastane mrtva zanka, če oba procesa hkrati čakata na še-ne-generiran vhod. Zanki se lahko izognemo tako, da obe strani upoštevata strog protokol brez mrtvih zank, toda, ker to zahteva sodelovanje med procesi, je neprimerno za knjižnično funkcijo, podobno popen().

Distribucija ,,expect`` vključuje knjižnico funkcij, ki jih lahko C-jevski programer kliče neposredno. Ena izmed funkcij dela isto stvar kot popen za hkratno branje in pisanje. Uporablja ptys namesto cevi, in nima problemov z zaciklanjem. Prenosljiva je na BSD in SV. Glejte vprašanje ,,Kako poženem passwd, ftp, telnet, tip in druge interaktivne programe v skriptu ukazne lupine v ozadju?`` za več podatkov o distribuciji expect.

5.6 Kako izvedem v C-ju sleep() za manj kot sekundo?

Subject: How do I sleep() in a C program for less than one second?
Date: Thu Mar 18 17:16:55 EST 1993

Najprej se zavedajte, da je vse, kar lahko določite minimalna količina zakasnitve; dejanska zakasnitev bo odvisna od upravniških zadev, kot je obremenitev sistema, in je lahko poljubno dolga, če nimate sreče.

Ne obstaja funkcija standardne knjižnice, na katero bi se lahko zanesli v vseh okolji za ,,dremanje`` (angl. napping, običajen izraz za kratke spance). Nekatera okolja priskrbijo funkcijo ,,usleep(n)``, ki zadrži izvajanje za n mikrosekund. Če vaše okolje ne podpira usleep(), je tukaj nekaj njenih implementacij za okolja BSD in System V.

Naslednja koda Douga Gwyna je prirejena z emulacijske podpore Systema V za 4BSD in izrablja sistemski klic select() na 4BSD-ju. Doug jo je prvotno imenoval ,,nap()``; vi jo boste verjetno želeli klicati ,,usleep()``:


/*
    usleep - podporna rutina za emulacijo sistemskih klicev 4.2BSD
    zadnja sprememba originalne verzije:  29. oktober 1984     D A Gwyn
*/

extern int        select();

int
usleep( usec )                          /* vrne 0, če je ok, sicer -1 */
    long                usec;           /* premor v mikrosekundah */
    {
    static struct                       /* `timeval' */
            {
            long        tv_sec;         /* sekunde */
            long        tv_usec;        /* mikrosekunde */
            }   delay;          /* premor _select() */

    delay.tv_sec = usec / 1000000L;
    delay.tv_usec = usec % 1000000L;

    return select( 0, (long *)0, (long *)0, (long *)0, &delay );
    }

Na Unixih System V bi lahko to storili takole:


/*
podsekundni premori za System V - ali karkoli, kar ima poll()
Don Libes, 4/1/1991

BSD-jeva analogija te funkcije je definirana v mikrosekundah, medtem,
kot je poll() definiran v milisekundah.  Zaradi združljivosti, ta
funkcija priskrbi natančnost "po dolgem teku" tako, da oklesti prave
zahteve na milisekundo natančno in akumulira mikrosekunde med
posameznimi klici z idejo, da jo verjetno kličete v tesni zanki, in se
bo po dolgem teku napaka izničila.

Če je ne kličete v tesni zanki, potem skoraj gotovo ne potrebujete
mikrosekundne natančnosti in v tem primeru vam ni mar za mikrosekunde.
Če vam bi bilo mar, tako ali tako ne bi uporabljali Unixa, saj lahko
naključno prebavljanje sistema (npr. razporejanje) zmelje časomerilno
kodo.

Vrne 0 ob uspešnem premoru, -1 ob neuspešnem.

*/

#include <poll.h>

int
usleep(usec)
unsigned int usec;                /* mikrosekunde */
{
    static subtotal = 0;        /* mikrosekunde */
    int msec;                   /* milisekunde */

    /* 'foo' je tukaj le zato, ker so imele nekatere verzije 5.3
     * hrošča, pri katerem se prvi argument poll() preverja za
     * obstoj pravilnega pomnilniškega naslova, čeprav je drugi
     * argument enak 0.
     */
    struct pollfd foo;

    subtotal += usec;
    /* če je premor < 1 ms, ne naredi ničesar, a si zapomni */
    if (subtotal < 1000) return(0);
    msec = subtotal/1000;
    subtotal = subtotal%1000;
    return poll(&foo,(unsigned long)0,msec);
}

Še ena možnost za dremanje na System V in verjetno tudi drugih ne-BSD Unixih je paket s5nap Jona Zeeffa, objavljen v comp.sources.misc, volume 4. Ne potrebuje namestitve gonilnika naprave, a deluje brez napak, ko se namesti, (Njegova ločljivost je omejena z vrednostjo HZ v jedru, saj uporablja rutino delay() jedra.)

Mnogo novejših Unixov ima funkcijo nanosleep.

5.7 Kako pripravim skripte ,setuid` ukazne lupine do delovanja?

Subject: How can I get setuid shell scripts to work?
Date: Thu Mar 18 17:16:55 EST 1993
[ Ta odgovor je dolg, a to je zapleteno in pogosto zastavljeno vprašanje. Hvala Maartenu Litmaathu za ta odgovor in za spodaj omenjeni program ,,indir``. ]
  1. Najprej predpostavimo, da ste na različici Unixa (npr. 4.3BSD ali SunOS), ki pozna tako imenovane ,,izvedljive skripte ukaznih lupin``. Takšen skript se mora začeti s podobno vrstico:
    #!/bin/sh
    
    Skript se imenuje ,,izvedljivi``, ker se tako kot resnična (binarna) izvedljiva datoteka začne s tako imenovano ,,magično številko``, ki določa tip izvedljive datoteke. Glejte tudi razdelek ,,Zakaj se nekateri skripti začnejo z ,,#!...``?``. V našem primeru je ta številka enaka ,,#!`` in OS vzame ostanek prve vrstice kot interpreter za skript, ki mu morda sledi 1 uvodna izbira kot je:
    #!/bin/sed -f
    
    Denimo, da se ta skript imenuje ,foo` in se nahaja v imeniku /bin. Če potem napišete:
    foo arg1 arg2 arg3
    
    bo OS preuredil zadeve tako, kot bi napisali:
    /bin/sed -f /bin/foo arg1 arg2 arg3
    
    Vendar je tukaj neka razlika: če je prižgan bit setuid za ,foo`, bo spoštovan v prvi obliki ukaza; če zares vpišete drugo obliko, bo OS spoštoval bite z dovoljenji programa /bin/sed, ki seveda ni setuid.

  2. Prav, toda kaj, če se moj skript ukazne lupine ne začne s takšno vrstico ,#!`, ali, če moj OS o tem nič ne ve?

    No, če ga ukazna lupina (ali kdorkoli drug) poskuša izvesti, bo OS vrnil indikacijo napake, saj se datoteka ne začne z veljavno magično številko. Ukazna lupina bo po sprejetju te indikacije predpostavila, da gre za skript ukazne lupine in mu dala še eno priložnost:

    /bin/sh lupinski_skript argumenti
    
    A zdaj smo že videli, da se v tem primeru bit setuid datoteke ,lupinski_skript` ne bo spoštoval!

  3. Prav, kaj pa varnostna tveganja pri lupinskih skriptih setuid?

    Dobro, denimo, da se skript imenuje ,/etc/skript_setuid` in se začenja z vrstico:

    #!/bin/sh
    
    Zdaj pa poglejmo kaj se zgodi, če izvedemo naslednje ukaze:
    $ cd /tmp
    $ ln /etc/skript_setuid -i
    $ PATH=.
    $ -i
    
    Vemo, da bo zadnji ukaz preurejen v:
    /bin/sh -i
    
    Toda ta ukaz nam bo dal interaktivno ukazno lupino, ki bo z bitom setuid tekla, kot da bi jo pognal lastnik skripta!

    Na srečo lahko to varnostno luknjo zlahka zapremo, če napišemo v prvo vrstico:

    #!/bin/sh -
    
    Znak ,-` označuje konec seznama izbir: naslednji argument ,-i` bo vzet kot ime datoteke, iz katere naj se berejo ukazi, kot bi tudi moral biti!

  4. Vendar pa obstajajo veliko resnejši problemi:
    $ cd /tmp
    $ ln /etc/skript_setuid temp
    $ nice -20 temp &
    $ mv moj_skript temp
    
    Tretji ukaz bo preurejen v:
    nice -20 /bin/sh - temp
    
    Ker se ta ukaz izvaja počasi, bo morda lahko četrti ukaz zamenjal originalno datoteko ,temp` s trojanskim konjem ,moj_skript` preden ukazna lupina sploh odpre ,temp`! Obstajajo 4 načini za krpanje te varnostne luknje:

    1. naj OS zažene skript setuid na drugačen, varen način - System V R4 in 4.4BSD uporabljata gonilnik /dev/fd, s katerim podata interpreterju datotečni deskriptor skripta;

    2. naj bo skript interpretiran posredno, skozi vmesnik, ki se prepriča, da je vse v redu, preden požene pravi interpreter - če uporabljate program ,indir` iz arhiva comp.sources.unix bodo skripti setuid izgledali takole:
      #!/bin/indir -u
      #?/bin/sh /etc/skript_setuid
      

    3. napravite ,,binarni ovoj``: pravo izvedljivo datoteko, ki je setuid in katere edina naloga je pognati interpreter z imenom skripta kot argumentom;

    4. napravite splošen ,,strežnik za skripte setuid``, ki poskuša najti zahtevano ,opravilo` v bazi podatkov veljavnih skriptov in ob uspehu požene pravi interpreter s pravimi argumenti.

  5. Zdaj, ko smo se prepričali, da se interpretira pravilna datoteka, ali so prisotna še kakšna tveganja?

    Seveda! Za skripte ukazne lupine ne smete pozabiti eksplicitno nastavitev spremenljivke PATH na varno pot. Lahko ugotovite zakaj? Tukaj je še spremenljivka IFS, ki lahko povzroča težave, če ni pravilno nastavljena. Tudi druge okoljske spremenljivke lahko ogrozijo varnost sistema, npr. SHELL ... Nadalje se morate prepričati, da ukazi v skriptih ne dovoljujejo interaktivnih ubežnih zaporedij ukazne lupine! Potem je tukaj umask, ki je lahko nastavljen na kaj čudnega ...

    Et cetera. Zavedati se morate, da skript setuid ,,podeduje`` vse hrošče in varnostna tveganja ukazov, ki jih kliče!

Po vsem tem dobimo vtis, da so lupinski skripti setuid precej tvegan posel! Morda bo za vas bolje programirati v C-ju!

5.8 Kako lahko ugotovim, kateri uporabnik ali proces ima odprto datoteko ali uporablja določen datotečni sistem (da ga lahko odklopim)?

  
Subject: How can I find out which user or process has a file open ... ?
Date: Thu Mar 18 17:16:55 EST 1993

Uporabite fuser (system V), fstat (BSD), ofiles (v javni lasti) ali pff (v javni lasti). Ti programi vam bodo povedali različne stvari o procesih, ki uporabljajo določene datoteke.

V arhivih comp.sources.unix, volume 18, najdete prenos fstat z 4.3 BSD na Dynix, SunOS in Ultrix.

pff je del paketa kstuff in deluje na kar nekaj sistemih. Navodila za nabavo kstuff najdete v vprašanju ,,Kako lahko v skriptu ali programu ugotovim ID procesa programa z določenim imenom?``.

Obveščen sem bil, da obstaja tudi program, imenovan lsof, a ne vem, kje se ga dobi.

Michael Fink <Michael.Fink@uibk.ac.at> dodaja:

Če ne morete odmestiti datotečnega sistema (z umount), za katerega zgornja orodja ne poročajo o odprtih datotekah, se prepričajte, da datotečni sistem, ki ga poskušate odmeščati ne vsebuje aktivnih točk nameščanja (uporabite df(1)).

5.9 Kako izsledim ljudi, ki me tipajo (s finger)?

Subject: How do I keep track of people who are fingering me?
From: Jonathan I. Kamens
From: malenovi@plains.NoDak.edu (Nikola Malenović)
Date: Thu, 29 Sep 1994 07:28:37 -0400

V splošnem ne morete ugotoviti userid nekoga, ki vas tipa z oddaljenega stroja. Morda lahko ugotovite stroj, s katerega prihajajo oddaljene zahteve. Ena od možnosti, če jo vaš sistem podpira, in, če je tipalnemu strežniku (angl. finger daemon) prav, jo, da naredite vašo datoteko .plan za ,,imenovano cev`` (angl. named pipe) namesto običajno datoteko. (Za to uporabite ,mknod`.)

Potem lahko poženete program, ki bo odprl vašo datoteko .plan za pisanje; odpiranje bo blokirano dokler nek drugi proces (namreč fingerd) ne odpre .plan za branje. Zdaj lahko skozi to pipo pošljete, kar pač želite, kar vam omogoča, da prikažete različno informacijo .plan vsakič, ko vas nekdo potipa. Eden od programov za to je paket ,,planner``, objavljen v volume 41 arhivov comp.sources.misc.

Seveda to sploh ne bo delovalo, če vaš sistem ne podpira imenovanih cevi ali, če vaš lokalni fingerd vztraja, da imate navadne datoteke .plan.

Vaš program lahko tudi izkoristi priložnost in pogleda v izhod programa ,,netstat``, ter s tem ugotovi, odkod prihaja zahteva za finger, toda to vam ne bo izdalo identitete oddaljenega uporabnika.

Če želite dobiti oddaljeni userid, mora oddaljena stran poganjati identifikacijski strežnik, kot je RFC 931. Trenutno obstajajo tri implementacije RFC 931 za popularne stroje BSD, in nekaj aplikacij (kot ftpd z wuarchive), ki podpirajo ta strežnik. Za več informacij se priključite elektronskemu spisku rfc931-users z običajno zahtevo ,,subscribe`` na rfc931-users-request@kramden.acf.nyu.edu.

Glede tega odgovora obstajajo tri opozorila. Prvo je, da mnogi sistemi NFS napačno prepoznajo imenovano cev. To pomeni, da bo poskus branja cevi na drugem stroju lahko blokiral, dokler ne poteče predviden čas, ali videl cev kot datoteko dolžine 0, in je ne bo nikoli izpisal.

Drugi problem je, da na veliko sistemih strežnik fingerd preveri, ali datoteka .plan vsebuje podatke (in je bralna), preden jo poskuša brati. V tem primeru bodo oddaljeni klici finger popolnoma zgrešili vašo datoteko .plan.

Tretji problem je, da imajo sistemi, ki podpirajo imenovane cevi, na voljo v danem času le fiksno (končno) število le-teh - preverite nastavitveno datoteko jedra in izbiro FIFOCNT. Če število cevi na sistemu prekorači vrednost FIFOCNT, sistem prepreči izdelavo novih cevi, dokler nekdo ne sprosti virov. Razlog za to je, da je vmesni pomnilnik odmerjen v pomnilniku, ki se ne preklaplja (angl. buffers are allocated in a non-paged memory).

5.10 Je mogoče ponovno priključiti proces na terminal, ko je bil ta odklopljen, tj. po zagonu programa v ozadju in odjavi?

Subject: Is it possible to reconnect a process to a terminal ... ?
Date: Thu Mar 18 17:16:55 EST 1993

Večina različic Unixa ne podpira ,,odklopa`` in ,,priklopa`` procesov (angl. detaching/attaching processes), kot ju podpirajo operacijski sistemi kot sta VMS in Multics. Vendar obstajajo prosto dostopni paketi, s katerimi lahko poženete procese na takšen način, da se lahko pozneje spet pritrdijo na terminal.

Nobeden od teh paketov ne deluje retroaktivno, se pravi, da morate pognati proces pod pripomočkom screen ali pty, če ga želite odklopiti in priklopiti.

5.11 Je mogoče ,,vohuniti`` na terminalu, gledajoč izhod, ki se prikazuje na drugem terminalu?

Subject: Is it possible to "spy" on a terminal ... ?
Date: Wed, 28 Dec 1994 18:35:00 -0500

Za to obstaja več različnih poti, čeprav nobena od njih ni popolna:


Naprej Nazaj Vsebina