1// UserlandFSDispatcher.cpp
2
3#include <new>
4
5#include <Application.h>
6#include <Clipboard.h>
7#include <Locker.h>
8#include <Message.h>
9#include <Roster.h>
10
11#include "AutoDeleter.h"
12#include "AutoLocker.h"
13#include "Compatibility.h"
14#include "Debug.h"
15#include "DispatcherDefs.h"
16#include "FileSystem.h"
17#include "FSInfo.h"
18#include "RequestAllocator.h"
19#include "RequestPort.h"
20#include "Requests.h"
21#include "ServerDefs.h"
22#include "String.h"
23#include "UserlandFSDispatcher.h"
24
25// constructor
26UserlandFSDispatcher::UserlandFSDispatcher(const char* signature)
27	: BApplication(signature),
28	  fTerminating(false),
29	  fRequestProcessor(-1),
30	  fConnectionPort(-1),
31	  fConnectionReplyPort(-1),
32	  fRequestLock(),
33	  fRequestPort(NULL)
34{
35}
36
37// destructor
38UserlandFSDispatcher::~UserlandFSDispatcher()
39{
40	fTerminating = true;
41	// stop roster watching
42	be_roster->StopWatching(this);
43	// close/delete the ports
44	fRequestLock.Lock();
45	if (fRequestPort)
46		fRequestPort->Close();
47	fRequestLock.Unlock();
48	if (fConnectionPort >= 0)
49		delete_port(fConnectionPort);
50	if (fConnectionReplyPort >= 0)
51		delete_port(fConnectionReplyPort);
52	// wait for the request processor
53 	if (fRequestProcessor >= 0) {
54		int32 result;
55		wait_for_thread(fRequestProcessor, &result);
56	}
57}
58
59// Init
60status_t
61UserlandFSDispatcher::Init()
62{
63	// ensure that we are the only dispatcher
64	BClipboard clipboard(kUserlandFSDispatcherClipboardName);
65	if (!clipboard.Lock()) {
66		ERROR(("Failed to lock the clipboard.\n"));
67		return B_ERROR;
68	}
69	status_t error = B_OK;
70	if (BMessage* data = clipboard.Data()) {
71		// check the old value in the clipboard
72		BMessenger messenger;
73		if (data->FindMessenger("messenger", &messenger) == B_OK) {
74			if (messenger.IsValid()) {
75				PRINT(("There's already a dispatcher running.\n"));
76				error = B_ERROR;
77			}
78		}
79		// clear the clipboard
80		if (error == B_OK) {
81			clipboard.Clear();
82			data = clipboard.Data();
83			if (!data)
84				error = B_ERROR;
85		}
86		// add our messenger
87		if (error == B_OK) {
88			SET_ERROR(error, data->AddMessenger("messenger", be_app_messenger));
89			if (error == B_OK)
90				SET_ERROR(error, clipboard.Commit());
91			// work-around for BeOS R5: The very first commit to a clipboard
92			// (i.e. the one that creates the clipboard) seems to be ignored.
93			if (error == B_OK)
94				SET_ERROR(error, clipboard.Commit());
95			if (error != B_OK)
96				ERROR(("Failed to set clipboard messenger.\n"));
97		}
98	} else {
99		ERROR(("Failed to get clipboard data container\n"));
100		error = B_ERROR;
101	}
102	clipboard.Unlock();
103	if (error != B_OK)
104		return error;
105	// create the connection port and connection reply port
106	fConnectionPort = create_port(1, kUserlandFSDispatcherPortName);
107	if (fConnectionPort < 0)
108		return fConnectionPort;
109	fConnectionReplyPort = create_port(1, kUserlandFSDispatcherReplyPortName);
110	if (fConnectionReplyPort < 0)
111		return fConnectionReplyPort;
112	// start watching for terminated applications
113	error = be_roster->StartWatching(this, B_REQUEST_QUIT);
114	if (error != B_OK)
115		return error ;
116	// spawn request processor thread
117	fRequestProcessor = spawn_thread(_RequestProcessorEntry,
118		"main request processor", B_NORMAL_PRIORITY, this);
119	if (fRequestProcessor < 0)
120		return fRequestProcessor;
121	resume_thread(fRequestProcessor);
122	return B_OK;
123}
124
125// MessageReceived
126void
127UserlandFSDispatcher::MessageReceived(BMessage* message)
128{
129	switch (message->what) {
130		case UFS_REGISTER_FS:
131		{
132			// get the team
133			team_id team;
134			status_t error = message->FindInt32("team", &team);
135			if (error != B_OK)
136				PRINT(("UFS_REGISTER_FS failed: no team\n"));
137			// get the FS info
138			FSInfo* info = NULL;
139			if (error == B_OK) {
140				info = new(nothrow) FSInfo;
141				if (info) {
142					error = info->SetTo(message);
143				} else {
144					error = B_NO_MEMORY;
145					PRINT(("UFS_REGISTER_FS failed: failed to allocate "
146						"FSInfo\n"));
147				}
148			}
149			ObjectDeleter<FSInfo> infoDeleter(info);
150			// find the FileSystem
151			FileSystem* fileSystem = NULL;
152			if (error == B_OK) {
153				AutoLocker<FileSystemMap> _(fFileSystems);
154				fileSystem = _GetFileSystemNoInit(team);
155				if (fileSystem) {
156					fileSystem->CompleteInit(info);
157					infoDeleter.Detach();
158				} else {
159					PRINT(("UFS_REGISTER_FS: no FileSystem found for "
160						"team %ld, trying to register anyway\n", team));
161					// try to find by name
162					fileSystem = fFileSystems.Get(info->GetName());
163					if (fileSystem) {
164						// there's already an FS with that name registered
165						PRINT(("UFS_REGISTER_FS failed: FileSystem with "
166							"name %s does already exist.\n", info->GetName()));
167						fileSystem = NULL;
168						error = B_ERROR;
169					} else {
170						// the FS is not known yet: create one
171						fileSystem = new FileSystem(team, info, &error);
172						if (fileSystem) {
173							infoDeleter.Detach();
174						} else {
175							error = B_NO_MEMORY;
176							PRINT(("UFS_REGISTER_FS failed: failed to allocate "
177								"FileSystem\n"));
178						}
179						// add it
180						if (error == B_OK) {
181							error = fFileSystems.Put(info->GetName(),
182								fileSystem);
183							if (error != B_OK) {
184								PRINT(("UFS_REGISTER_FS failed: failed to "
185									"add FileSystem\n"));
186								delete fileSystem;
187							}
188						}
189					}
190				}
191			}
192			// send the reply
193			if (error == B_OK)
194				message->SendReply(UFS_REGISTER_FS_ACK);
195			else
196				message->SendReply(UFS_REGISTER_FS_DENIED);
197			if (fileSystem)
198				_PutFileSystem(fileSystem);
199			break;
200		}
201		case B_SOME_APP_QUIT:
202		{
203			// get the team
204			team_id team;
205			status_t error = message->FindInt32("be:team", &team);
206			if (error != B_OK)
207				return;
208			// find the FileSystem
209			FileSystem* fileSystem = _GetFileSystemNoInit(team);
210			if (!fileSystem)
211				return;
212			// abort the initialization
213			fileSystem->AbortInit();
214			_PutFileSystem(fileSystem);
215		}
216		default:
217			BApplication::MessageReceived(message);
218	}
219}
220
221// _GetFileSystem
222status_t
223UserlandFSDispatcher::_GetFileSystem(const char* name, FileSystem** _fileSystem)
224{
225	if (!name || !_fileSystem)
226		RETURN_ERROR(B_BAD_VALUE);
227	// get the file system
228	FileSystem* fileSystem;
229	{
230		AutoLocker<FileSystemMap> _(fFileSystems);
231		fileSystem = fFileSystems.Get(name);
232		if (fileSystem) {
233			fileSystem->AddReference();
234		} else {
235			// doesn't exists yet: create
236			status_t error;
237			fileSystem = new(nothrow) FileSystem(name, &error);
238			if (!fileSystem)
239				RETURN_ERROR(B_NO_MEMORY);
240			if (error == B_OK)
241				error = fFileSystems.Put(fileSystem->GetName(), fileSystem);
242			if (error != B_OK) {
243				delete fileSystem;
244				RETURN_ERROR(error);
245			}
246		}
247	}
248	// prepare access
249	status_t error = fileSystem->Access();
250	if (error != B_OK) {
251		_PutFileSystem(fileSystem);
252		RETURN_ERROR(error);
253	}
254	*_fileSystem = fileSystem;
255	return B_OK;
256}
257
258// _GetFileSystemNoInit
259FileSystem*
260UserlandFSDispatcher::_GetFileSystemNoInit(team_id team)
261{
262	AutoLocker<FileSystemMap> _(fFileSystems);
263	for (FileSystemMap::Iterator it = fFileSystems.GetIterator();
264		 it.HasNext();) {
265		FileSystem* fileSystem = it.Next().value;
266		if (fileSystem->GetTeam() == team) {
267			// found it
268			if (fileSystem)
269				fileSystem->AddReference();
270			return fileSystem;
271		}
272	}
273	return NULL;
274}
275
276// _PutFileSystem
277status_t
278UserlandFSDispatcher::_PutFileSystem(FileSystem* fileSystem)
279{
280	if (!fileSystem)
281		RETURN_ERROR(B_BAD_VALUE);
282	AutoLocker<FileSystemMap> _(fFileSystems);
283	if (fFileSystems.Get(fileSystem->GetName()) != fileSystem)
284		RETURN_ERROR(B_BAD_VALUE);
285	if (fileSystem->RemoveReference() && fileSystem->InitCheck() != B_OK) {
286PRINT(("removing FileSystem `%s'\n", fileSystem->GetName()));
287		fFileSystems.Remove(fileSystem->GetName());
288		delete fileSystem;
289	}
290	return B_OK;
291}
292
293// _WaitForConnection
294bool
295UserlandFSDispatcher::_WaitForConnection()
296{
297	while (!fTerminating) {
298		int32 code;
299		char buffer;
300		size_t bytesRead = read_port(fConnectionPort, &code, &buffer, 0);
301		if (bytesRead >= 0 && code == UFS_DISPATCHER_CONNECT) {
302			const Port::Info* info = fRequestPort->GetPortInfo();
303			size_t bytesWritten = write_port(fConnectionReplyPort,
304				UFS_DISPATCHER_CONNECT_ACK, info, sizeof(Port::Info));
305			if (bytesWritten >= 0)
306				return true;
307		}
308	}
309	return false;
310}
311
312// _ProcessRequests
313status_t
314UserlandFSDispatcher::_ProcessRequests()
315{
316	while (!fTerminating) {
317		Request* request;
318		status_t error = fRequestPort->ReceiveRequest(&request);
319		if (error != B_OK)
320			RETURN_ERROR(error);
321		RequestReleaser _(fRequestPort, request);
322		// check the request type
323		if (request->GetType() == UFS_DISCONNECT_REQUEST)
324			return B_OK;
325		if (request->GetType() != FS_CONNECT_REQUEST)
326			RETURN_ERROR(B_BAD_VALUE);
327		PRINT(("UserlandFSDispatcher::_ProcessRequests(): received FS connect "
328			"request\n"));
329		// it's an FS connect request
330		FSConnectRequest* connectRequest = (FSConnectRequest*)request;
331		// get the FS name
332		int32 len = connectRequest->fsName.GetSize();
333		status_t result = B_OK;
334		if (len <= 0)
335			result = B_BAD_DATA;
336		String fsName;
337		if (result == B_OK)
338			fsName.SetTo((const char*)connectRequest->fsName.GetData(), len);
339		if (result == B_OK && fsName.GetLength() == 0)
340			result = B_BAD_DATA;
341		// prepare the reply
342		RequestAllocator allocator(fRequestPort->GetPort());
343		FSConnectReply* reply;
344		error = AllocateRequest(allocator, &reply);
345		if (error != B_OK)
346			RETURN_ERROR(error);
347		FileSystem* fileSystem = NULL;
348		if (result == B_OK)
349			result = _GetFileSystem(fsName.GetString(), &fileSystem);
350		if (result == B_OK) {
351			const FSInfo* info = fileSystem->GetInfo();
352			result = allocator.AllocateData(reply->portInfos,
353				info->GetInfos(), info->GetSize(), sizeof(Port::Info));
354			if (result == B_OK)
355				reply->portInfoCount = info->CountInfos();
356			_PutFileSystem(fileSystem);
357		}
358		reply->error = result;
359		// send it
360		error = fRequestPort->SendRequest(&allocator);
361		if (error != B_OK)
362			RETURN_ERROR(error);
363	}
364	return B_OK;
365}
366
367// _RequestProcessorEntry
368int32
369UserlandFSDispatcher::_RequestProcessorEntry(void* data)
370{
371	return ((UserlandFSDispatcher*)data)->_RequestProcessor();
372}
373
374// _RequestProcessor
375int32
376UserlandFSDispatcher::_RequestProcessor()
377{
378PRINT(("UserlandFSDispatcher::_RequestProcessor()\n"));
379	while (!fTerminating) {
380		// allocate a request port
381		status_t error = B_OK;
382		{
383			fRequestLock.Lock();
384			fRequestPort = new(nothrow) RequestPort(kRequestPortSize);
385			if (fRequestPort)
386				error = fRequestPort->InitCheck();
387			else
388				error = B_NO_MEMORY;
389			if (error != B_OK) {
390				delete fRequestPort;
391				fRequestPort = NULL;
392			}
393			fRequestLock.Unlock();
394		}
395		if (error != B_OK) {
396			be_app->PostMessage(B_QUIT_REQUESTED);
397PRINT(("  failed to allocate request port: %s\n", strerror(error)));
398			return error;
399		}
400		// wait for a connection and process the requests
401		if (_WaitForConnection()) {
402			PRINT(("UserlandFSDispatcher::_RequestProcessor(): connected\n"));
403			_ProcessRequests();
404			PRINT(("UserlandFSDispatcher::_RequestProcessor(): "
405				"disconnected\n"));
406		}
407		// delete the request port
408		fRequestLock.Lock();
409		delete fRequestPort;
410		fRequestPort = NULL;
411		fRequestLock.Unlock();
412	}
413PRINT(("UserlandFSDispatcher::_RequestProcessor() done\n"));
414	return B_OK;
415}
416
417