Programación en X68000 capítulo 4.1, Operaciones con ficheros.

Cuando hacemos un programa muchas veces necesitamos leer o escribir ficheros externos al propio programa.

En el contexto de un videojuego podría ser un gráfico, un mapa de tiles o un PCM.

También podríamos necesitar escribir en el disco un log, los datos de una partida.

En X68000 podemos usar la librería <stdio.h> como haríamos en otros sistemas y ya esta, pero el SO Human64k tiene sus propio juego de funciones para el manejo de ficheros.

¿Porque deberíamos usar estas funciones si tenemos stdio.h? En nfg.forums hay una entrada de Lydux comentando como hacer drivers en X68000. https://nfggames.com/forum2/index.php?topic=5417.0 Y según parece en etapas tempranas del arranque del sistema operativo Libc no estaría aún completamente disponible de modo que lo mas fiable en este escenario sería usar estas funciones en vez de las de stdio.h.

Y también para que el conocimiento no se pierda es que hago este post explicando lo que se de ello.

En este artículo voy a escribir sobre todas estas funciones:

int _dos_create (const char *file, int attr)

int _dos_newfile (const char *file, int attr)

int _dos_maketmp (const char *file, int attr)

int _dos_open (const char *file, int mode)

int _dos_read (int fno, char *buffer, int size)

int _dos_write (int fno, const char *buffer, int size)

long _dos_seek (int fno, int offset, int mode)

int _dos_close (int fno)

int _dos_delete (const char *file)

Todas ellas pertenecen a la librería doslib.h si usamos Mariko, o dos.h si usamos Lydux toolchain.


Crear fichero:

int _dos_create (const char *file, int attr)

int _dos_newfile (const char *file, int attr)

int _dos_maketmp (const char *file, int attr)

En stdio.h utilizaríamos la función FILE *fopen(const char *filename, const char *mode) tanto para abrir un fichero como para crearlo. Además fopen nos devolvería un puntero al manejador del fichero que hemos abierto o creado y que se usaría con otras funciones.

En Humand68k tenemos 3 funciones diferentes para crear ficheros e incluso directorios, estas son _dos_create(), _dos_newfile(), y _dos_maketmp().

Y para solo abrir un fichero podemos usar _dos_open().

Estas funciones nos devuelven un número entero que el SO asigna al fichero y que viene a ser el manejador de fichero. Si el fichero no se pudo abrir o crear, el número devuelto será negativo y representa un código de error.

Las tres funciones para crear fichero tienen una cadena de caracteres con el nombre de fichero como primer parámetro y un número entero como segundo parámetro.

El entero que pasamos como segundo parámetro es un byte de flags en que cada bit tiene un significado.

Acorde con este post https://www.target-earth.net/wiki/doku.php?id=blog:x68_devcode los bits significan esto:

000: escritura y lectura, 1: R – solo lectura
010: visible, 1: H - Oculto
020: normal, 1: S – fichero del sistema
030: no es un volumen, 1: V - nombre de Volumen
040: no es un directorio, 1: D – nombre de directorio
050: no es un archivo, 1: A – es un archivo.
06 - 15Los demás bits se ignoran.


Estas letras R, H, S, V, D, A son atributos que se pueden ver con el comando ATTRIB <nombre de fichero> en la línea de comandos.

Para facilitar las cosas podemos crear las siguientes macros:

#define F_ATTR_  0x00  //0b000000 //0 read and write mode and A attribute (default)
#define F_ATTR_R 0x01  //0b000001 //1 read only
#define F_ATTR_H 0x02  //0b000010 //2 Hidden
#define F_ATTR_S 0x04  //0b000100 //4 System file
#define F_ATTR_V 0x08  //0b001000 //8 Volume name
#define F_ATTR_D 0x10  //0b010000 //16 Directory name
#define F_ATTR_A 0x20  //0b100000 //32 Archive name

Las tres funciones crean ficheros pero tienen matices que las diferencian:

int _dos_create (const char *file, int attr)Si el fichero existe puede sobre escribirlo si el bit 0 es 0
int _dos_newfile (const char *file, int attr)Si el fichero existe no lo sobre escribe
int _dos_maketmp (const char *file, int attr)Si el fichero existe lo sobre escribe

Entonces, crear el fichero sería así:

int16_t file_number, status = 0;
//We create a file and open it so we get its number to manage it.
file_number = _dos_create(
	"file",    //file name
	F_ATTR_    //read and write mode and A attribute
);
//if any error...
if(file_number > 0){
	_dos_c_print("Can't create the file\r\n");
}

La función _dos_create() tiene el nombre de fichero como primer parámetro y como segundo un flag donde 0 es para sobre escribir el fichero si ya existe y 1 es para no sobre escribirlo.


Abrir un fichero: int _dos_open (const char *file, int mode)

La función _dos_open() también tiene el nombre de fichero como primer parámetro y como segundo tiene un entero en que cada bit tiene un significado.

En X68000, como hemos visto antes, hay muchas funciones que en vez de tener un parámetro para cada aspecto de la configuración usa un entero con flags.

Para facilitar el uso de la función podemos usar una macro o bien una estructura con bit-fields.

El significado de cada bit es el siguiente:

Bits 0 -1 modo de apertura 00: solo escritura.
01: solo lectura.
11: lectura y escritura.
Bits 2, 3 y 4, permisos para otros mientras está abierto 000: modo por defecto.
001: nadie mas tiene acceso.
010: permitir solo lectura para otros procesos.
011: permitir solo escritura para otros procesos.
100: lectura y escritura para otros procesos.
Bit 7 0: como fichero normal,
1: como diccionario (no sé que es esto)


Para entendernos mejor declararemos estas macros:

#define OPEN_ACCESS_DICTIONARY           0x80    //0b10000000 //1 not for users
#define OPEN_ACCESS_NORMAL               0x00    //0b00000000 //0

#define OPEN_SHARING_TOTAL               0x08    //0b00010000  //4 allow others to write and read
#define OPEN_SHARING_WRITE_ONLY          0x0C    //0b00001100  //3 allow others to write only
#define OPEN_SHARING_READ_ONLY           0x08    //0b00001000  //2 allow others to read only
#define OPEN_SHARING_RESTRICTED          0x04    //0b00000100  //1 don't allow others anything
#define OPEN_SHARING_COMPATIBILITY_MODE  0x00    //0b00000000  //0

#define OPEN_MODE_RW                     0x02    //0b00000010  //2 open for write and read
#define OPEN_MODE_W                      0x01    //0b00000001  //1 open only for writing
#define OPEN_MODE_R                      0x00    //0b00000000  //0 open only for reading

Estas macros ya tienen los bits calculados en hexadecimal. Solo resta usar otra macro para ponerlos juntos:

#define OPENING_MODE(access, sharing, mode) (access | sharing | mode)

El código sería como esto:

//now we open the same file and capture the handler number
file_number = _dos_open(
	"file",                             //file name
    OPENING_MODE(
    	OPEN_ACCESS_NORMAL,
        OPEN_SHARING_COMPATIBILITY_MODE,
        OPEN_MODE_R                          //only read mode
  	)
);

En este ejemplo estamos abriendo el fichero “file” y con el macro OPENING_MODE y los 3 parámetros calculamos los flags del segundo parámetro.


Leyendo del fichero: int _dos_read (int fno, char *buffer, int size)

Para leer de un fichero usamos la función _dos_read() a la que le pasamos el número de fichero que obtuvimos con _dos_create(), _dos_newfile(), _dos_maketmp() o _dos_open().

Como segundo parámetro el puntero al buffer y como tercer parámetro el número de bytes.

La función nos devuelve un entero con el numero de bytes que se han leído. Si el fichero es mas pequeño que el numero de bytes o hemos llegado al final de este pues ese numero sera los bytes que se hayan podido leer.

El buffer tiene que ser un array de tipo char. Si necesitas guardar los datos en otro formato le tienes que hacer un casting.

//we read the file and capture the number of characters we manage to read
int16_t bytes_read = _dos_read(
	file_number,        //handler number
    (char *) buffer,         //destination
    sizeof buffer  //bytes to read
);

Como se puede observar es muy parecido a la función fread() de <stdio.h>.


Escribiendo en el fichero: int _dos_write (int fno, const char *buffer, int size)

Para escribir en el fichero usamos la funcion _dos_write() a la que le pasamos el número de fichero como en _dos_read(), el buffer que es un array de tipo char y el tamaño en bytes.

//we write the message in the file
status = _dos_write (
	file_number,    //this is like the file handler in stdio but represented by an integer
    message,        //the message
    sizeof message //the size of the message
);

Si se produjese un error devolvería un numero negativo que sería el código del error.


Moverse por el fichero: long _dos_seek (int fno, int offset, int mode)

Igual que <stdio.h> Human68k nos proporciona una función para poder mover el puntero del fichero hacia delante, hacia atrás o en cualquier lugar aleatorio.

La función es _dos_seek() y acepta 3 parámetros. EL primer parámetro es el número de fichero, el segundo el desplazamiento y el tercero el punto del que el desplazamiento es relativo.

Ese tercer parámetro puede ser 0: principio del fichero, 1: desde el puntero y 2: desde el final del fichero.

La función nos devuelve el número de byte donde está ahora el puntero o un numero negativo si ha habido un error.

status = _dos_seek(
	file_handler,
    0, //offset
    2  //0 = beginning, 1 = on the spot, 2 = end
);

En este ejemplo estamos moviendo el puntero el final del fichero para así obtener el número de byte del final y por tanto el tamaño.


Cerrar fichero: int _dos_close (int fno)

Sencillamente cerramos el fichero pasando como parámetro el número de fichero.

//now we close the file
status = _dos_close(file_handler);

Si no hay problemas nos devolverá 0 y si hay alguno devolverá un número negativo indicando el error.


Borrar fichero: int _dos_delete (const char *file)

Para borrar un fichero usamos _dos_delete() con el nombre de fichero como parámetro. Obtenemos 0 si no hubo problemas y un número negativo si hubo alguno.


Errores:

Como hemos visto todas estas funciones devuelven cero, o un numero positivo indicando algún dato de interés si no ha habido errores, y negativo si ha habido un error.

Estos números negativos identifican el error que ha ocurrido y aquí esta la lista de errores:


-1 Executed invalid function code

-2 Specified file not found

-3 Specified directory not found

-4 Too many open files

-5 Cannot access directory or volume label

-6 Specified handle is not open

-7 Memory manager region was destroyed

-8 Not enough memory to execute

-9 Invalid memory manager pointer specified

-10 Illegal environment specified

-11 Abnormal executable file format

-12 Abnormal open access mode

-13 Error in selecting a filename

-14 Called with invalid parameter

-15 Error in selecting a drive

-16 Cannot remove current directory

-17 Cannot ioctrl device

-18 No more files found"; break;

-19 Cannot write to specified file

-20 Specified directory already registered

-21 Cannot delete because file exists

-22 Cannot name because file exists

-23 Cannot create file because disk is full

-24 Cannot create file because directory is full

-25 Cannot seek to specified location

-26 Specified supervisor mode with supervisor status on

-27 Thread with same name exists

-28 Interprocess communication buffer is write-protected

-29 Cannot start any more background processes

-32 Not enough lock regions

-33 Locked; cannot access

-34 Handler for specified drive is opened

-35 Symbolic link nest exceeded 16 steps (lndrv)

-80 File exists


En un próximo artículo hablare de funciones para crear directorios, cambiar attributos y hacer búsquedas de ficheros y directorios.


Comentarios

Entradas populares