Added a flush option to pnd_log, which makes log-to-stdout useful
[pandora-libraries.git] / apps / pndnotifyd.c
1
2 /* pndnotifyd - a daemon whose job is to monitor searchpaths for app changes (appearing, disappearing, changing).
3  * If a change is found, the discovery code is invoked and apps registered for the launchers to see
4  *
5  */
6
7 #include <stdio.h>     // for stdio
8 #include <unistd.h>    // for exit()
9 #include <stdlib.h>    // for exit()
10 #define __USE_GNU /* for strcasestr */
11 #include <string.h>
12 #include <time.h>      // for time()
13 #include <ctype.h>     // for isdigit()
14 #include <sys/types.h> // for umask
15 #include <sys/stat.h>  // for umask
16 #include <dirent.h>    // for opendir()
17 #include <signal.h>    // for sigaction
18
19 #include "pnd_conf.h"
20 #include "pnd_container.h"
21 #include "pnd_apps.h"
22 #include "pnd_notify.h"
23 #include "../lib/pnd_pathiter.h"
24 #include "pnd_discovery.h"
25 #include "pnd_locate.h"
26 #include "pnd_pxml.h"
27 #include "pnd_utility.h"
28 #include "pnd_desktop.h"
29 #include "pnd_logger.h"
30
31 // this piece of code was simpler once; but need to grow it a bit and in a rush
32 // moving all these to globals rather than refactor the code a bit; tsk tsk..
33
34 // op mode; emitting stdout or no?
35 static unsigned char g_daemon_mode = 0;
36
37 typedef enum {
38   pndn_debug = 0,
39   pndn_rem,          // will set default log level to here, so 'debug' is omitted
40   pndn_warning,
41   pndn_error,
42   pndn_none
43 } pndnotify_loglevels_e;
44
45 // like discotest
46 char *configpath;
47 char *overridespath;
48 // daemon stuff
49 char *searchpath = NULL;
50 char *notifypath = NULL;
51 time_t createtime = 0; // all 'new' .destops are created at or after this time; prev are old.
52 // dotfiles; this used to be a single pai .. now two pairs, a little unwieldy; pnd_box it up?
53 char *desktop_dotdesktoppath = NULL;
54 char *desktop_iconpath = NULL;
55 char *desktop_appspath = NULL;
56 char *menu_dotdesktoppath = NULL;
57 char *menu_iconpath = NULL;
58 char *menu_appspath = NULL;
59 // pnd runscript
60 char *run_searchpath; // searchpath to find pnd_run.sh
61 char *run_script;     // name of pnd_run.sh script from config
62 char *pndrun;         // full path to located pnd_run.sh
63 char *pndhup = NULL;  // full path to located pnd_hup.sh
64 // notifier handle
65 pnd_notify_handle nh = 0;
66
67 // constants
68 #define PNDNOTIFYD_LOGLEVEL "pndnotifyd.loglevel"
69
70 // decl's
71 void consume_configuration ( void );
72 void setup_notifications ( void );
73 void sighup_handler ( int n );
74 void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *emiticonpath );
75 unsigned char perform_discoveries ( char *appspath, char *overridespath,
76                                     char *emitdesktoppath, char *emiticonpath );
77
78 int main ( int argc, char *argv[] ) {
79   // behaviour
80   unsigned char scanonlaunch = 1;
81   unsigned int interval_secs = 5;
82   // misc
83   int i;
84
85   /* iterate across args
86    */
87   for ( i = 1; i < argc; i++ ) {
88
89     if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'd' ) {
90       //printf ( "Going daemon mode. Silent running.\n" );
91       g_daemon_mode = 1;
92     } else if ( isdigit ( argv [ i ][ 0 ] ) ) {
93       interval_secs = atoi ( argv [ i ] );
94     } else if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'n' ) {
95       scanonlaunch = 0;
96     } else {
97       printf ( "%s [-d] [##]\n", argv [ 0 ] );
98       printf ( "-d\tDaemon mode; detach from terminal, chdir to /tmp, suppress output. Optional.\n" );
99       printf ( "-n\tDo not scan on launch; default is to run a scan for apps when %s is invoked.\n", argv [ 0 ] );
100       printf ( "##\tA numeric value is interpreted as number of seconds between checking for filesystem changes. Default %u.\n",
101                interval_secs );
102       printf ( "Signal: HUP the process to force reload of configuration and reset the notifier watch paths\n" );
103       exit ( 0 );
104     }
105
106   } // for
107
108   /* enable logging?
109    */
110   if ( g_daemon_mode ) {
111     // nada
112   } else {
113     pnd_log_set_filter ( pndn_rem );
114     pnd_log_set_pretext ( "pndnotifyd" );
115     pnd_log_to_stdout();
116     pnd_log_set_flush ( 1 );
117     pnd_log ( pndn_rem, "log level starting as %u", pnd_log_get_filter() );
118   }
119
120   pnd_log ( pndn_rem, "Interval between checks is %u seconds\n", interval_secs );
121
122   // check if inotify is awake yet; if not, try waiting for awhile to see if it does
123   pnd_log ( pndn_rem, "Starting INOTIFY test; should be instant, but may take awhile...\n" );
124
125   if ( ! pnd_notify_wait_until_ready ( 120 /* seconds */ ) ) {
126     pnd_log ( pndn_error, "ERROR: INOTIFY refuses to be useful and quite awhile has passed. Bailing out.\n" );
127     return ( -1 );
128   }
129
130   pnd_log ( pndn_rem, "INOTIFY seems to be useful, whew.\n" );
131
132   // basic daemon set up
133   if ( g_daemon_mode ) {
134
135     // set a CWD somewhere else
136 #if 0
137     chdir ( "/tmp" );
138 #endif
139
140     // detach from terminal
141     if ( ( i = fork() ) < 0 ) {
142       pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
143       exit ( i );
144     }
145     if ( i ) {
146       exit ( 0 ); // exit parent
147     }
148     setsid();
149
150     // umask
151     umask ( 022 ); // emitted files can be rwxr-xr-x
152     
153   } // set up daemon
154
155   // wait for a user to be logged in - we should probably get hupped when a user logs in, so we can handle
156   // log-out and back in again, with SDs popping in and out between..
157   pnd_log ( pndn_rem, "Checking to see if a user is logged in\n" );
158   char tmp_username [ 128 ];
159   while ( 1 ) {
160     if ( pnd_check_login ( tmp_username, 127 ) ) {
161       break;
162     }
163     pnd_log ( pndn_debug, "  No one logged in yet .. spinning.\n" );
164     sleep ( 2 );
165   } // spin
166   pnd_log ( pndn_rem, "Looks like user '%s' is in, continue.\n", tmp_username );
167
168   /* parse configs
169    */
170
171   consume_configuration();
172
173   /* startup
174    */
175
176   pnd_log ( pndn_rem, "PXML overrides searchpath is '%s'\n", overridespath );
177   pnd_log ( pndn_rem, "Notify searchpath is '%s'\n", notifypath );
178
179   pnd_log ( pndn_rem, "Desktop apps ---------------------------------\n" );
180   pnd_log ( pndn_rem, "Apps searchpath is '%s'\n", desktop_appspath );
181   pnd_log ( pndn_rem, ".desktop files emit to '%s'\n", desktop_dotdesktoppath );
182   pnd_log ( pndn_rem, ".desktop icon files emit to '%s'\n", desktop_iconpath );
183
184   pnd_log ( pndn_rem, "Menu apps ---------------------------------\n" );
185   pnd_log ( pndn_rem, "Apps searchpath is '%s'\n", menu_appspath );
186   pnd_log ( pndn_rem, ".desktop files emit to '%s'\n", menu_dotdesktoppath );
187   pnd_log ( pndn_rem, ".desktop icon files emit to '%s'\n", menu_iconpath );
188
189   /* set up signal handler
190    */
191   sigset_t ss;
192   sigemptyset ( &ss );
193
194   struct sigaction siggy;
195   siggy.sa_handler = sighup_handler;
196   siggy.sa_mask = ss; /* implicitly blocks the origin signal */
197   siggy.sa_flags = 0; /* don't need anything */
198
199   sigaction ( SIGHUP, &siggy, NULL );
200
201   /* set up notifies
202    */
203
204   // if we're gong to scan on launch, it'll set things right up and then set up notifications.
205   // if no scan on launch, we need to set the notif's up now.
206   //if ( ! scanonlaunch ) {
207   setup_notifications();
208   //}
209
210   /* daemon main loop
211    */
212   while ( 1 ) {
213
214     // need to rediscover?
215     if ( scanonlaunch ||
216          pnd_notify_rediscover_p ( nh ) )
217     {
218       createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
219
220       // if this was a forced scan, lets not do that next iteration
221       if ( scanonlaunch ) {
222         scanonlaunch = 0;
223       }
224
225       // by this point, the watched directories have notified us that something of relevent
226       // has occurred; we should be clever, but we're not, so just re-brute force the
227       // discovery and spit out .desktop files..
228       pnd_log ( pndn_rem, "------------------------------------------------------\n" );
229       pnd_log ( pndn_rem, "Changes within watched paths .. performing re-discover!\n" );
230
231       /* run the discovery
232        */
233
234       pnd_log ( pndn_rem, "Scanning desktop paths----------------------------\n" );
235       if ( ! perform_discoveries ( desktop_appspath, overridespath, desktop_dotdesktoppath, desktop_iconpath ) ) {
236         pnd_log ( pndn_rem, "No applications found in desktop search path\n" );
237       }
238
239       if ( menu_appspath && menu_dotdesktoppath && menu_iconpath ) {
240         pnd_log ( pndn_rem, "Scanning menu paths----------------------------\n" );
241         if ( ! perform_discoveries ( menu_appspath, overridespath, menu_dotdesktoppath, menu_iconpath ) ) {
242           pnd_log ( pndn_rem, "No applications found in menu search path\n" );
243         }
244       }
245
246       // if we've got a hup script located, lets invoke it
247       if ( pndhup ) {
248         pnd_log ( pndn_rem, "Invoking hup script '%s'.\n", pndhup );
249         pnd_exec_no_wait_1 ( pndhup, NULL );
250       }
251
252       // since its entirely likely new directories have been found (ie: SD with a directory structure was inserted)
253       // we should re-apply watches to catch all these new directories; ie: user might use on-device browser to
254       // drop in new applications, or use the shell to juggle them around, or any number of activities.
255       //setup_notifications();
256
257     } // need to rediscover?
258
259     // lets not eat up all the CPU
260     // should use an alarm or select() or something -- I mean really, why aren't I putting interval_secs into
261     // the select() call above in pnd_notify_whatsitcalled()? -- but lets not break this right before release shall we
262     // NOTE: Oh right, I remember now -- inotify will spam when a card is inserted, and it will not be instantaneoous..
263     // the events will dribble in over a second. So this sleep is a lame trick that generally works. I really should
264     // do select(), and then when it returns just spin for a couple seconds slurping up events until no more and a thresh-hold
265     // time is hit, but this will do for now. I suck.
266     sleep ( interval_secs );
267
268   } // while
269
270   /* shutdown
271    */
272   pnd_notify_shutdown ( nh );
273
274   return ( 0 );
275 }
276
277 void consume_configuration ( void ) {
278
279   // attempt to fetch a sensible default searchpath for configs
280   configpath = pnd_conf_query_searchpath();
281
282   // attempt to fetch the apps config to pick up a searchpath
283   pnd_conf_handle apph;
284
285   apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath );
286
287   if ( apph ) {
288
289     overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY );
290
291     if ( ! overridespath ) {
292       overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
293     }
294
295     notifypath = pnd_conf_get_as_char ( apph, PND_APPS_NOTIFY_KEY );
296
297     if ( ! notifypath ) {
298       notifypath = PND_APPS_NOTIFYPATH;
299     }
300
301   } else {
302     // couldn't find a useful app search path so use the default
303     overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
304     notifypath = PND_APPS_NOTIFYPATH;
305   }
306
307   // attempt to figure out where to drop dotfiles .. now that we're going
308   // multi-target we see the limit of my rudimentary conf-file parser; should
309   // just parse to an array of targets, rather that hardcoding two, but
310   // on the other hand, don't likely see the need for more than two? (famous
311   // last words.)
312   pnd_conf_handle desktoph;
313
314   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, configpath );
315
316   // for 'desktop' main applications
317   if ( desktoph ) {
318     desktop_dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_DOTDESKTOP_PATH_KEY );
319     desktop_iconpath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_ICONS_PATH_KEY );
320     desktop_appspath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_SEARCH_KEY );
321   }
322
323   if ( ! desktop_dotdesktoppath ) {
324     desktop_dotdesktoppath = PND_DESKTOP_DOTDESKTOP_PATH_DEFAULT;
325   }
326
327   if ( ! desktop_iconpath ) {
328     desktop_iconpath = PND_DESKTOP_ICONS_PATH_DEFAULT;
329   }
330
331   if ( ! desktop_appspath ) {
332     desktop_appspath = PND_DESKTOP_SEARCH_PATH_DEFAULT;
333   }
334
335   // for 'menu' applications
336   if ( desktoph ) {
337     menu_dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_MENU_DOTDESKTOP_PATH_KEY );
338     menu_iconpath = pnd_conf_get_as_char ( desktoph, PND_MENU_ICONS_PATH_KEY );
339     menu_appspath = pnd_conf_get_as_char ( desktoph, PND_MENU_SEARCH_KEY );
340   }
341
342   /* try to locate a runscript and optional hupscript
343    */
344
345   if ( apph ) {
346     run_searchpath = pnd_conf_get_as_char ( apph, PND_PNDRUN_SEARCHPATH_KEY );
347     run_script = pnd_conf_get_as_char ( apph, PND_PNDRUN_KEY );
348     pndrun = NULL;
349
350     if ( ! run_searchpath ) {
351       run_searchpath = PND_APPS_SEARCHPATH;
352       run_script = PND_PNDRUN_FILENAME;
353     }
354
355     if ( pnd_conf_get_as_int ( apph, PNDNOTIFYD_LOGLEVEL ) != PND_CONF_BADNUM ) {
356       pnd_log_set_filter ( pnd_conf_get_as_int ( apph, PNDNOTIFYD_LOGLEVEL ) );
357       pnd_log ( pndn_rem, "config file causes loglevel to change to %u", pnd_log_get_filter() );
358     }
359
360   } else {
361     run_searchpath = NULL;
362     run_script = NULL;
363     pndrun = PND_PNDRUN_DEFAULT;
364   }
365
366   if ( ! pndrun ) {
367     pndrun = pnd_locate_filename ( run_searchpath, run_script );
368
369     if ( pndrun ) {
370       pndrun = strdup ( pndrun ); // so we don't just use the built in buffer; next locate will overwrite it
371     } else {
372       pndrun = PND_PNDRUN_DEFAULT;
373     }
374
375   }
376
377   if ( desktoph && run_searchpath ) {
378     char *t;
379
380     if ( ( t = pnd_conf_get_as_char ( desktoph, PND_PNDHUP_KEY ) ) ) {
381       pndhup = pnd_locate_filename ( run_searchpath, t );
382
383       if ( pndhup ) {
384         pndhup = strdup ( pndhup ); // so we don't just use the built in buffer; next locate will overwrite it
385       }
386
387 #if 0 // don't enable this; if no key in config, we don't want to bother hupping
388     } else {
389       pndhup = pnd_locate_filename ( run_searchpath, PND_PNDHUP_FILENAME );
390 #endif
391     }
392
393   }
394
395   // debug
396   if ( run_searchpath ) pnd_log ( pndn_rem, "Locating pnd run in %s\n", run_searchpath );
397   if ( run_script ) pnd_log ( pndn_rem, "Locating pnd runscript as %s\n", run_script );
398   if ( pndrun ) pnd_log ( pndn_rem, "pndrun is %s\n", pndrun );
399   if ( pndhup ) {
400     pnd_log ( pndn_rem, "pndhup is %s\n", pndhup );
401   } else {
402     pnd_log ( pndn_rem, "No pndhup found (which is fine.)\n" );
403   }
404
405   /* handle globbing or variable substitution
406    */
407   desktop_dotdesktoppath = pnd_expand_tilde ( strdup ( desktop_dotdesktoppath ) );
408   desktop_iconpath = pnd_expand_tilde ( strdup ( desktop_iconpath ) );
409   mkdir ( desktop_dotdesktoppath, 0777 );
410   mkdir ( desktop_iconpath, 0777 );
411
412   if ( menu_dotdesktoppath ) {
413     menu_dotdesktoppath = pnd_expand_tilde ( strdup ( menu_dotdesktoppath ) );
414     mkdir ( menu_dotdesktoppath, 0777 );
415   }
416   if ( menu_iconpath ) {
417     menu_iconpath = pnd_expand_tilde ( strdup ( menu_iconpath ) );
418     mkdir ( menu_iconpath, 0777 );
419   }
420
421   // done
422   return;
423 }
424
425 void setup_notifications ( void ) {
426   searchpath = notifypath;
427
428   // if this is first time through, we can just set it up; for subsequent times
429   // through, we need to close existing fd and re-open it, since we're too lame
430   // to store the list of watches and 'rm' them
431 #if 1
432   if ( nh ) {
433     pnd_notify_shutdown ( nh );
434     nh = 0;
435   }
436 #endif
437
438   // set up a new set of notifies
439   if ( ! nh ) {
440     nh = pnd_notify_init();
441   }
442
443   if ( ! nh ) {
444     pnd_log ( pndn_rem, "INOTIFY failed to init.\n" );
445     exit ( -1 );
446   }
447
448 #if 0
449   pnd_log ( pndn_rem, "INOTIFY is up.\n" );
450 #endif
451
452   SEARCHPATH_PRE
453   {
454
455     pnd_log ( pndn_rem, "Watching path '%s' and its descendents.\n", buffer );
456     pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
457
458   }
459   SEARCHPATH_POST
460
461   return;
462 }
463
464 void sighup_handler ( int n ) {
465
466   pnd_log ( pndn_rem, "---[ SIGHUP received ]---\n" );
467
468   // reparse config files
469   consume_configuration();
470
471   // re set up the notifier watches
472   setup_notifications();
473
474   return;
475 }
476
477 // This very recently was inline code; just slight refactor to functionize it so that it can be
478 // reused in a couple of places. Simple code with simple design quickly became too large for
479 // its simple design; should revisit a lot of these little things..
480 void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *emiticonpath ) {
481   pnd_disco_t *d = pnd_box_get_head ( applist );
482
483   while ( d ) {
484
485     pnd_log ( pndn_rem, "Found app: %s\n", pnd_box_get_key ( d ) );
486
487     // check if icon already exists (from a previous extraction say); if so, we needn't
488     // do it again
489     char existingpath [ FILENAME_MAX ];
490     sprintf ( existingpath, "%s/%s.png", emiticonpath, d -> unique_id );
491
492     struct stat dirs;
493     if ( stat ( existingpath, &dirs ) == 0 ) {
494       // icon seems to exist, so just crib the location into the .desktop
495
496       pnd_log ( pndn_rem, "  Found icon already existed, so reusing it! %s\n", existingpath );
497
498       if ( d -> icon ) {
499         free ( d -> icon );
500       }
501       d -> icon = strdup ( existingpath );
502
503     } else {
504       // icon seems unreadable or does not exist; lets try to create it..
505
506       pnd_log ( pndn_debug, "  Icon not already present, so trying to write it! %s\n", existingpath );
507
508       // attempt to create icon files; if successful, alter the disco struct to contain new
509       // path, otherwise leave it alone (since it could be a generic icon reference..)
510       if ( pnd_emit_icon ( emiticonpath, d ) ) {
511         // success; fix up icon path to new one..
512         if ( d -> icon ) {
513           free ( d -> icon );
514         }
515         d -> icon = strdup ( existingpath );
516       } else {
517         pnd_log ( pndn_debug, "  WARN: Couldn't write out icon %s\n", existingpath );
518       }
519
520     } // icon already exists?
521
522     // create the .desktop file
523     if ( pnd_emit_dotdesktop ( emitdesktoppath, pndrun, d ) ) {
524       // add a watch onto the newly created .desktop?
525 #if 0
526       char buffer [ FILENAME_MAX ];
527       sprintf ( buffer, "%s/%s", emitdesktoppath, d -> unique_id );
528       pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
529 #endif
530     } else {
531       pnd_log ( pndn_rem, "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
532     }
533
534     // next!
535     d = pnd_box_get_next ( d );
536
537   } // while applist
538
539   return;
540 }
541
542 // returns true if any applications were found
543 unsigned char perform_discoveries ( char *appspath, char *overridespath,              // args to do discovery
544                                     char *emitdesktoppath, char *emiticonpath )       // args to do emitting
545 {
546   pnd_box_handle applist;
547
548   // attempt to auto-discover applications in the given path
549   applist = pnd_disco_search ( appspath, overridespath );
550
551   if ( applist ) {
552     process_discoveries ( applist, emitdesktoppath, emiticonpath );
553   }
554
555   // run a clean up, to remove any dotdesktop files that we didn't
556   // just now create (that seem to have been created by pndnotifyd
557   // previously.) This allows SD eject (or .pnd remove) to remove
558   // an app from the launcher
559   //   NOTE: Could opendir and iterate across all .desktop files,
560   // removing any that have Source= something else, and that the
561   // app name is not in the list found in applist box above. But
562   // a cheesy simple way right now is to just remove .desktop files
563   // that have a last mod time prior to the time we stored above.
564   {
565     DIR *dir;
566
567     if ( ( dir = opendir ( emitdesktoppath ) ) ) {
568       struct dirent *dirent;
569       struct stat dirs;
570       char buffer [ FILENAME_MAX ];
571
572       while ( ( dirent = readdir ( dir ) ) ) {
573
574         // file is a .desktop?
575         if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) {
576           continue;
577         }
578
579         // figure out full path
580         sprintf ( buffer, "%s/%s", emitdesktoppath, dirent -> d_name );
581
582         // file was previously created by libpnd; check Source= line
583         // logic: default to 'yes' (in case we can't open the file for some reason)
584         //        if we can open the file, default to no and look for the source flag we added; if
585         //          that matches then we know its libpnd created, otherwise assume not.
586         unsigned char source_libpnd = 1;
587         {
588           char line [ 256 ];
589           FILE *grep = fopen ( buffer, "r" );
590           if ( grep ) {
591             source_libpnd = 0;
592             while ( fgets ( line, 255, grep ) ) {
593               if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) {
594                 source_libpnd = 2;
595               }
596             } // while
597             fclose ( grep );
598           }
599         }
600         if ( source_libpnd ) {
601 #if 0
602           pnd_log ( pndn_rem,
603                     "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd );
604 #endif
605         } else {
606 #if 0
607           pnd_log ( pndn_rem, "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer );
608 #endif
609           continue; // skip deleting it
610         }
611
612         // file is 'new'?
613         if ( stat ( buffer, &dirs ) == 0 ) {
614           if ( dirs.st_mtime >= createtime ) {
615 #if 0
616             pnd_log ( pndn_rem, "File '%s' seems 'new', so leave it alone.\n", buffer );
617 #endif
618             continue; // skip deleting it
619           }
620         }
621
622         // by this point, the .desktop file must be 'old' and created by pndnotifyd
623         // previously, so can remove it
624         pnd_log ( pndn_rem, "File '%s' seems nolonger relevent; removing it.\n", dirent -> d_name );
625         unlink ( buffer );
626
627       } // while getting filenames from dir
628
629       closedir ( dir );
630     }
631
632   } // purge old .desktop files
633
634   //WARN: MEMORY LEAK HERE
635   pnd_log ( pndn_debug, "pndnotifyd - memory leak here - perform_discoveries()\n" );
636   if ( applist ) {
637     pnd_box_delete ( applist ); // does not free the disco_t contents!
638   }
639
640   return ( 1 );
641 }