1/*
2 * Copyright 2008, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Fran��ois Revol <revol@free.fr>
7 */
8
9
10#include "Stream.h"
11
12#include <stdint.h>
13#include <stdlib.h>
14#include <string.h>
15#include <unistd.h>
16
17#include <algorithm>
18
19#include <boot/FileMapDisk.h>
20
21#include "CachedBlock.h"
22#include "Directory.h"
23#include "File.h"
24
25
26//#define TRACE(x) dprintf x
27#define TRACE(x) do {} while (0)
28
29
30using namespace FATFS;
31
32
33Stream::Stream(Volume &volume, uint32 chain, off_t size, const char *name)
34	:
35	fVolume(volume),
36	fFirstCluster(chain),
37	//fClusters(NULL),
38	fClusterMapCacheLast(0),
39	fSize(size)
40{
41	TRACE(("FATFS::Stream::(, %d, %lld, %s)\n", chain, size, name));
42	fName[FATFS_NAME_LENGTH] = '\0';
43	strlcpy(fName, name, FATFS_NAME_LENGTH+1);
44	fClusterCount = (fSize + fVolume.ClusterSize() - 1) / fVolume.ClusterSize();
45	if (size == UINT32_MAX)
46		fClusterCount = 10; // ?
47	for (int i = 0; i < CLUSTER_MAP_CACHE_SIZE; i++) {
48		fClusterMapCache[i].block = -1;
49		fClusterMapCache[i].cluster = fVolume.InvalidClusterID();
50	}
51}
52
53
54Stream::~Stream()
55{
56	TRACE(("FATFS::Stream::~()\n"));
57}
58
59
60status_t
61Stream::InitCheck()
62{
63	if (fSize && !fVolume.IsValidCluster(fFirstCluster))
64		return B_BAD_VALUE;
65	return B_OK;
66}
67
68
69status_t
70Stream::GetName(char *nameBuffer, size_t bufferSize) const
71{
72	return strlcpy(nameBuffer, fName, bufferSize);
73}
74
75
76status_t
77Stream::GetFileMap(struct file_map_run *runs, int32 *count)
78{
79	int32 i;
80	uint32 cluster = fFirstCluster;
81	uint32 next = fVolume.InvalidClusterID();
82	off_t offset = 0LL;
83
84	for (i = 0; i < *count; i++) {
85		runs[i].offset = offset;
86		runs[i].block = fVolume.ToBlock(cluster);
87		runs[i].len = fVolume.ClusterSize();
88		do {
89			next = fVolume.NextCluster(cluster);
90			if (next != cluster + 1)
91				break;
92			runs[i].len += fVolume.ClusterSize();
93		} while (true);
94		if (!fVolume.IsValidCluster(next))
95			break;
96		cluster = next;
97		offset += runs[i].len;
98	}
99
100	// too big
101	if (i == *count && fVolume.IsValidCluster(next))
102		return B_ERROR;
103
104	*count = i;
105	return B_OK;
106}
107
108
109status_t
110Stream::_FindCluster(off_t pos, uint32& _cluster)
111{
112	//TRACE(("FATFS::Stream::%s(%lld,,)\n", __FUNCTION__, pos));
113	uint32 index = (uint32)(pos / fVolume.ClusterSize());
114	if (pos > fSize || index >= fClusterCount)
115		return B_BAD_VALUE;
116
117	uint32 cluster = 0;
118	bool found = false;
119	uint32 i;
120	for (i = 0; i < CLUSTER_MAP_CACHE_SIZE; i++) {
121		if (fClusterMapCache[i].block == index) {
122			cluster = fClusterMapCache[i].cluster;
123			found = true;
124			break;
125		}
126	}
127	if (!found) {
128#if 1
129		cluster = fFirstCluster;
130		for (i = 0; i < index && fVolume.IsValidCluster(cluster); i++) {
131			if (fVolume.IsLastCluster(cluster))
132				break;
133			//TRACE(("FATFS::Stream::%s: [%d] = %d\n", __FUNCTION__, i, cluster));
134			cluster = fVolume.NextCluster(cluster);
135			//TRACE(("FATFS::Stream::%s: %04lx ?\n", __FUNCTION__, cluster));
136		}
137#endif
138#if 0
139		cluster = fVolume.NextCluster(cluster, index);
140#endif
141	}
142	if (!fVolume.IsValidCluster(cluster))
143		return B_ENTRY_NOT_FOUND;
144
145	fClusterMapCache[fClusterMapCacheLast].block = index;
146	fClusterMapCache[fClusterMapCacheLast].cluster = cluster;
147	fClusterMapCacheLast++;
148	fClusterMapCacheLast %= CLUSTER_MAP_CACHE_SIZE;
149
150	_cluster = cluster;
151	return B_OK;
152}
153
154
155status_t
156Stream::_FindOrCreateCluster(off_t pos, uint32& _cluster, bool& _added)
157{
158	status_t error = _FindCluster(pos, _cluster);
159	if (error == B_OK) {
160		_added = false;
161		return B_OK;
162	}
163
164	// iterate through the cluster list
165	uint32 index = (uint32)(pos / fVolume.ClusterSize());
166	uint32 cluster = fFirstCluster;
167	uint32 clusterCount = 0;
168	if (cluster != 0) {
169		uint32 nextCluster = cluster;
170		while (clusterCount <= index) {
171			if (!fVolume.IsValidCluster(nextCluster)
172					|| fVolume.IsLastCluster(nextCluster)) {
173				break;
174			}
175
176			cluster = nextCluster;
177			clusterCount++;
178			nextCluster = fVolume.NextCluster(nextCluster);
179		}
180	}
181
182	if (clusterCount > index) {
183		// the cluster existed after all
184		_cluster = cluster;
185		_added = false;
186		return B_OK;
187	}
188
189	while (clusterCount <= index) {
190		uint32 newCluster;
191		error = fVolume.AllocateCluster(cluster, newCluster);
192		if (error != B_OK)
193			return error;
194
195		if (clusterCount == 0)
196			fFirstCluster = newCluster;
197
198		// TODO: We should support to zero out the new cluster. Maybe make this
199		// and optional parameter of WriteAt().
200
201		cluster = newCluster;
202		clusterCount++;
203	}
204
205	_cluster = cluster;
206	_added = true;
207	return B_OK;
208}
209
210
211status_t
212Stream::FindBlock(off_t pos, off_t &block, off_t &offset)
213{
214	uint32 cluster;
215	status_t error = _FindCluster(pos, cluster);
216	if (error != B_OK)
217		return error;
218
219	// convert to position
220	offset = fVolume.ClusterToOffset(cluster);
221	offset += (pos %= fVolume.ClusterSize());
222
223	// convert to block + offset
224	block = fVolume.ToBlock(offset);
225	offset %= fVolume.BlockSize();
226
227	return B_OK;
228}
229
230
231status_t
232Stream::ReadAt(off_t pos, void *_buffer, size_t *_length, off_t *diskOffset)
233{
234	TRACE(("FATFS::Stream::%s(%lld, )\n", __FUNCTION__, pos));
235
236	uint8* buffer = (uint8*)_buffer;
237
238	// set/check boundaries for pos/length
239	if (pos < 0)
240		return B_BAD_VALUE;
241	if (pos >= fSize) {
242		*_length = 0;
243		return B_OK;
244	}
245
246#if 0
247	// lazily build the cluster list
248	if (!fClusters) {
249		status_t status = BuildClusterList();
250		if (status != B_OK)
251			return status;
252	}
253#endif
254
255	size_t length = *_length;
256
257	if (pos + (off_t)length > fSize)
258		length = fSize - pos;
259
260	off_t num; // block number
261	off_t offset;
262	if (FindBlock(pos, num, offset) < B_OK) {
263		*_length = 0;
264		return B_BAD_VALUE;
265	}
266
267	if (diskOffset != NULL)
268		*diskOffset = fVolume.BlockToOffset(num) + offset;
269
270	uint32 bytesRead = 0;
271	uint32 blockSize = fVolume.BlockSize();
272	uint8 *block;
273
274	// the first block_run we read could not be aligned to the block_size boundary
275	// (read partial block at the beginning)
276
277	// pos % block_size == (pos - offset) % block_size, offset % block_size == 0
278	if (pos % blockSize != 0) {
279		CachedBlock cached(fVolume, num);
280		if ((block = cached.Block()) == NULL) {
281			*_length = 0;
282			return B_BAD_VALUE;
283		}
284
285		bytesRead = blockSize - (pos % blockSize);
286		if (length < bytesRead)
287			bytesRead = length;
288
289		memcpy(buffer, block + (pos % blockSize), bytesRead);
290		pos += bytesRead;
291
292		length -= bytesRead;
293		if (length == 0) {
294			*_length = bytesRead;
295			return B_OK;
296		}
297
298		if (FindBlock(pos, num, offset) < B_OK) {
299			*_length = bytesRead;
300			return B_BAD_VALUE;
301		}
302	}
303
304	// the first block_run is already filled in at this point
305	// read the following complete blocks using cached_read(),
306	// the last partial block is read using the generic Cache class
307
308	while (length > 0) {
309		// offset is the offset to the current pos in the block_run
310
311		if (length < blockSize) {
312			CachedBlock cached(fVolume, num);
313			if ((block = cached.Block()) == NULL) {
314				*_length = bytesRead;
315				return B_BAD_VALUE;
316			}
317			memcpy(buffer + bytesRead, block, length);
318			bytesRead += length;
319			break;
320		}
321
322		if (read_pos(fVolume.Device(), fVolume.BlockToOffset(num),
323			buffer + bytesRead, fVolume.BlockSize()) < B_OK) {
324			*_length = bytesRead;
325			return B_BAD_VALUE;
326		}
327
328		int32 bytes = fVolume.BlockSize();
329		length -= bytes;
330		bytesRead += bytes;
331		if (length == 0)
332			break;
333
334		pos += bytes;
335
336		if (FindBlock(pos, num, offset) < B_OK) {
337			*_length = bytesRead;
338			return B_BAD_VALUE;
339		}
340	}
341
342	*_length = bytesRead;
343	return B_OK;
344}
345
346
347status_t
348Stream::WriteAt(off_t pos, const void* _buffer, size_t* _length,
349	off_t* diskOffset)
350{
351	if (pos < 0)
352		return B_BAD_VALUE;
353
354	const uint8* buffer = (const uint8*)_buffer;
355	size_t length = *_length;
356	size_t totalWritten = 0;
357	status_t error = B_OK;
358
359	CachedBlock cachedBlock(fVolume);
360
361	while (length > 0) {
362		// get the cluster
363		uint32 cluster;
364		bool added;
365		error = _FindOrCreateCluster(pos, cluster, added);
366		if (error != B_OK)
367			break;
368
369		// convert to position
370		off_t inClusterOffset = pos % fVolume.ClusterSize();
371		off_t offset = fVolume.ClusterToOffset(cluster) + inClusterOffset;
372
373		if (diskOffset != NULL) {
374			*diskOffset = offset;
375			diskOffset = NULL;
376		}
377
378		// convert to block + offset
379		off_t block = fVolume.ToBlock(offset);
380		size_t inBlockOffset = offset % fVolume.BlockSize();
381
382		// write
383		size_t toWrite = std::min((size_t)fVolume.BlockSize() - inBlockOffset,
384			length);
385		if (toWrite == (size_t)fVolume.BlockSize()) {
386			// write the whole block
387			ssize_t written = write_pos(fVolume.Device(),
388				fVolume.BlockToOffset(block), buffer, fVolume.BlockSize());
389			if (written < 0) {
390				error = written;
391				break;
392			}
393			if (written != fVolume.BlockSize()) {
394				error = B_ERROR;
395				break;
396			}
397		} else {
398			// write a partial block -- need to read it from disk first
399			error = cachedBlock.SetTo(block, CachedBlock::READ);
400			if (error != B_OK)
401				break;
402
403			memcpy(cachedBlock.Block() + inBlockOffset, buffer, toWrite);
404
405			error = cachedBlock.Flush();
406			if (error != B_OK)
407				break;
408		}
409
410		totalWritten += toWrite;
411		pos += toWrite;
412		buffer += toWrite;
413		length -= toWrite;
414
415		if (pos > fSize)
416			fSize = pos;
417	}
418
419	*_length = totalWritten;
420	return totalWritten > 0 ? B_OK : error;
421}
422
423
424status_t
425Stream::BuildClusterList()
426{
427#if 0
428	TRACE(("FATFS::Stream::%s()\n", __FUNCTION__));
429	uint32 count = (fSize + fVolume.ClusterSize() - 1) / fVolume.ClusterSize();
430	uint32 c = fFirstCluster;
431	int i;
432
433	if (fSize == UINT32_MAX) // it's a directory, try a large enough value
434		count = 10;
435	//fClusters = (uint32 *)malloc(count * sizeof(uint32));
436	for (i = 0; i < count && fVolume.IsValidCluster(c); i++) {
437		if (fVolume.IsLastCluster(c))
438			break;
439		TRACE(("FATFS::Stream::%s: [%d] = %d\n", __FUNCTION__, i, c));
440		fClusters[i] = c;
441		c = fVolume.NextCluster(c);
442		TRACE(("FATFS::Stream::%s: %04lx ?\n", __FUNCTION__, c));
443		// XXX: try to realloc() for dirs maybe ?
444	}
445	fClusterCount = i;
446	TRACE(("FATFS::Stream::%s: %d clusters in chain\n", __FUNCTION__, i));
447#endif
448	return B_OK;
449}
450