Dove mi trovo? 

Perdersi in un groviglio di drive e directory non è una bella esperienza. Per questo è opportuno che un programma possa sapere, se necessario, qual è l'attuale directory di lavoro. La libreria del C Borland mette a disposizione due funzioni che forniscono informazioni al proposito: getcurdir(), che permette di conoscere la directory corrente per un dato drive (compreso quello di default), e getcwd(), che consente di conoscere la directory corrente del drive di default. Come si può facilmente constatare, esse sono piuttosto simili e inoltre, dato l'algoritmo utilizzato[1], vengono entrambe influenzate dai comandi SUBST e JOIN del DOS. Ad esempio, dopo avere assegnato al path C:\LAVORO\GRAFICA l'identificativo di drive K: mediante SUBST, possiamo referenziare la directory GRAFICA sia come sottodirectory di C:\LAVORO, sia come root del drive fittizio K: e posizionarci in essa con il comando CHDIR su C: oppure cambiando in K: il drive di default. In questo caso getcurdir() e getcwd() indicano che la directory corrente è la root di K:. Se il programma necessita conoscere la reale directory di lavoro, ignorando eventuali combinazioni di SUBST e JOIN, può ricorrere al   servizio 60h dell'int21h. Vediamone un esempio di utilizzo: 
/********************

    BARNINGA_Z! - 1991

    GETRDIR.C - getrealdir()

    int cdecl getrealdir(char *userpath);
    char *userpath; puntatore ad array di caratteri allocato a cura
                    del programmatore e di grandezza sufficiente a
                    contenere il pathname completo della directory
                    corrente
    Restituisce:    0 se tutto o.k.
                    0 se si e' verificato un errore

    COMPILABILE CON TURBO C++ 1.0

        tcc -O -d -rd -c -mx getrdir.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#include <dos.h>
#include <string.h>

#define  MAXPATH  80

int cdecl getrealdir(char *userpath)
{
    int retcode;
    char realpath[MAXPATH];
    struct SREGS sregs;
    union REGS regs;

    realpath[0] = '.';
    realpath[1] = (char)0;
    segread(&sregs);
    regs.h.ah = 0x60;
    sregs.ds = sregs.es = sregs.ss;
    regs.x.si = regs.x.di = (unsigned)realpath;
    retcode = intdosx(&regs,&regs,&sregs);
    if(!regs.x.cflag) {
        retcode = 0;
        (void)strcpy(userpath,realpath);
    }
    return(retcode);
}
L'array realpath funge da buffer sia di input che di output; occorre dunque caricare i registri DS e ES con il suo indirizzo di segmento, e i registri SI e DI con il suo offset. In quanto variabile automatica, realpath è allocato nello stack: il suo indirizzo di segmento è dunque rappresentato dal registro SS; in quanto array, il suo offset è rappresentato dal nome stesso (che è un puntatore all'array). Il valore di SS è ricavato mediante la funzione di libreria segread(), che copia i valori dei registri di segmento nei campi della struttura sregs (di tipo SREGS)[2]. SI e DI sono caricati, attraverso la union regs di tipo REGS, con l'offset dell'array; il cast ad unsigned è necessario in quanto realpath è puntatore a char, cioè di tipo char *. Se la funzione di libreria intdosx() non ha riscontrato la restituzione di un errore da parte dell'int 21h, da essa invocato, il campo .x.cflag della union REGS è nullo[3]: la stringa preparata dal  servizio 60h è copiata all'indirizzo passato come parametro a getrealdir(), che restituisce 0. Se .x.cflag non è nullo allora getrealdir() restituisce il codice di errore riportato dall'int 21h. Si noti che il puntatore userpath, parametro della funzione, deve essere allocato a cura della routine che invoca la getrealdir() e deve essere in grado di contenere l'intero pathname (la massima lunghezza di un pathname, in DOS, è 80 caratteri compreso il terminatore nullo[4]). 

Poche modifiche sono sufficienti per trasformare getrealdir() in una funzione in grado di risolvere in pathname completo qualunque nome di file o directory passatole come parametro. 
/********************

    BARNINGA_Z! - 1991

    RSLVPATH.C - resolvepath()

    int cdecl resolvepath(char *userpath);
    char *userpath; puntatore alla stringa contenente il pathname
                    parziale da risolvere in pathname completo. Lo
                    spazio allocato deve essere sufficiente a
                    contenere quest'ultimo.
    Restituisce:    0 se tutto o.k.
                   !0 se si e' verificato un errore:
                        2 = userpath contiene caratteri non leciti in
                            un pathname
                        3 = drive specificato in userpath non valido

    COMPILABILE CON TURBO C++ 1.0

        tcc -O -d -rd -c -mx RSLVPATH.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#include <dos.h>
#include <string.h>

#define  MAXPATH  80

int cdecl resolvepath(char *userpath)
{
    int retcode;
    char realpath[MAXPATH];
    struct SREGS sregs;
    union REGS regs;

    segread(&sregs);
    regs.h.ah = 0x60;
    sregs.es = sregs.ss;
    regs.x.di = (unsigned)realpath;
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
    regs.x.si = (unsigned)userpath;
#else
    sregs.ds = FP_SEG(userpath);
    regs.x.si = FP_OFF(userpath);
#endif
    retcode = intdosx(&regs,&regs,&sregs);
    if(!regs.x.cflag) {
        retcode = 0;
        (void)strcpy(userpath,realpath);
    }
    return(retcode);
}
La differenza sostanziale tra le due funzioni è che resolvepath() utilizza userpath come buffer di input per il  servizio 60h. Esso punta ad un array allocato nel segmento dati; pertanto la sua gestione varia a seconda del modello di memoria prescelto per la compilazione. Se lo heap è limitato a 64Kb (modelli tiny, small e medium), userpath esprime un offset rispetto a DS: segread() carica dunque sregs.ds con il valore effettivamente necessario all'int 21h. Nei modelli compact, large e huge userpath è un puntatore far: si rende pertanto necessario l'uso delle macro FP_SEG() e FP_OFF(), definite in DOS.H, che ricavano, rispettivamente, segmento ed offset da puntatori di tipo far

Se userpath punta alla stringa ".", resolvepath() può essere utilizzata in luogo di getrealdir()

Il servizio 60h dell'int 21h non è ufficialmente documentato[5]: ad esempio, non sembra essere implementato nel DR­DOS 5.0, rilasciato alcuni mesi dopo lo sviluppo delle due funzioni descritte. Come accade spesso quando si sfruttano caratteristiche non ufficiali del sistema operativo, è stato indispensabile correre ai ripari, scrivendo una funzione in grado di emulare il  servizio 60h
/********************

    BARNINGA_Z! - 1991

    PATHNAME.C - pathname()

pathname()         Risolve un pathname in pathname completo

SINTASSI     int cdecl pathname(char *path,char *src,char *badchrs);

PARAMETRI    path          puntatore ad  un'area di  memoria di  almeno
                           MAXPATH (80)  caratteri (MAXPATH  è definita
                           in  fileutil.h)   in  cui   è  costrutio  il
                           pathname completo. La funzione non controlla
                           la    lunghezza     del    buffer;    assume
                           semplicemente che  esso  sia  sufficiente  a
                           contenere tutto il pathname.

             src           puntatore  al   pathname  sorgente.   Se  la
                           stringa è  vuota, in  pcompl viene  posto il
                           pathname della  directory corrente del drive
                           di default, seguita da una backslash.

             badchrs       puntatore  ad   una  stringa   contenente  i
                           caratteri  che   devono  essere  considerati
                           illeciti in  un pathname  oltre ai caratteri
                           da 0x00  a 0x1F. In fileutil.h è definita la
                           stringa BADCHARS  ";,:|><". I  due punti (:)
                           sono comunque  ammessi  nell'indicatore  del
                           drive.

SCOPO        Trasforma un pathname (src) in pathname completo di drive,
             percorso e nome di file, considerando gli attuali defaults
             di sistema.

RESTITUISCE  0         Operazione completata con successo.

             EOF       In caso  di errore. Le variabili globali errno e
                       doserrno contengono  il codice di errore ENOPATH
                       (path non trovato); errno e doserrno  contengono
                       il codice dell'errore.

NOTE         Il path  sorgente può  contenere '*'  e '?'.  Gli '*' sono
             trasformati nell'opportuno  numero di  '?'. Le slashes (/)
             eventualmente presenti sono convertite in backslashes (\).
             Il path  sorgente può  anche  non  esistere,  e  può  fare
             riferimento ad un drive inesistente (nel qual caso il path
             fornito è  assunto quale entry della root). La presenza di
             un carattere  illecito nel  pathname, o di due o più punti
             (.) anche  non consecutivi,  o due  o  più  backslashes  o
             slashes consecutive  determinano errore. Tutti i caratteri
             che seguono  un '*'  e precedono il '.' o la fine del nome
             (se il  '.' è già stato incontrato) sono ignorati. Un nome
             di directory  (o di file) che superi gli 8 caratteri viene
             troncato ad  8. L'estensione  che superi  i 3  caratteri è
             troncata a  3. La funzione interpreta '.' e '..'; tuttavia
             esse devono  (ovviamente)  trovarsi  all'inizio  del  path
             sorgente  o   immediatamente  dopo   i   due   punti   (:)
             dell'indicativo del  drive, onde evitare una condizione di
             errore. Il  pathname costruito  in pcompl  non termina con
             backslash, a meno che si tratti di root o il path in input
             (src) sia una stringa vuota.

    COMPILABILE CON TURBO C++ 1.0

        tcc -O -d -rd -c -mx PATHNAME.C

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/

#pragma  warn -pia

#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <dir.h>

#define  NAMELEN        (MAXFILE-1)     /* lunghezza nomi file */
#define  EXTLEN         (MAXEXT-2)       /* lunghezza estensione */
#define  CURRENT        "."
#define  CURDIR         ".\\"
#define  PARENT         ".."
#define  PARDIR         "..\\"
#define  PARDIR2        "\\.."
#define  ROOTSYM        ":\\"
#define  SLASH          '/'
#define  BSLASH         '\\'
#define  COLON          ':'
#define  POINT          '.'
#define  ASTERISK       '*'
#define  QMARK          '?'
#define  D_FIRST        'A'

#define  islwctl(c)  ((c >= 0 && c < 0x20) ? 1 : 0)       /* macro per chr ctl */
#define  isslash(c)  ((c == SLASH || c == BSLASH) ? 1 : 0)        /* separatore? */

int pascal __IOerror(int dosErr);       /* funzione C per condizione d'errore */


int pathname(char *pcompl,char *src,char *badchrs)
{
    register sx, px, cnt;
    char *ptr, temp[MAXPATH];
    struct {
        unsigned b:1;  /* il car. precedente e' BSLASH */
        unsigned c:1;  /* il path in input parte da .\ */
        unsigned p:1;  /* il car. precedente e' POINT */
        unsigned w:1;  /* gia' incontrato QMARK */
    } flag;

    for(cnt = 0; src[cnt]; cnt++)
        temp[cnt] = (src[cnt] == SLASH) ? BSLASH : src[cnt];   /* / --> \ */
    temp[cnt] = NULL;
    if(temp[--cnt] == BSLASH)      /* il path in input non puo' */
        if(cnt > 0 && strstr(temp,ROOTSYM) != temp+cnt-1)      /* finire in \ a */
            return(__IOerror(ENOPATH));    /* meno che sia root */
    if(cnt > 0 && temp[1] == COLON) {
        if(!isalpha(*temp))    /* errore se 1^ car non alfabet. */
            return(__IOerror(ENOPATH));
        *pcompl = toupper(*temp);
        sx = 2;
    }
    else {
        *pcompl = getdisk()+D_FIRST;   /* drive di default */
        sx = 0;
    }
    pcompl[1] = COLON;
    pcompl[2] = BSLASH;
    px = MAXDRIVE; /* costruito d:\ */
    flag.c = 0;
    if(temp[sx] == BSLASH) {      /* se in temp c'e' BSLASH iniziale o dopo d: */
        ++sx;  /* il percorso e' completo; BSLASH gia' in pcompl */
        flag.b = 1;
    }
    else {        /* altrimenti bisogna cercare . e .. */
        flag.b = 0;
        cnt = 0;
        if(!strcmp(temp+sx,CURRENT))   /* temp = "." */
            sx += strlen(CURRENT);
        else {
            if(!strcmp(temp+sx,PARENT)) { /* temp = ".." */
                ++cnt;
                sx += strlen(PARENT);
            }
            else {
                ptr = strstr(temp+sx,CURDIR);  /* c'e' ".\" */
                if(ptr == temp+sx) {  /* nella posiz. attuale */
                    sx += strlen(CURDIR)-1;
                    flag.c = 1;
                }
                else {
                    ptr = strstr(temp+sx,PARDIR);  /* c'e' "..\" */
                    if(ptr == temp+sx) {  /* nella posiz. attuale */
                        sx += strlen(PARDIR)-1;
                        cnt = 1;
                    }
                }
                while(ptr = strstr(temp+sx,PARDIR2)) {        /* cerca "\.." */
                    if(ptr == temp+sx) {  /* seguiti da \ o */
                        sx += strlen(PARDIR);  /* a fine stringa */
                        if(!temp[sx] || (temp[sx] == BSLASH)) {
                            ++cnt;
                            continue;
                        }
                    }
                    return(__IOerror(ENOPATH));    /* "\.." in posiz. errata */
                }
            }
        }
        if(!getcurdir((*pcompl)-D_FIRST+1,pcompl+px))  /* dir di default */
            if((px = strlen(pcompl)) > MAXDRIVE) {
                pcompl[px++] = BSLASH;
                flag.b = 1;
            }
        if(cnt) {
            pcompl[px] = NULL;     /* esclude dal percorso costruito */
            for(++cnt; cnt; cnt--) /* le ultime dirs annullate */
                if(!(ptr = strrchr(pcompl,BSLASH)))    /* dai "\.." */
                    return(__IOerror(ENOPATH));    /* errore se "\.." piu' */
                else   /* numerosi delle dirs nel */
                    *ptr = NULL;   /* path costruito */
            px = (unsigned)(ptr-pcompl);
            flag.b = 0;
        }
    }
    for(flag.w = 0, flag.p = 0, cnt = NAMELEN; temp[sx]; ) {      /* copia */
        switch(pcompl[px] = toupper(temp[sx])) {      /* il resto di temp */
            case BSLASH:
                if(flag.c) {  /* temp = ".\xxxxxx" */
                    flag.c = 0;
                    if(flag.b)     /* temp = ".\..\xxxxxx" */
                        --px;
                }
                else
                    if(flag.b || flag.p || flag.w)
                        return(__IOerror(ENOPATH));
                flag.b = 1;
                cnt = NAMELEN;
                break;
            case POINT:
                if(flag.b || flag.p)
                    return(__IOerror(ENOPATH));
                flag.p = 1;
                cnt = EXTLEN;
                break;
            default:
                if(strchr(badchrs,pcompl[px]) || islwctl(pcompl[px]))
                    return(__IOerror(ENOPATH));
                flag.b = 0;
                if(!cnt) {
                    while(temp[sx] && (temp[sx] != POINT) &&
                                                     !(temp[sx] == BSLASH))
                        ++sx;
                    continue;
                }
                if(pcompl[px] == QMARK)
                    flag.w = 1;
                else
                    if(pcompl[px] == ASTERISK) {  /* risolve asterischi */
                        for(; cnt; cnt--)
                            pcompl[px++] = QMARK;
                        while(temp[sx] && (temp[sx] != POINT)) {
                            if(temp[sx] == BSLASH)
                                return(__IOerror(ENOPATH));
                            ++sx;
                        }
                continue;
                }
                --cnt;
        }
        ++sx;
        ++px;
    }
    pcompl[px] = NULL;
    cnt = strlen(pcompl)-1;
    if((pcompl[cnt] == BSLASH) && strlen(pcompl) != MAXDRIVE && *temp)
        --px;  /* elimina \ finale a meno che \ sola o temp vuota */
    else
        if(strlen(pcompl) == MAXDRIVE-1)       /* aggiunge \ se path e' */
            pcompl[px++] = BSLASH; /* d:\ e la \ finale e' stata tolta */
    return(pcompl[px] = NULL);     /* da eliminzaione dirs per "\.." */
}
La pathname() non è in grado di riconoscere i pathname originali sottostanti ridefinizioni effettuate da JOIN e SUBST

Si noti l'uso della funzione (non documentata) di libreria __IOerror(), mediante la quale sono valorizzate le variabili globali errno e doserrno, al fine di rendere la gestione degli errori coerente con lo standard di libreria.

OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...