/* * File Analysis Functions * Written by Michael Rywalt - 2569 * Operating System Concepts */ #include "file.h" #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) /*********************** * VerifyFSMagicNumber * *********************** * * Function: VerifyFSMagicNumber * * Purpose: Makes sure we're actually looking at an EXT2 filesystem. * * Parameters: Pointer to superblock. * * Returns: TRUE if filesystem is EXT2, * FALSE if filesystem is not. */ BOOL VerifyFSMagicNumber(tsSuperBlock *pSuperBlock) { if(pSuperBlock->s_magic == EXT2_SUPER_MAGIC) { return TRUE; } return FALSE; } /*************** * GetFileSize * *************** * * Function: GetFileSize * * Purpose: To get the size of the file, so we know * how much space to allocate. * * Parameters: Filename. * * Returns: Size of the file. */ int GetFileSize(char *szFileName) { FILE *fp; int iSize = -1; fp = fopen(szFileName, "rb"); if(fp) { fseek(fp, 0L, SEEK_END); iSize = ftell(fp); fclose(fp); } return iSize; } /****************** * LoadFileSystem * ****************** * * Function: LoadFileSystem * * Purpose: Loads the first 100KB of the filesystem into memory. * If the filesystem is smaller than 100KB, this will * load only the filesystem's size. * * Parameters: Filename of the filesystem on disk. * * Returns: Pointer to super block of newly allocated filesystem. * NULL if failed. */ tsSuperBlock *LoadFileSystem(char *szFileName) { FILE *fp; void *pReturn = NULL; int iFileSystemSize; iFileSystemSize = GetFileSize(szFileName); /* If the size is ridiculous, exit now. */ if(iFileSystemSize < SUPERBLOCK_SIZE) { return NULL; } fp = fopen(szFileName, "rb"); if(fp) { /* Allocate enough memory for whole filesystem. * * NOTE: * * I didn't want to have to do this in the event the filesystem was huge. * The reason is, I cannot get fwrite to at the seek point; * it keeps writing at the end of the file no matter what I do. So I must * read the whole filesystem and then save it back out if we plan on * modifying it in some way (i.e. using lnhdir program). */ pReturn = malloc(iFileSystemSize); if(pReturn) { /* Read the whole filesystem into memory. */ fread(pReturn, 1, iFileSystemSize, fp); /* Close the file pointer, since we're done with it. */ fclose(fp); } } /* Return pointer to the filesystem, or NULL if failed. */ return (tsSuperBlock*) pReturn; } /****************************** * SaveModifiedFileSystemData * ****************************** * * Function: SaveModifiedFileSystemData * * Purpose: To save only that data which has been modified back into the fs file. * We are saving N bytes from starting point K. * * Parameters: Filename, * pointer to data, * Length of data to write. * * Returns: True if successful, False if not. */ BOOL SaveModifiedFileSystemData(char *szFileName, void *pData, int iLengthToWrite) { FILE *fp; fp = fopen(szFileName, "wb"); if(fp) { fwrite(pData, 1, iLengthToWrite, fp); fclose(fp); return TRUE; } return FALSE; } /****************** * FreeFileSystem * ****************** * * Function: FreeFileSystem * * Purpose: Frees up memory allocated for the file system. * * Parameters: Pointer to file system memory. * * Returns: Nothing. */ void FreeFileSystem(tsSuperBlock *pFileSystem) { if(pFileSystem) { free(pFileSystem); pFileSystem = NULL; } } /****************** * GetFSBlockSize * ****************** * * Function: GetFSBlockSize * * Purpose: Looks at superblock to determine block size for this file system. * * Parameters: Super Block structure pointer. * * Returns: Block size in bytes. */ int GetFSBlockSize(const tsSuperBlock *pSuperBlock) { return 1024 << pSuperBlock->s_log_block_size; } /********************* * BlockNumToPointer * ********************* * * Function: BlockNumToPointer * * Purpose: To convert a block number to the correct offset into the block group. * * Parameters: Block number, pointer to start of filesystem (after boot block). * * Returns: Pointer to desired block. */ void *BlockNumToPointer(int iBlockNum, const void *pFileSystem) { tsSuperBlock *pSuperBlock = (tsSuperBlock*)pFileSystem; char *pcOffset = (char*)pFileSystem + (iBlockNum * GetFSBlockSize(pSuperBlock)); return (void*)pcOffset; } /**************************** * FindGroupDescriptorStart * **************************** * * Function: FindGroupDescriptorStart * * Purpose: Locates the start of the group descriptors blocks. * * Parameters: Pointer to the superblock. * * Returns: Pointer to the group descriptors block start. */ tsGroupDescriptor *FindGroupDescriptorStart(const void *pFileSystem) { tsSuperBlock *pSuperBlock = (tsSuperBlock*)((char*)pFileSystem + SUPERBLOCK_OFFSET); int iBlockSize = GetFSBlockSize(pSuperBlock); int iStartOffset = pSuperBlock->s_first_data_block * iBlockSize; tsGroupDescriptor *pGroupDescriptor = (tsGroupDescriptor*)((char*)pFileSystem + iStartOffset + iBlockSize); return pGroupDescriptor; } /*********************** * FindInodeTableStart * *********************** * * Function: FindInodeTableStart * * Purpose: Locates the first Inode for this block. * * Parameters: Filesystem pointer. * * Returns: Pointer to first Inode. */ tsInode *FindInodeTableStart(const void *pFileSystem) { tsSuperBlock *pSuperBlock = (tsSuperBlock*)((char*)pFileSystem + SUPERBLOCK_OFFSET); int iBlockSize = GetFSBlockSize(pSuperBlock); char *pOffset; tsGroupDescriptor *pGroupDescriptor; tsInode *pInode; pGroupDescriptor = FindGroupDescriptorStart(pFileSystem); /* Return pointer to first Inode table entry. */ pOffset = (((char*)pFileSystem) + ((pGroupDescriptor->bg_inode_table) * iBlockSize)); pInode = (tsInode*) pOffset; return pInode; } /**************** * GetNextInode * **************** * * Function: GetNextInode * * Purpose: Returns pointer to the next Inode table entry. * * Parameters: Pointer to the current inode table. * * Returns: Next Inode table entry pointer. */ tsInode *GetNextInode(const tsInode *pInode, void *pFileSystem) { tsSuperBlock *pSuperBlock = (tsSuperBlock*)((char*)pFileSystem + SUPERBLOCK_OFFSET); tsInode *pOffsetInode = (tsInode*)((char*)pInode + pSuperBlock->s_inode_size); return pOffsetInode; } /************ * GetInode * ************ * * Function: GetInode * * Purpose: Returns an Inode by its number in the table. * * Parameters: Inode number (starting at 0 = first inode), * pointer to the filesystem. * * Returns: Pointer to inode structure. */ tsInode *GetInode(int iInodeNum, void *pFileSystem) { int iInodeIndex; tsInode *pInodeStart = FindInodeTableStart(pFileSystem); tsInode *pInodeIterator = pInodeStart; /* Locate the desired inode table entry. */ for(iInodeIndex = 0; iInodeIndex < iInodeNum; ++iInodeIndex) { pInodeIterator = GetNextInode(pInodeIterator, pFileSystem); } return pInodeIterator; } /**************** * GetDataBlock * **************** * * Function: GetDataBlock * * Purpose: Returns a pointer to the desired data block. * * Parameters: Inode pointer, * pointer to filesystem. * * Returns: Pointer to desired data block on disk. */ void *GetDataBlock(const tsInode *pInode, const void *pFileSystem) { tsSuperBlock *pSuperBlock = (tsSuperBlock*)((char*)pFileSystem + SUPERBLOCK_OFFSET); int iBlockSize = GetFSBlockSize(pSuperBlock); void *pDataBlock = (void*)((char*)pFileSystem + (pInode->i_block[0] * iBlockSize)); return pDataBlock; } /******************** * GetMatchingInode * ******************** * * Function: GetMatchingInode * * Purpose: Pass it a file/dir name, and it will return the inode pointer to it. * Useful for our purposes in creating a hard link to a directory. * * Note: For this project, this function only searches the current dir inode. * For practical applications, it should search the entire filesystem. * That is, however, beyond the scope of the project. This is still * pretty generic, as we can give it any inode and it should be able * to locate the desired one, if it exists in the current directory. * * Parameters: Name, * Current Inode, * Pointer to filesystem. * * Returns: Pointer to desired Inode, or NULL if it wasn't found. */ tsInode *GetMatchingInode(char *szName, tsInode *pCurDirInode, void *pFileSystem) { int iRecordLenSum = 0; tsDirEntry *pDirIterator; tsInode *pInode; /* First, we need to get a pointer to the current directory inode table. */ pDirIterator = (tsDirEntry*)GetDataBlock(pCurDirInode, pFileSystem); /* Now we need to search the table for the corrsponding string. */ while(iRecordLenSum < pCurDirInode->i_size) { iRecordLenSum += pDirIterator->rec_len; /* If we find a match, return immediately with a pointer to our match. */ if(strcmp(pDirIterator->name, szName) == 0) { pInode = GetInode((pDirIterator->inode - 1), pFileSystem); return pInode; } pDirIterator = (tsDirEntry*)(((char*)pDirIterator) + pDirIterator->rec_len); } /* If we didn't return from the while loop, we didn't find a match. */ return NULL; } /****************** * InsertHardLink * ****************** * * Function: InsertHardLink * * Purpose: To add a hard link to the current directory. * * Parameters: Name of the filesystem file, * Name of the link, * the directory Inode pointer, * Pointer to the filesystem. * * Returns: Error level: * 1 = Success, * -1 = Failed - the Inode is not for a directory, * -2 = Failed - the linkname coincides with an existing file/dir in the directory. */ int InsertHardLink(char *szFileSysName, char *szLinkName, tsInode *pInode, void *pFileSystem) { short wPadRecordSize = 0; short wLastRecordSize = 0; int iRecordLenSum = 0; int iLengthOfLinkName = strlen(szLinkName); short wNewRecordSize; int iThisInode; tsDirEntry *pDirIterator = (tsDirEntry*)GetDataBlock(pInode, pFileSystem); /* Make absolute sure we're not trying link to anything OTHER than a directory. */ if(pDirIterator->file_type != EXT2_FT_DIR) { return -1; } /* Calculate the length of the new hard link record and land on 4 byte boundary. */ wNewRecordSize = (short)((8 + iLengthOfLinkName + 3) & ~ 3); /* We want the inode to THIS directory. */ iThisInode = pDirIterator->inode; /* We need to increment this now so we break out of the while-loop ON the last dir entry. */ iRecordLenSum += pDirIterator->rec_len; /* Iterate to last dir entry. */ while(iRecordLenSum < pInode->i_size) { pDirIterator = (tsDirEntry*)(((char*)pDirIterator) + pDirIterator->rec_len); if(!strcmp(szLinkName, pDirIterator->name)) { return -2; } iRecordLenSum += pDirIterator->rec_len; } /* Save our padded length. */ wPadRecordSize = pDirIterator->rec_len; /* Calculate our last record size before incrementing. Make sure it lands on 4 byte boundary. */ wLastRecordSize = (short)(pDirIterator->name_len + 8 + 3) & ~ 3; /* Set it to the size of our new entry. */ pDirIterator->rec_len = wLastRecordSize; /* Decrement iRecordSize. */ wPadRecordSize -= wLastRecordSize; /* Move up one entry to an empty spot. */ pDirIterator = (tsDirEntry*)(((char*)pDirIterator) + wLastRecordSize); /* Create new entry information. */ pDirIterator->inode = iThisInode; pDirIterator->rec_len = wPadRecordSize; pDirIterator->name_len = strlen(szLinkName); pDirIterator->file_type = EXT2_FT_DIR; strcpy(pDirIterator->name, szLinkName); return TRUE; } /************************* * PrintDirectoryListing * ************************* * * Function: PrintDirectoryListing * * Purpose: Traverses directory and prints out list of names * together with file types. * * Parameters: Directory Inode, * Pointer to filesystem. * * Returns: Nothing. */ void PrintDirectoryListing(const tsInode *pInode, const void *pFileSystem) { int iRecordLenSum = 0; tsDirEntry *pDirIterator; printf("\nFileType FileName\n------------------\n"); if(pFileSystem && pInode) { pDirIterator = (tsDirEntry*)GetDataBlock(pInode, pFileSystem); while(iRecordLenSum < pInode->i_size) { iRecordLenSum += pDirIterator->rec_len; PrintFileType(pDirIterator); printf("%s\n", pDirIterator->name); pDirIterator = (tsDirEntry*)(((char*)pDirIterator) + pDirIterator->rec_len); } } } /***************** * PrintFileType * ***************** * * Function: PrintFileType * * Purpose: Prints the type of file based on the type enumeration * field in the directory entry. * * Parameters: Directory entry. * * Returns: Nothing. */ void PrintFileType(const tsDirEntry *pDirEntry) { /* File type strings. */ char *szFileType[] = {"Unknown ", "File ", "Directory ", "Char Dev ", "Block Dev ", "FIFO Pipe ", "Socket ", "Symlink ", "Error "}; /* Print the appropriate string. */ if(pDirEntry) { switch(pDirEntry->file_type) { case EXT2_FT_UNKNOWN: printf("%s", szFileType[EXT2_FT_UNKNOWN]); break; case EXT2_FT_REG_FILE: printf("%s", szFileType[EXT2_FT_REG_FILE]); break; case EXT2_FT_DIR: printf("%s", szFileType[EXT2_FT_DIR]); break; case EXT2_FT_CHRDEV: printf("%s", szFileType[EXT2_FT_CHRDEV]); break; case EXT2_FT_BLKDEV: printf("%s", szFileType[EXT2_FT_BLKDEV]); break; case EXT2_FT_FIFO: printf("%s", szFileType[EXT2_FT_FIFO]); break; case EXT2_FT_SOCK: printf("%s", szFileType[EXT2_FT_SOCK]); break; case EXT2_FT_SYMLINK: printf("%s", szFileType[EXT2_FT_SYMLINK]); break; default: printf("%s", szFileType[EXT2_FT_MAX]); break; } } }