/* * PROJECT: Mke2fs * FILE: Disk.c * PROGRAMMER: Matt Wu * HOMEPAGE: http://ext2.yeah.net */ /* INCLUDES **************************************************************/ #include "Mke2fs.h" #include #include /* GLOBALS ***************************************************************/ int inode_ratio = 4096; BOOLEAN bLocked = FALSE; /* This is needed for the ext2fs driver to mount the volume */ #define ZAP_BOOTBLOCK /* FUNCTIONS *************************************************************/ int int_log2(int arg) { int l = 0; arg >>= 1; while (arg) { l++; arg >>= 1; } return l; } int int_log10(unsigned int arg) { int l; for (l=0; arg ; l++) arg = arg / 10; return l; } static char default_str[] = "default"; struct mke2fs_defaults { const char *type; int size; int blocksize; int inode_ratio; } settings[] = { { default_str, 0, 4096, 8192 }, { default_str, 512, 1024, 4096 }, { default_str, 3, 1024, 8192 }, { "journal", 0, 4096, 8192 }, { "news", 0, 4096, 4096 }, { "largefile", 0, 4096, 1024 * 1024 }, { "largefile4", 0, 4096, 4096 * 1024 }, { 0, 0, 0, 0}, }; void set_fs_defaults(const char *fs_type, PEXT2_SUPER_BLOCK super, int blocksize, int *inode_ratio) { int megs; int ratio = 0; struct mke2fs_defaults *p; megs = (super->s_blocks_count * (EXT2_BLOCK_SIZE(super) / 1024) / 1024); if (inode_ratio) ratio = *inode_ratio; if (!fs_type) fs_type = default_str; for (p = settings; p->type; p++) { if ((strcmp(p->type, fs_type) != 0) && (strcmp(p->type, default_str) != 0)) continue; if ((p->size != 0) && (megs > p->size)) continue; if (ratio == 0) *inode_ratio = p->inode_ratio; if (blocksize == 0) { super->s_log_frag_size = super->s_log_block_size = int_log2(p->blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); } } if (blocksize == 0) { super->s_blocks_count /= EXT2_BLOCK_SIZE(super) / 1024; } } /* * Helper function which zeros out _num_ blocks starting at _blk_. In * case of an error, the details of the error is returned via _ret_blk_ * and _ret_count_ if they are non-NULL pointers. Returns 0 on * success, and an error code on an error. * * As a special case, if the first argument is NULL, then it will * attempt to free the static zeroizing buffer. (This is to keep * programs that check for memory leaks happy.) */ bool zero_blocks(PEXT2_FILESYS fs, ULONG blk, ULONG num, ULONG *ret_blk, ULONG *ret_count) { ULONG j, count; static unsigned char *buf; bool retval; /* If fs is null, clean up the static buffer and return */ if (!fs) { if (buf) { RtlFreeHeap(RtlGetProcessHeap(), 0, buf); buf = 0; } return true; } #define STRIDE_LENGTH 8 /* Allocate the zeroizing buffer if necessary */ if (!buf) { buf = (unsigned char *) RtlAllocateHeap(RtlGetProcessHeap(), 0, fs->blocksize * STRIDE_LENGTH); if (!buf) { DPRINT1("Mke2fs: while allocating zeroizing buffer"); if (ret_blk) *ret_blk = blk; return false; } memset(buf, 0, fs->blocksize * STRIDE_LENGTH); } /* OK, do the write loop */ for (j=0; j < num; j += STRIDE_LENGTH, blk += STRIDE_LENGTH) { if (num-j > STRIDE_LENGTH) count = STRIDE_LENGTH; else count = num - j; retval = NT_SUCCESS(Ext2WriteDisk( fs, ((ULONGLONG)blk * fs->blocksize), count * fs->blocksize, buf)); if (!retval) { if (ret_count) *ret_count = count; if (ret_blk) *ret_blk = blk; return retval; } } return true; } bool zap_sector(PEXT2_FILESYS Ext2Sys, int sect, int nsect) { unsigned char *buf; ULONG *magic; buf = (unsigned char *) RtlAllocateHeap(RtlGetProcessHeap(), 0, SECTOR_SIZE*nsect); if (!buf) { DPRINT1("Mke2fs: Out of memory erasing sectors %d-%d\n", sect, sect + nsect - 1); return false; } #define BSD_DISKMAGIC (0x82564557UL) /* The disk magic number */ #define BSD_MAGICDISK (0x57455682UL) /* The disk magic number reversed */ #define BSD_LABEL_OFFSET 64 if (sect == 0) { Ext2ReadDisk( Ext2Sys, (LONGLONG)(sect * SECTOR_SIZE), SECTOR_SIZE, buf); // Check for a BSD disklabel, and don't erase it if so magic = (ULONG *) (buf + BSD_LABEL_OFFSET); if ((*magic == BSD_DISKMAGIC) || (*magic == BSD_MAGICDISK)) goto clean_up; } memset(buf, 0, (ULONG)nsect * SECTOR_SIZE); // Write buf to disk Ext2WriteDisk( Ext2Sys, (LONGLONG)(sect * SECTOR_SIZE), (ULONG)nsect * SECTOR_SIZE, buf ); clean_up: RtlFreeHeap(RtlGetProcessHeap(), 0, buf); return true; } bool ext2_mkdir( PEXT2_FILESYS fs, ULONG parent, ULONG inum, char *name, ULONG *no, PEXT2_INODE pid ) { bool retval; EXT2_INODE parent_inode, inode; ULONG ino = inum; //ULONG scratch_ino; ULONG blk; char *block = 0; int filetype = 0; LARGE_INTEGER SysTime; NtQuerySystemTime(&SysTime); /* * Allocate an inode, if necessary */ if (!ino) { retval = ext2_new_inode(fs, parent, LINUX_S_IFDIR | 0755, 0, &ino); if (!retval) goto cleanup; } if (no) *no = ino; /* * Allocate a data block for the directory */ retval = ext2_new_block(fs, 0, 0, &blk); if (!retval) goto cleanup; /* * Create a scratch template for the directory */ retval = ext2_new_dir_block(fs, ino, parent, &block); if (!retval) goto cleanup; /* * Get the parent's inode, if necessary */ if (parent != ino) { retval = ext2_load_inode(fs, parent, &parent_inode); if (!retval) goto cleanup; } else { memset(&parent_inode, 0, sizeof(parent_inode)); } /* * Create the inode structure.... */ memset(&inode, 0, sizeof(EXT2_INODE)); inode.i_mode = (USHORT)(LINUX_S_IFDIR | (0777 & ~fs->umask)); inode.i_uid = inode.i_gid = 0; inode.i_blocks = fs->blocksize / 512; inode.i_block[0] = blk; inode.i_links_count = 2; RtlTimeToSecondsSince1970(&SysTime, &inode.i_mtime); inode.i_ctime = inode.i_atime = inode.i_mtime; inode.i_size = fs->blocksize; /* * Write out the inode and inode data block */ retval = ext2_write_block(fs, blk, block); if (!retval) goto cleanup; retval = ext2_save_inode(fs, ino, &inode); if (!retval) goto cleanup; if (pid) { *pid = inode; } if (parent != ino) { /* * Add entry for this inode to parent dir 's block */ if (fs->ext2_sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) filetype = EXT2_FT_DIR; retval = ext2_add_entry(fs, parent, ino, filetype, name); if (!retval) goto cleanup; /* * Update parent inode's counts */ parent_inode.i_links_count++; retval = ext2_save_inode(fs, parent, &parent_inode); if (!retval) goto cleanup; } /* * Update accounting.... */ ext2_block_alloc_stats(fs, blk, +1); ext2_inode_alloc_stats2(fs, ino, +1, 1); cleanup: if (block) { RtlFreeHeap(RtlGetProcessHeap(), 0, block); block = NULL; } return retval; } bool create_root_dir(PEXT2_FILESYS fs) { bool retval; EXT2_INODE inode; retval = ext2_mkdir(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, 0, NULL, &inode); if (!retval) { DPRINT1("Mke2fs: while creating root dir"); return false; } { inode.i_uid = 0; inode.i_gid = 0; retval = ext2_save_inode(fs, EXT2_ROOT_INO, &inode); if (!retval) { DPRINT1("Mke2fs: while setting root inode ownership"); return false; } } return true; } bool create_lost_and_found(PEXT2_FILESYS Ext2Sys) { bool retval; ULONG ino; char *name = "lost+found"; int lpf_size = 0; EXT2_INODE inode; ULONG dwBlk = 0; BOOLEAN bExt= TRUE; PEXT2_DIR_ENTRY dir; char * buf; buf = (char *)RtlAllocateHeap(RtlGetProcessHeap(), 0, Ext2Sys->blocksize); if (!buf) { bExt = FALSE; } else { memset(buf, 0, Ext2Sys->blocksize); dir = (PEXT2_DIR_ENTRY) buf; dir->rec_len = Ext2Sys->blocksize; } Ext2Sys->umask = 077; retval = ext2_mkdir(Ext2Sys, EXT2_ROOT_INO, 0, name, &ino, &inode); if (!retval) { DPRINT1("Mke2fs: while creating /lost+found.\n"); return false; } if (!bExt) goto errorout; lpf_size = inode.i_size; while(TRUE) { if (lpf_size >= 16*1024) break; retval = ext2_alloc_block(Ext2Sys, 0, &dwBlk); if (! retval) { DPRINT1("Mke2fs: create_lost_and_found: error alloc block.\n"); break; } retval = ext2_expand_inode(Ext2Sys, &inode, dwBlk); if (!retval) { DPRINT1("Mke2fs: errors when expanding /lost+found.\n"); break; } ext2_write_block(Ext2Sys, dwBlk, buf); inode.i_blocks += (Ext2Sys->blocksize/SECTOR_SIZE); lpf_size += Ext2Sys->blocksize; } { inode.i_size = lpf_size; ASSERT( (inode.i_size/Ext2Sys->blocksize) == Ext2DataBlocks(Ext2Sys, inode.i_blocks/(Ext2Sys->blocksize/SECTOR_SIZE))); ASSERT( (inode.i_blocks/(Ext2Sys->blocksize/SECTOR_SIZE)) == Ext2TotalBlocks(Ext2Sys, inode.i_size/Ext2Sys->blocksize)); } ext2_save_inode(Ext2Sys, ino, &inode); errorout: if (buf) { RtlFreeHeap(RtlGetProcessHeap(), 0, buf); } return true; } /* * This function forces out the primary superblock. We need to only * write out those fields which we have changed, since if the * filesystem is mounted, it may have changed some of the other * fields. * * It takes as input a superblock which has already been byte swapped * (if necessary). */ bool write_primary_superblock(PEXT2_FILESYS Ext2Sys, PEXT2_SUPER_BLOCK super) { bool bRet; bRet = NT_SUCCESS(Ext2WriteDisk( Ext2Sys, ((LONGLONG)SUPERBLOCK_OFFSET), SUPERBLOCK_SIZE, (PUCHAR)super)); return bRet; } /* * Updates the revision to EXT2_DYNAMIC_REV */ void ext2_update_dynamic_rev(PEXT2_FILESYS fs) { PEXT2_SUPER_BLOCK sb = fs->ext2_sb; if (sb->s_rev_level > EXT2_GOOD_OLD_REV) return; sb->s_rev_level = EXT2_DYNAMIC_REV; sb->s_first_ino = EXT2_GOOD_OLD_FIRST_INO; sb->s_inode_size = EXT2_GOOD_OLD_INODE_SIZE; /* s_uuid is handled by e2fsck already */ /* other fields should be left alone */ } bool ext2_flush(PEXT2_FILESYS fs) { ULONG i,j,maxgroup,sgrp; ULONG group_block; bool retval; char *group_ptr; unsigned long fs_state; PEXT2_SUPER_BLOCK super_shadow = 0; PEXT2_GROUP_DESC group_shadow = 0; LARGE_INTEGER SysTime; NtQuerySystemTime(&SysTime); fs_state = fs->ext2_sb->s_state; RtlTimeToSecondsSince1970(&SysTime, &fs->ext2_sb->s_wtime); fs->ext2_sb->s_block_group_nr = 0; super_shadow = fs->ext2_sb; group_shadow = fs->group_desc; /* * Write out master superblock. This has to be done * separately, since it is located at a fixed location * (SUPERBLOCK_OFFSET). */ retval = write_primary_superblock(fs, super_shadow); if (!retval) goto errout; /* * If this is an external journal device, don't write out the * block group descriptors or any of the backup superblocks */ if (fs->ext2_sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { retval = false; goto errout; } /* * Set the state of the FS to be non-valid. (The state has * already been backed up earlier, and will be restored when * we exit.) */ fs->ext2_sb->s_state &= ~EXT2_VALID_FS; /* * Write out the master group descriptors, and the backup * superblocks and group descriptors. */ group_block = fs->ext2_sb->s_first_data_block; maxgroup = fs->group_desc_count; for (i = 0; i < maxgroup; i++) { if (!ext2_bg_has_super(fs->ext2_sb, i)) goto next_group; sgrp = i; if (sgrp > ((1 << 16) - 1)) sgrp = (1 << 16) - 1; fs->ext2_sb->s_block_group_nr = (USHORT) sgrp; if (i !=0 ) { retval = NT_SUCCESS(Ext2WriteDisk( fs, ((ULONGLONG)group_block * fs->blocksize), SUPERBLOCK_SIZE, (PUCHAR)super_shadow)); if (!retval) { goto errout; } } group_ptr = (char *) group_shadow; for (j=0; j < fs->desc_blocks; j++) { retval = NT_SUCCESS(Ext2WriteDisk( fs, ((ULONGLONG)(group_block+1+j) * fs->blocksize), fs->blocksize, (PUCHAR) group_ptr)); if (!retval) { goto errout; } group_ptr += fs->blocksize; } next_group: group_block += EXT2_BLOCKS_PER_GROUP(fs->ext2_sb); } fs->ext2_sb->s_block_group_nr = 0; /* * If the write_bitmaps() function is present, call it to * flush the bitmaps. This is done this way so that a simple * program that doesn't mess with the bitmaps doesn't need to * drag in the bitmaps.c code. */ retval = ext2_write_bitmaps(fs); if (!retval) goto errout; /* * Flush the blocks out to disk */ // retval = io_channel_flush(fs->io); errout: fs->ext2_sb->s_state = (USHORT) fs_state; return retval; } bool create_journal_dev(PEXT2_FILESYS fs) { bool retval = false; char *buf = NULL; ULONG blk; ULONG count; if (!retval) { DPRINT1("Mke2fs: ext2_create_journal_dev: while initializing journal superblock.\n"); return false; } DPRINT("Mke2fs: Zeroing journal device: \n"); retval = zero_blocks(fs, 0, fs->ext2_sb->s_blocks_count, &blk, &count); zero_blocks(0, 0, 0, 0, 0); if (!retval) { DPRINT1("Mke2fs: create_journal_dev: while zeroing journal device (block %lu, count %lu).\n", blk, count); return false; } retval = NT_SUCCESS(Ext2WriteDisk( fs, ((ULONGLONG)blk * (fs->ext2_sb->s_first_data_block+1)), fs->blocksize, (unsigned char *)buf)); if (!retval) { DPRINT1("Mke2fs: create_journal_dev: while writing journal superblock.\n"); return false; } return true; } #define BLOCK_BITS (Ext2Sys->ext2_sb->s_log_block_size + 10) ULONG Ext2DataBlocks(PEXT2_FILESYS Ext2Sys, ULONG TotalBlocks) { ULONG dwData[4] = {1, 1, 1, 1}; ULONG dwMeta[4] = {0, 0, 0, 0}; ULONG DataBlocks = 0; ULONG i, j; if (TotalBlocks <= EXT2_NDIR_BLOCKS) { return TotalBlocks; } TotalBlocks -= EXT2_NDIR_BLOCKS; for (i = 0; i < 4; i++) { dwData[i] = dwData[i] << ((BLOCK_BITS - 2) * i); if (i > 0) { dwMeta[i] = 1 + (dwMeta[i - 1] << (BLOCK_BITS - 2)); } } for( i=1; (i < 4) && (TotalBlocks > 0); i++) { if (TotalBlocks >= (dwData[i] + dwMeta[i])) { TotalBlocks -= (dwData[i] + dwMeta[i]); DataBlocks += dwData[i]; } else { ULONG dwDivide = 0; ULONG dwRemain = 0; for (j=i; (j > 0) && (TotalBlocks > 0); j--) { dwDivide = (TotalBlocks - 1) / (dwData[j-1] + dwMeta[j-1]); dwRemain = (TotalBlocks - 1) % (dwData[j-1] + dwMeta[j-1]); DataBlocks += (dwDivide * dwData[j-1]); TotalBlocks = dwRemain; } } } return (DataBlocks + EXT2_NDIR_BLOCKS); } ULONG Ext2TotalBlocks(PEXT2_FILESYS Ext2Sys, ULONG DataBlocks) { ULONG dwData[4] = {1, 1, 1, 1}; ULONG dwMeta[4] = {0, 0, 0, 0}; ULONG TotalBlocks = 0; ULONG i, j; if (DataBlocks <= EXT2_NDIR_BLOCKS) { return DataBlocks; } DataBlocks -= EXT2_NDIR_BLOCKS; for (i = 0; i < 4; i++) { dwData[i] = dwData[i] << ((BLOCK_BITS - 2) * i); if (i > 0) { dwMeta[i] = 1 + (dwMeta[i - 1] << (BLOCK_BITS - 2)); } } for( i=1; (i < 4) && (DataBlocks > 0); i++) { if (DataBlocks >= dwData[i]) { DataBlocks -= dwData[i]; TotalBlocks += (dwData[i] + dwMeta[i]); } else { ULONG dwDivide = 0; ULONG dwRemain = 0; for (j=i; (j > 0) && (DataBlocks > 0); j--) { dwDivide = (DataBlocks) / (dwData[j-1]); dwRemain = (DataBlocks) % (dwData[j-1]); TotalBlocks += (dwDivide * (dwData[j-1] + dwMeta[j-1]) + 1); DataBlocks = dwRemain; } } } return (TotalBlocks + EXT2_NDIR_BLOCKS); } NTSTATUS NTAPI Ext2Format(IN PUNICODE_STRING DriveRoot, IN FMIFS_MEDIA_FLAG MediaFlag, IN PUNICODE_STRING Label, IN BOOLEAN QuickFormat, IN ULONG ClusterSize, IN PFMIFSCALLBACK Callback) { BOOLEAN bRet = FALSE; NTSTATUS Status = STATUS_UNSUCCESSFUL; /* Super Block: 1024 bytes long */ EXT2_SUPER_BLOCK Ext2Sb; /* File Sys Structure */ EXT2_FILESYS FileSys; ULONG Percent; ULONG rsv; ULONG blocks; ULONG start; ULONG ret_blk; if (Callback != NULL) { Callback(PROGRESS, 0, (PVOID)&Percent); } RtlZeroMemory(&Ext2Sb, sizeof(EXT2_SUPER_BLOCK)); RtlZeroMemory(&FileSys, sizeof(EXT2_FILESYS)); FileSys.ext2_sb = &Ext2Sb; if (!NT_SUCCESS(Ext2OpenDevice(&FileSys, DriveRoot))) { DPRINT1("Mke2fs: Volume %wZ does not exist, ...\n", DriveRoot); goto clean_up; } if (!NT_SUCCESS(Ext2GetMediaInfo(&FileSys))) { DPRINT1("Mke2fs: Can't get media information\n"); goto clean_up; } set_fs_defaults(NULL, &Ext2Sb, ClusterSize, &inode_ratio); Ext2Sb.s_blocks_count = FileSys.PartInfo.PartitionLength.QuadPart / EXT2_BLOCK_SIZE(&Ext2Sb); /* * Calculate number of inodes based on the inode ratio */ Ext2Sb.s_inodes_count = (ULONG)(((LONGLONG) Ext2Sb.s_blocks_count * EXT2_BLOCK_SIZE(&Ext2Sb)) / inode_ratio); /* * Calculate number of blocks to reserve */ Ext2Sb.s_r_blocks_count = (Ext2Sb.s_blocks_count * 5) / 100; Status = Ext2LockVolume(&FileSys); if (NT_SUCCESS(Status)) { bLocked = TRUE; } // Initialize if (!ext2_initialize_sb(&FileSys)) { DPRINT1("Mke2fs: error...\n"); goto clean_up; } zap_sector(&FileSys, 2, 6); /* * Generate a UUID for it... */ { __u8 uuid[16]; uuid_generate(&uuid[0]); memcpy(&Ext2Sb.s_uuid[0], &uuid[0], 16); } /* * Add "jitter" to the superblock's check interval so that we * don't check all the filesystems at the same time. We use a * kludgy hack of using the UUID to derive a random jitter value. */ { ULONG i, val; for (i = 0, val = 0 ; i < sizeof(Ext2Sb.s_uuid); i++) val += Ext2Sb.s_uuid[i]; Ext2Sb.s_max_mnt_count += val % EXT2_DFL_MAX_MNT_COUNT; } /* * Set the volume label... */ if (Label) { ANSI_STRING ansi_label; ansi_label.MaximumLength = sizeof(Ext2Sb.s_volume_name); ansi_label.Length = 0; ansi_label.Buffer = Ext2Sb.s_volume_name; RtlUnicodeStringToAnsiString(&ansi_label, Label, FALSE); } ext2_print_super(&Ext2Sb); bRet = ext2_allocate_tables(&FileSys); if (!bRet) { goto clean_up; } /* rsv must be a power of two (64kB is MD RAID sb alignment) */ rsv = 65536 / FileSys.blocksize; blocks = Ext2Sb.s_blocks_count; #ifdef ZAP_BOOTBLOCK DPRINT1("Mke2fs: zeroing volume boot record\n"); zap_sector(&FileSys, 0, 2); #endif /* * Wipe out any old MD RAID (or other) metadata at the end * of the device. This will also verify that the device is * as large as we think. Be careful with very small devices. */ start = (blocks & ~(rsv - 1)); if (start > rsv) start -= rsv; if (start > 0) bRet = zero_blocks(&FileSys, start, blocks - start, &ret_blk, NULL); if (!bRet) { DPRINT1("Mke2fs: zeroing block %lu at end of filesystem", ret_blk); goto clean_up; } write_inode_tables(&FileSys); create_root_dir(&FileSys); create_lost_and_found(&FileSys); ext2_reserve_inodes(&FileSys); create_bad_block_inode(&FileSys, NULL); DPRINT("Mke2fs: Writing superblocks and filesystem accounting information ... \n"); if (!QuickFormat) { DPRINT1("Mke2fs: Slow format not supported yet\n"); } if (!ext2_flush(&FileSys)) { bRet = false; DPRINT1("Mke2fs: Warning, had trouble writing out superblocks.\n"); goto clean_up; } DPRINT("Mke2fs: Writing superblocks and filesystem accounting information done!\n"); bRet = true; Status = STATUS_SUCCESS; clean_up: // Clean up ... ext2_free_group_desc(&FileSys); ext2_free_block_bitmap(&FileSys); ext2_free_inode_bitmap(&FileSys); if(bLocked) { Ext2DisMountVolume(&FileSys); Ext2UnLockVolume(&FileSys); } Ext2CloseDevice(&FileSys); if (Callback != NULL) { Callback(DONE, 0, (PVOID)&bRet); } return Status; } NTSTATUS NTAPI Ext2Chkdsk(IN PUNICODE_STRING DriveRoot, IN BOOLEAN FixErrors, IN BOOLEAN Verbose, IN BOOLEAN CheckOnlyIfDirty, IN BOOLEAN ScanDrive, IN PFMIFSCALLBACK Callback) { UNIMPLEMENTED; return STATUS_SUCCESS; }