vfs: check userland buffers before reading them.
[haiku.git] / src / apps / terminal / TerminalRoster.cpp
blobec4e8ac6a32a49d2a1ee08c0f73bf1dc1bcca7d2
1 /*
2 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "TerminalRoster.h"
9 #include <stdio.h>
11 #include <new>
13 #include <Looper.h>
14 #include <Roster.h>
15 #include <String.h>
17 #include <AutoLocker.h>
19 #include "TermConst.h"
22 static const bigtime_t kAppsRunningCheckInterval = 1000000;
25 // #pragma mark - Info
28 /*! Creates an Info with the given \a id and \a team ID.
29 \c workspaces is set to 0 and \c minimized to \c true.
31 TerminalRoster::Info::Info(int32 id, team_id team)
33 id(id),
34 team(team),
35 workspaces(0),
36 minimized(true)
41 /*! Create an Info and initializes its data from \a archive.
43 TerminalRoster::Info::Info(const BMessage& archive)
45 if (archive.FindInt32("id", &id) != B_OK)
46 id = -1;
47 if (archive.FindInt32("team", &team) != B_OK)
48 team = -1;
49 if (archive.FindUInt32("workspaces", &workspaces) != B_OK)
50 workspaces = 0;
51 if (archive.FindBool("minimized", &minimized) != B_OK)
52 minimized = true;
56 /*! Writes the Info's data into fields of \a archive.
57 The const BMessage& constructor can restore an identical Info from it.
59 status_t
60 TerminalRoster::Info::Archive(BMessage& archive) const
62 status_t error;
63 if ((error = archive.AddInt32("id", id)) != B_OK
64 || (error = archive.AddInt32("team", team)) != B_OK
65 || (error = archive.AddUInt32("workspaces", workspaces)) != B_OK
66 || (error = archive.AddBool("minimized", minimized)) != B_OK) {
67 return error;
70 return B_OK;
74 /*! Compares two Infos.
75 Infos are considered equal, iff all data members are.
77 bool
78 TerminalRoster::Info::operator==(const Info& other) const
80 return id == other.id && team == other.team
81 && workspaces == other.workspaces && minimized == other.minimized;
85 // #pragma mark - TerminalRoster
88 /*! Creates a TerminalRoster.
89 Most methods cannot be used until Register() has been invoked.
91 TerminalRoster::TerminalRoster()
93 BHandler("terminal roster"),
94 fLock("terminal roster"),
95 fClipboard(TERM_SIGNATURE),
96 fInfos(10, true),
97 fOurInfo(NULL),
98 fLastCheckedTime(0),
99 fListener(NULL),
100 fInfosUpdated(false)
105 /*! Locks the object.
106 Also makes sure the roster list is reasonably up-to-date.
108 bool
109 TerminalRoster::Lock()
111 // lock
112 bool locked = fLock.Lock();
113 if (!locked)
114 return false;
116 // make sure we're registered
117 if (fOurInfo == NULL) {
118 fLock.Unlock();
119 return false;
122 // If the check interval has passed, make sure all infos still have running
123 // teams.
124 bigtime_t now = system_time();
125 if (fLastCheckedTime + kAppsRunningCheckInterval) {
126 bool needsUpdate = false;
127 for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
128 if (!_TeamIsRunning(info->team)) {
129 needsUpdate = true;
130 break;
134 if (needsUpdate) {
135 AutoLocker<BClipboard> clipboardLocker(fClipboard);
136 if (clipboardLocker.IsLocked()) {
137 if (_UpdateInfos(true) == B_OK)
138 _UpdateClipboard();
140 } else
141 fLastCheckedTime = now;
144 return true;
148 /*! Unlocks the object.
149 As a side effect the listener will be notified, if the terminal list has
150 changed in any way.
152 void
153 TerminalRoster::Unlock()
155 if (fOurInfo != NULL && fInfosUpdated) {
156 // the infos have changed -- notify our listener
157 _NotifyListener();
160 fLock.Unlock();
164 /*! Registers a terminal with the roster and establishes a link.
166 The object attaches itself to the supplied \a looper and will receive
167 updates via messaging (obviously the looper must run (not necessarily
168 right now) for this to work).
170 \param teamID The team ID of this team.
171 \param looper A looper the object can attach itself to.
172 \return \c B_OK, if successful, another error code otherwise.
174 status_t
175 TerminalRoster::Register(team_id teamID, BLooper* looper)
177 AutoLocker<BLocker> locker(fLock);
179 if (fOurInfo != NULL) {
180 // already registered
181 return B_BAD_VALUE;
184 // lock the clipboard
185 AutoLocker<BClipboard> clipboardLocker(fClipboard);
186 if (!clipboardLocker.IsLocked())
187 return B_BAD_VALUE;
189 // get the current infos from the clipboard
190 status_t error = _UpdateInfos(true);
191 if (error != B_OK)
192 return error;
194 // find an unused ID
195 int32 id = 0;
196 for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
197 if (info->id > id)
198 break;
199 id++;
202 // create our own info
203 fOurInfo = new(std::nothrow) Info(id, teamID);
204 if (fOurInfo == NULL)
205 return B_NO_MEMORY;
207 // insert it
208 if (!fInfos.BinaryInsert(fOurInfo, &_CompareInfos)) {
209 delete fOurInfo;
210 fOurInfo = NULL;
211 return B_NO_MEMORY;
214 // update the clipboard
215 error = _UpdateClipboard();
216 if (error != B_OK) {
217 fInfos.MakeEmpty(true);
218 fOurInfo = NULL;
219 return error;
222 // add ourselves to the looper and start watching
223 looper->AddHandler(this);
225 be_roster->StartWatching(this, B_REQUEST_QUIT);
226 fClipboard.StartWatching(this);
228 // Update again in case we've missed a update message sent before we were
229 // listening.
230 _UpdateInfos(false);
232 return B_OK;
236 /*! Unregisters the terminal from the roster and closes the link.
238 Basically undoes all effects of Register().
240 void
241 TerminalRoster::Unregister()
243 AutoLocker<BLocker> locker(fLock);
244 if (!locker.IsLocked())
245 return;
247 // stop watching and remove ourselves from the looper
248 be_roster->StartWatching(this);
249 fClipboard.StartWatching(this);
251 Looper()->RemoveHandler(this);
253 // lock the clipboard and get the current infos
254 AutoLocker<BClipboard> clipboardLocker(fClipboard);
255 if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK)
256 return;
258 // remove our info and update the clipboard
259 fInfos.RemoveItem(fOurInfo);
260 fOurInfo = NULL;
262 _UpdateClipboard();
266 /*! Returns the ID assigned to this terminal when it was registered.
268 int32
269 TerminalRoster::ID() const
271 return fOurInfo != NULL ? fOurInfo->id : -1;
275 /*! Updates this terminal's window status.
276 All other running terminals will be notified, if the status changed.
278 \param minimized \c true, if the window is minimized.
279 \param workspaces The window's workspaces mask.
281 void
282 TerminalRoster::SetWindowInfo(bool minimized, uint32 workspaces)
284 AutoLocker<TerminalRoster> locker(this);
285 if (!locker.IsLocked())
286 return;
288 if (minimized == fOurInfo->minimized && workspaces == fOurInfo->workspaces)
289 return;
291 fOurInfo->minimized = minimized;
292 fOurInfo->workspaces = workspaces;
293 fInfosUpdated = true;
295 // lock the clipboard and get the current infos
296 AutoLocker<BClipboard> clipboardLocker(fClipboard);
297 if (!clipboardLocker.IsLocked() || _UpdateInfos(false) != B_OK)
298 return;
300 // update the clipboard to make our change known to the others
301 _UpdateClipboard();
305 /*! Overriden to handle update messages.
307 void
308 TerminalRoster::MessageReceived(BMessage* message)
310 switch (message->what) {
311 case B_SOME_APP_QUIT:
313 BString signature;
314 if (message->FindString("be:signature", &signature) != B_OK
315 || signature != TERM_SIGNATURE) {
316 break;
318 // fall through
321 case B_CLIPBOARD_CHANGED:
323 // lock ourselves and the clipboard and update the infos
324 AutoLocker<TerminalRoster> locker(this);
325 AutoLocker<BClipboard> clipboardLocker(fClipboard);
326 if (clipboardLocker.IsLocked()) {
327 _UpdateInfos(false);
329 if (fInfosUpdated)
330 _NotifyListener();
333 break;
336 default:
337 BHandler::MessageReceived(message);
338 break;
343 /*! Updates the terminal info list from the clipboard.
345 \param checkApps If \c true, it is checked for each found info whether the
346 respective team is still running. If not, the info is removed from the
347 list (though not from the clipboard).
348 \return \c B_OK, if the update went fine, another error code otherwise. When
349 an error occurs the object state will still be consistent, but might no
350 longer be up-to-date.
352 status_t
353 TerminalRoster::_UpdateInfos(bool checkApps)
355 BMessage* data = fClipboard.Data();
357 // find out how many infos we can expect
358 type_code type;
359 int32 count;
360 status_t error = data->GetInfo("teams", &type, &count);
361 if (error != B_OK)
362 count = 0;
364 // create an info list from the message
365 InfoList infos(10, true);
366 for (int32 i = 0; i < count; i++) {
367 // get the team's message
368 BMessage teamData;
369 error = data->FindMessage("teams", i, &teamData);
370 if (error != B_OK)
371 return error;
373 // create the info
374 Info* info = new(std::nothrow) Info(teamData);
375 if (info == NULL)
376 return B_NO_MEMORY;
377 if (info->id < 0 || info->team < 0
378 || infos.BinarySearchByKey(info->id, &_CompareIDInfo) != NULL
379 || (checkApps && !_TeamIsRunning(info->team))) {
380 // invalid/duplicate info -- skip
381 delete info;
382 fInfosUpdated = true;
383 continue;
386 // add it to the list
387 if (!infos.BinaryInsert(info, &_CompareInfos)) {
388 delete info;
389 return B_NO_MEMORY;
393 // update the current info list from the infos we just read
394 int32 oldIndex = 0;
395 int32 newIndex = 0;
396 while (oldIndex < fInfos.CountItems() || newIndex < infos.CountItems()) {
397 Info* oldInfo = fInfos.ItemAt(oldIndex);
398 Info* newInfo = infos.ItemAt(newIndex);
400 if (oldInfo == NULL || (newInfo != NULL && oldInfo->id > newInfo->id)) {
401 // new info is not in old list -- transfer it
402 if (!fInfos.AddItem(newInfo, oldIndex++))
403 return B_NO_MEMORY;
404 infos.RemoveItemAt(newIndex);
405 fInfosUpdated = true;
406 } else if (newInfo == NULL || oldInfo->id < newInfo->id) {
407 // old info is not in new list -- delete it, unless it's our own
408 if (oldInfo == fOurInfo) {
409 oldIndex++;
410 } else {
411 delete fInfos.RemoveItemAt(oldIndex);
412 fInfosUpdated = true;
414 } else {
415 // info is in both lists -- update the old info, unless it's our own
416 if (oldInfo != fOurInfo) {
417 if (*oldInfo != *newInfo) {
418 *oldInfo = *newInfo;
419 fInfosUpdated = true;
423 oldIndex++;
424 newIndex++;
428 if (checkApps)
429 fLastCheckedTime = system_time();
431 return B_OK;
435 /*! Updates the clipboard with the object's terminal info list.
437 \return \c B_OK, if the update went fine, another error code otherwise. When
438 an error occurs the object state will still be consistent, but might no
439 longer be in sync with the clipboard.
441 status_t
442 TerminalRoster::_UpdateClipboard()
444 // get the clipboard data message
445 BMessage* data = fClipboard.Data();
446 if (data == NULL)
447 return B_BAD_VALUE;
449 // clear the message and add all infos
450 data->MakeEmpty();
452 for (int32 i = 0; const Info* info = TerminalAt(i); i++) {
453 BMessage teamData;
454 status_t error = info->Archive(teamData);
455 if (error != B_OK
456 || (error = data->AddMessage("teams", &teamData)) != B_OK) {
457 fClipboard.Revert();
458 return error;
462 // commit the changes
463 status_t error = fClipboard.Commit();
464 if (error != B_OK) {
465 fClipboard.Revert();
466 return error;
469 return B_OK;
473 /*! Notifies the listener, if something has changed.
475 void
476 TerminalRoster::_NotifyListener()
478 if (!fInfosUpdated)
479 return;
481 if (fListener != NULL)
482 fListener->TerminalInfosUpdated(this);
484 fInfosUpdated = false;
488 /*static*/ int
489 TerminalRoster::_CompareInfos(const Info* a, const Info* b)
491 return a->id - b->id;
495 /*static*/ int
496 TerminalRoster::_CompareIDInfo(const int32* id, const Info* info)
498 return *id - info->id;
502 bool
503 TerminalRoster::_TeamIsRunning(team_id teamID)
505 // we are running for sure
506 if (fOurInfo != NULL && fOurInfo->team == teamID)
507 return true;
509 team_info info;
510 return get_team_info(teamID, &info) == B_OK;
514 // #pragma mark - Listener
517 TerminalRoster::Listener::~Listener()