1 /* 2 * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de. 3 * This file may be used under the terms of the MIT License. 4 */ 5 6 7 //! Super block, mounting, etc. 8 9 10 #include "Volume.h" 11 12 #include <errno.h> 13 #include <new> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 18 #include <fs_cache.h> 19 #include <fs_volume.h> 20 21 #include <util/AutoLock.h> 22 23 #include "Inode.h" 24 25 26 //#define TRACE_EXT2 27 #ifdef TRACE_EXT2 28 # define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) 29 #else 30 # define TRACE(x...) ; 31 #endif 32 33 34 class DeviceOpener { 35 public: 36 DeviceOpener(int fd, int mode); 37 DeviceOpener(const char* device, int mode); 38 ~DeviceOpener(); 39 40 int Open(const char* device, int mode); 41 int Open(int fd, int mode); 42 void* InitCache(off_t numBlocks, uint32 blockSize); 43 void RemoveCache(bool allowWrites); 44 45 void Keep(); 46 47 int Device() const { return fDevice; } 48 int Mode() const { return fMode; } 49 bool IsReadOnly() const { return _IsReadOnly(fMode); } 50 51 status_t GetSize(off_t* _size, uint32* _blockSize = NULL); 52 53 private: 54 static bool _IsReadOnly(int mode) 55 { return (mode & O_RWMASK) == O_RDONLY;} 56 static bool _IsReadWrite(int mode) 57 { return (mode & O_RWMASK) == O_RDWR;} 58 59 int fDevice; 60 int fMode; 61 void* fBlockCache; 62 }; 63 64 65 DeviceOpener::DeviceOpener(const char* device, int mode) 66 : 67 fBlockCache(NULL) 68 { 69 Open(device, mode); 70 } 71 72 73 DeviceOpener::DeviceOpener(int fd, int mode) 74 : 75 fBlockCache(NULL) 76 { 77 Open(fd, mode); 78 } 79 80 81 DeviceOpener::~DeviceOpener() 82 { 83 if (fDevice >= 0) { 84 RemoveCache(false); 85 close(fDevice); 86 } 87 } 88 89 90 int 91 DeviceOpener::Open(const char* device, int mode) 92 { 93 fDevice = open(device, mode | O_NOCACHE); 94 if (fDevice < 0) 95 fDevice = errno; 96 97 if (fDevice < 0 && _IsReadWrite(mode)) { 98 // try again to open read-only (don't rely on a specific error code) 99 return Open(device, O_RDONLY | O_NOCACHE); 100 } 101 102 if (fDevice >= 0) { 103 // opening succeeded 104 fMode = mode; 105 if (_IsReadWrite(mode)) { 106 // check out if the device really allows for read/write access 107 device_geometry geometry; 108 if (!ioctl(fDevice, B_GET_GEOMETRY, &geometry)) { 109 if (geometry.read_only) { 110 // reopen device read-only 111 close(fDevice); 112 return Open(device, O_RDONLY | O_NOCACHE); 113 } 114 } 115 } 116 } 117 118 return fDevice; 119 } 120 121 122 int 123 DeviceOpener::Open(int fd, int mode) 124 { 125 fDevice = dup(fd); 126 if (fDevice < 0) 127 return errno; 128 129 fMode = mode; 130 131 return fDevice; 132 } 133 134 135 void* 136 DeviceOpener::InitCache(off_t numBlocks, uint32 blockSize) 137 { 138 return fBlockCache = block_cache_create(fDevice, numBlocks, blockSize, 139 IsReadOnly()); 140 } 141 142 143 void 144 DeviceOpener::RemoveCache(bool allowWrites) 145 { 146 if (fBlockCache == NULL) 147 return; 148 149 block_cache_delete(fBlockCache, allowWrites); 150 fBlockCache = NULL; 151 } 152 153 154 void 155 DeviceOpener::Keep() 156 { 157 fDevice = -1; 158 } 159 160 161 /*! Returns the size of the device in bytes. It uses B_GET_GEOMETRY 162 to compute the size, or fstat() if that failed. 163 */ 164 status_t 165 DeviceOpener::GetSize(off_t* _size, uint32* _blockSize) 166 { 167 device_geometry geometry; 168 if (ioctl(fDevice, B_GET_GEOMETRY, &geometry) < 0) { 169 // maybe it's just a file 170 struct stat stat; 171 if (fstat(fDevice, &stat) < 0) 172 return B_ERROR; 173 174 if (_size) 175 *_size = stat.st_size; 176 if (_blockSize) // that shouldn't cause us any problems 177 *_blockSize = 512; 178 179 return B_OK; 180 } 181 182 if (_size) { 183 *_size = 1LL * geometry.head_count * geometry.cylinder_count 184 * geometry.sectors_per_track * geometry.bytes_per_sector; 185 } 186 if (_blockSize) 187 *_blockSize = geometry.bytes_per_sector; 188 189 return B_OK; 190 } 191 192 193 // #pragma mark - 194 195 196 bool 197 ext2_super_block::IsValid() 198 { 199 // TODO: check some more values! 200 if (Magic() != (uint32)EXT2_SUPER_BLOCK_MAGIC) 201 return false; 202 203 return true; 204 } 205 206 207 // #pragma mark - 208 209 210 Volume::Volume(fs_volume* volume) 211 : 212 fFSVolume(volume), 213 fFlags(0), 214 fGroupBlocks(NULL), 215 fRootNode(NULL) 216 { 217 mutex_init(&fLock, "ext2 volume"); 218 } 219 220 221 Volume::~Volume() 222 { 223 if (fGroupBlocks != NULL) { 224 uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1) 225 / fGroupsPerBlock; 226 for (uint32 i = 0; i < blockCount; i++) { 227 free(fGroupBlocks[i]); 228 } 229 230 free(fGroupBlocks); 231 } 232 } 233 234 235 bool 236 Volume::IsValidSuperBlock() 237 { 238 return fSuperBlock.IsValid(); 239 } 240 241 242 const char* 243 Volume::Name() const 244 { 245 if (fSuperBlock.name[0]) 246 return fSuperBlock.name; 247 248 return fName; 249 } 250 251 252 status_t 253 Volume::Mount(const char* deviceName, uint32 flags) 254 { 255 flags |= B_MOUNT_READ_ONLY; 256 // we only support read-only for now 257 258 DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0 259 ? O_RDONLY : O_RDWR); 260 fDevice = opener.Device(); 261 if (fDevice < B_OK) 262 return fDevice; 263 264 if (opener.IsReadOnly()) 265 fFlags |= VOLUME_READ_ONLY; 266 267 // read the super block 268 if (Identify(fDevice, &fSuperBlock) != B_OK) { 269 //FATAL(("invalid super block!\n")); 270 return B_BAD_VALUE; 271 } 272 273 if (_UnsupportedIncompatibleFeatures(fSuperBlock) != 0) 274 return B_NOT_SUPPORTED; 275 276 // initialize short hands to the super block (to save byte swapping) 277 fBlockShift = fSuperBlock.BlockShift(); 278 fBlockSize = 1UL << fSuperBlock.BlockShift(); 279 fFirstDataBlock = fSuperBlock.FirstDataBlock(); 280 281 fNumGroups = (fSuperBlock.NumBlocks() - fFirstDataBlock - 1) 282 / fSuperBlock.BlocksPerGroup() + 1; 283 fGroupsPerBlock = fBlockSize / sizeof(ext2_block_group); 284 285 TRACE("block size %ld, num groups %ld, groups per block %ld, first %lu\n", 286 fBlockSize, fNumGroups, fGroupsPerBlock, fFirstDataBlock); 287 TRACE("features %lx, incompatible features %lx, read-only features %lx\n", 288 fSuperBlock.CompatibleFeatures(), fSuperBlock.IncompatibleFeatures(), 289 fSuperBlock.ReadOnlyFeatures()); 290 291 uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1) / fGroupsPerBlock; 292 293 fGroupBlocks = (ext2_block_group**)malloc(blockCount * sizeof(void*)); 294 if (fGroupBlocks == NULL) 295 return B_NO_MEMORY; 296 297 memset(fGroupBlocks, 0, blockCount * sizeof(void*)); 298 fInodesPerBlock = fBlockSize / InodeSize(); 299 300 // check if the device size is large enough to hold the file system 301 off_t diskSize; 302 status_t status = opener.GetSize(&diskSize); 303 if (status != B_OK) 304 return status; 305 if (diskSize < (NumBlocks() << BlockShift())) 306 return B_BAD_VALUE; 307 308 fBlockCache = opener.InitCache(NumBlocks(), fBlockSize); 309 if (fBlockCache == NULL) 310 return B_ERROR; 311 312 status = get_vnode(fFSVolume, EXT2_ROOT_NODE, (void**)&fRootNode); 313 if (status != B_OK) { 314 TRACE("could not create root node: get_vnode() failed!\n"); 315 return status; 316 } 317 318 // all went fine 319 opener.Keep(); 320 321 if (!fSuperBlock.name[0]) { 322 // generate a more or less descriptive volume name 323 uint32 divisor = 1UL << 30; 324 char unit = 'G'; 325 if (diskSize < divisor) { 326 divisor = 1UL << 20; 327 unit = 'M'; 328 } 329 330 double size = double((10 * diskSize + divisor - 1) / divisor); 331 // %g in the kernel does not support precision... 332 333 snprintf(fName, sizeof(fName), "%g %cB Ext2 Volume", 334 size / 10, unit); 335 } 336 337 return B_OK; 338 } 339 340 341 status_t 342 Volume::Unmount() 343 { 344 put_vnode(fFSVolume, RootNode()->ID()); 345 block_cache_delete(fBlockCache, !IsReadOnly()); 346 close(fDevice); 347 348 return B_OK; 349 } 350 351 352 status_t 353 Volume::GetInodeBlock(ino_t id, uint32& block) 354 { 355 ext2_block_group* group; 356 status_t status = GetBlockGroup((id - 1) / fSuperBlock.InodesPerGroup(), 357 &group); 358 if (status != B_OK) 359 return status; 360 361 block = group->InodeTable() 362 + ((id - 1) % fSuperBlock.InodesPerGroup()) / fInodesPerBlock; 363 return B_OK; 364 } 365 366 367 uint32 368 Volume::InodeBlockIndex(ino_t id) const 369 { 370 return ((id - 1) % fSuperBlock.InodesPerGroup()) % fInodesPerBlock; 371 } 372 373 374 /*static*/ uint32 375 Volume::_UnsupportedIncompatibleFeatures(ext2_super_block& superBlock) 376 { 377 uint32 supportedIncompatible = EXT2_INCOMPATIBLE_FEATURE_FILE_TYPE 378 | EXT2_INCOMPATIBLE_FEATURE_RECOVER 379 | EXT2_INCOMPATIBLE_FEATURE_JOURNAL 380 /*| EXT2_INCOMPATIBLE_FEATURE_META_GROUP*/; 381 382 if ((superBlock.IncompatibleFeatures() & ~supportedIncompatible) != 0) { 383 dprintf("ext2: incompatible features not supported: %lx (extents %x)\n", 384 superBlock.IncompatibleFeatures() & ~supportedIncompatible, 385 EXT2_INCOMPATIBLE_FEATURE_EXTENTS); 386 return superBlock.IncompatibleFeatures() & ~supportedIncompatible; 387 } 388 389 return 0; 390 } 391 392 393 off_t 394 Volume::_GroupBlockOffset(uint32 blockIndex) 395 { 396 if ((fSuperBlock.IncompatibleFeatures() 397 & EXT2_INCOMPATIBLE_FEATURE_META_GROUP) == 0 398 || blockIndex < fSuperBlock.FirstMetaBlockGroup()) 399 return off_t(fFirstDataBlock + blockIndex + 1) << fBlockShift; 400 401 panic("meta block"); 402 return 0; 403 } 404 405 406 /*! Makes the requested block group available. 407 The block groups are loaded on demand, but are kept in memory until the 408 volume is unmounted; therefore we don't use the block cache. 409 */ 410 status_t 411 Volume::GetBlockGroup(int32 index, ext2_block_group** _group) 412 { 413 if (index < 0 || (uint32)index > fNumGroups) 414 return B_BAD_VALUE; 415 416 int32 blockIndex = index / fGroupsPerBlock; 417 418 MutexLocker _(fLock); 419 420 if (fGroupBlocks[blockIndex] == NULL) { 421 ext2_block_group* groupBlock = (ext2_block_group*)malloc(fBlockSize); 422 if (groupBlock == NULL) 423 return B_NO_MEMORY; 424 425 ssize_t bytesRead = read_pos(fDevice, _GroupBlockOffset(blockIndex), 426 groupBlock, fBlockSize); 427 if (bytesRead >= B_OK && (uint32)bytesRead != fBlockSize) 428 bytesRead = B_IO_ERROR; 429 if (bytesRead < B_OK) { 430 free(groupBlock); 431 return bytesRead; 432 } 433 434 fGroupBlocks[blockIndex] = groupBlock; 435 436 TRACE("group [%ld]: inode table %ld\n", index, 437 (fGroupBlocks[blockIndex] + index % fGroupsPerBlock)->InodeTable()); 438 } 439 440 *_group = fGroupBlocks[blockIndex] + index % fGroupsPerBlock; 441 return B_OK; 442 } 443 444 445 // #pragma mark - Disk scanning and initialization 446 447 448 /*static*/ status_t 449 Volume::Identify(int fd, ext2_super_block* superBlock) 450 { 451 if (read_pos(fd, EXT2_SUPER_BLOCK_OFFSET, superBlock, 452 sizeof(ext2_super_block)) != sizeof(ext2_super_block)) 453 return B_IO_ERROR; 454 455 if (!superBlock->IsValid()) 456 return B_BAD_VALUE; 457 458 return _UnsupportedIncompatibleFeatures(*superBlock) == 0 459 ? B_OK : B_NOT_SUPPORTED; 460 } 461 462