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