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