xref: /haiku/src/kits/storage/AddOnMonitorHandler.cpp (revision 1d9d47fc72028bb71b5f232a877231e59cfe2438)
1 #include "AddOnMonitorHandler.h"
2 #include <Directory.h>
3 
4 #ifndef ADD_ON_STABLE_SECONDS
5 #define ADD_ON_STABLE_SECONDS 15
6 #endif
7 
8 /*
9  * public functions
10  */
11 
12 AddOnMonitorHandler::AddOnMonitorHandler(const char * name)
13 	: NodeMonitorHandler(name)
14 {
15 }
16 
17 
18 AddOnMonitorHandler::~AddOnMonitorHandler()
19 {
20 }
21 
22 
23 /* virtual */ void
24 AddOnMonitorHandler::MessageReceived(BMessage * msg)
25 {
26 	if (msg->what == B_PULSE) {
27 		HandlePulse();
28 	}
29 	inherited::MessageReceived(msg);
30 }
31 
32 
33 /* virtual */ status_t
34 AddOnMonitorHandler::AddDirectory(const node_ref * nref)
35 {
36 	// ignore directories added twice
37 	std::list<add_on_directory_info>::iterator diter = directories.begin();
38 	for( ; diter != directories.end() ; diter++) {
39 		if (diter->nref == *nref) {
40 			return B_OK;
41 		}
42 	}
43 
44 	BDirectory directory(nref);
45 	status_t status = directory.InitCheck();
46 	if (status != B_OK) {
47 		return status;
48 	}
49 
50 	add_on_directory_info dir_info;
51 	dir_info.nref = *nref;
52 	directories.push_back(dir_info);
53 
54 	status = watch_node(nref, B_WATCH_DIRECTORY, this);
55 	if (status != B_OK) {
56 		directories.pop_back();
57 		return status;
58 	}
59 
60 	BEntry entry;
61 	while (directory.GetNextEntry(&entry, true) == B_OK) {
62 		add_on_entry_info entry_info;
63 		if (entry.GetName(entry_info.name) != B_OK) {
64 			continue; // discard and proceed
65 		}
66 		if (entry.GetNodeRef(&entry_info.nref) != B_OK) {
67 			continue; // discard and proceed
68 		}
69 		entry_info.dir_nref = *nref;
70 		pending_entries.push_back(entry_info);
71 	}
72 
73 	return B_OK;
74 }
75 
76 
77 /*
78  * AddOnMonitorHandler hooks
79  */
80 
81 /* virtual */ void
82 AddOnMonitorHandler::AddOnCreated(const add_on_entry_info * entry_info)
83 {
84 
85 }
86 
87 
88 /* virtual */ void
89 AddOnMonitorHandler::AddOnEnabled(const add_on_entry_info * entry_info)
90 {
91 
92 }
93 
94 
95 /* virtual */ void
96 AddOnMonitorHandler::AddOnDisabled(const add_on_entry_info * entry_info)
97 {
98 
99 }
100 
101 
102 /* virtual */ void
103 AddOnMonitorHandler::AddOnRemoved(const add_on_entry_info * entry_info)
104 {
105 
106 }
107 
108 
109 /*
110  * NodeMonitorHandler hooks
111  */
112 
113 
114 /* virtual */ void
115 AddOnMonitorHandler::EntryCreated(const char *name, ino_t directory,
116                            dev_t device, ino_t node)
117 {
118 	add_on_entry_info entry_info;
119 	strncpy(entry_info.name, name, sizeof(entry_info.name));
120 	make_node_ref(device, node, &entry_info.nref);
121 	make_node_ref(device, directory, &entry_info.dir_nref);
122 	pending_entries.push_back(entry_info);
123 }
124 
125 
126 /* virtual */ void
127 AddOnMonitorHandler::EntryRemoved(ino_t directory, dev_t device, ino_t node)
128 {
129 	node_ref entry_nref;
130 	make_node_ref(device, node, &entry_nref);
131 
132 	// Search pending entries first, which can simply be discarded
133 	// We might have this entry in the pending list multiple times,
134 	// so we search entire list through, even after finding one.
135 	std::list<add_on_entry_info>::iterator eiter = pending_entries.begin();
136 	while (eiter != pending_entries.end()) {
137 		if (eiter->nref == entry_nref) {
138 			eiter = pending_entries.erase(eiter);
139 		} else {
140 			eiter++;
141 		}
142 	}
143 
144 	add_on_entry_info info;
145 	node_ref dir_nref;
146 	make_node_ref(device, directory, &dir_nref);
147 
148 	// find the entry's info, and the entry's directory info
149 	std::list<add_on_directory_info>::iterator diter = directories.begin();
150 	for( ; diter != directories.end() ; diter++) {
151 		if (diter->nref == dir_nref) {
152 			std::list<add_on_entry_info>::iterator eiter = diter->entries.begin();
153 			for( ; eiter != diter->entries.end() ; eiter++) {
154 				info = *eiter;
155 				if (eiter->nref == entry_nref) {
156 					info = *eiter;
157 					diter->entries.erase(eiter);
158 					break;
159 				}
160 			}
161 			break;
162 		}
163 	}
164 
165 	// if it was not found, we're done
166 	if (diter == directories.end()) {
167 		return;
168 	}
169 
170 	// Start at the top again, and search until the directory we found
171 	// the old add_on in.  If we find a add_on with the same name then
172 	// the old add_on was not enabled.  So we deallocate the old
173 	// add_on and return.
174 	std::list<add_on_directory_info>::iterator diter2 = directories.begin();
175 	for( ; diter2 != diter ; diter2++) {
176 		std::list<add_on_entry_info>::iterator eiter = diter2->entries.begin();
177 		for( ; eiter != diter2->entries.end() ; eiter++) {
178 			if (strcmp(eiter->name, info.name) == 0) {
179 				AddOnRemoved(&info);
180 				return;
181 			}
182 		}
183 	}
184 
185 	// The active plugin was removed.  We need to disable and
186 	// then subsequently deallocate it.
187 	AddOnDisabled(&info);
188 	AddOnRemoved(&info);
189 
190 	// Continue searching for a add_on below us.  If we find a add_on
191 	// with the same name, we must enable it.
192 	for (diter++ ; diter != directories.end() ; diter++) {
193 		std::list<add_on_entry_info>::iterator eiter = diter->entries.begin();
194 		for( ; eiter != diter->entries.end() ; eiter++) {
195 			if (strcmp(eiter->name, info.name) == 0) {
196 				AddOnEnabled(&*eiter);
197 				return;
198 			}
199 		}
200 	}
201 }
202 
203 
204 /* virtual */ void
205 AddOnMonitorHandler::EntryMoved(const char *name, ino_t from_directory,
206                          ino_t to_directory, dev_t device, ino_t node)
207 {
208 	node_ref from_nref;
209 	make_node_ref(device, from_directory, &from_nref);
210 	std::list<add_on_directory_info>::iterator from_iter = directories.begin();
211 	for ( ; from_iter != directories.end() ; from_iter++) {
212 		if (from_iter->nref == from_nref) {
213 			break;
214 		}
215 	}
216 
217 	node_ref to_nref;
218 	make_node_ref(device, to_directory, &to_nref);
219 	std::list<add_on_directory_info>::iterator to_iter = directories.begin();
220 	for ( ; to_iter != directories.end() ; to_iter++) {
221 		if (to_iter->nref == to_nref) {
222 			break;
223 		}
224 	}
225 
226 	if ((from_iter == directories.end()) && (to_iter == directories.end())) {
227 		// huh? whatever...
228 		return;
229 	}
230 
231 	add_on_entry_info info;
232 	node_ref entry_nref;
233 	make_node_ref(device, node, &entry_nref);
234 
235 	if (to_iter == directories.end()) {
236 		// moved out of our view
237 		std::list<add_on_entry_info>::iterator eiter = from_iter->entries.begin();
238 		for( ; eiter != from_iter->entries.end() ; eiter++) {
239 			if (entry_nref == eiter->nref) {
240 				// save the info and remove the entry
241 				info = *eiter;
242 				from_iter->entries.erase(eiter);
243 				break;
244 			}
245 		}
246 
247 		if (eiter == from_iter->entries.end()) {
248 			// we don't know anything about this entry yet.. ignore it
249 			return;
250 		}
251 
252 		// if the name is the same, save the information about this
253 		// add_on into former_entries for later use
254 		if (strcmp(info.name, name) == 0) {
255 			former_entries.push_back(info);
256 		}
257 
258 		// Start at the top again, and search until the from directory.
259 		// If we find a add_on with the same name then the moved add_on
260 		// was not enabled.  So we are done.
261 		std::list<add_on_directory_info>::iterator diter = directories.begin();
262 		for( ; diter != from_iter ; diter++) {
263 			std::list<add_on_entry_info>::iterator eiter2 = diter->entries.begin();
264 			for( ; eiter2 != diter->entries.end() ; eiter2++) {
265 				if (strcmp(eiter2->name, info.name) == 0) {
266 					return;
267 				}
268 			}
269 		}
270 
271 		// finally disable the add_on
272 		AddOnDisabled(&info);
273 
274 		// Continue searching for a add_on below us.  If we find a add_on
275 		// with the same name, we must enable it.
276 		for (from_iter++ ; from_iter != directories.end() ; from_iter++) {
277 			std::list<add_on_entry_info>::iterator eiter2 = from_iter->entries.begin();
278 			for( ; eiter2 != from_iter->entries.end() ; eiter2++) {
279 				if (strcmp(eiter2->name, info.name) == 0) {
280 					AddOnEnabled(&*eiter2);
281 					return;
282 				}
283 			}
284 		}
285 
286 		// finally, if the new name is different, destroy the addon
287 		if (strcmp(info.name, name) != 0) {
288 			AddOnRemoved(&info);
289 		}
290 
291 		// done
292 		return;
293 	}
294 
295 	if (from_iter == directories.end()) {
296 		// moved into our view
297 		std::list<add_on_entry_info>::iterator eiter = former_entries.begin();
298 		for( ; eiter != former_entries.end() ; eiter++) {
299 			if (entry_nref == eiter->nref) {
300 				// save the info and remove the entry
301 				info = *eiter;
302 				former_entries.erase(eiter);
303 				break;
304 			}
305 		}
306 
307 		if (eiter != former_entries.end()) {
308 			if (strcmp(info.name, name) != 0) {
309 				// name changed on the way in, remove the old one
310 				AddOnRemoved(&info);
311 			}
312 		}
313 
314 		// update the info
315 		strncpy(info.name, name, sizeof(info.name));
316 		info.nref = entry_nref;
317 		info.dir_nref = to_nref;
318 
319 		if (eiter == former_entries.end()) {
320 			// this add_on was not seen before
321 			AddOnCreated(&info);
322 		}
323 
324 		// Start at the top again, and search until the to directory.
325 		// If we find a add_on with the same name then the moved add_on
326 		// is not to be enabled.  So we are done.
327 		std::list<add_on_directory_info>::iterator diter = directories.begin();
328 		for( ; diter != to_iter ; diter++) {
329 			std::list<add_on_entry_info>::iterator eiter2 = diter->entries.begin();
330 			for( ; eiter2 != diter->entries.end() ; eiter2++) {
331 				if (strcmp(eiter2->name, info.name) == 0) {
332 					return;
333 				}
334 			}
335 		}
336 
337 		// The new add_on should be enabled, but first we check to see
338 		// if there is an add_on below us.  If we find one, we disable it.
339 		bool shadowing = false;
340 		for (diter++ ; diter != directories.end() ; diter++) {
341 			std::list<add_on_entry_info>::iterator eiter2 = diter->entries.begin();
342 			for( ; eiter2 != diter->entries.end() ; eiter2++) {
343 				if (strcmp(eiter2->name, info.name) == 0) {
344 					AddOnDisabled(&*eiter2);
345 					shadowing = true;
346 					break;
347 				}
348 			}
349 			if (shadowing) {
350 				break;
351 			}
352 		}
353 
354 		// enable the new add_on
355 		AddOnEnabled(&info);
356 
357 		// put the new entry into the target directory
358 		to_iter->entries.push_back(info);
359 
360 		// done
361 		return;
362 	}
363 
364 	std::list<add_on_entry_info>::iterator eiter = from_iter->entries.begin();
365 	for( ; eiter != from_iter->entries.end() ; eiter++) {
366 		if (entry_nref == eiter->nref) {
367 			// save the old info and remove the entry
368 			info = *eiter;
369 			from_iter->entries.erase(eiter);
370 			break;
371 		}
372 	}
373 
374 	if (strcmp(info.name, name) == 0) {
375 		// same name moved in heirarchy
376 		debugger("add_on moved inside the heirarchy");
377 	} else {
378 		// check to see if it was formerly enabled
379 		bool was_enabled = true;
380 		std::list<add_on_directory_info>::iterator old_iter = directories.begin();
381 		for( ; old_iter != from_iter ; old_iter++) {
382 			std::list<add_on_entry_info>::iterator eiter2 = old_iter->entries.begin();
383 			for( ; eiter2 != old_iter->entries.end() ; eiter2++) {
384 				if (strcmp(eiter2->name, info.name) == 0) {
385 					was_enabled = false;
386 					break;
387 				}
388 			}
389 			if (!was_enabled) {
390 				break;
391 			}
392 		}
393 
394 		// if it was enabled, disable it and enable the one under us, if it exists
395 		if (was_enabled) {
396 			AddOnDisabled(&info);
397 			bool done = false;
398 			for( ; old_iter != directories.end() ; old_iter++) {
399 				std::list<add_on_entry_info>::iterator eiter2 = old_iter->entries.begin();
400 				for( ; eiter2 != old_iter->entries.end() ; eiter2++) {
401 					if (strcmp(eiter2->name, info.name) == 0) {
402 						AddOnEnabled(&*eiter2);
403 						done = true;
404 						break;
405 					}
406 				}
407 				if (done) {
408 					break;
409 				}
410 			}
411 		}
412 
413 		// kaboom!
414 		AddOnRemoved(&info);
415 
416 		// set up new addon info
417 		strncpy(info.name, name, sizeof(info.name));
418 		info.dir_nref = to_nref;
419 
420 		// presto!
421 		AddOnCreated(&info);
422 
423 		// check to see if we are newly enabled
424 		bool is_enabled = true;
425 		std::list<add_on_directory_info>::iterator new_iter = directories.begin();
426 		for( ; new_iter != to_iter ; new_iter++) {
427 			std::list<add_on_entry_info>::iterator eiter2 = new_iter->entries.begin();
428 			for( ; eiter2 != new_iter->entries.end() ; eiter2++) {
429 				if (strcmp(eiter2->name, info.name) == 0) {
430 					is_enabled = false;
431 					break;
432 				}
433 			}
434 			if (!is_enabled) {
435 				break;
436 			}
437 		}
438 
439 		// if it is newly enabled, check under us for an enabled one, and disable that first
440 		if (is_enabled) {
441 			bool done = false;
442 			for( ; new_iter != directories.end() ; new_iter++) {
443 				std::list<add_on_entry_info>::iterator eiter2 = new_iter->entries.begin();
444 				for( ; eiter2 != new_iter->entries.end() ; eiter2++) {
445 					if (strcmp(eiter2->name, info.name) == 0) {
446 						AddOnDisabled(&*eiter2);
447 						done = true;
448 						break;
449 					}
450 				}
451 				if (done) {
452 					break;
453 				}
454 			}
455 			AddOnEnabled(&info);
456 		}
457 	}
458 
459 	// put the new entry into the target directory
460 	to_iter->entries.push_back(info);
461 }
462 
463 
464 /*
465  * process pending entries
466  */
467 
468 void
469 AddOnMonitorHandler::HandlePulse()
470 {
471 	BDirectory directory;
472 	std::list<add_on_entry_info>::iterator iter = pending_entries.begin();
473 	while (iter != pending_entries.end()) {
474 		add_on_entry_info info = *iter;
475 
476 		node_ref dir_nref;
477 		if ((directory.GetNodeRef(&dir_nref) != B_OK) ||
478 			(dir_nref != info.dir_nref)) {
479 			if (directory.SetTo(&info.dir_nref) != B_OK) {
480 				// invalid directory, discard this pending entry
481 				iter = pending_entries.erase(iter);
482 				continue;
483 			}
484 			dir_nref = info.dir_nref;
485 		}
486 
487 		struct stat st;
488 		if (directory.GetStatFor(info.name, &st) != B_OK) {
489 			// invalid file name, discard this pending entry
490 			iter = pending_entries.erase(iter);
491 			continue;
492 		}
493 
494 		// stat units are seconds, real_time_clock units are seconds
495 		if (real_time_clock() - st.st_mtime < ADD_ON_STABLE_SECONDS) {
496 			// entry not stable, skip the entry for this pulse
497 			iter++;
498 			continue;
499 		}
500 
501 		// we are going to deal with the stable entry, so remove it
502 		iter = pending_entries.erase(iter);
503 
504 		// put the new entry into the directory info
505 		std::list<add_on_directory_info>::iterator diter = directories.begin();
506 		for( ; diter != directories.end() ; diter++) {
507 			if (diter->nref == dir_nref) {
508 				diter->entries.push_back(info);
509 				break;
510 			}
511 		}
512 
513 		// report it
514 		AddOnCreated(&info);
515 
516 		// Start at the top again, and search until the directory we put
517 		// the new add_on in.  If we find a add_on with the same name then
518 		// the new add_on should not be enabled.
519 		bool enabled = true;
520 		std::list<add_on_directory_info>::iterator diter2 = directories.begin();
521 		for( ; diter2 != diter ; diter2++) {
522 			std::list<add_on_entry_info>::iterator eiter = diter2->entries.begin();
523 			for( ; eiter != diter2->entries.end() ; eiter++) {
524 				if (strcmp(eiter->name, info.name) == 0) {
525 					enabled = false;
526 					break;
527 				}
528 			}
529 			if (!enabled) {
530 				break;
531 			}
532 		}
533 		if (!enabled) {
534 			// if we are not enabled, go on to the next pending entry
535 			continue;
536 		}
537 
538 		// The new add_on should be enabled, but first we check to see
539 		// if there is an add_on below us.  If we find one, we disable it.
540 		bool shadowing = false;
541 		for (diter++ ; diter != directories.end() ; diter++) {
542 			std::list<add_on_entry_info>::iterator eiter = diter->entries.begin();
543 			for( ; eiter != diter->entries.end() ; eiter++) {
544 				if (strcmp(eiter->name, info.name) == 0) {
545 					AddOnDisabled(&*eiter);
546 					shadowing = true;
547 					break;
548 				}
549 			}
550 			if (shadowing) {
551 				break;
552 			}
553 		}
554 
555 		// finally, enable the new entry
556 		AddOnEnabled(&info);
557 	}
558 }
559