xref: /haiku/src/add-ons/kernel/file_systems/ext2/Volume.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
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