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 "NFS4Server.h"
11
12#include <AutoDeleter.h>
13
14#include "FileSystem.h"
15#include "Inode.h"
16#include "Request.h"
17#include "WorkQueue.h"
18
19
20NFS4Server::NFS4Server(RPC::Server* serv)
21	:
22	fThreadCancel(true),
23	fWaitCancel(create_sem(0, NULL)),
24	fLeaseTime(0),
25	fClientIdLastUse(0),
26	fUseCount(0),
27	fServer(serv)
28{
29	ASSERT(serv != NULL);
30
31	mutex_init(&fClientIdLock, NULL);
32	mutex_init(&fFSLock, NULL);
33	mutex_init(&fThreadStartLock, NULL);
34
35}
36
37
38NFS4Server::~NFS4Server()
39{
40	fThreadCancel = true;
41	fUseCount = 0;
42	release_sem(fWaitCancel);
43	status_t result;
44	wait_for_thread(fThread, &result);
45
46	delete_sem(fWaitCancel);
47	mutex_destroy(&fClientIdLock);
48	mutex_destroy(&fFSLock);
49	mutex_destroy(&fThreadStartLock);
50}
51
52
53uint64
54NFS4Server::ServerRebooted(uint64 clientId)
55{
56	if (clientId != fClientId)
57		return fClientId;
58
59	fClientId = ClientId(clientId, true);
60
61	// reclaim all opened files and held locks from all filesystems
62	MutexLocker _(fFSLock);
63	FileSystem* fs = fFileSystems.Head();
64	while (fs != NULL) {
65		DoublyLinkedList<OpenState>::Iterator iterator
66			= fs->OpenFilesLock().GetIterator();
67		OpenState* current = iterator.Next();
68		while (current != NULL) {
69			current->Reclaim(fClientId);
70
71			current = iterator.Next();
72		}
73		fs->OpenFilesUnlock();
74
75		fs = fFileSystems.GetNext(fs);
76	}
77
78	return fClientId;
79}
80
81
82void
83NFS4Server::AddFileSystem(FileSystem* fs)
84{
85	ASSERT(fs != NULL);
86
87	MutexLocker _(fFSLock);
88	fFileSystems.Add(fs);
89
90	fUseCount += fs->OpenFilesCount();
91	if (fs->OpenFilesCount() > 0)
92		_StartRenewing();
93}
94
95
96void
97NFS4Server::RemoveFileSystem(FileSystem* fs)
98{
99	ASSERT(fs != NULL);
100
101	MutexLocker _(fFSLock);
102	fFileSystems.Remove(fs);
103	fUseCount -= fs->OpenFilesCount();
104}
105
106
107uint64
108NFS4Server::ClientId(uint64 prevId, bool forceNew)
109{
110	MutexLocker _(fClientIdLock);
111	if ((fUseCount == 0 && fClientIdLastUse + (time_t)LeaseTime() < time(NULL))
112		|| (forceNew && fClientId == prevId)) {
113
114		Request request(fServer, NULL);
115		request.Builder().SetClientID(fServer);
116
117		status_t result = request.Send();
118		if (result != B_OK)
119			return fClientId;
120
121		uint64 ver;
122		result = request.Reply().SetClientID(&fClientId, &ver);
123		if (result != B_OK)
124			return fClientId;
125
126		request.Reset();
127		request.Builder().SetClientIDConfirm(fClientId, ver);
128
129		result = request.Send();
130		if (result != B_OK)
131			return fClientId;
132
133		result = request.Reply().SetClientIDConfirm();
134		if (result != B_OK)
135			return fClientId;
136	}
137
138	fClientIdLastUse = time(NULL);
139	return fClientId;
140}
141
142
143status_t
144NFS4Server::FileSystemMigrated()
145{
146	// reclaim all opened files and held locks from all filesystems
147	MutexLocker _(fFSLock);
148	FileSystem* fs = fFileSystems.Head();
149	while (fs != NULL) {
150		fs->Migrate(fServer);
151		fs = fFileSystems.GetNext(fs);
152	}
153
154	return B_OK;
155}
156
157
158status_t
159NFS4Server::_GetLeaseTime()
160{
161	Request request(fServer, NULL);
162	request.Builder().PutRootFH();
163	Attribute attr[] = { FATTR4_LEASE_TIME };
164	request.Builder().GetAttr(attr, sizeof(attr) / sizeof(Attribute));
165
166	status_t result = request.Send();
167	if (result != B_OK)
168		return result;
169
170	ReplyInterpreter& reply = request.Reply();
171
172	reply.PutRootFH();
173
174	AttrValue* values;
175	uint32 count;
176	result = reply.GetAttr(&values, &count);
177	if (result != B_OK)
178		return result;
179	ArrayDeleter<AttrValue> valuesDeleter(values);
180
181	// FATTR4_LEASE_TIME is mandatory
182	if (count < 1 || values[0].fAttribute != FATTR4_LEASE_TIME)
183		return B_BAD_VALUE;
184
185	fLeaseTime = values[0].fData.fValue32;
186
187	return B_OK;
188}
189
190
191status_t
192NFS4Server::_StartRenewing()
193{
194	if (!fThreadCancel)
195		return B_OK;
196
197	MutexLocker _(fThreadStartLock);
198
199	if (!fThreadCancel)
200		return B_OK;
201
202	if (fLeaseTime == 0) {
203		status_t result = _GetLeaseTime();
204		if (result != B_OK)
205			return result;
206	}
207
208	fThreadCancel = false;
209	fThread = spawn_kernel_thread(&NFS4Server::_RenewalThreadStart,
210		"NFSv4 Renewal", B_NORMAL_PRIORITY, this);
211	if (fThread < B_OK)
212		return fThread;
213
214	status_t result = resume_thread(fThread);
215	if (result != B_OK) {
216		kill_thread(fThread);
217		return result;
218	}
219
220	return B_OK;
221}
222
223
224status_t
225NFS4Server::_Renewal()
226{
227	while (!fThreadCancel) {
228		// TODO: operations like OPEN, READ, CLOSE, etc also renew leases
229		status_t result = acquire_sem_etc(fWaitCancel, 1,
230			B_RELATIVE_TIMEOUT, sSecToBigTime(fLeaseTime - 2));
231		if (result != B_TIMED_OUT) {
232			if (result == B_OK)
233				release_sem(fWaitCancel);
234			return result;
235		}
236
237		uint64 clientId = fClientId;
238
239		if (fUseCount == 0) {
240			MutexLocker _(fFSLock);
241			if (fUseCount == 0) {
242				fThreadCancel = true;
243				return B_OK;
244			}
245		}
246
247		Request request(fServer, NULL);
248		request.Builder().Renew(clientId);
249		result = request.Send();
250		if (result != B_OK)
251			continue;
252
253		switch (request.Reply().NFS4Error()) {
254			case NFS4ERR_CB_PATH_DOWN:
255				RecallAll();
256				break;
257			case NFS4ERR_STALE_CLIENTID:
258				ServerRebooted(clientId);
259				break;
260			case NFS4ERR_LEASE_MOVED:
261				FileSystemMigrated();
262				break;
263		}
264	}
265
266	return B_OK;
267}
268
269
270status_t
271NFS4Server::_RenewalThreadStart(void* ptr)
272{
273	ASSERT(ptr != NULL);
274	NFS4Server* server = reinterpret_cast<NFS4Server*>(ptr);
275	return server->_Renewal();
276}
277
278
279status_t
280NFS4Server::ProcessCallback(RPC::CallbackRequest* request,
281	Connection* connection)
282{
283	ASSERT(request != NULL);
284	ASSERT(connection != NULL);
285
286	RequestInterpreter req(request);
287	ReplyBuilder reply(request->XID());
288
289	status_t result;
290	uint32 count = req.OperationCount();
291
292	for (uint32 i = 0; i < count; i++) {
293		switch (req.Operation()) {
294			case OpCallbackGetAttr:
295				result = CallbackGetAttr(&req, &reply);
296				break;
297			case OpCallbackRecall:
298				result = CallbackRecall(&req, &reply);
299				break;
300			default:
301				result = B_NOT_SUPPORTED;
302		}
303
304		if (result != B_OK)
305			break;
306	}
307
308	XDR::WriteStream& stream = reply.Reply()->Stream();
309	connection->Send(stream.Buffer(), stream.Size());
310
311	return B_OK;
312}
313
314
315status_t
316NFS4Server::CallbackRecall(RequestInterpreter* request, ReplyBuilder* reply)
317{
318	ASSERT(request != NULL);
319	ASSERT(reply != NULL);
320
321	uint32 stateID[3];
322	uint32 stateSeq;
323	bool truncate;
324	FileHandle handle;
325
326	status_t result = request->Recall(&handle, truncate, &stateSeq, stateID);
327	if (result != B_OK)
328		return result;
329
330	MutexLocker locker(fFSLock);
331
332	Delegation* delegation = NULL;
333	FileSystem* current = fFileSystems.Head();
334	while (current != NULL) {
335		delegation = current->GetDelegation(handle);
336		if (delegation != NULL)
337			break;
338
339		current = fFileSystems.GetNext(current);
340	}
341	locker.Unlock();
342
343	if (delegation == NULL) {
344		reply->Recall(B_ENTRY_NOT_FOUND);
345		return B_ENTRY_NOT_FOUND;
346	}
347
348	DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
349	args->fDelegation = delegation;
350	args->fTruncate = truncate;
351	gWorkQueue->EnqueueJob(DelegationRecall, args);
352
353	reply->Recall(B_OK);
354
355	return B_OK;
356}
357
358
359status_t
360NFS4Server::CallbackGetAttr(RequestInterpreter* request, ReplyBuilder* reply)
361{
362	ASSERT(request != NULL);
363	ASSERT(reply != NULL);
364
365	FileHandle handle;
366	int mask;
367
368	status_t result = request->GetAttr(&handle, &mask);
369	if (result != B_OK)
370		return result;
371
372	MutexLocker locker(fFSLock);
373
374	Delegation* delegation = NULL;
375	FileSystem* current = fFileSystems.Head();
376	while (current != NULL) {
377		delegation = current->GetDelegation(handle);
378		if (delegation != NULL)
379			break;
380
381		current = fFileSystems.GetNext(current);
382	}
383	locker.Unlock();
384
385	if (delegation == NULL) {
386		reply->GetAttr(B_ENTRY_NOT_FOUND, 0, 0, 0);
387		return B_ENTRY_NOT_FOUND;
388	}
389
390	struct stat st;
391	delegation->GetInode()->Stat(&st);
392
393	uint64 change;
394	change = delegation->GetInode()->Change();
395	if (delegation->GetInode()->Dirty())
396		change++;
397	reply->GetAttr(B_OK, mask, st.st_size, change);
398
399	return B_OK;
400}
401
402
403status_t
404NFS4Server::RecallAll()
405{
406	MutexLocker _(fFSLock);
407	FileSystem* fs = fFileSystems.Head();
408	while (fs != NULL) {
409		DoublyLinkedList<Delegation>& list = fs->DelegationsLock();
410		DoublyLinkedList<Delegation>::Iterator iterator = list.GetIterator();
411
412		Delegation* current = iterator.Next();
413		while (current != NULL) {
414			DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
415			args->fDelegation = current;
416			args->fTruncate = false;
417			gWorkQueue->EnqueueJob(DelegationRecall, args);
418
419			current = iterator.Next();
420		}
421		fs->DelegationsUnlock();
422
423		fs = fFileSystems.GetNext(fs);
424	}
425
426	return B_OK;
427}
428
429