Para lograr escribir, se deben resolver muchos problemas. Estos problemas se resuelven en funciones separadas, de manera que el código final sea lo mas modular posible. Esta implementación utiliza preasignación de bloques (prealloc), es decir, cuando un archivo pide cierta cantidad de bloques, se le asignan de más, previendo que necesitará más en el futuro. Esto tiene varias ventajas, entre ellas que se reduce la fragmentación del archivo (dado que los bloques que se piden son siempre contiguos) y además se necesita asignar bloques menos veces, que es una operación costosa. fs_write -------- uint64_t fs_write(fs_info* info, fs_file* file, void *buffer, uint64_t count) write escribe a partir del offset en el que se encuentra, la cantidad de bytes indicada por count del buffer. Pueden pasar 3 cosas: 1. Entra la información y no hay que hacer ningún tipo de modificación al inodo. 2. Entra la información en los bloques que tenemos actualmente, pero el EOF se ve modificado, esto debe verse reflejado en el tamaño del archivo, que se guarda en el inodo. 3. La información no entra en los bloques asociados al archivo y debemos pedir más. Es importante recordar que cierta información puede verse modificada (en particular el superbloque o la tabla de descriptores de grupos) si cambia la cantidad de bloques libres, entonces debemos marcar nuestras copias en la cache como /dirty/ para que sus cambios sean commiteados más tarde. Cuando write encuentra que debe seguir escribiendo pero no tiene más bloques para hacerlo, llama a ext2_request_block, que se ocupa de poner un bloque para que write pueda seguir escribiendo sin hacer ningun tipo de trabajo extra. ext2_request_block ------------------ uint32_t ext2_request_block(fs_info* info, fs_file* file) Se ocupa de calcular cuantos bloques son necesarios para brindar un bloque de datos (dado que por la estructura de ext2 pueden llegar a ser necesarios más de uno) para ello llama a ext2_needed_blocks. Luego chequea si el archivo tiene suficientes bloques preasignados y en caso contrario llama a ext2_balloc para que le asigne bloques al archivo. Luego actualiza el inodo y asigna los bloques, armando correctamente la estructura de arbol del inodo de ext2, de tal manera de dejarle el bloque colocado de manera correcta a write para que pueda seguir escribiendo; para esto se utiliza la función ext2_assign_blocks. ext2_needed_blocks ------------------ uint64_t ext2_needed_blocks(fs_info* info, fs_file* file) Cuenta cuantos bloques reales necesito para devolver un bloque real de datos. ext2_balloc ----------- uint32_t ext2_balloc(fs_info* info, fs_file* file) Intenta alocar una constante fija de bloques para el archivo dado. La constante esta dada por PREALLOC_BLOCK_COUNT. Se podria usar tambien la constante que viene dada en el superbloque, pero es opcional. El algoritmo es simple e intenta minimizar la fragmentacion externa: intenta alocar desde el ultimo bloque utilizado por el archivo en adelante. Si no encuentra en el grupo actual, sigue con los grupos siguientes. En caso de que el ultimo bloque del archivo sea el 0 (o sea, no tiene bloques asignados hasta ahora, por ejemplo, si el archivo estaba vacio) entonces se empezará a buscar desde el primer grupo. Para llevar esto a cabo, se debe cargar el block usage bitmap de cada grupo de bloques y usar la funcion bitmap_search_zeros que busca n (en este caso PREALLOC_BLOCK_COUNT) bits en 0 juntos y devuelve el offset en el que se encuentran. En caso de tener exito, los reserva usando la funcion ext2_set_resvd_blocks. ext2_assign_blocks ------------------ uint32_t ext2_assign_blocks(fs_info* info, fs_file* file) Arma la estructura de arbol del inodo como corresponda para expandir el tamaño efectivo del archivo en 1 bloque. Para esto, tiene que eventualmente escribir nuevos bloques indirectos simples, dobles o triples. Esta es definitivamente la función más compleja del driver de ext2 (junto con ext2_unassign_blocks que es prácticamente igual), sin embargo la idea es muy simple. Se divide en todos los casos posibles (ver ext2_inode.txt) y se asignan todos los bloques según corresponda. ext2_set_resvd_blocks | ext2_set_resvd_inodes --------------------------------------------- uint32_t ext2_set_resvd_blocks(fs_info* info, uint32_t from, uint32_t count, bool reserve) uint32_t ext2_set_resvd_inodes(fs_info* info, uint32_t from, uint32_t count, bool reserve) Estas funciones, muy similares, se ocupan de reservar/liberar bloques/inodos en el filesystem. Para eso, debe cargar el usage bitmap que corresponda y setear o limpiar los bits que correspondan, si se quiere reservar o liberar, respectivamente. El cuarto parametro indica si lo que se quiere hacer es reservar o desreservar. Si es true se reserva y si es false se libera. ext2_get_next_prealloc ---------------------- uint32_t ext2_get_next_prealloc(fs_file* file) Devuelve el proximo bloque de los prealocados por el archivo. Además actualiza la estructiura interna del archivo (dado que aumenta el puntero al primer bloque prealocado y disminuye la cantidad de bloques prealocados). ext2_ialloc ----------- uint32_t ext2_ialloc(fs_info* info, fs_dir* parent) Busca, empezando por el grupo 0, algun inodo libre y devuelve su numero. fs_create --------- int64_t fs_create(fs_info* info, fs_dir* dir, dir_entry* file_info) Crea un archivo en la carpeta pasada como parametro. Para eso, pide un inodo con ext2_ialloc y escribe la direntry correspondiente en los bloques de datos del inodo del directorio. Hay dos casos: se puede meter en el medio de la carpeta (porque hay entradas obsoletas en el medio) o debe ponerse al final. Además, debe setear información como mode (permisos), creation time, modification time, entre otros. ext2_unassign_block ------------------- uint32_t ext2_unassign_block(fs_info* info, fs_file* file) Le desasigna el bloque actual al archivo, que _debe_ ser el ultimo (a menos que se quiera romper todo, o se sepa lo que se hace, dado que se rompe el invariante del archivo y queda un "hueco" en el medio). La lógica es la misma que la de ext2_assign_blocks, solo que se deben descargar aquellos indirectos que están cargados y no se necesitan más tambien. fs_truncate ----------- int64_t fs_truncate(fs_info* info, fs_file* file, uint64_t length) Trunca al archivo y lo deja de tamaño length (bytes). Se asume que length es menor al tamaño del archivo (en caso de que no, se resuelve en la interfaz de io.c). Para eso va hasta el final del archivo y va retrocediendo, desasignando bloques con ext2_unassign_block en caso de que sea necesario, actualizando toda la información que hace falta (el tamaño del archivo, que se encuentra en el inodo). fs_remove --------- int64_t fs_remove(fs_info* info, fs_dir* dir) Borra el archivo apuntado actualmente por el directorio. Hay que sobreescribir la entrada marcandola como invalida (seteando el numero de inodo en 0) y el deletion time acordemente. ext2_has_super -------------- uint32_t ext2_has_super(fs_info* info, uint64_t group) Dice si un grupo del filesystem debe contener o no un backup del superbloque y el group descriptor table. Si el filesystem tiene seteado la feature de sparse superblock, hay que chequear si el numero de grupo es 0 o potencia de 3, 5 o 7. ext2_restore_backup ------------------- uint32_t ext2_restore_backup(fs_info *info) Escribe el superbloque y la group descriptor table en todos los grupos que deben tener el backup. Para eso, se va a usar ext2_has_super. ext2_new_group -------------- uint64_t ext2_new_group(fs_info* info) NO IMPLEMENTADA. Esta funcion se encarga de inaugurar nuevos grupos de bloques. Debe tener en cuenta no pasarse del tamano del drive y mantener el superbloque esparso si asi corresponde. De todos modos, esta funcion se usa en casos muy borde, cuando nos quedamos sin el espacio actual alocado en todo el filesystem. De hecho la especificacion ni siquiera es clara si esta operacion se puede hacer mientras el disco esta online y montado.