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#include <util/kernel_cpp.h>
21
22#include "CachedBlock.h"
23#include "Directory.h"
24#include "File.h"
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, %Ld, %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(%Ld,,)\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		uint32 count = (fSize + fVolume.ClusterSize() - 1) / fVolume.ClusterSize();
130		cluster = fFirstCluster;
131		if (fSize == UINT32_MAX) // it's a directory, try a large enough value
132			count = 10;
133		for (i = 0; i < index && fVolume.IsValidCluster(cluster); i++) {
134			if (fVolume.IsLastCluster(cluster))
135				break;
136			//TRACE(("FATFS::Stream::%s: [%d] = %d\n", __FUNCTION__, i, cluster));
137			cluster = fVolume.NextCluster(cluster);
138			//TRACE(("FATFS::Stream::%s: %04lx ?\n", __FUNCTION__, cluster));
139		}
140#endif
141#if 0
142		cluster = fVolume.NextCluster(cluster, index);
143#endif
144	}
145	if (!fVolume.IsValidCluster(cluster))
146		return B_ENTRY_NOT_FOUND;
147
148	fClusterMapCache[fClusterMapCacheLast].block = index;
149	fClusterMapCache[fClusterMapCacheLast].cluster = cluster;
150	fClusterMapCacheLast++;
151	fClusterMapCacheLast %= CLUSTER_MAP_CACHE_SIZE;
152
153	_cluster = cluster;
154	return B_OK;
155}
156
157
158status_t
159Stream::_FindOrCreateCluster(off_t pos, uint32& _cluster, bool& _added)
160{
161	status_t error = _FindCluster(pos, _cluster);
162	if (error == B_OK) {
163		_added = false;
164		return B_OK;
165	}
166
167	// iterate through the cluster list
168	uint32 index = (uint32)(pos / fVolume.ClusterSize());
169	uint32 cluster = fFirstCluster;
170	uint32 clusterCount = 0;
171	if (cluster != 0) {
172		uint32 nextCluster = cluster;
173		while (clusterCount <= index) {
174			if (!fVolume.IsValidCluster(nextCluster)
175					|| fVolume.IsLastCluster(nextCluster)) {
176				break;
177			}
178
179			cluster = nextCluster;
180			clusterCount++;
181			nextCluster = fVolume.NextCluster(nextCluster);
182		}
183	}
184
185	if (clusterCount > index) {
186		// the cluster existed after all
187		_cluster = cluster;
188		_added = false;
189		return B_OK;
190	}
191
192	while (clusterCount <= index) {
193		uint32 newCluster;
194		error = fVolume.AllocateCluster(cluster, newCluster);
195		if (error != B_OK)
196			return error;
197
198		if (clusterCount == 0)
199			fFirstCluster = newCluster;
200
201		// TODO: We should support to zero out the new cluster. Maybe make this
202		// and optional parameter of WriteAt().
203
204		cluster = newCluster;
205		clusterCount++;
206	}
207
208	_cluster = cluster;
209	_added = true;
210	return B_OK;
211}
212
213
214status_t
215Stream::FindBlock(off_t pos, off_t &block, off_t &offset)
216{
217	uint32 cluster;
218	status_t error = _FindCluster(pos, cluster);
219	if (error != B_OK)
220		return error;
221
222	// convert to position
223	offset = fVolume.ClusterToOffset(cluster);
224	offset += (pos %= fVolume.ClusterSize());
225
226	// convert to block + offset
227	block = fVolume.ToBlock(offset);
228	offset %= fVolume.BlockSize();
229
230	return B_OK;
231}
232
233
234status_t
235Stream::ReadAt(off_t pos, void *_buffer, size_t *_length, off_t *diskOffset)
236{
237	TRACE(("FATFS::Stream::%s(%Ld, )\n", __FUNCTION__, pos));
238
239	uint8* buffer = (uint8*)_buffer;
240
241	// set/check boundaries for pos/length
242	if (pos < 0)
243		return B_BAD_VALUE;
244	if (pos >= fSize) {
245		*_length = 0;
246		return B_OK;
247	}
248
249#if 0
250	// lazily build the cluster list
251	if (!fClusters) {
252		status_t status = BuildClusterList();
253		if (status != B_OK)
254			return status;
255	}
256#endif
257
258	size_t length = *_length;
259
260	if (pos + length > fSize)
261		length = fSize - pos;
262
263	off_t num; // block number
264	off_t offset;
265	if (FindBlock(pos, num, offset) < B_OK) {
266		*_length = 0;
267		return B_BAD_VALUE;
268	}
269
270	if (diskOffset != NULL)
271		*diskOffset = fVolume.BlockToOffset(num) + offset;
272
273	uint32 bytesRead = 0;
274	uint32 blockSize = fVolume.BlockSize();
275	uint8 *block;
276
277	// the first block_run we read could not be aligned to the block_size boundary
278	// (read partial block at the beginning)
279
280	// pos % block_size == (pos - offset) % block_size, offset % block_size == 0
281	if (pos % blockSize != 0) {
282		CachedBlock cached(fVolume, num);
283		if ((block = cached.Block()) == NULL) {
284			*_length = 0;
285			return B_BAD_VALUE;
286		}
287
288		bytesRead = blockSize - (pos % blockSize);
289		if (length < bytesRead)
290			bytesRead = length;
291
292		memcpy(buffer, block + (pos % blockSize), bytesRead);
293		pos += bytesRead;
294
295		length -= bytesRead;
296		if (length == 0) {
297			*_length = bytesRead;
298			return B_OK;
299		}
300
301		if (FindBlock(pos, num, offset) < B_OK) {
302			*_length = bytesRead;
303			return B_BAD_VALUE;
304		}
305	}
306
307	// the first block_run is already filled in at this point
308	// read the following complete blocks using cached_read(),
309	// the last partial block is read using the generic Cache class
310
311	bool partial = false;
312
313	while (length > 0) {
314		// offset is the offset to the current pos in the block_run
315
316		if (length < blockSize) {
317			CachedBlock cached(fVolume, num);
318			if ((block = cached.Block()) == NULL) {
319				*_length = bytesRead;
320				return B_BAD_VALUE;
321			}
322			memcpy(buffer + bytesRead, block, length);
323			bytesRead += length;
324			partial = true;
325			break;
326		}
327
328		if (read_pos(fVolume.Device(), fVolume.BlockToOffset(num),
329			buffer + bytesRead, fVolume.BlockSize()) < B_OK) {
330			*_length = bytesRead;
331			return B_BAD_VALUE;
332		}
333
334		int32 bytes = fVolume.BlockSize();
335		length -= bytes;
336		bytesRead += bytes;
337		if (length == 0)
338			break;
339
340		pos += bytes;
341
342		if (FindBlock(pos, num, offset) < B_OK) {
343			*_length = bytesRead;
344			return B_BAD_VALUE;
345		}
346	}
347
348	*_length = bytesRead;
349	return B_OK;
350}
351
352
353status_t
354Stream::WriteAt(off_t pos, const void* _buffer, size_t* _length,
355	off_t* diskOffset)
356{
357	if (pos < 0)
358		return B_BAD_VALUE;
359
360	const uint8* buffer = (const uint8*)_buffer;
361	size_t length = *_length;
362	size_t totalWritten = 0;
363	status_t error = B_OK;
364
365	CachedBlock cachedBlock(fVolume);
366
367	while (length > 0) {
368		// get the cluster
369		uint32 cluster;
370		bool added;
371		error = _FindOrCreateCluster(pos, cluster, added);
372		if (error != B_OK)
373			break;
374
375		// convert to position
376		off_t inClusterOffset = pos % fVolume.ClusterSize();
377		off_t offset = fVolume.ClusterToOffset(cluster) + inClusterOffset;
378
379		if (diskOffset != NULL) {
380			*diskOffset = offset;
381			diskOffset = NULL;
382		}
383
384		// convert to block + offset
385		off_t block = fVolume.ToBlock(offset);
386		size_t inBlockOffset = offset % fVolume.BlockSize();
387
388		// write
389		size_t toWrite = std::min(fVolume.BlockSize() - inBlockOffset, length);
390		if (toWrite == (size_t)fVolume.BlockSize()) {
391			// write the whole block
392			ssize_t written = write_pos(fVolume.Device(),
393				fVolume.BlockToOffset(block), buffer, fVolume.BlockSize());
394			if (written < 0) {
395				error = written;
396				break;
397			}
398			if (written != fVolume.BlockSize()) {
399				error = B_ERROR;
400				break;
401			}
402		} else {
403			// write a partial block -- need to read it from disk first
404			error = cachedBlock.SetTo(block, CachedBlock::READ);
405			if (error != B_OK)
406				break;
407
408			memcpy(cachedBlock.Block() + inBlockOffset, buffer, toWrite);
409
410			error = cachedBlock.Flush();
411			if (error != B_OK)
412				break;
413		}
414
415		totalWritten += toWrite;
416		pos += toWrite;
417		buffer += toWrite;
418		length -= toWrite;
419
420		if (pos > fSize)
421			fSize = pos;
422	}
423
424	*_length = totalWritten;
425	return totalWritten > 0 ? B_OK : error;
426}
427
428
429status_t
430Stream::BuildClusterList()
431{
432#if 0
433	TRACE(("FATFS::Stream::%s()\n", __FUNCTION__));
434	uint32 count = (fSize + fVolume.ClusterSize() - 1) / fVolume.ClusterSize();
435	uint32 c = fFirstCluster;
436	int i;
437
438	if (fSize == UINT32_MAX) // it's a directory, try a large enough value
439		count = 10;
440	//fClusters = (uint32 *)malloc(count * sizeof(uint32));
441	for (i = 0; i < count && fVolume.IsValidCluster(c); i++) {
442		if (fVolume.IsLastCluster(c))
443			break;
444		TRACE(("FATFS::Stream::%s: [%d] = %d\n", __FUNCTION__, i, c));
445		fClusters[i] = c;
446		c = fVolume.NextCluster(c);
447		TRACE(("FATFS::Stream::%s: %04lx ?\n", __FUNCTION__, c));
448		// XXX: try to realloc() for dirs maybe ?
449	}
450	fClusterCount = i;
451	TRACE(("FATFS::Stream::%s: %d clusters in chain\n", __FUNCTION__, i));
452#endif
453	return B_OK;
454}
455