vfs: check userland buffers before reading them.
[haiku.git] / src / bin / bfs_tools / lib / Disk.cpp
blobc35de297938b3cf776394f00c0064ed0ffa73366
1 /*
2 * Copyright (c) 2001-2008 pinc Software. All Rights Reserved.
3 * Released under the terms of the MIT license.
4 */
6 //! Handles BFS superblock, disk access etc.
8 #include "Disk.h"
9 #include "dump.h"
11 #include <Drivers.h>
12 #include <File.h>
13 #include <Entry.h>
14 #include <List.h>
15 #include <fs_info.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <ctype.h>
24 #define MIN_BLOCK_SIZE_INODES 50
25 #define MAX_BLOCK_SIZE_INODES 500
28 struct bfs_disk_info {
29 off_t offset;
30 disk_super_block super_block;
34 class CacheableBlockRun : public BlockRunCache::Cacheable {
35 public:
36 CacheableBlockRun(block_run run,uint8 *data)
38 fRun(run),
39 fData(data)
43 virtual ~CacheableBlockRun()
45 free(fData);
48 virtual bool Equals(block_run run)
50 return run == fRun;
53 void SetData(uint8 *data)
55 fData = data;
58 uint8 *Data()
60 return fData;
63 protected:
64 block_run fRun;
65 uint8 *fData;
69 BlockRunCache::BlockRunCache(Disk *disk)
70 : Cache<block_run>(),
71 fDisk(disk)
76 Cache<block_run>::Cacheable *BlockRunCache::NewCacheable(block_run run)
78 ssize_t length = (int32)run.length << fDisk->BlockShift();
80 void *buffer = malloc(length);
81 if (buffer == NULL)
82 return NULL;
84 ssize_t read = fDisk->ReadAt(fDisk->ToOffset(run),buffer,length);
85 if (read < length) {
86 free(buffer);
87 return NULL;
90 return new CacheableBlockRun(run,(uint8 *)buffer);
94 // #pragma mark -
97 Disk::Disk(const char *deviceName, bool rawMode, off_t start, off_t stop)
99 fBufferedFile(NULL),
100 fRawDiskOffset(0),
101 fSize(0LL),
102 fCache(this),
103 fRawMode(rawMode)
105 BEntry entry(deviceName);
106 fPath.SetTo(deviceName);
108 if (entry.IsDirectory()) {
109 dev_t on = dev_for_path(deviceName);
110 fs_info info;
111 if (fs_stat_dev(on, &info) != B_OK)
112 return;
114 fPath.SetTo(info.device_name);
115 deviceName = fPath.Path();
118 if (deviceName == NULL) {
119 fprintf(stderr, "Path is not mapped to any device.\n");
120 return;
123 if (!rawMode && !strncmp(deviceName, "/dev/", 5)
124 && !strcmp(deviceName + strlen(deviceName) - 3, "raw"))
125 fprintf(stderr, "Raw mode not selected, but raw device specified.\n");
127 if (fFile.SetTo(deviceName, B_READ_WRITE) < B_OK) {
128 //fprintf(stderr,"Could not open file: %s\n",strerror(fFile.InitCheck()));
129 return;
131 fBufferedFile = new BBufferIO(&fFile, 1024 * 1024, false);
133 int device = open(deviceName, O_RDONLY);
134 if (device < B_OK) {
135 //fprintf(stderr,"Could not open device\n");
136 return;
139 partition_info partitionInfo;
140 device_geometry geometry;
141 if (ioctl(device, B_GET_PARTITION_INFO, &partitionInfo,
142 sizeof(partition_info)) == 0) {
143 //if (gDumpPartitionInfo)
144 // dump_partition_info(&partitionInfo);
145 fSize = partitionInfo.size;
146 } else if (ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(device_geometry))
147 == 0) {
148 fSize = (off_t)geometry.cylinder_count * geometry.sectors_per_track
149 * geometry.head_count * geometry.bytes_per_sector;
150 } else {
151 fprintf(stderr,"Disk: Could not get partition info.\n Use file size as partition size\n");
152 fFile.GetSize(&fSize);
154 close(device);
156 if (fSize == 0LL) {
157 fprintf(stderr,"Disk: Invalid file size (%" B_PRIdOFF " bytes)!\n",
158 fSize);
161 if (rawMode && ScanForSuperBlock(start, stop) < B_OK) {
162 fFile.Unset();
163 return;
166 if (fBufferedFile->ReadAt(512 + fRawDiskOffset, &fSuperBlock,
167 sizeof(disk_super_block)) < 1)
168 fprintf(stderr,"Disk: Could not read superblock\n");
170 //dump_super_block(&fSuperBlock);
174 Disk::~Disk()
176 delete fBufferedFile;
180 status_t Disk::InitCheck()
182 status_t status = fFile.InitCheck();
183 if (status == B_OK)
184 return fSize == 0LL ? B_ERROR : B_OK;
186 return status;
190 block_run Disk::ToBlockRun(off_t start, int16 length) const
192 block_run run;
193 run.allocation_group = start >> fSuperBlock.ag_shift;
194 run.start = start & ((1UL << fSuperBlock.ag_shift) - 1);
195 run.length = length;
197 return run;
201 off_t Disk::LogSize() const
203 if (fSuperBlock.num_blocks >= 4096)
204 return 2048;
206 return 512;
210 uint8 *Disk::ReadBlockRun(block_run run)
212 CacheableBlockRun *entry = (CacheableBlockRun *)fCache.Get(run);
213 if (entry)
214 return entry->Data();
216 return NULL;
220 status_t
221 Disk::DumpBootBlockToFile()
223 BFile file("/boot/home/bootblock.old", B_READ_WRITE | B_CREATE_FILE);
224 if (file.InitCheck() < B_OK)
225 return file.InitCheck();
227 char buffer[BlockSize()];
228 ssize_t bytes = ReadAt(0, buffer, BlockSize());
229 if (bytes < (int32)BlockSize())
230 return bytes < B_OK ? bytes : B_ERROR;
232 file.Write(buffer, BlockSize());
234 // initialize buffer
235 memset(buffer, 0, BlockSize());
236 memcpy(buffer + 512, &fSuperBlock, sizeof(disk_super_block));
238 // write buffer to disk
239 WriteAt(0, buffer, BlockSize());
241 return B_OK;
245 // #pragma mark - Superblock recovery methods
248 status_t
249 Disk::ScanForSuperBlock(off_t start, off_t stop)
251 printf("Disk size %" B_PRIdOFF " bytes, %.2f GB\n", fSize, 1.0 * fSize
252 / (1024*1024*1024));
254 uint32 blockSize = 4096;
255 char buffer[blockSize + 1024];
257 if (stop == -1)
258 stop = fSize;
260 char escape[3] = {0x1b, '[', 0};
262 BList superBlocks;
264 printf("Scanning Disk from %" B_PRIdOFF " to %" B_PRIdOFF "\n", start,
265 stop);
267 for (off_t offset = start; offset < stop; offset += blockSize)
269 if (((offset-start) % (blockSize * 100)) == 0) {
270 printf(" %12" B_PRIdOFF ", %.2f GB %s1A\n", offset,
271 1.0 * offset / (1024*1024*1024),escape);
274 ssize_t bytes = fBufferedFile->ReadAt(offset, buffer, blockSize + 1024);
275 if (bytes < B_OK)
277 fprintf(stderr,"Could not read from device: %s\n", strerror(bytes));
278 return -1;
281 for (uint32 i = 0;i < blockSize - 2;i++)
283 disk_super_block *super = (disk_super_block *)&buffer[i];
285 if (super->magic1 == (int32)SUPER_BLOCK_MAGIC1
286 && super->magic2 == (int32)SUPER_BLOCK_MAGIC2
287 && super->magic3 == (int32)SUPER_BLOCK_MAGIC3)
289 printf("\n(%" B_PRId32 ") *** BFS superblock found at: %"
290 B_PRIdOFF "\n", superBlocks.CountItems() + 1, offset);
291 dump_super_block(super);
293 // add a copy of the superblock to the list
294 bfs_disk_info *info = (bfs_disk_info *)malloc(sizeof(bfs_disk_info));
295 if (info == NULL)
296 return B_NO_MEMORY;
298 memcpy(&info->super_block, super, sizeof(disk_super_block));
299 info->offset = offset + i - 512;
300 /* location off the BFS superblock is 512 bytes after the partition start */
301 superBlocks.AddItem(info);
306 if (superBlocks.CountItems() == 0) {
307 puts("\nCouldn't find any BFS superblocks!");
308 return B_ENTRY_NOT_FOUND;
311 // Let the user decide which block to use
313 puts("\n\nThe following disks were found:");
315 for (int32 i = 0; i < superBlocks.CountItems(); i++) {
316 bfs_disk_info *info = (bfs_disk_info *)superBlocks.ItemAt(i);
318 printf("%" B_PRId32 ") %s, offset %" B_PRIdOFF ", size %g GB (%svalid)"
319 "\n", i + 1, info->super_block.name, info->offset,
320 1.0 * info->super_block.num_blocks * info->super_block.block_size
321 / (1024*1024*1024),
322 ValidateSuperBlock(info->super_block) < B_OK ? "in" : "");
325 char answer[16];
326 printf("\nChoose one (by number): ");
327 fflush(stdout);
329 fgets(answer, 15, stdin);
330 int32 index = atol(answer);
332 if (index > superBlocks.CountItems() || index < 1) {
333 puts("No disk there... exiting.");
334 return B_BAD_VALUE;
337 bfs_disk_info *info = (bfs_disk_info *)superBlocks.ItemAt(index - 1);
339 // ToDo: free the other disk infos
341 fRawDiskOffset = info->offset;
342 fBufferedFile->Seek(fRawDiskOffset, SEEK_SET);
344 if (ValidateSuperBlock(info->super_block))
345 fSize = info->super_block.block_size * info->super_block.block_size;
346 else {
347 // just make it open-end
348 fSize -= fRawDiskOffset;
351 return B_OK;
355 status_t
356 Disk::ValidateSuperBlock(disk_super_block &superBlock)
358 if (superBlock.magic1 != (int32)SUPER_BLOCK_MAGIC1
359 || superBlock.magic2 != (int32)SUPER_BLOCK_MAGIC2
360 || superBlock.magic3 != (int32)SUPER_BLOCK_MAGIC3
361 || (int32)superBlock.block_size != superBlock.inode_size
362 || superBlock.fs_byte_order != SUPER_BLOCK_FS_LENDIAN
363 || (1UL << superBlock.block_shift) != superBlock.block_size
364 || superBlock.num_ags < 1
365 || superBlock.ag_shift < 1
366 || superBlock.blocks_per_ag < 1
367 || superBlock.num_blocks < 10
368 || superBlock.used_blocks > superBlock.num_blocks
369 || superBlock.num_ags != divide_roundup(superBlock.num_blocks,
370 1L << superBlock.ag_shift))
371 return B_ERROR;
373 return B_OK;
377 status_t
378 Disk::ValidateSuperBlock()
380 if (ValidateSuperBlock(fSuperBlock) < B_OK)
381 return B_ERROR;
383 fBitmap.SetTo(this);
385 return B_OK;
389 status_t
390 Disk::RecreateSuperBlock()
392 memset(&fSuperBlock, 0, sizeof(disk_super_block));
394 puts("\n*** Determine block size");
396 status_t status = DetermineBlockSize();
397 if (status < B_OK)
398 return status;
400 printf("\tblock size = %" B_PRId32 "\n", BlockSize());
402 strcpy(fSuperBlock.name,"recovered");
403 fSuperBlock.magic1 = SUPER_BLOCK_MAGIC1;
404 fSuperBlock.fs_byte_order = SUPER_BLOCK_FS_LENDIAN;
405 fSuperBlock.block_shift = get_shift(BlockSize());
406 fSuperBlock.num_blocks = fSize / BlockSize(); // only full blocks
407 fSuperBlock.inode_size = BlockSize();
408 fSuperBlock.magic2 = SUPER_BLOCK_MAGIC2;
410 fSuperBlock.flags = SUPER_BLOCK_CLEAN;
412 // size of the block bitmap + root block
413 fLogStart = 1 + divide_roundup(fSuperBlock.num_blocks,BlockSize() * 8);
415 // set it anywhere in the log
416 fSuperBlock.log_start = fLogStart + (LogSize() >> 1);
417 fSuperBlock.log_end = fSuperBlock.log_start;
420 // scan for indices and root inode
423 puts("\n*** Scanning for indices and root node...");
424 fValidOffset = 0LL;
425 bfs_inode indexDir;
426 bfs_inode rootDir;
427 if (ScanForIndexAndRoot(&indexDir,&rootDir) < B_OK) {
428 fprintf(stderr,"ERROR: Could not find root directory! Trying to recreate the superblock will have no effect!\n\tSetting standard values for the root dir.\n");
429 rootDir.inode_num.allocation_group = 8;
430 rootDir.inode_num.start = 0;
431 rootDir.inode_num.length = 1;
432 //gErrors++;
434 if (fValidOffset == 0LL) {
435 printf("No valid inode found so far - perform search.\n");
437 off_t offset = 8LL * 65536 * BlockSize();
438 char buffer[1024];
439 GetNextSpecialInode(buffer,&offset,offset + 32LL * 65536 * BlockSize(),true);
441 if (fValidOffset == 0LL)
443 fprintf(stderr,"FATAL ERROR: Could not find valid inode!\n");
444 return B_ERROR;
448 // calculate allocation group size
450 int32 allocationGroupSize = (fValidOffset - fValidBlockRun.start
451 * BlockSize()
452 + BlockSize() - 1) / (BlockSize() * fValidBlockRun.allocation_group);
454 fSuperBlock.blocks_per_ag = allocationGroupSize / (BlockSize() << 3);
455 fSuperBlock.ag_shift = get_shift(allocationGroupSize);
456 fSuperBlock.num_ags = divide_roundup(fSuperBlock.num_blocks,allocationGroupSize);
458 // calculate rest of log area
460 fSuperBlock.log_blocks.allocation_group = fLogStart / allocationGroupSize;
461 fSuperBlock.log_blocks.start = fLogStart - fSuperBlock.log_blocks.allocation_group * allocationGroupSize;
462 fSuperBlock.log_blocks.length = LogSize(); // assumed length of 2048 blocks
464 if (fLogStart != ((indexDir.inode_num.allocation_group
465 << fSuperBlock.ag_shift) + indexDir.inode_num.start - LogSize())) {
466 fprintf(stderr,"ERROR: start of logging area doesn't fit assumed value "
467 "(%" B_PRIdOFF " blocks before indices)!\n", LogSize());
468 //gErrors++;
471 fSuperBlock.magic3 = SUPER_BLOCK_MAGIC3;
472 fSuperBlock.root_dir = rootDir.inode_num;
473 fSuperBlock.indices = indexDir.inode_num;
475 // calculate used blocks (from block bitmap)
477 fBitmap.SetTo(this);
478 if (fBitmap.UsedBlocks())
479 fSuperBlock.used_blocks = fBitmap.UsedBlocks();
480 else {
481 fprintf(stderr,"ERROR: couldn't calculate number of used blocks, marking all blocks as used!\n");
482 fSuperBlock.used_blocks = fSuperBlock.num_blocks;
483 //gErrors++;
486 return B_OK;
490 status_t
491 Disk::DetermineBlockSize()
493 // scan for about 500 inodes to determine the disk's block size
495 // min. block bitmap size (in bytes, rounded to a 1024 boundary)
496 // root_block_size + (num_blocks / bits_per_block) * block_size
497 off_t offset = 1024 + divide_roundup(fSize / 1024,8 * 1024) * 1024;
499 off_t inodesFound = 0;
500 char buffer[1024];
501 bfs_inode *inode = (bfs_inode *)buffer;
502 // valid block sizes from 1024 to 32768 bytes
503 int32 blockSizeCounter[6] = {0};
504 status_t status = B_OK;
506 // read a quarter of the drive at maximum
507 for (; offset < (fSize >> 2); offset += 1024) {
508 if (fBufferedFile->ReadAt(offset, buffer, sizeof(buffer)) < B_OK) {
509 fprintf(stderr, "could not read from device (offset = %" B_PRIdOFF
510 ", size = %ld)!\n", offset, sizeof(buffer));
511 status = B_IO_ERROR;
512 break;
515 if (inode->magic1 != INODE_MAGIC1
516 || inode->inode_size != 1024
517 && inode->inode_size != 2048
518 && inode->inode_size != 4096
519 && inode->inode_size != 8192)
520 continue;
522 inodesFound++;
524 // update block size counter
525 for (int i = 0;i < 6;i++)
526 if ((1 << (i + 10)) == inode->inode_size)
527 blockSizeCounter[i]++;
529 int32 count = 0;
530 for (int32 i = 0;i < 6;i++)
531 if (blockSizeCounter[i])
532 count++;
534 // do we have a clear winner at 100 inodes?
535 // if not, break at 500 inodes
536 if (inodesFound >= MAX_BLOCK_SIZE_INODES
537 || (inodesFound >= MIN_BLOCK_SIZE_INODES && count == 1))
538 break;
541 // find the safest bet
542 int32 maxCounter = -1;
543 int32 maxIndex = 0;
544 for (int32 i = 0; i < 6; i++) {
545 if (maxCounter < blockSizeCounter[i]) {
546 maxIndex = i;
547 maxCounter = blockSizeCounter[i];
550 fSuperBlock.block_size = (1 << (maxIndex + 10));
552 return status;
556 status_t
557 Disk::GetNextSpecialInode(char *buffer, off_t *_offset, off_t end,
558 bool skipAfterValidInode = false)
560 off_t offset = *_offset;
561 if (end == offset)
562 end++;
564 bfs_inode *inode = (bfs_inode *)buffer;
566 for (; offset < end; offset += BlockSize()) {
567 if (fBufferedFile->ReadAt(offset, buffer, 1024) < B_OK) {
568 fprintf(stderr,"could not read from device (offset = %" B_PRIdOFF
569 ", size = %d)!\n", offset, 1024);
570 *_offset = offset;
571 return B_IO_ERROR;
574 if (inode->magic1 != INODE_MAGIC1
575 || inode->inode_size != fSuperBlock.inode_size)
576 continue;
578 // can compute allocation group only for inodes which are
579 // a) not in the first allocation group
580 // b) not in the log area
582 if (inode->inode_num.allocation_group > 0
583 && offset >= (BlockSize() * (fLogStart + LogSize()))) {
584 fValidBlockRun = inode->inode_num;
585 fValidOffset = offset;
587 if (skipAfterValidInode)
588 return B_OK;
591 // is the inode a special root inode (parent == self)?
593 if (!memcmp(&inode->parent, &inode->inode_num, sizeof(inode_addr))) {
594 printf("\t special inode found at %" B_PRIdOFF "\n", offset);
596 *_offset = offset;
597 return B_OK;
600 *_offset = offset;
601 return B_ENTRY_NOT_FOUND;
605 void
606 Disk::SaveInode(bfs_inode *inode, bool *indices, bfs_inode *indexDir,
607 bool *root, bfs_inode *rootDir)
609 if ((inode->mode & S_INDEX_DIR) == 0) {
610 *root = true;
611 printf("\troot node found!\n");
612 if (inode->inode_num.allocation_group != 8
613 || inode->inode_num.start != 0
614 || inode->inode_num.length != 1) {
615 printf("WARNING: root node has unexpected position: (ag = %"
616 B_PRId32 ", start = %d, length = %d)\n",
617 inode->inode_num.allocation_group,
618 inode->inode_num.start, inode->inode_num.length);
620 if (rootDir)
621 memcpy(rootDir, inode, sizeof(bfs_inode));
622 } else if (inode->mode & S_INDEX_DIR) {
623 *indices = true;
624 printf("\tindices node found!\n");
625 if (indexDir)
626 memcpy(indexDir, inode, sizeof(bfs_inode));
628 // if (gDumpIndexRootNode)
629 // dump_inode(inode);
633 status_t
634 Disk::ScanForIndexAndRoot(bfs_inode *indexDir,bfs_inode *rootDir)
636 memset(rootDir,0,sizeof(bfs_inode));
637 memset(indexDir,0,sizeof(bfs_inode));
639 bool indices = false,root = false;
640 char buffer[1024];
641 bfs_inode *inode = (bfs_inode *)buffer;
643 // The problem here is that we don't want to find copies of the
644 // inodes in the log.
645 // The first offset where a root node can be is therefore the
646 // first allocation group with a block size of 1024, and 16384
647 // blocks per ag; that should be relatively save.
649 // search for the indices inode, start reading from log size + boot block size
650 off_t offset = (fLogStart + LogSize()) * BlockSize();
651 if (GetNextSpecialInode(buffer,&offset,offset + 65536LL * BlockSize()) == B_OK)
652 SaveInode(inode,&indices,indexDir,&root,rootDir);
654 if (!indices) {
655 fputs("ERROR: no indices node found!\n",stderr);
656 //gErrors++;
659 // search common places for root node, iterating from allocation group
660 // size from 1024 to 65536
661 for (off_t start = 8LL * 1024 * BlockSize();start <= 8LL * 65536 * BlockSize();start <<= 1) {
662 if (start < fLogStart)
663 continue;
665 off_t commonOffset = start;
666 if (GetNextSpecialInode(buffer, &commonOffset, commonOffset) == B_OK) {
667 SaveInode(inode, &indices, indexDir, &root, rootDir);
668 if (root)
669 break;
673 if (!root) {
674 printf("WARNING: Could not find root node at common places!\n");
675 printf("\tScanning log area for root node\n");
677 off_t logOffset = ToOffset(fSuperBlock.log_blocks);
678 if (GetNextSpecialInode(buffer,&logOffset,logOffset + LogSize() * BlockSize()) == B_OK)
680 SaveInode(inode,&indices,indexDir,&root,rootDir);
682 printf("root node at: 0x%" B_PRIxOFF " (DiskProbe)\n",
683 logOffset / 512);
684 //fBufferedFile->ReadAt(logOffset + BlockSize(),buffer,1024);
685 //if (*(uint32 *)buffer == BPLUSTREE_MAGIC)
687 // puts("\t\tnext block in log contains a bplustree!");
692 /*if (!root)
694 char txt[64];
695 printf("Should I perform a deeper search (that will take some time) (Y/N) [N]? ");
696 gets(txt);
698 if (!strcasecmp("y",txt))
700 // search not so common places for the root node (all places)
702 if (indices)
703 offset += BlockSize(); // the block after the indices inode
704 else
705 offset = (fLogStart + 1) * BlockSize();
707 if (GetNextSpecialInode(buffer,&offset,65536LL * 16 * BlockSize()) == B_OK)
708 SaveInode(inode,&indices,indexDir,&root,rootDir);
711 if (root)
712 return B_OK;
714 return B_ERROR;
718 // #pragma mark - BPositionIO methods
721 ssize_t
722 Disk::Read(void *buffer, size_t size)
724 return fBufferedFile->Read(buffer, size);
728 ssize_t
729 Disk::Write(const void *buffer, size_t size)
731 return fBufferedFile->Write(buffer, size);
735 ssize_t
736 Disk::ReadAt(off_t pos, void *buffer, size_t size)
738 return fBufferedFile->ReadAt(pos + fRawDiskOffset, buffer, size);
742 ssize_t
743 Disk::WriteAt(off_t pos, const void *buffer, size_t size)
745 return fBufferedFile->WriteAt(pos + fRawDiskOffset, buffer, size);
749 off_t
750 Disk::Seek(off_t position, uint32 seekMode)
752 // ToDo: only correct for seekMode == SEEK_SET, right??
753 if (seekMode != SEEK_SET)
754 puts("OH NO, I AM BROKEN!");
755 return fBufferedFile->Seek(position + fRawDiskOffset, seekMode);
759 off_t
760 Disk::Position() const
762 return fBufferedFile->Position() - fRawDiskOffset;
766 status_t
767 Disk::SetSize(off_t /*size*/)
769 // SetSize() is not supported
770 return B_ERROR;