xref: /haiku/src/add-ons/kernel/file_systems/ext2/Volume.cpp (revision 37c7d5d83a2372a6971e383411d5bacbeef0ebdc)
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 	fNumInodes = fSuperBlock.NumInodes();
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 / InodeSize();
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 	fBlockCache = opener.InitCache(NumBlocks(), fBlockSize);
310 	if (fBlockCache == NULL)
311 		return B_ERROR;
312 
313 	status = get_vnode(fFSVolume, EXT2_ROOT_NODE, (void**)&fRootNode);
314 	if (status != B_OK) {
315 		TRACE("could not create root node: get_vnode() failed!\n");
316 		return status;
317 	}
318 
319 	// all went fine
320 	opener.Keep();
321 
322 	if (!fSuperBlock.name[0]) {
323 		// generate a more or less descriptive volume name
324 		uint32 divisor = 1UL << 30;
325 		char unit = 'G';
326 		if (diskSize < divisor) {
327 			divisor = 1UL << 20;
328 			unit = 'M';
329 		}
330 
331 		double size = double((10 * diskSize + divisor - 1) / divisor);
332 			// %g in the kernel does not support precision...
333 
334 		snprintf(fName, sizeof(fName), "%g %cB Ext2 Volume",
335 			size / 10, unit);
336 	}
337 
338 	return B_OK;
339 }
340 
341 
342 status_t
343 Volume::Unmount()
344 {
345 	put_vnode(fFSVolume, RootNode()->ID());
346 	block_cache_delete(fBlockCache, !IsReadOnly());
347 	close(fDevice);
348 
349 	return B_OK;
350 }
351 
352 
353 status_t
354 Volume::GetInodeBlock(ino_t id, uint32& block)
355 {
356 	ext2_block_group* group;
357 	status_t status = GetBlockGroup((id - 1) / fSuperBlock.InodesPerGroup(),
358 		&group);
359 	if (status != B_OK)
360 		return status;
361 
362 	block = group->InodeTable()
363 		+ ((id - 1) % fSuperBlock.InodesPerGroup()) / fInodesPerBlock;
364 	return B_OK;
365 }
366 
367 
368 uint32
369 Volume::InodeBlockIndex(ino_t id) const
370 {
371 	return ((id - 1) % fSuperBlock.InodesPerGroup()) % fInodesPerBlock;
372 }
373 
374 
375 /*static*/ uint32
376 Volume::_UnsupportedIncompatibleFeatures(ext2_super_block& superBlock)
377 {
378 	uint32 supportedIncompatible = EXT2_INCOMPATIBLE_FEATURE_FILE_TYPE
379 		| EXT2_INCOMPATIBLE_FEATURE_RECOVER
380 		| EXT2_INCOMPATIBLE_FEATURE_JOURNAL
381 		/*| EXT2_INCOMPATIBLE_FEATURE_META_GROUP*/;
382 
383 	if ((superBlock.IncompatibleFeatures() & ~supportedIncompatible) != 0) {
384 		dprintf("ext2: incompatible features not supported: %lx (extents %x)\n",
385 			superBlock.IncompatibleFeatures() & ~supportedIncompatible,
386 			EXT2_INCOMPATIBLE_FEATURE_EXTENTS);
387 		return superBlock.IncompatibleFeatures() & ~supportedIncompatible;
388 	}
389 
390 	return 0;
391 }
392 
393 
394 off_t
395 Volume::_GroupBlockOffset(uint32 blockIndex)
396 {
397 	if ((fSuperBlock.IncompatibleFeatures()
398 			& EXT2_INCOMPATIBLE_FEATURE_META_GROUP) == 0
399 		|| blockIndex < fSuperBlock.FirstMetaBlockGroup())
400 		return off_t(fFirstDataBlock + blockIndex + 1) << fBlockShift;
401 
402 	panic("meta block");
403 	return 0;
404 }
405 
406 
407 /*!	Makes the requested block group available.
408 	The block groups are loaded on demand, but are kept in memory until the
409 	volume is unmounted; therefore we don't use the block cache.
410 */
411 status_t
412 Volume::GetBlockGroup(int32 index, ext2_block_group** _group)
413 {
414 	if (index < 0 || (uint32)index > fNumGroups)
415 		return B_BAD_VALUE;
416 
417 	int32 blockIndex = index / fGroupsPerBlock;
418 
419 	MutexLocker _(fLock);
420 
421 	if (fGroupBlocks[blockIndex] == NULL) {
422 		ext2_block_group* groupBlock = (ext2_block_group*)malloc(fBlockSize);
423 		if (groupBlock == NULL)
424 			return B_NO_MEMORY;
425 
426 		ssize_t bytesRead = read_pos(fDevice, _GroupBlockOffset(blockIndex),
427 			groupBlock, fBlockSize);
428 		if (bytesRead >= B_OK && (uint32)bytesRead != fBlockSize)
429 			bytesRead = B_IO_ERROR;
430 		if (bytesRead < B_OK) {
431 			free(groupBlock);
432 			return bytesRead;
433 		}
434 
435 		fGroupBlocks[blockIndex] = groupBlock;
436 
437 		TRACE("group [%ld]: inode table %ld\n", index,
438 			(fGroupBlocks[blockIndex] + index % fGroupsPerBlock)->InodeTable());
439 	}
440 
441 	*_group = fGroupBlocks[blockIndex] + index % fGroupsPerBlock;
442 	return B_OK;
443 }
444 
445 
446 //	#pragma mark - Disk scanning and initialization
447 
448 
449 /*static*/ status_t
450 Volume::Identify(int fd, ext2_super_block* superBlock)
451 {
452 	if (read_pos(fd, EXT2_SUPER_BLOCK_OFFSET, superBlock,
453 			sizeof(ext2_super_block)) != sizeof(ext2_super_block))
454 		return B_IO_ERROR;
455 
456 	if (!superBlock->IsValid())
457 		return B_BAD_VALUE;
458 
459 	return _UnsupportedIncompatibleFeatures(*superBlock) == 0
460 		? B_OK : B_NOT_SUPPORTED;
461 }
462 
463