1/*
2 * Copyright 2012 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Pawe�� Dziepak, pdziepak@quarnos.org
7 */
8
9
10#include "Inode.h"
11
12#include <dirent.h>
13#include <string.h>
14
15#include "IdMap.h"
16#include "Request.h"
17#include "RootInode.h"
18
19
20status_t
21Inode::CreateDir(const char* name, int mode, ino_t* id)
22{
23	return CreateObject(name, NULL, mode, NF4DIR, id);
24}
25
26
27status_t
28Inode::OpenDir(OpenDirCookie* cookie)
29{
30	ASSERT(cookie != NULL);
31
32	if (fType != NF4DIR)
33		return B_NOT_A_DIRECTORY;
34
35	status_t result = Access(R_OK);
36	if (result != B_OK)
37		return result;
38
39	cookie->fSpecial = 0;
40	cookie->fSnapshot = NULL;
41	cookie->fCurrent = NULL;
42	cookie->fEOF = false;
43	cookie->fAttrDir = false;
44
45	return B_OK;
46}
47
48
49status_t
50Inode::OpenAttrDir(OpenDirCookie* cookie)
51{
52	ASSERT(cookie != NULL);
53
54	cookie->fSpecial = 0;
55	cookie->fSnapshot = NULL;
56	cookie->fCurrent = NULL;
57	cookie->fEOF = false;
58	cookie->fAttrDir = true;
59
60	return LoadAttrDirHandle();
61}
62
63
64status_t
65Inode::LoadAttrDirHandle()
66{
67	if (fInfo.fAttrDir.fSize != 0)
68		return B_OK;
69
70	FileHandle handle;
71	status_t result;
72
73	if (fFileSystem->NamedAttrs()) {
74		result = NFS4Inode::OpenAttrDir(&handle);
75		if (result == B_OK) {
76			fInfo.fAttrDir = handle;
77			return B_OK;
78		}
79
80		if (result != B_UNSUPPORTED)
81			return result;
82
83		fFileSystem->SetNamedAttrs(false);
84	}
85
86	if (!fFileSystem->GetConfiguration().fEmulateNamedAttrs)
87		return B_UNSUPPORTED;
88
89	char* attrDir
90		= reinterpret_cast<char*>(malloc(strlen(Name()) + 32));
91	if (attrDir == NULL)
92		return B_NO_MEMORY;
93	strcpy(attrDir, ".");
94	strcat(attrDir, Name());
95	strcat(attrDir, "-haiku-attrs");
96
97	result = NFS4Inode::LookUp(attrDir, NULL, NULL, &handle, true);
98	if (result == B_ENTRY_NOT_FOUND) {
99		ChangeInfo change;
100		struct stat st;
101		Stat(&st);
102		st.st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
103		result = NFS4Inode::CreateObject(attrDir, NULL, st.st_mode, NF4DIR,
104			&change, NULL, &handle, true);
105	}
106
107	free(attrDir);
108
109	if (result != B_OK)
110		return result;
111
112	fInfo.fAttrDir = handle;
113	return B_OK;
114}
115
116
117status_t
118Inode::FillDirEntry(struct dirent* de, ino_t id, const char* name, uint32 pos,
119	uint32 size)
120{
121	ASSERT(de != NULL);
122	ASSERT(name != NULL);
123
124	uint32 nameSize = strlen(name) + 1;
125	const uint32 entSize = offsetof(struct dirent, d_name);
126
127	if (pos + entSize + nameSize > size)
128		return B_BUFFER_OVERFLOW;
129
130	de->d_dev = fFileSystem->DevId();
131	de->d_ino = id;
132	de->d_reclen = entSize + nameSize;
133	if (de->d_reclen % 8 != 0)
134		de->d_reclen += 8 - de->d_reclen % 8;
135
136	strcpy(de->d_name, name);
137
138	return B_OK;
139}
140
141
142status_t
143Inode::ReadDirUp(struct dirent* de, uint32 pos, uint32 size)
144{
145	ASSERT(de != NULL);
146
147	uint32 attempt = 0;
148	do {
149		RPC::Server* serv = fFileSystem->Server();
150		Request request(serv, fFileSystem);
151		RequestBuilder& req = request.Builder();
152
153		req.PutFH(fInfo.fHandle);
154		req.LookUpUp();
155		req.GetFH();
156
157		if (fFileSystem->IsAttrSupported(FATTR4_FILEID)) {
158			Attribute attr[] = { FATTR4_FILEID };
159			req.GetAttr(attr, sizeof(attr) / sizeof(Attribute));
160		}
161
162		status_t result = request.Send();
163		if (result != B_OK)
164			return result;
165
166		ReplyInterpreter& reply = request.Reply();
167
168		if (HandleErrors(attempt, reply.NFS4Error(), serv))
169			continue;
170
171		reply.PutFH();
172		result = reply.LookUpUp();
173		if (result != B_OK)
174			return result;
175
176		FileHandle fh;
177		reply.GetFH(&fh);
178
179		uint64 fileId;
180		if (fFileSystem->IsAttrSupported(FATTR4_FILEID)) {
181			AttrValue* values;
182			uint32 count;
183			result = reply.GetAttr(&values, &count);
184			if (result != B_OK)
185				return result;
186
187			fileId = values[0].fData.fValue64;
188			delete[] values;
189		} else
190			fileId = fFileSystem->AllocFileId();
191
192		return FillDirEntry(de, FileIdToInoT(fileId), "..", pos, size);
193	} while (true);
194}
195
196
197static char*
198FileToAttrName(const char* path)
199{
200	ASSERT(path != NULL);
201
202	char* name = strdup(path);
203	if (name == NULL)
204		return NULL;
205
206	char* current = strpbrk(name, "#$");
207	while (current != NULL) {
208		switch (*current) {
209			case '#':
210				*current = '/';
211				break;
212			case '$':
213				*current = ':';
214				break;
215		}
216		current = strpbrk(name, "#$");
217	}
218
219	return name;
220}
221
222
223status_t
224Inode::GetDirSnapshot(DirectoryCacheSnapshot** _snapshot,
225	OpenDirCookie* cookie, uint64* _change, bool attribute)
226{
227	ASSERT(_snapshot != NULL);
228
229	DirectoryCacheSnapshot* snapshot = new DirectoryCacheSnapshot;
230	if (snapshot == NULL)
231		return B_NO_MEMORY;
232
233	uint64 change = 0;
234	uint64 dirCookie = 0;
235	uint64 dirCookieVerf = 0;
236	bool eof = false;
237
238	while (!eof) {
239		uint32 count;
240		DirEntry* dirents;
241
242		status_t result = ReadDirOnce(&dirents, &count, cookie, &eof, &change,
243			&dirCookie, &dirCookieVerf, attribute);
244		if (result != B_OK) {
245			delete snapshot;
246			return result;
247		}
248
249		uint32 i;
250		for (i = 0; i < count; i++) {
251
252			// FATTR4_FSID is mandatory
253			void* data = dirents[i].fAttrs[0].fData.fPointer;
254			FileSystemId* fsid = reinterpret_cast<FileSystemId*>(data);
255			if (*fsid != fFileSystem->FsId())
256				continue;
257
258			if (strstr(dirents[i].fName, "-haiku-attrs") != NULL)
259				continue;
260
261			ino_t id;
262			if (!attribute) {
263				if (dirents[i].fAttrCount == 2)
264					id = FileIdToInoT(dirents[i].fAttrs[1].fData.fValue64);
265				else
266					id = FileIdToInoT(fFileSystem->AllocFileId());
267			} else
268				id = 0;
269
270			const char* name = dirents[i].fName;
271			if (attribute)
272				name = FileToAttrName(name);
273			if (name == NULL) {
274				delete snapshot;
275				delete[] dirents;
276				return B_NO_MEMORY;
277			}
278
279			NameCacheEntry* entry = new NameCacheEntry(name, id);
280			if (attribute)
281				free(const_cast<char*>(name));
282
283			if (entry == NULL || entry->fName == NULL) {
284				if (entry != NULL)
285					delete entry;
286				delete snapshot;
287				delete[] dirents;
288				return B_NO_MEMORY;
289			}
290			snapshot->fEntries.Add(entry);
291		}
292
293		delete[] dirents;
294	}
295
296	*_snapshot = snapshot;
297	*_change = change;
298
299	return B_OK;
300}
301
302
303status_t
304Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
305	OpenDirCookie* cookie)
306{
307	ASSERT(_buffer != NULL);
308	ASSERT(_count != NULL);
309	ASSERT(cookie != NULL);
310
311	if (cookie->fEOF) {
312		*_count = 0;
313		return B_OK;
314	}
315
316	status_t result;
317	DirectoryCache* cache = cookie->fAttrDir ? fAttrCache : fCache;
318	if (cookie->fSnapshot == NULL) {
319		cache->Lock();
320		result = cache->Revalidate();
321		if (result != B_OK) {
322			cache->Unlock();
323			return result;
324		}
325
326		DirectoryCacheSnapshot* snapshot;
327		result = cache->GetSnapshot(&snapshot);
328		if (result != B_OK) {
329			cache->Unlock();
330			return result;
331		}
332
333		cookie->fSnapshot = new DirectoryCacheSnapshot(*snapshot);
334		cache->Unlock();
335
336		if (cookie->fSnapshot == NULL)
337			return B_NO_MEMORY;
338	}
339
340	char* buffer = reinterpret_cast<char*>(_buffer);
341	uint32 pos = 0;
342	uint32 i = 0;
343	bool overflow = false;
344
345	if (cookie->fSpecial == 0 && i < *_count && !cookie->fAttrDir) {
346		struct dirent* de = reinterpret_cast<dirent*>(buffer + pos);
347
348		status_t result;
349		result = FillDirEntry(de, fInfo.fFileId, ".", pos, size);
350
351		if (result == B_BUFFER_OVERFLOW)
352			overflow = true;
353		else if (result == B_OK) {
354			pos += de->d_reclen;
355			i++;
356			cookie->fSpecial++;
357		} else
358			return result;
359	}
360
361	if (cookie->fSpecial == 1 && i < *_count && !cookie->fAttrDir) {
362		struct dirent* de = reinterpret_cast<dirent*>(buffer + pos);
363
364		status_t result;
365		result = ReadDirUp(de, pos, size);
366		if (result == B_ENTRY_NOT_FOUND) {
367			result = FillDirEntry(de, FileIdToInoT(fInfo.fFileId), "..", pos,
368				size);
369		}
370
371		if (result == B_BUFFER_OVERFLOW)
372			overflow = true;
373		else if (result == B_OK) {
374			pos += de->d_reclen;
375			i++;
376			cookie->fSpecial++;
377		} else
378			return result;
379	}
380
381	MutexLocker _(cookie->fSnapshot->fLock);
382	for (; !overflow && i < *_count; i++) {
383		struct dirent* de = reinterpret_cast<dirent*>(buffer + pos);
384		NameCacheEntry* temp = cookie->fCurrent;
385
386		if (cookie->fCurrent == NULL)
387			cookie->fCurrent = cookie->fSnapshot->fEntries.Head();
388		else {
389			cookie->fCurrent
390				= cookie->fSnapshot->fEntries.GetNext(cookie->fCurrent);
391		}
392
393		if (cookie->fCurrent == NULL) {
394			cookie->fEOF = true;
395			break;
396		}
397
398		if (FillDirEntry(de, cookie->fCurrent->fNode, cookie->fCurrent->fName,
399			pos, size) == B_BUFFER_OVERFLOW) {
400			cookie->fCurrent = temp;
401			overflow = true;
402			break;
403		}
404
405		pos += de->d_reclen;
406	}
407
408	if (i == 0 && overflow)
409		return B_BUFFER_OVERFLOW;
410
411	*_count = i;
412
413	return B_OK;
414}
415
416