Sistemas de ficheros en Windows NT.
Los sistemas de ficheros montados en un ordenador que corra
Windows NT están organizados en volúmenes. Para hacer referencia
a un volumen, se usan las letras de unidad. Esta situación
cambia en Windows 2000.
Podemos saber cuántas unidades hay montadas en el sistema con
GetLogicalDrives
o con
GetLogicalDriveStrings
. La primera nos devuelve
la información sobre un mapa de bits donde la unidad A viene
representada por el bit menos significativo. La segunda nos
da los nombres de las unidades en un búfer separados por el
carácter nulo. Debido a la gran cantidad de llamadas que existen,
os comentaré sólo las dificultades de manejo de cada una,
debiéndoos remitir al resumen
para encontrar la información completa.
Una vez sabemos que una unidad existe, podemos obtener su
tipo con GetDriveType
. Hay que pasarle la ruta
del directorio raíz como parámetro. Por ejemplo,
GetDriveType("a:\\");
. El hecho de doblar el
carácter \
viene porque estamos programando en
C y el C exige doblarlo para que no actúe como carácter de
escape en las cadenas. Tened cuidado porque incluso en la
ayuda del compilador ¡a veces se les olvida!
Sabemos ya su tipo. ¿Qué más datos podemos obtener? Por ejemplo
los que nos brinda:
BOOL GetVolumeInformation( LPCTSTR lpRootPathName, LPTSTR lpVolumeNameBuffer,
DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber,
LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags,
LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize);
La mayor parte de parámetros de la función son parámetros de
retorno, donde la función nos devuelve información y nosotros
sólo tenemos que proporcionarle la variable donde almacenarla.
En el caso de que sean arrays o cadenas de caracteres le pasamos
también la longitud para que la función no invada zonas de memoria
que no le correspondan.
¿Pero qué os voy a contar a estas alturas? Esta función, en concreto,
devuelve el nombre del volumen, su número de serie, el tamaño
máximo que puede tener en caracteres el nombre de un fichero,
información adicional del volumen y de qué tipo es el sistema
de ficheros: FAT, NTFS, etc. Los valores almacenados en
lpFileSystemFlags
vienen en el
resumen.
Tenemos también una función para cambiar el nombre de un volumen,
a saber, SetVolumeLabel
y otra que nos dará el espacio
libre que queda en la unidad: GetDiskFreeSpace
. Esta
información nos la dará en clústeres (sectores lógicos).
Directorios y manejo de ficheros.
En el tratamiento de los directorios, Windows NT no presenta ninguna
novedad. Disponemos de las funciones de la API:
GetCurrentDirectory
,
SetCurrentDirectory
,
CreateDirectory
y
RemoveDirectory
, de significado evidente. Para borrar
un directorio, tiene que estar vacío.
Lo que es más novedoso es que se disponga de llamadas a la
API para realizar operaciones sobre ficheros. Fáciles de manejar,
tenemos
CopyFile
,
DeleteFile
y
MoveFile
.
Creación/apertura de ficheros.
El equivalente en Windows NT a la llamada al sistema
open
de UNIX es:
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
Devuelve un HANDLE, pues el fichero abierto o creado es, a
todos los efectos, un objeto de Windows NT. Existen muchas
posibilidades para la apertura del fichero. Las que nos interesan
vienen en el resumen.
Se puede especificar el acceso deseado (lectura sólo o lectura
y escritura). En dwShareMode
decimos si queremos
que mientras tengamos abierto el fichero puedan otros procesos
acceder o modificarlo o ninguna de las dos cosas.
dwCreationDisposition
sirve para especificar
qué queremos que ocurra si el fichero ya existe o, por el contrario,
si no existe.
En cuanto a dwFlagsAndAttributes, ahí se meterá
información variada acerca del nuevo fichero. Por ejemplo,
los atributos que queremos que posea. Si especificamos
FILE_FLAG_WRITE_THROUGH
indicamos que queremos
que las modificaciones hechas al fichero se reflejen automáticamente
en el disco magnético. Si activamos FILE_FLAG_OVERLAPPED
se indica que el fichero se manejará con
entrada/salida solapada.
Si se activa FILE_FLAG_DELETE_ON_CLOSE
significa que
queremos que el fichero se borre automáticamente cuando se
cierre (por ejemplo, para un fichero temporal).
También se puede indicar uno de los flags
FILE_FLAG_RANDOM_ACCESS
o
FILE_FLAG_SEQUENTIAL_SCAN
si sabemos de antemano
el modo en que vamos a acceder al fichero, al azar o
secuencialmente. Si especificamos uno de estos flags, no
significa que no podamos acceder al fichero de otra manera,
sino que el sistema operativo organizará la entrada/salida
para que el tiempo de acceso sea óptimo para uno u otro modo.
Lectura, escritura y posicionado en un fichero.
La lectura y escritura en un fichero es equivalente a lo ya
visto en read
y write
para UNIX.
Los prototipos de las funciones de Windows NT que realizan
esta función son:
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
Tan sólo debéis poner a NULL el último parámetro, a no ser
que queráis realizar entrada/salida solapada.
También la función de la API encargada de colocar el puntero
de fichero parece una copia exacta de la que vimos para UNIX,
lseek. También podrá usarse para
conocer la longitud del fichero, aunque se disponga de una
función para esto también: GetFileSize
. Observemos
el prototipo de la función de colocación del puntero de fichero:
DWORD SetFilePointer( HANDLE hFile, LONG lDistanceToMove, LPLONG lpDistanceToMoveHigh,
DWORD dwMoveMethod);
lDistanceToMove
tiene su sentido así como el valor
devuelto por la función, que será el desplazamiento del puntero
de fichero después de realizado el movimiento.
dwMoveMethod
indicará mediante macros cuál será
el punto de origen respecto del cual se efectuará el movimiento.
Pero, ¿qué significa lpDistanceToMoveHigh
? Este valor
aparece porque con lDistanceToMove
, de tipo LONG
(32 bits) "sólo" podemos expresar hasta un tamaño máximo de
4294967296 bytes, o sea, algo más de cuatro Gb. Si el tamaño
del fichero fuera mayor, no podríamos colocar el puntero en
cualquier lugar de un solo movimiento. La solución es que se
construye un entero de 64 bits formado por
lpDistanceToMoveHigh
como los 32 bits superiores
y lDistanceToMove
como los 32 bits inferiores.
El apaño es malo, pero ahí está y casi nunca superaremos
los cuatro Gb por lo que el valor alto será cero. La razón
por la que es un puntero es porque lpDistanceToMoveHigh
también es un valor de retorno de la función. La parte baja
no lo es porque ya la devuelve la propia función. Este esquema
lo encontraremos en muchas otras funciones que manejen ficheros.
Para truncar la longitud de un fichero, se puede usar la función
SetEndOfFile
. Hay primero que poner el puntero
de fichero en la posición donde deseemos cortar el fichero.
Obtener información de un fichero.
De un fichero podemos conocer su tamaño con:
DWORD GetFileSize( HANDLE hFile, LPDWORD lpFileSizeHigh);
La razón de tan extraños parámetros la acabamos de ver unas
líneas más arriba.
También podemos conocer o cambiar alguno de los tiempos propios
del fichero:
BOOL GetFileTime( HANDLE hFile, LPFILETIME lpCreationTime,
LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime);
Estos tiempos son el tiempo en que se creó el fichero, el
tiempo en que´por última vez se accedió a el y el tiempo más tardío
en el que ha sido modificado. Si el sistema de ficheros no
proporciona alguno de estos tiempos (p. ej. FAT), nos devolverá
cero. Sin embargo, el problema es que la estructura donde nos
da los datos no es "de consumo humano". Una vez obtenida la información
debemos usar otras funciones para interpretarla. Si queremos
comparar dos de estos tiempos, usaremos CopareFileTime
y si lo que deseamos es que nos lo traduzcan, usaremos:
BOOL FileTimeToSystemTime( CONST FILETIME *lpFileTime, LPSYSTEMTIME lpSystemTime);
La estructura SYSTEMTIME
ya sí tiene interpretación
directa. Para cambiar los tiempos de un fichero (haciendo
trampa) usaremos SetFileTime
. Los más avispados
os habréis dado ya cuenta de que hace falta la función opuesta de
traducción. Pues sí, se llama SystemTimeToFileTime
.
Los muy exigentes disponen de la función que más información da
de un fichero (la "fstat
" de Windows NT). Su nombre es
GetFileInformationByHandle
y su especificación está
en el resumen.
Otras opciones de fichero.
Si queremos vaciar los búferes intermedios de un fichero, hay
que usar FlushFileBuffers
.
Para bloquear una parte de un fichero para acceso exclusivo,
es decir, para que ningún otro proceso pueda acceder a ella,
se usará:
BOOL LockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh,
DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh);
Esta función, salvo por el problema de los enteros de 64 bits
comentado más arriba, es muy fácil de manejar.
Listado de directorios/búsqueda de ficheros.
El procedimiento para encontrar un fichero o para hacer un listado
de los ficheros contenidos en un directorio es el mismo. Consta
de tres pasos:
- Apertura de un objeto de búsqueda y obtención del
correspondiente handle con:
HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
En lpFileName
irá el nombre del fichero
que se busca (con posibilidad de uso de comodines para
hacer listados).
- Llamar cuantas veces sea necesario hasta acabar
con el directorio a la función
FindNextFile
.
- Destruir el objeto de búsqueda con la función
FindClose
.
Estas funciones, según van encontrando ficheros devuelven información
en una estructura del tipo WIN32_FIND_DATA
. Sus campos
tienen interpretación directa, excepción hecha del último:
typedef struct _WIN32_FIND_DATA
{DWORD dwFileAttributes;
FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime;
DWORD nFileSizeHigh; DWORD nFileSizeLow;
DWORD dwReserved0; DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA;
El último es el nombre alternativo que usan los sistemas FAT
para nombrar los ficheros largos. Por ejemplo, "Escritorio"
puede aparecer como "ESCRIT~1".
Práctica.
Hacer un programa que imprima el nombre y tamaño de los ficheros
del directorio actual que han sido modificados por última vez
un miércoles.
Entrada/salida solapada. Versión simple.
La entrada/salida solapada consiste en un método de poder
solapar entrada/salida con trabajo intenso de CPU en una
aplicación. Cuando efectuamos por ejemplo una petición
de lectura de un fichero con ReadFile
por
ejemplo, sabemos que el hilo solicitante se queda bloqueado
hasta que los datos estén disponibles. En la entrada/salida
solapada, el hilo efectúa la petición de lectura y no
se bloquea. Cuando los datos estén listos es notificado de
modo asíncrono.
Para poder realizar la entrada/salida solapada necesitamos
especificar el flag FILE_FLAG_OVERLAPPED
en
CreateFile
. Además, en las funciones de lectura
y escritura usaremos una estructura del tipo:
typedef struct _OVERLAPPED {
DWORD Internal; DWORD InternalHigh; /* Reservado, no tocar. */
DWORD Offset; DWORD OffsetHigh; /* Desplazamiento donde leer/escribir */
HANDLE hEvent; /* NULL, o evento que queremos se active cuando acabe */
} OVERLAPPED;
De esta estructura no tocaremos ninguno de los cuatro primeros
campos. De hecho el tercero y el cuarto especificarán el
desplazamiento del puntero de fichero. El último parámetro tiene que
ver con el método como deseamos ser avisados de que la entrada/salida
solapada ha acabado. Si hEvent es NULL, no seremos avisados.
Si no, tiene que ser un handle a un objeto de tipo evento
de los vistos en la sesión anterior. Este objeto pasará a estar
señalado cuando haya acabado la entrada/salida.
Si lo dejamos a NULL, ¿cómo nos enteraremos? Pues podemos usar
la función GetOverlappedResult
. Que dónde podéis
encontrar cómo se maneja... efectivamente, en el
resumen.
Las funciones de lectura y escritura, cuando estamos trabajando
con entrada/salida solapada, si la petición se va a posponer
devuelven fallo y el error ERROR_IO_PENDING
en
GetLastError
, aunque sabemos que esto no es
un error en sí.
Entrada/salida solapada. Versión avanzada.
El colmo de la comodidad en esto de la entrada/salida solapada
es que cuando se realice la operación de entrada/salida,
automáticamente se invoque una
función de rellamada.
Pues es posible. Para ello, sólo hay que usar las funciones
de lectura y escritura extendidas:
BOOL ReadFileEx( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
BOOL WriteFileEx( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
En la estructura OVERLAPPED
, que sigue siendo
necesaria, hay que dejar el campo hEvent a NULL. El prototipo
de la función de rellamada será:
VOID CALLBACK FunciOn( DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
Como en todas las funciones de rellamada, los parámetros
con los que se llamará variarán y serán puestos por el
sistema operativo.
Disponemos también de las versiones extendidas
SleepEx
, WaitForSingleObjectEx
y
WaitForMultipleObjectsEx
de sus correspondientes
versiones simples para poder detectar si ya se ha llamado
a la función de rellamada de la entrada/salida solapada.
Aplicaciones relacionadas.
-
-
Funciones de biblioteca relacionadas.
-
-
LPEs.