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(®s,®s,&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 unionregs
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(®s,®s,&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 DRDOS
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...