/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Implementation of the pcjs v2 floppy image format (read-only) * * Authors: cold-brewed * * Copyright 2024 cold-brewed */ #include #include #include #include #include #include #ifndef _MSC_VER #include #else #include #endif #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/timer.h> #include <86box/plat.h> #include <86box/fdd.h> #include <86box/fdd_86f.h> #include <86box/fdd_common.h> #include <86box/fdd_pcjs.h> #include static pcjs_t *images[FDD_NUM]; static pcjs_error_t pcjs_error = E_SUCCESS; struct pcjs_error_description { int code; const char *message; } pcjs_error_description[] = { { E_SUCCESS, "No error" }, { E_MISSING_KEY, "The requested key was missing" }, { E_UNEXPECTED_VALUE, "The value was not of the expected type" }, { E_INTEGRITY, "Integrity check failed" }, { E_INVALID_OBJECT, "Object is missing or invalid" }, { E_ALLOC, "Memory allocation failure" }, { E_PARSE, "Parsing failure" }, { -1, "Unknown error" }, }; #ifdef ENABLE_PCJS_LOG int pcjs_do_log = ENABLE_PCJS_LOG; static void pcjs_log(const char *fmt, ...) { if (pcjs_do_log) { va_list ap; va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define pcjs_log(fmt, ...) #endif void pcjs_init(void) { memset(images, 0x00, sizeof(images)); } const char* pcjs_errmsg(void) { int i = 0; while (pcjs_error_description[i].code >= 0) { if (pcjs_error_description[i].code == pcjs_error) { return pcjs_error_description[i].message; } i++; } return "Unknown error"; } int parse_image_info(pcjs_t *dev, const cJSON *parsed_json) { const cJSON *imageInfo = NULL; if(dev == NULL || parsed_json == NULL) { pcjs_log("Null values passed\n"); pcjs_error = E_INTEGRITY; return 1; } imageInfo = cJSON_GetObjectItemCaseSensitive(parsed_json, "imageInfo"); if (imageInfo == NULL || !cJSON_IsObject(imageInfo)) { pcjs_log("imageInfo object does not exist or is invalid\n"); pcjs_error = E_INVALID_OBJECT; return 1; } /* Macros are used here to avoid repetition */ /* First get strings */ IMAGE_INFO_GET_STRING(type) IMAGE_INFO_GET_STRING(name) IMAGE_INFO_GET_STRING(format) IMAGE_INFO_GET_STRING(hash) IMAGE_INFO_GET_STRING(version) IMAGE_INFO_GET_STRING(repository) /* Then the numbers */ IMAGE_INFO_GET_NUMBER(cylinders) IMAGE_INFO_GET_NUMBER(heads) IMAGE_INFO_GET_NUMBER(trackDefault) IMAGE_INFO_GET_NUMBER(sectorDefault) IMAGE_INFO_GET_NUMBER(diskSize) /* Special cases */ /* Convert bootSector to array if it exists */ /* For some reason the array itself is stored as a string * which needs to be converted into an array before parsing. */ dev->image_info.boot_sector_array_size = 0; const cJSON *array_string = cJSON_GetObjectItemCaseSensitive(imageInfo, "bootSector"); cJSON *bootSector = NULL; if(cJSON_IsString(array_string) && array_string != NULL) { bootSector = cJSON_Parse(array_string->valuestring); } if(cJSON_IsArray(bootSector)) { const int array_size = cJSON_GetArraySize(bootSector); dev->image_info.boot_sector_array_size = array_size; const cJSON *array_item = NULL; int array_index = 0; cJSON_ArrayForEach(array_item, bootSector) { /* Make sure each item is a number */ if(!cJSON_IsNumber(array_item)) { pcjs_log("Non-number item in bootSector array\n"); dev->image_info.boot_sector_array_size = 0; /* Prevent the loop from continuing */ array_item = NULL; break; } /* Make sure each number is in range */ const int value = array_item->valueint; if (value < 0 || value > 255) { pcjs_log("bootSector value %i out of range (0-255)\n", value); dev->image_info.boot_sector_array_size = 0; /* Prevent the loop from continuing */ array_item = NULL; break; } /* Make sure we don't exceed the array length */ if (array_index + 1 > PCJS_IMAGE_INFO_ARRAY_LEN) { pcjs_log("bootSector array length exceeded (max %i)\n", PCJS_IMAGE_INFO_ARRAY_LEN); dev->image_info.boot_sector_array_size = 0; /* Prevent the loop from continuing */ array_item = NULL; break; } dev->image_info.boot_sector[array_index] = value; array_index++; } } /* checksum: Can't use the number macro like the others because it uses valueInt * which is 32-bit signed and we need unsigned. Use the double value (valuedouble) instead. */ const cJSON *checksum_json = cJSON_GetObjectItemCaseSensitive(imageInfo, "checksum"); if (cJSON_IsNumber(checksum_json)) { dev->image_info.checksum = checksum_json->valuedouble; } else { pcjs_log("Required number value for \"%s\" missing from imageInfo\n", "checksum"); pcjs_error = E_MISSING_KEY; cJSON_Delete(bootSector); return 1; } /* Use the metadata as the official source */ dev->total_tracks = dev->image_info.cylinders; dev->total_sides = dev->image_info.heads; dev->total_sectors = dev->image_info.trackDefault; cJSON_Delete(bootSector); return 0; } int parse_file_table(pcjs_t *dev, const cJSON *parsed_json) { const cJSON *fileTable = NULL; if(dev == NULL || parsed_json == NULL) { pcjs_log("Null values passed\n"); pcjs_error = E_INTEGRITY; return 1; } fileTable = cJSON_GetObjectItemCaseSensitive(parsed_json, "fileTable"); if (fileTable == NULL || !cJSON_IsArray(fileTable)) { pcjs_log("fileTable object does not exist or is invalid\n"); pcjs_error = E_INVALID_OBJECT; return 1; } const cJSON *each_file_table = NULL; dev->file_table.num_entries = cJSON_GetArraySize(fileTable); uint16_t processed_entries = 0; uint16_t current_entry = 0; if (dev->file_table.num_entries == 0) { pcjs_log("No fileTable entries to process\n"); return 0; } pcjs_log("Processing %i file table entries\n", dev->file_table.num_entries); /* Allocate the entries */ dev->file_table.entries = (pcjs_file_table_entry_t *)calloc(dev->file_table.num_entries, sizeof(pcjs_file_table_entry_t)); if (dev->file_table.entries == NULL ) { pcjs_log("Failed to allocate file table entries\n"); pcjs_error = E_ALLOC; return 1; } cJSON_ArrayForEach(each_file_table, fileTable) { /* The -1 length of the temporary buffer brought to you by gcc's -Wstringop-truncation */ char hash[PCJS_FILE_TABLE_STRING_LEN-1] = {0}; char path[PCJS_FILE_TABLE_STRING_LEN-1] = {0}; char attr[PCJS_FILE_TABLE_STRING_LEN-1] = {0}; char date[PCJS_FILE_TABLE_STRING_LEN-1] = {0}; uint16_t f_size = 0; JSON_GET_OBJECT_STRING_OPTIONAL(hash, each_file_table, PCJS_OBJECT_KEY_FT_HASH) JSON_GET_OBJECT_STRING_OPTIONAL(path, each_file_table, PCJS_OBJECT_KEY_FT_PATH) JSON_GET_OBJECT_STRING_REQUIRED(attr, each_file_table, PCJS_OBJECT_KEY_FT_ATTR) JSON_GET_OBJECT_STRING_REQUIRED(date, each_file_table, PCJS_OBJECT_KEY_FT_DATE) JSON_GET_OBJECT_NUMBER_OPTIONAL(f_size, each_file_table, PCJS_OBJECT_KEY_FT_SIZE) strncpy(dev->file_table.entries[current_entry].hash, hash, sizeof(dev->file_table.entries[current_entry].hash) - 1); strncpy(dev->file_table.entries[current_entry].path, path, sizeof(dev->file_table.entries[current_entry].path) - 1); strncpy(dev->file_table.entries[current_entry].attr, attr, sizeof(dev->file_table.entries[current_entry].attr) - 1); strncpy(dev->file_table.entries[current_entry].date, date, sizeof(dev->file_table.entries[current_entry].date) - 1); dev->file_table.entries[current_entry].size = f_size; processed_entries++; current_entry++; } if(processed_entries != dev->file_table.num_entries) { pcjs_log("fileTable entries processed (%i) inconsistent with number of entries in the table (%i)\n", processed_entries, dev->file_table.num_entries); pcjs_error = E_INTEGRITY; goto fail; } return 0; fail: /* Deallocate the array */ free(dev->file_table.entries); return 1; } int json_parse(pcjs_t *dev) { const cJSON *diskData = NULL; /* Determine the size of the file, reset back */ fseek(dev->fp, 0L, SEEK_END); const long numbytes = ftell(dev->fp); fseek(dev->fp, 0L, SEEK_SET); /* Allocate memory for the contents */ char *buffer = calloc(numbytes + 1, sizeof(char)); if(buffer == NULL) { pcjs_error = E_ALLOC; return 1; } /* Read and null terminate */ (void) !fread(buffer, sizeof(char), numbytes, dev->fp); buffer[numbytes] = '\0'; cJSON *parsed_json = cJSON_Parse(buffer); if (parsed_json == NULL) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { fprintf(stderr, "Error parsing json before: %s\n", error_ptr); } pcjs_error = E_PARSE; goto fail; } if(parse_image_info(dev, parsed_json)) { pcjs_log("Failed to parse imageInfo metadata\n"); goto fail; } /* File table metadata is optional */ if(parse_file_table(dev, parsed_json)) { pcjs_log("File table metadata is not present or invalid\n"); } diskData = cJSON_GetObjectItemCaseSensitive(parsed_json, "diskData"); const cJSON *each_track = NULL; int total_c = 0; /* The diskData array is essentially [c][h][s] */ /* Start with the tracks in [c] */ cJSON_ArrayForEach(each_track, diskData) { int total_heads = 0; const cJSON *each_head = NULL; /* For each track, loop on each head */ /* Now in [c][h] */ cJSON_ArrayForEach(each_head, each_track) { int total_sectors = 0; const cJSON *each_sector = NULL; /* Now loop on the sectors in [c][h][s] */ /* Each sector item will have the information needed to fill in a pcjs_sector_t */ cJSON_ArrayForEach(each_sector, each_head) { const cJSON *data = NULL; const cJSON *each_data = NULL; int data_array_size = 0; int total_d = 0; int32_t current_track = 0; int32_t current_head = 0; int32_t current_sector = 0; int32_t current_length = 0; int32_t file_mapping = 0; int32_t offset = 0; pcjs_sector_t *sector = NULL; /* Macros to keep things tidy */ JSON_GET_OBJECT_NUMBER_REQUIRED(current_track, each_sector, PCJS_OBJECT_KEY_TRACK) JSON_GET_OBJECT_NUMBER_REQUIRED(current_head, each_sector, PCJS_OBJECT_KEY_HEAD) JSON_GET_OBJECT_NUMBER_REQUIRED(current_sector, each_sector, PCJS_OBJECT_KEY_SECTOR) JSON_GET_OBJECT_NUMBER_REQUIRED(current_length, each_sector, PCJS_OBJECT_KEY_LENGTH) JSON_GET_OBJECT_NUMBER_OPTIONAL_DEFAULT(offset, each_sector, PCJS_OBJECT_KEY_OFFSET, -1) JSON_GET_OBJECT_NUMBER_OPTIONAL_DEFAULT(file_mapping, each_sector, PCJS_OBJECT_KEY_FILE, -1) /* NOTE: The sectors array is zero indexed, but the metadata for each sector shows its conventional sector number */ sector = &dev->sectors[current_track][current_head][current_sector-1]; if (sector->data == NULL ) { /* We could verify the sector size against the metadata here */ sector->data = (uint8_t *)calloc(1, current_length); if (sector->data == NULL ) { pcjs_log("Failed to allocate\n"); pcjs_error = E_ALLOC; goto fail; } } sector->track = current_track; sector->side = current_head; sector->sector = current_sector; sector->size = current_length; sector->encoded_size = fdd_sector_size_code(current_length); sector->offset = offset; sector->file = file_mapping; sector->pattern_repeat = 0; sector->last_entry = 0; data = cJSON_GetObjectItemCaseSensitive(each_sector, PCJS_OBJECT_KEY_DATA); if(data != NULL && cJSON_IsArray(data)) { data_array_size = cJSON_GetArraySize(data); cJSON_ArrayForEach(each_data, data) { /* total_d is our current position in the data array */ /* That number will be used to determine where to store the * value in its destination (sector->data). * Each value in the data array is a 32-bit integer, but the * destination (sector->data) is a uint8_t array. Therefore * we'll need to manually calculate offsets and place the bytes. */ const int dest_offset = total_d * 4; if(total_d == data_array_size - 1) { /* This is the last value in the data array. Check to see if we'll need it * for the repeating pattern. * We use current_length / 4 because each data element is 32 bits in size. * For example, if current_length = 512 * then 512 / 4 = 128 * so anything less than that value will have padding */ if (total_d + 1 < current_length / 4) { sector->last_entry = each_data->valueint; /* total_d + 1 because it is zero indexed at this point */ sector->pattern_repeat = current_length / 4 - (total_d + 1); } } const int value = each_data->valueint; /* Take each value and shift in as necessary */ for (int i = 0; i < 4; i++) { sector->data[dest_offset + i] = (value >> i * 8) & 0xff; } total_d++; } } else { pcjs_log("Data array missing from [%d][%d][%d]", current_track, current_head, current_sector); pcjs_error = E_MISSING_KEY; goto fail; } /* Fill in the repeating pattern if needed */ /* total_d was already advanced at the end of the previous loop */ for (int i = 0; i < sector->pattern_repeat; i++ ) { const int position = total_d + i; const int dest_offset = position * 4; if(position >= (sector->size / 4)) { /* Something is wrong */ pcjs_log("Out of bounds write attempt to data array at %i", position); pcjs_error = E_INTEGRITY; goto fail; } for (int j = 0; j < 4; j++) { sector->data[dest_offset + j] = (sector->last_entry >> j * 8) & 0xff; } } total_sectors++; dev->calc_total_sectors = total_sectors; /* End sectors */ } dev->spt[total_c][total_heads] = total_sectors; total_heads++; dev->calc_total_sides = total_heads; /* End heads */ } total_c++; dev->calc_total_tracks = total_c; /* End tracks */ } pcjs_log("calculated totals: c/h/s %i/%i/%i\n", dev->calc_total_tracks, dev->calc_total_sides, dev->calc_total_sectors); pcjs_log("metadata totals: c/h/s %i/%i/%i\n", dev->image_info.cylinders, dev->image_info.heads, dev->image_info.trackDefault); free(buffer); cJSON_Delete(parsed_json); return 0; fail: free(buffer); cJSON_Delete(parsed_json); return pcjs_error; } /* Handlers */ static uint16_t disk_flags(int drive) { const pcjs_t *dev = images[drive]; return dev->disk_flags; } static uint16_t track_flags(int drive) { const pcjs_t *dev = images[drive]; return dev->track_flags; } static void set_sector(int drive, int side, uint8_t c, uint8_t h, uint8_t r, uint8_t n) { pcjs_t *dev = images[drive]; dev->current_sector[side] = 0; /* Make sure we are on the desired track. */ if (c != dev->current_track) return; /* Set the desired side. */ dev->current_side = side; /* Sectors are stored zero indexed, but sector zero should not be requested */ if(r == 0) { pcjs_log("set_sector: Sector 0 requested?\n"); } else { dev->current_sector[side] = r - 1; } /* The ifdef is necessary because if ENABLE_PCJS_LOG is not defined gcc throws an unused variable warning */ #ifdef ENABLE_PCJS_LOG const int file_index = dev->sectors[dev->current_track][side][dev->current_sector[side]].file; pcjs_log("set sector: %i/%i/%i %s%s%s\n", c, h, r, file_index == -1 ? "" : "(", file_index == -1 ? "" : dev->file_table.entries[file_index].path, file_index == -1 ? "" : ")"); #endif } static uint8_t poll_read_data(int drive, int side, uint16_t pos) { const pcjs_t *dev = images[drive]; const uint8_t sec = dev->current_sector[side]; return (dev->sectors[dev->current_track][side][sec].data[pos]); } static void pcjs_seek(int drive, int track) { uint8_t id[4] = { 0, 0, 0, 0 }; pcjs_t *dev = images[drive]; int rate; int gap2; int gap3; int pos; int ssize; int rsec; int asec; if (dev->fp == NULL) { pcjs_log("pcjs_seek: no file loaded\n"); return; } /* Allow for doublestepping tracks. */ if (!dev->track_width && fdd_doublestep_40(drive)) track /= 2; /* Set the new track. */ dev->current_track = track; d86f_set_cur_track(drive, track); /* Reset the 86F state machine. */ d86f_reset_index_hole_pos(drive, 0); d86f_destroy_linked_lists(drive, 0); d86f_reset_index_hole_pos(drive, 1); d86f_destroy_linked_lists(drive, 1); if (track > dev->total_tracks) { d86f_zero_track(drive); return; } pcjs_log("seeking to track %i\n", track); for (uint8_t side = 0; side < dev->total_sides; side++) { /* Get transfer rate for this side. */ rate = dev->track_flags & 0x07; if (!rate && (dev->track_flags & 0x20)) rate = 4; /* Get correct GAP3 value for this side. */ gap3 = fdd_get_gap3_size(rate, // dev->sectors[track][side][0].size, dev->sectors[track][side][0].encoded_size, dev->spt[track][side]); /* Get correct GAP2 value for this side. */ gap2 = ((dev->track_flags & 0x07) >= 3) ? 41 : 22; pos = d86f_prepare_pretrack(drive, side, 0); for (uint8_t sector = 0; sector < dev->spt[track][side]; sector++) { rsec = dev->sectors[track][side][sector].sector; asec = sector; id[0] = track; id[1] = side; id[2] = rsec; if (dev->sectors[track][side][asec].encoded_size > 255) perror("PCJS: pcjs_seek: sector size too big."); id[3] = dev->sectors[track][side][asec].encoded_size & 0xff; ssize = fdd_sector_code_size(dev->sectors[track][side][asec].encoded_size & 0xff); pos = d86f_prepare_sector( drive, side, pos, id, dev->sectors[track][side][asec].data, ssize, gap2, gap3, 0 ); if (sector == 0) d86f_initialize_last_sector_id(drive, id[0], id[1], id[2], id[3]); } } } static int pcjs_load_image(pcjs_t *dev) { if (dev->fp == NULL) { pcjs_log("No file loaded!\n"); return 1; } /* Initialize. */ for (uint16_t i = 0; i < PCJS_MAX_TRACKS; i++) { for (uint8_t j = 0; j < PCJS_MAX_SIDES; j++) memset(dev->sectors[i][j], 0x00, sizeof(pcjs_sector_t)); } dev->current_track = 0; dev->current_side = 0; return json_parse(dev); } void pcjs_load(int drive, char *fn) { double bit_rate = 0; int temp_rate; const pcjs_sector_t *sector; pcjs_t *dev; d86f_unregister(drive); /* Allocate a drive block */ dev = (pcjs_t *) malloc(sizeof(pcjs_t)); memset(dev, 0x00, sizeof(pcjs_t)); /* Open the image file, read-only */ dev->fp = plat_fopen(fn, "rb"); if (dev->fp == NULL) { free(dev); memset(fn, 0x00, sizeof(char)); return; } pcjs_log("Opening filename: %s\n", fn); /* Always set the drive to write-protected mode */ writeprot[drive] = 1; /* Place in the correct slot */ images[drive] = dev; /* Parse and load the information from the json file */ if (pcjs_load_image(dev)) { pcjs_log("Failed to initialize: %s\n", pcjs_errmsg()); (void) fclose(dev->fp); free(dev); images[drive] = NULL; memset(fn, 0x00, sizeof(char)); return; } pcjs_log("Drive %d: %s (%i tracks, %i sides, %i sectors, sector size %i)\n", drive, fn, dev->image_info.cylinders, dev->image_info.heads, dev->image_info.trackDefault, dev->image_info.sectorDefault); /* * If the image has more than 43 tracks, then * the tracks are thin (96 tpi). */ dev->track_width = (dev->total_tracks > 43) ? 1 : 0; /* If the image has 2 sides, mark it as such. */ dev->disk_flags = 0x00; if (dev->total_sides == 2) dev->disk_flags |= 0x08; /* PCJS files are always assumed to be MFM-encoded. */ dev->track_flags = 0x08; dev->interleave = 0; temp_rate = 0xff; sector = &dev->sectors[0][0][0]; for (uint8_t i = 0; i < 6; i++) { if (dev->spt[0][0] > fdd_max_sectors[sector->encoded_size][i]) continue; bit_rate = fdd_bit_rates_300[i]; temp_rate = fdd_rates[i]; dev->disk_flags |= (fdd_holes[i] << 1); if ((bit_rate == 500.0) && (dev->spt[0][0] == 21) && (sector->encoded_size == 2) && (dev->total_tracks >= 80) && (dev->total_tracks <= 82) && (dev->total_sides == 2)) { /* * This is a DMF floppy, set the flag so * we know to interleave the sectors. */ dev->dmf = 1; } else { if ((bit_rate == 500.0) && (dev->spt[0][0] == 22) && (sector->encoded_size == 2) && (dev->total_tracks >= 80) && (dev->total_tracks <= 82) && (dev->total_sides == 2)) { /* * This is marked specially because of the * track flag (a RPM slow down is needed). */ dev->interleave = 2; } dev->dmf = 0; } break; } if (temp_rate == 0xff) { pcjs_log("Invalid image (temp_rate=0xff)\n"); (void) fclose(dev->fp); dev->fp = NULL; free(dev); images[drive] = NULL; memset(fn, 0x00, sizeof(char)); return; } if (dev->interleave == 2) { dev->interleave = 1; dev->disk_flags |= 0x60; } dev->gap2_len = (temp_rate == 3) ? 41 : 22; if (dev->dmf) dev->gap3_len = 8; else dev->gap3_len = fdd_get_gap3_size(temp_rate, sector->encoded_size, dev->spt[0][0]); if (!dev->gap3_len) { pcjs_log("Image of unknown format was inserted into drive %c:\n", 'C' + drive); (void) fclose(dev->fp); dev->fp = NULL; free(dev); images[drive] = NULL; memset(fn, 0x00, sizeof(char)); return; } dev->track_flags |= (temp_rate & 0x03); /* data rate */ if (temp_rate & 0x04) dev->track_flags |= 0x20; /* RPM */ pcjs_log(" disk_flags: 0x%02x, track_flags: 0x%02x, GAP3 length: %i\n", dev->disk_flags, dev->track_flags, dev->gap3_len); pcjs_log(" bit rate 300: %.2f, temporary rate: %i, hole: %i, DMF: %i\n", bit_rate, temp_rate, (dev->disk_flags >> 1), dev->dmf); pcjs_log(" encoded_size: %i spt: %i\n", sector->encoded_size, dev->spt[0][0]); /* Set up 86F handlers */ d86f_handler[drive].disk_flags = disk_flags; d86f_handler[drive].side_flags = track_flags; d86f_handler[drive].writeback = null_writeback; d86f_handler[drive].set_sector = set_sector; d86f_handler[drive].read_data = poll_read_data; d86f_handler[drive].write_data = null_write_data; d86f_handler[drive].format_conditions = null_format_conditions; d86f_handler[drive].extra_bit_cells = null_extra_bit_cells; d86f_handler[drive].encoded_data = common_encoded_data; d86f_handler[drive].read_revolution = common_read_revolution; d86f_handler[drive].index_hole_pos = null_index_hole_pos; d86f_handler[drive].get_raw_size = common_get_raw_size; d86f_handler[drive].check_crc = 1; d86f_set_version(drive, 0x0063); d86f_common_handlers(drive); drives[drive].seek = pcjs_seek; } void pcjs_close(int drive) { pcjs_t *dev = images[drive]; if (dev == NULL) return; /* Unlink image from the system. */ d86f_unregister(drive); /* Release all the sector buffers. */ for (int c = 0; c < PCJS_MAX_TRACKS; c++) { for (int h = 0; h < PCJS_MAX_SIDES; h++) { for (uint16_t s = 0; s < PCJS_MAX_SECTORS; s++) { if (dev->sectors[c][h][s].data != NULL) free(dev->sectors[c][h][s].data); dev->sectors[c][h][s].data = NULL; } } } /* Release file table entries */ if(dev->file_table.entries != NULL) free(dev->file_table.entries); dev->file_table.entries = NULL; dev->file_table.num_entries = 0; if (dev->fp != NULL) (void) fclose(dev->fp); /* Release the memory. */ free(dev); images[drive] = NULL; }