1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "FileDevice.h"
8
9#include <errno.h>
10#include <string.h>
11#include <unistd.h>
12
13#include <new>
14
15#include <fs_interface.h>
16
17#include <vfs.h>
18
19
20static const uint32 kBlockSize = 512;
21
22
23struct FileDevice::Cookie {
24	int	fd;
25
26	Cookie(int fd)
27		:
28		fd(fd)
29	{
30	}
31
32	~Cookie()
33	{
34		if (fd >= 0)
35			close(fd);
36	}
37};
38
39
40FileDevice::FileDevice()
41	:
42	fFD(-1),
43	fFileSize(0)
44{
45}
46
47
48FileDevice::~FileDevice()
49{
50	if (fFD >= 0)
51		close(fFD);
52}
53
54
55status_t
56FileDevice::Init(const char* path)
57{
58	fFD = open(path, O_RDONLY | O_NOTRAVERSE);
59	if (fFD < 0)
60		return errno;
61
62	struct stat st;
63	if (fstat(fFD, &st) != 0)
64		return errno;
65
66	if (!S_ISREG(st.st_mode))
67		return B_BAD_TYPE;
68
69	fFileSize = st.st_size / kBlockSize * kBlockSize;
70
71	return B_OK;
72}
73
74
75status_t
76FileDevice::InitDevice()
77{
78	return B_OK;
79}
80
81
82void
83FileDevice::UninitDevice()
84{
85}
86
87
88void
89FileDevice::Removed()
90{
91	delete this;
92}
93
94
95bool
96FileDevice::HasSelect() const
97{
98	return false;
99}
100
101
102bool
103FileDevice::HasDeselect() const
104{
105	return false;
106}
107
108
109bool
110FileDevice::HasRead() const
111{
112	return true;
113}
114
115
116bool
117FileDevice::HasWrite() const
118{
119	return true;
120}
121
122
123bool
124FileDevice::HasIO() const
125{
126	// TODO: Support!
127	return false;
128}
129
130
131status_t
132FileDevice::Open(const char* path, int openMode, void** _cookie)
133{
134	// get the vnode
135	struct vnode* vnode;
136	status_t error = vfs_get_vnode_from_fd(fFD, true, &vnode);
137	if (error != B_OK)
138		return error;
139
140	// open it
141	int fd = vfs_open_vnode(vnode, openMode, true);
142	if (fd < 0) {
143		vfs_put_vnode(vnode);
144		return fd;
145	}
146	// our vnode reference does now belong to the FD
147
148	Cookie* cookie = new(std::nothrow) Cookie(fd);
149	if (cookie == NULL) {
150		close(fd);
151		return B_NO_MEMORY;
152	}
153
154	*_cookie = cookie;
155	return B_OK;
156}
157
158
159status_t
160FileDevice::Read(void* _cookie, off_t pos, void* buffer, size_t* _length)
161{
162	Cookie* cookie = (Cookie*)_cookie;
163
164	ssize_t bytesRead = pread(cookie->fd, buffer, *_length, pos);
165	if (bytesRead < 0) {
166		*_length = 0;
167		return errno;
168	}
169
170	*_length = bytesRead;
171	return B_OK;
172}
173
174
175status_t
176FileDevice::Write(void* _cookie, off_t pos, const void* buffer, size_t* _length)
177{
178	Cookie* cookie = (Cookie*)_cookie;
179
180	ssize_t bytesWritten = pwrite(cookie->fd, buffer, *_length, pos);
181	if (bytesWritten < 0) {
182		*_length = 0;
183		return errno;
184	}
185
186	*_length = bytesWritten;
187	return B_OK;
188}
189
190
191status_t
192FileDevice::IO(void* _cookie, io_request* request)
193{
194//	Cookie* cookie = (Cookie*)_cookie;
195//	return do_fd_io(cookie->fd, request);
196// TODO: The implementation is fine in principle, but do_fd_io() requires either
197// the io() hook or the {read,write}_pages() hooks of the underlying FS to be
198// implemented, which we can't guarantee. do_fd_io() should work around by using
199// read() and write(), but it's all quite of a mess, since we mix up the io()
200// hook -- which ATM has the semantics of uncached_io() hook (i.e. ignoring the
201// file cache) -- with the actual io() hook semantics (i.e. using the file
202// cache).
203	return B_UNSUPPORTED;
204}
205
206
207template<typename ResultType>
208static status_t
209set_ioctl_result(const ResultType& result, void* buffer, size_t length)
210{
211	// NOTE: We omit the buffer size check for sake of callers (e.g. BFS) not
212	// specifying a length argument.
213//	if (sizeof(ResultType) < length)
214//		return B_BAD_VALUE;
215
216	if (buffer == NULL)
217		return B_BAD_ADDRESS;
218
219	if (!IS_USER_ADDRESS(buffer))
220		return user_memcpy(buffer, &result, sizeof(ResultType));
221
222	memcpy(buffer, &result, sizeof(ResultType));
223	return B_OK;
224}
225
226
227status_t
228FileDevice::Control(void* _cookie, int32 op, void* buffer, size_t length)
229{
230	Cookie* cookie = (Cookie*)_cookie;
231
232	switch (op) {
233		case B_GET_DEVICE_SIZE:
234			return set_ioctl_result(
235				(uint64)fFileSize > (uint64)(~(size_t)0) ? ~(size_t)0 : (size_t)fFileSize,
236				buffer, length);
237
238		case B_SET_BLOCKING_IO:
239		case B_SET_NONBLOCKING_IO:
240			// TODO: Translate to O_NONBLOCK and pass on!
241			return B_OK;
242
243		case B_GET_READ_STATUS:
244		case B_GET_WRITE_STATUS:
245			// TODO: poll() the FD!
246			return set_ioctl_result(true, buffer, length);
247
248		case B_GET_ICON:
249			return B_UNSUPPORTED;
250
251		case B_GET_GEOMETRY:
252		case B_GET_BIOS_GEOMETRY:
253		{
254			// fill in the geometry
255			// Optimally we have only 1 block per sector and only one head.
256			// Since we have only a uint32 for the cylinder count, this won't
257			// work for files > 2TB. So, we set the head count to the minimally
258			// possible value.
259			off_t blocks = fFileSize / kBlockSize;
260			uint32 heads = (blocks + 0xfffffffe) / 0xffffffff;
261			if (heads == 0)
262				heads = 1;
263
264			device_geometry geometry;
265			geometry.bytes_per_sector = kBlockSize;
266		    geometry.sectors_per_track = 1;
267		    geometry.cylinder_count = blocks / heads;
268		    geometry.head_count = heads;
269		    geometry.device_type = B_DISK;
270		    geometry.removable = false;
271		    geometry.read_only = false;
272		    geometry.write_once = false;
273
274			return set_ioctl_result(geometry, buffer, length);
275		}
276
277		case B_GET_MEDIA_STATUS:
278			return set_ioctl_result((status_t)B_OK, buffer, length);
279
280		case B_SET_INTERRUPTABLE_IO:
281		case B_SET_UNINTERRUPTABLE_IO:
282			return B_OK;
283
284		case B_FLUSH_DRIVE_CACHE:
285			return fsync(cookie->fd) == 0 ? B_OK : errno;
286
287		case B_GET_BIOS_DRIVE_ID:
288			return set_ioctl_result((uint8)0xf8, buffer, length);
289
290		case B_GET_DRIVER_FOR_DEVICE:
291		case B_SET_DEVICE_SIZE:
292		case B_SET_PARTITION:
293		case B_FORMAT_DEVICE:
294		case B_EJECT_DEVICE:
295		case B_LOAD_MEDIA:
296		case B_GET_NEXT_OPEN_DEVICE:
297		default:
298			return B_BAD_VALUE;
299	}
300
301	return B_OK;
302}
303
304
305status_t
306FileDevice::Select(void* _cookie, uint8 event, selectsync* sync)
307{
308	// TODO: Support (select_fd())!
309	return B_UNSUPPORTED;
310}
311
312
313status_t
314FileDevice::Deselect(void* cookie, uint8 event, selectsync* sync)
315{
316	// TODO: Support (deselect_fd())!
317	return B_UNSUPPORTED;
318}
319
320
321status_t
322FileDevice::Close(void* cookie)
323{
324	// TODO: This should probably really close the FD. Depending on the
325	// underlying FS operations could block and close() would be needed to
326	// unblock them.
327	return B_OK;
328}
329
330
331status_t
332FileDevice::Free(void* _cookie)
333{
334	delete (Cookie*)_cookie;
335	return B_OK;
336}
337