Files
partial-FAT32/FAT32.c
2025-06-23 22:22:12 -04:00

662 lines
34 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Author: Alexander Wong
* Date: 04/18/22
* Description: The goal of this project is to replicate partial functionality of FAT32. There are two specific commands supported by
* this program. EXTRACT <target> and DIR. DIR will output the files and mimic (not fully) Windows' DIR command. The EXTRACT
* command will attempt to extract the target file specified. If the files is found, it will be output to the same directory
* as the program. If no file is found, an error message saying "File not found." will be displayed. For more information on how
* specific parts of the program work, it's highly recommended that you check the comments below.
* Changes:
* 05/03/22 - Fixed a malloc bug and implemented AM/ PM
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
// Defined constants used within the program
#define BUF_SIZE 512
#define SEC_SIZE 512
// More constants
const unsigned char ATTR_READ_ONLY = 0x01;
const unsigned char ATTR_HIDDEN = 0x02;
const unsigned char ATTR_SYSTEM = 0x04;
const unsigned char ATTR_VOLUME_ID = 0x08;
const unsigned char ATTR_DIRECTORY = 0x10;
const unsigned char ATTR_ARCHIVE = 0x20;
const unsigned char LAST_LONG_ENTRY = 0x40;
const unsigned char ATTR_LONG_NAME = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID;
const unsigned char ATTR_LONG_NAME_MASK = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE;
const char delim[2] = " ";
FILE *fp;
unsigned char buf[BUF_SIZE];
#pragma pack(push, 1) // Temporarily disable byte padding to ensure structs are properly sized
struct PartitionTableEntry // Holds the relevant bytes of the MBR
{
unsigned char bootFlag; // Boot Flag
unsigned char CHSBegin[3]; // Outdated
unsigned char typeCode; // Indicates FAT type
unsigned char CHSEnd[3]; // Outdated
unsigned int LBABegin; // The start of the partition block
unsigned int LBAEnd; // The end of the partition block
};
struct MBRStruct
{
unsigned char bootCode[446]; // Skip the boot code
struct PartitionTableEntry part1; // Partition 1
struct PartitionTableEntry part2; // Partition 2
struct PartitionTableEntry part3; // Partition 3
struct PartitionTableEntry part4; // Partition 4
unsigned short flag; // Should be 0x55 AA
} MBR;
struct BPBStruct
{
unsigned char BS_jmpBoot[3]; // Jump instruction to boot code
unsigned char BS_OEMNane[8]; // 8-Character string (not null terminated)
unsigned short BPB_BytsPerSec; // Had BETTER be 512!
unsigned char BPB_SecPerClus; // How many sectors make up a cluster?
unsigned short BPB_RsvdSecCnt; // # of reserved sectors at the beginning (including the BPB)?
unsigned char BPB_NumFATs; // How many copies of the FAT are there? (had better be 2)
unsigned short BPB_RootEntCnt; // ZERO for FAT32
unsigned short BPB_TotSec16; // ZERO for FAT32
unsigned char BPB_Media; // SHOULD be 0xF8 for "fixed", but isn't critical for us
unsigned short BPB_FATSz16; // ZERO for FAT32
unsigned short BPB_SecPerTrk; // Don't care; we're using LBA; no CHS
unsigned short BPB_NumHeads; // Don't care; we're using LBA; no CHS
unsigned int BPB_HiddSec; // Don't care ?
unsigned int BPB_TotSec32; // Total Number of Sectors on the volume
unsigned int BPB_FATSz32; // How many sectors long is ONE Copy of the FAT?
unsigned short BPB_Flags; // Flags (see document)
unsigned short BPB_FSVer; // Version of the File System
unsigned int BPB_RootClus; // Cluster number where the root directory starts (should be 2)
unsigned short BPB_FSInfo; // What sector is the FSINFO struct located in? Usually 1
unsigned short BPB_BkBootSec; // REALLY should be 6 (sector # of the boot record backup)
unsigned char BPB_Reserved[12]; // Should be all zeroes -- reserved for future use
unsigned char BS_DrvNum; // Drive number for int 13 access (ignore)
unsigned char BS_Reserved1; // Reserved (should be 0)
unsigned char BS_BootSig; // Boot Signature (must be 0x29)
unsigned char BS_VolID[4]; // Volume ID
unsigned char BS_VolLab[11]; // Volume Label
unsigned char BS_FilSysType[8]; // Must be "FAT32 "
unsigned char unused[420]; // Not used
unsigned char signature[2]; // MUST BE 0x55 AA
} BPB;
struct SDirectoryEntryStruct
{
unsigned char Name[11]; // Short name.
unsigned char Attr; // File attributes, see constants at the top of the file.The upper two bits of the attribute byte are reserved.
unsigned char NTRes; // Reserved for use by Windows NT.
unsigned char CrtTimeTenth; // Millisecond stamp at file creation time.
unsigned char _unused[6]; // Additional CRT optional bits that we're ignoring since we're not using.
unsigned short FstClusHI; // High word of entry's first cluster.
unsigned short WrtTime; // Time of last write.
unsigned short WrtDate; // Date of last write.
unsigned short FstClusLO; // Low word of this entry's first cluster.
unsigned int FileSize; // 32-bit file size in bytes.
};
struct LDirectoryEntryStruct
{
unsigned char Ord; // The order of this entry in the sequence of long dir entries associated with the short dir entry at the end of the long dir set. Masking with 0x40 means LAST_LONG_ENTRY.
unsigned char Name1[10]; // Characters 1-5 of the long-name sub-component in this dir entry.
unsigned char Attr; // Must be ATTR_LONG_NAME
unsigned char Type; // 0 = sub-component, non-zero implies other dirent types
unsigned char Chksum; // Checksum of name in the short dir entry at the end of the long dir set.
unsigned char Name2[12]; // Characters 6-11 of the long-name sub-component in this dir entry.
unsigned short FstClusLO; // Must be ZERO.
unsigned char Name3[4]; // Characters 12-13 of the long-name sub-component in this dir entry.
};
union DirectoryEntryUnion {
// Used to map bytes written to both rather than making two structs that take up x2 the space. Both have the same size which is why we can do this.
struct SDirectoryEntryStruct SDIR;
struct LDirectoryEntryStruct LDIR;
};
#pragma pack(pop) // Enable byte padding to ensure safe operation.
void constructShortName(unsigned char out[], unsigned char in[]) {
/*
* This function constructs the short name of a file by taking an
* input array and parsing the relevant characters out and putting
* them in an output array. Short names are guaranteed to have 8
* characters in length and then are followed by an extension.
*/
int cnt = 0;
while(cnt < 8 && in[cnt] != 0x20) out[cnt++] = in[cnt]; // Write the first 8 characters to output
if(in[8] != 0x20) {
int cntExt = 8;
out[cnt++] = '.'; // Add extension dot
while(cntExt < 11 && in[cntExt] != 0x20) out[cnt++] = in[cntExt++]; // Write the extension
}
}
void constructLongName(unsigned char out[], struct LDirectoryEntryStruct *entries, int rows) {
/*
* This function constructs the long name of a file by taking list of long name entries,
* the number of entries (rows), and the output array. We use a count of rows to know the
* length of entries since there is no safe way to guarantee entries since they can be dynamic
* in size. The way the output name is formed is fundamentally different from short name since
* we need to sift through the entries, combine the three names they have, and then combine the
* entries together. Furthermore, the entries can vary in length meaning some names will be populated
* with 0xFF to indicate the end.
*/
char Ord = 1; // Begin with the first entry,
int cnt = 0;
while(true) {
// This loop assembles the name based on a number of conditions
int i = 0;
for (; i < rows; i++) {
// Determine the order
if((entries[i].Ord & LAST_LONG_ENTRY) != 0 && (entries[i].Ord ^ LAST_LONG_ENTRY) == Ord) {
// For the case when the entry is masked as LAST_LONG_ENTRY
Ord++;
break;
}
if((int)entries[i].Ord == Ord) {
Ord++;
break;
}
}
// The next three for loops all do the same thing, except with different names (byte sections reserved for the name). 0xFF indicates nothing.
for(int j = 0; j < sizeof(entries[i].Name1); j++) {
if(entries[i].Name1[j] != 0x00 && entries[i].Name1[j] != 0xFF)
out[cnt++] = entries[i].Name1[j];
}
for(int j = 0; j < sizeof(entries[i].Name2); j++) {
if(entries[i].Name2[j] != 0x00 && entries[i].Name2[j] != 0xFF)
out[cnt++] = entries[i].Name2[j];
}
for(int j = 0; j < sizeof(entries[i].Name3); j++) {
if(entries[i].Name3[j] != 0x00 && entries[i].Name3[j] != 0xFF)
out[cnt++] = entries[i].Name3[j];
}
if((entries[i].Ord & LAST_LONG_ENTRY) == LAST_LONG_ENTRY) {
// When we reached the last entry we can break the loop.
break;
}
}
}
void listdir(unsigned long sector, unsigned long offset) {
/*
* This function lists the directory entries if there are any found and takes a
* starting sector and offset. These two variables are used to determine the data
* sectors starting location. Once the data sector has been located, this function
* will go through a cluster and retrieve the directory entries information. This
* information includes, short name, long name, date and time, file size, and the total
* number of files found and total bytes.
*/
union DirectoryEntryUnion DirEntry;
struct LDirectoryEntryStruct *LongEntries;
unsigned int dataSec = sector + offset; // Calculate the data sector
fseek(fp, SEC_SIZE * dataSec, SEEK_SET); // Seek to the data sector
int count = 0;
int row;
int longRows = 0;
int byteTot = 0;
do {
/*
* This outer loop is to run through all the directory entries as they are not contiguous.
* The first set of entries may be at x location but the next set after one cluster of dir
* entries will be y clusters away. This is found by finding the last cluster used by the
* last entry in the directory entries for its data. The next set of entries will follow
* after this. If we run out of entries before a cluster is fully read through, we're ran
* into the last directory entry.
*/
row = 0; // Reset for new cluster
do {
if (row == ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32)) break; // End of cluster
fread(buf, sizeof(DirEntry), 1 , fp); row++; // Read a new row of 32 bytes
memcpy(&DirEntry, buf, sizeof(DirEntry)); // Copy row to directory entry union
if (DirEntry.LDIR.Ord == 0 || DirEntry.LDIR.Ord == 0x2E) break; // Check for values that would indicate termination
if (((DirEntry.LDIR.Attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) && (DirEntry.LDIR.Ord != 0xE5)) { // Check for long entry
LongEntries = (struct LDirectoryEntryStruct *)malloc(sizeof(LongEntries) * DirEntry.LDIR.Ord ^ LAST_LONG_ENTRY); // Allocate long entry size
int entCount = 0x01; // Starting count
LongEntries[0] = DirEntry.LDIR; longRows++; // Store the first long entry and increment the number of long rows
while(entCount < (LongEntries[0].Ord ^ LAST_LONG_ENTRY)) {
// Save the next long row entry and increment our working row and the number of long rows stored
fread(buf, sizeof(DirEntry), 1 , fp); row++; longRows++;
memcpy(&DirEntry, buf, sizeof(DirEntry));
LongEntries[entCount++] = DirEntry.LDIR;
}
}
if (((DirEntry.LDIR.Attr & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME) && (DirEntry.LDIR.Ord != 0xE5)) { // Check for short entry
char *shortName[11];
char *longName[260];
unsigned long SecBuff;
if ((DirEntry.SDIR.Attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == 0x00) {
// If we have a valid entry that is not a directory or volume ID, process it
constructShortName(&shortName, &DirEntry.SDIR.Name); // Construct the short name
int hour = DirEntry.SDIR.WrtTime >> 11 & 0x1F;
char timeInd[2];
// Determine if the time is AM or PM
if ( hour >= 12) {
if (hour > 12) hour -= 12;
char timeInd[2] = {'P', 'M'};
} else {
char timeInd[2] = {'A', 'M'};
}
if (longRows > 0) { // If there is a long name we should have more than 0 rows
constructLongName(&longName, LongEntries, longRows); // Construct the long name
fprintf(stdout, "%02d/%02d/%d\t%02d:%02d:%02d %s\t%-10d\t%-15s\t%-15s\n", // Shifts and masks are based of FAT32gen doc from Microsoft.
DirEntry.SDIR.WrtDate & 0x1F,
DirEntry.SDIR.WrtDate >> 5 & 0x0F,
(DirEntry.SDIR.WrtDate >> 9 & 0xFF) + 1980,
hour,
DirEntry.SDIR.WrtTime >> 5 & 0x3F,
DirEntry.SDIR.WrtTime & 0x1F,
timeInd,
DirEntry.SDIR.FileSize,
shortName,
longName);
} else {
fprintf(stdout, "%02d/%02d/%d\t%02d:%02d:%02d %s\t%-10d\t%-15s\n", // Shifts and masks are based of FAT32gen doc from Microsoft.
DirEntry.SDIR.WrtDate & 0x1F,
DirEntry.SDIR.WrtDate >> 5 & 0x0F,
(DirEntry.SDIR.WrtDate >> 9 & 0xFF) + 1980,
hour,
DirEntry.SDIR.WrtTime >> 5 & 0x3F,
DirEntry.SDIR.WrtTime & 0x1F,
timeInd,
DirEntry.SDIR.FileSize,
shortName);
}
byteTot += DirEntry.SDIR.FileSize;
count++;
}
// These statements are included as reference since this was modeled off the instructions from the Microsoft documentation.
// Could be used for additional command support.
else if ((DirEntry.SDIR.Attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == ATTR_DIRECTORY) {
/* Found a directory. */
}
else if ((DirEntry.SDIR.Attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == ATTR_VOLUME_ID) {
/* Found a volume ID. */
}
else {
/* Found an invalid directory entry. */
}
memset(shortName, 0, sizeof(shortName)); // Clear the memory
memset(longName, 0, sizeof(longName)); // Clear the memory
longRows = 0;
}
} while(row != ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32)); // Loop until we've exhausted the clusters total number of rows
if (row == ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32)) { // Navigate to the next cluster by reading to the end of the last file and calculating its cluster
unsigned int nextOffset = (((DirEntry.SDIR.FileSize / BPB.BPB_BytsPerSec) / 8) + 1) * 8; // Determine offset due to data size
unsigned short clustLoc;
// Determine which cluster to use
if (DirEntry.SDIR.FstClusLO != 0) {
clustLoc = DirEntry.SDIR.FstClusLO;
}
else {
clustLoc = DirEntry.SDIR.FstClusHI;
}
fseek(fp, SEC_SIZE * (((clustLoc - 2) * BPB.BPB_SecPerClus) + dataSec + nextOffset), SEEK_SET); // Seek to the cluster with data offset to reach next dir entries
}
} while(row == ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32));
if (count == 0)
fprintf(stdout, "File not found.\n");
free(LongEntries); // Free the memory
fprintf(stdout, "File(s): %d\t Total Bytes: %d\n", count, byteTot);
}
void extractFile(union DirectoryEntryUnion DirEntry, char *target, unsigned long dataSec) {
/*
* extractFile will extract a file based on a given directory entry and output it to
* the target file specified. The function also takes a data sector to determine how
* far it needs to go. Once in the right position, the data fields are calulated,
* FAT is referenced, and the data is written to the file.
*/
int clustLoc;
// Determine the cluster location
if (DirEntry.SDIR.FstClusHI != 0)
clustLoc = DirEntry.SDIR.FstClusHI;
else
clustLoc = DirEntry.SDIR.FstClusLO;
// Calcualte the FAT related data, offsets, starting location, etc.
int fatOffset = clustLoc * 4;
int fatSecNum = BPB.BPB_RsvdSecCnt + (fatOffset / BPB.BPB_BytsPerSec);
int fatEntOffset = fatOffset % BPB.BPB_BytsPerSec;
FILE *outFile; // Output file
outFile = fopen(target, "wb");
if (outFile == NULL) {
printf("Unable to create a new file.");
return;
}
fseek(fp, SEC_SIZE * (MBR.part1.LBABegin + fatSecNum), SEEK_SET); // Navigate to the beginning of the fat
fseek(fp, fatEntOffset, SEEK_CUR); // Seek fatEntOffset bytes ahead of the starting section of the sector
unsigned int SecBuff;
unsigned char Cluster[BPB.BPB_SecPerClus * 512];
unsigned int wrkFSize = DirEntry.SDIR.FileSize; // Working file size
do {
fread(&SecBuff, 4, 1, fp); // Read a single FAT entry
int ClusEntryVal = SecBuff & 0x0FFFFFFF; // Mask everything but the 4 highest bits
int lastSeek = ftell(fp); // Store the last position
fseek(fp, SEC_SIZE * (((clustLoc - 2) * BPB.BPB_SecPerClus) + dataSec), SEEK_SET); // Navigate to the data
fread(Cluster, BPB.BPB_SecPerClus * 512, 1, fp); // Read the cluster
if (wrkFSize < BPB.BPB_SecPerClus * 512) { // If we're less than a cluster size, copy the remaining number of bytes based on wrkFSize
unsigned char RemainingBytes[wrkFSize];
memcpy(RemainingBytes, Cluster, wrkFSize); // Copy
fwrite(RemainingBytes, sizeof(RemainingBytes), 1, outFile); // Write to file
}
else
fwrite(Cluster, BPB.BPB_SecPerClus * 512, 1, outFile); // Write to file
fseek(fp, lastSeek, SEEK_SET); // Reset position to last seek value
if (ClusEntryVal >= 0x0FFFFFF8) { // Check for EOF
fclose(outFile);
printf("Successfully extracted!\n");
return;
} else
clustLoc = ClusEntryVal; // Update cluster location
if (wrkFSize > BPB.BPB_SecPerClus * 512) // Decrement the working file size until less than the a cluster size in bytes
wrkFSize -= BPB.BPB_SecPerClus * 512;
} while (true);
}
bool cmpStr(char *target, char target2[]) {
/*
* A wrapper of strcmp, ultimately this should live in an if statement and not a function.
* It lives here mostly as a result of a different string comparison function and ensuring
* comparisons are properly done.
*/
if (strcmp(target, target2) == 0) {
return true;
}
return false;
}
void extract(char *target, unsigned long sector, unsigned long offset) {
/*
* This function scans the directory entries if there are any found and takes a
* starting sector and offset. These two variables are used to determine the data
* sectors starting location. Once the data sector has been located, this function
* will go through a cluster and retrieve the directory entries information. This
* information includes, short name, long name, date and time, file size, and the total
* number of files found and total bytes. This directory entries name is then compared
* to the target name and if it matches it will attempt to extract the information. Much
* of this functionality is shared between list directory and ultimately should be
* moved into a single cohesive function in the future.
*/
union DirectoryEntryUnion DirEntry;
struct LDirectoryEntryStruct *LongEntries;
unsigned long dword;
unsigned int dataSec = sector + offset;
fseek(fp, SEC_SIZE * dataSec, SEEK_SET);
int count = 0;
int row;
int longRows = 0;
int byteTot = 0;
bool foundFile = false;
do {
/*
* This outer loop is to run through all the directory entries as they are not contiguous.
* The first set of entries may be at x location but the next set after one cluster of dir
* entries will be y clusters away. This is found by finding the last cluster used by the
* last entry in the directory entries for its data. The next set of entries will follow
* after this. If we run out of entries before a cluster is fully read through, we're ran
* into the last directory entry.
*/
row = 0; // Reset for new cluster
do {
if (row == ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32)) break; // End of cluster
fread(buf, sizeof(DirEntry), 1 , fp); row++; // Read a new row of 32 bytes
memcpy(&DirEntry, buf, sizeof(DirEntry)); // Copy row to directory entry union
if (DirEntry.LDIR.Ord == 0 || DirEntry.LDIR.Ord == 0x2E) break; // Check for values that would indicate termination
if (((DirEntry.LDIR.Attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) && (DirEntry.LDIR.Ord != 0xE5)) { // Check for long entry
LongEntries = (struct LDirectoryEntryStruct *)malloc(sizeof(LongEntries) * DirEntry.LDIR.Ord ^ LAST_LONG_ENTRY); // Allocate long entry size
int entCount = 0x01; // Starting count
LongEntries[0] = DirEntry.LDIR; longRows++; // Store the first long entry and increment the number of long rows
while(entCount < (LongEntries[0].Ord ^ LAST_LONG_ENTRY)) {
// Save the next long row entry and increment our working row and the number of long rows stored
fread(buf, sizeof(DirEntry), 1 , fp); row++; longRows++;
memcpy(&DirEntry, buf, sizeof(DirEntry));
LongEntries[entCount++] = DirEntry.LDIR;
}
}
if (((DirEntry.LDIR.Attr & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME) && (DirEntry.LDIR.Ord != 0xE5)) {
char *shortName[11];
char *longName[260];
unsigned long SecBuff;
if ((DirEntry.SDIR.Attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == 0x00) {
// If we have a valid entry that is not a directory or volume ID, process it
constructShortName(&shortName, &DirEntry.SDIR.Name); // Construct the short name
if (longRows > 0) { // If there is a long name we should have more than 0 rows
constructLongName(&longName, LongEntries, longRows); // Construct the long name
if (cmpStr(target, longName)) { // Compare the names
extractFile(DirEntry, target, dataSec);
return;
}
}
if (cmpStr(target, shortName)) { // Compare the names
extractFile(DirEntry, target, dataSec);
return;
}
}
// These statements are included as reference since this was modeled off the instructions from the Microsoft documentation.
// Could be used for additional command support.
else if ((DirEntry.SDIR.Attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == ATTR_DIRECTORY) {
/* Found a directory. */
}
else if ((DirEntry.SDIR.Attr & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == ATTR_VOLUME_ID) {
/* Found a volume ID. */
}
else {
/* Found an invalid directory entry. */
}
memset(shortName, 0, sizeof(shortName)); // Clear the memory
memset(longName, 0, sizeof(longName)); // Clear the memory
longRows = 0;
}
} while(row != ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32)); // Loop until we've exhausted the clusters total number of rows
if (row == ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32)) { // Navigate to the next cluster by reading to the end of the last file and calculating its cluster
unsigned int nextOffset = (((DirEntry.SDIR.FileSize / BPB.BPB_BytsPerSec) / 8) + 1) * 8; // Determine offset due to data size
unsigned short clustLoc;
// Determine which cluster to use
if (DirEntry.SDIR.FstClusLO != 0) {
clustLoc = DirEntry.SDIR.FstClusLO;
}
else {
clustLoc = DirEntry.SDIR.FstClusHI;
}
fseek(fp, SEC_SIZE * (((clustLoc - 2) * BPB.BPB_SecPerClus) + dataSec + nextOffset), SEEK_SET); // Seek to the cluster with data offset to reach next dir entries
}
} while(row == ((BPB.BPB_BytsPerSec * BPB.BPB_SecPerClus) / 32));
fprintf(stdout, "File not found.\n");
free(LongEntries); // Free the memory
}
int chkFAT32() {
/*
* Checks against a list of know conditions to see if the specified file
* that was input is indeed a FAT32 drive.
*/
if (BPB.BPB_BytsPerSec != 512) {
fprintf(stderr, "ERROR: Expected 512 bytes per sector, got %d.\n", BPB.BPB_BytsPerSec);
return 1;
}
if (strncmp(BPB.BS_FilSysType, "FAT32 ", 8) != 0) {
fprintf(stderr, "ERROR: File system type mismatch, expected \"FAT32 \", got %.*s.\n", (int)sizeof(BPB.BS_FilSysType), BPB.BS_FilSysType);
return 1;
}
if (BPB.BS_Reserved1 != 0) {
fprintf(stderr, "ERROR: Unexpected value for reserved byte, drive may be corrupted or not FAT32.\n");
return 1;
}
if (BPB.BPB_RootEntCnt != 0) {
fprintf(stderr, "ERROR: Unexpected value for root entry count, drive may be corrupted or not FAT32.\n");
return 1;
}
if (BPB.BPB_TotSec16 != 0) {
fprintf(stderr, "ERROR: Unexpected value for 16 bit total sector count, drive may be corrupted or not FAT32.\n");
return 1;
}
if (BPB.BPB_FATSz16 != 0) {
fprintf(stderr, "ERROR: Unexpected value for number of 16-bit sectors occupied, drive may be corrupted or not FAT32.\n");
return 1;
}
return 0;
}
int main(int argc, char *argv[]) {
char* prgArgs[BUF_SIZE] = { NULL }; // Initialize a null array of program arguments. This is likely larger than needed.
if (argc > 2) { // Check argument length
fprintf(stderr, "Expected 1 argument, got %d", argc - 1);
exit(1);
}
fp = fopen(argv[1], "rb");
if(fp == NULL) { // Check if file was opened
fprintf(stderr, "Unable to open the specified file.");
exit(1);
}
fread(buf, BUF_SIZE, 1, fp); // Read the first BUF_SIZE bytes
memcpy(&MBR, buf, sizeof(struct MBRStruct)); // Copy bytes to MBR struct
fseek(fp, SEC_SIZE * MBR.part1.LBABegin - ftell(fp), SEEK_CUR); // Jump to Partition 1
fread(buf, BUF_SIZE, 1, fp); // Read BUF_SIZE bytes
memcpy(&BPB, buf, sizeof(struct BPBStruct)); // Copy bytes to BPB struct
char cmdInp[BUF_SIZE];
fseek(fp, 0L, SEEK_SET); // Reset the seek
if(chkFAT32()) exit(1); // Check FAT32
// Calculate the location of the first sector of data cluster
int dataSec = BPB.BPB_RsvdSecCnt + (BPB.BPB_NumFATs * BPB.BPB_FATSz32);
int fstSecOfClust = ((BPB.BPB_RootClus - 2) * BPB.BPB_SecPerClus) + dataSec;
while(true) {
/*
* This is the main loop for getting user input, parsing it, and executing the
* appropriate function based on the command input.
*/
printf("\\> ");
char *tokens = (char *)malloc(sizeof(char) * BUF_SIZE); // Used for string tokenization
fgets(cmdInp, BUF_SIZE, stdin); // Load the user input
cmdInp[strcspn(cmdInp, "\n")] = 0; // Clear newline
tokens = strtok(cmdInp, delim); // Tokenize
int count = 0;
int replaced = 0;
bool done = false;
bool quotesFound = false;
while(tokens != NULL && strcmp(tokens, "\n") != 0) {
/*
* This loop will tokenize the arguments and store them
* in prgArgs. If there is an entry with double quotes, "",
* The string needs to be reconstructed. A better way would
* actually be to split by the quotation marks when first found
* and then ovewrite, however, this solution was found after implementing
* the current one which functions.
*/
prgArgs[count] = tokens;
if (prgArgs[count] != NULL && prgArgs[count][0] == '\"') {
// Checks for a quotation mark, if found, enter the loop trap for processing quotation marks.
quotesFound = true; // Quotes found!
while(!done && tokens != NULL) {
/*
* This loop acts as the main loop for tokenizing, but keeping everything restricted
* under the context of enter a quoted input of n-args/ length.
*/
tokens = strtok(NULL, delim); // Tokenize
int i;
if (tokens == NULL) {
break;
}
for (i = 0; i < strlen(tokens); i++) {
/*
* Checks for end of quote and instances where we
* have invalid quotes that will exceed count.
* Note: Might not work as intended but not bugs have
* arisen so re-evaluate later possibly.
*/
if (tokens[i] == '\"' && i == strlen(tokens)) {
count++;
done = true;
break;
} else if (tokens[i] == '\"') {
done = true;
break;
}
}
// This is messy code to append characters. It is like this due to memory overwriting issues historically.
// This code avoids that when concatenated.
char *ch = ' ';
char *append = (char *)malloc(sizeof(char) * (i + 1)); // Allocate based on the size of the token being added (account for space too)
strcat(append, &ch); // Add space
strncat(append, tokens, i + 1); // Add token
strcat(prgArgs[count], append); // Append to the program arguments
free(append); // Free memory
}
tokens = NULL; // Clear tokens, we know that it SHOULD be empty but it is not when it rolls around due to BAD MANAGEMENT of memory
replaced = 0;
for(int i = 0; i < strlen(prgArgs[count]); i++) {
// Removes the quotation marks from the string
if(prgArgs[count][i] == '\"') {
replaced++;
for (int j = i; j < strlen(prgArgs[count]); j++)
prgArgs[count][j] = prgArgs[count][j + 1];
}
}
}
tokens = strtok(NULL, delim); // Tokenize
count++;
}
if (count == 0) {
// Do nothing
} else if (quotesFound && replaced != 2) {
printf("ERROR: Quotation marks, you either forgot to close or start with one.\n");
} else {
/*
* Process user input and execute the corresponding command.
*/
if(!strcmp(prgArgs[0], "DIR")) {
if (count != 1)
printf("ERROR: Incorrect number of arguments, expected 1 got %d.\n", count);
else
listdir(fstSecOfClust, MBR.part1.LBABegin);
} else if (!strcmp(prgArgs[0], "EXTRACT")) {
if (count != 2)
printf("ERROR: Incorrect number of arguments, expected 2 got %d.\n", count);
else {
extract(prgArgs[1], fstSecOfClust, MBR.part1.LBABegin);
}
} else if (!strcmp(prgArgs[0], "QUIT")) {
if (count != 1)
printf("ERROR: Incorrect number of arguments, expected 1 got %d.\n", count);
else {
fclose(fp);
exit(0);
}
} else {
fprintf(stderr, "ERROR: Invalid argument, you must either specify DIR, EXTRACT, or QUIT.\n");
}
}
free(tokens); // Free memory
}
}