bf0b47d99791f56036a8e3e1e6dede6233bf8f86
[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   /* cheap hack, maybe it'll help when pndnotifyd is coming up before the rest of the system and before
406    * the user is formally logged in
407    */
408   pnd_log ( pndn_rem, "Setting a default $HOME to non-root user\n" );
409   {
410     DIR *dir;
411
412     if ( ( dir = opendir ( "/home" ) ) ) {
413       struct dirent *dirent;
414
415       while ( ( dirent = readdir ( dir ) ) ) {
416         pnd_log ( pndn_rem, "  Found user homedir '%s'\n", dirent -> d_name );
417
418         // file is a .desktop?
419         if ( dirent -> d_name [ 0 ] == '.' ) {
420           continue;
421         } else if ( strcmp ( dirent -> d_name, "root" ) == 0 ) {
422           continue;
423         }
424
425         // a non-root user is found
426         char buffer [ 200 ];
427         sprintf ( buffer, "/home/%s", dirent -> d_name );
428         pnd_log ( pndn_rem, "  Going with '%s'\n", buffer );
429         setenv ( "HOME", buffer, 1 /* overwrite */ );
430         break;
431
432       } // while iterating through dir listing
433
434       closedir ( dir );
435     } // opendir?
436
437   } // scope
438
439   /* handle globbing or variable substitution
440    */
441   desktop_dotdesktoppath = pnd_expand_tilde ( strdup ( desktop_dotdesktoppath ) );
442   desktop_iconpath = pnd_expand_tilde ( strdup ( desktop_iconpath ) );
443   mkdir ( desktop_dotdesktoppath, 0777 );
444   mkdir ( desktop_iconpath, 0777 );
445
446   if ( menu_dotdesktoppath ) {
447     menu_dotdesktoppath = pnd_expand_tilde ( strdup ( menu_dotdesktoppath ) );
448     mkdir ( menu_dotdesktoppath, 0777 );
449   }
450   if ( menu_iconpath ) {
451     menu_iconpath = pnd_expand_tilde ( strdup ( menu_iconpath ) );
452     mkdir ( menu_iconpath, 0777 );
453   }
454
455   // done
456   return;
457 }
458
459 void setup_notifications ( void ) {
460   searchpath = notifypath;
461
462   // if this is first time through, we can just set it up; for subsequent times
463   // through, we need to close existing fd and re-open it, since we're too lame
464   // to store the list of watches and 'rm' them
465 #if 1
466   if ( nh ) {
467     pnd_notify_shutdown ( nh );
468     nh = 0;
469   }
470 #endif
471
472   // set up a new set of notifies
473   if ( ! nh ) {
474     nh = pnd_notify_init();
475   }
476
477   if ( ! nh ) {
478     pnd_log ( pndn_rem, "INOTIFY failed to init.\n" );
479     exit ( -1 );
480   }
481
482 #if 0
483   pnd_log ( pndn_rem, "INOTIFY is up.\n" );
484 #endif
485
486   SEARCHPATH_PRE
487   {
488
489     pnd_log ( pndn_rem, "Watching path '%s' and its descendents.\n", buffer );
490     pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
491
492   }
493   SEARCHPATH_POST
494
495   return;
496 }
497
498 void sighup_handler ( int n ) {
499
500   pnd_log ( pndn_rem, "---[ SIGHUP received ]---\n" );
501
502   // reparse config files
503   consume_configuration();
504
505   // re set up the notifier watches
506   setup_notifications();
507
508   return;
509 }
510
511 // This very recently was inline code; just slight refactor to functionize it so that it can be
512 // reused in a couple of places. Simple code with simple design quickly became too large for
513 // its simple design; should revisit a lot of these little things..
514 void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *emiticonpath ) {
515   pnd_disco_t *d = pnd_box_get_head ( applist );
516
517   while ( d ) {
518
519     pnd_log ( pndn_rem, "Found app: %s\n", pnd_box_get_key ( d ) );
520
521     // check if icon already exists (from a previous extraction say); if so, we needn't
522     // do it again
523     char existingpath [ FILENAME_MAX ];
524     sprintf ( existingpath, "%s/%s.png", emiticonpath, d -> unique_id );
525
526     struct stat dirs;
527     if ( stat ( existingpath, &dirs ) == 0 ) {
528       // icon seems to exist, so just crib the location into the .desktop
529
530       pnd_log ( pndn_rem, "  Found icon already existed, so reusing it! %s\n", existingpath );
531
532       if ( d -> icon ) {
533         free ( d -> icon );
534       }
535       d -> icon = strdup ( existingpath );
536
537     } else {
538       // icon seems unreadable or does not exist; lets try to create it..
539
540       pnd_log ( pndn_debug, "  Icon not already present, so trying to write it! %s\n", existingpath );
541
542       // attempt to create icon files; if successful, alter the disco struct to contain new
543       // path, otherwise leave it alone (since it could be a generic icon reference..)
544       if ( pnd_emit_icon ( emiticonpath, d ) ) {
545         // success; fix up icon path to new one..
546         if ( d -> icon ) {
547           free ( d -> icon );
548         }
549         d -> icon = strdup ( existingpath );
550       } else {
551         pnd_log ( pndn_debug, "  WARN: Couldn't write out icon %s\n", existingpath );
552       }
553
554     } // icon already exists?
555
556     // create the .desktop file
557     if ( pnd_emit_dotdesktop ( emitdesktoppath, pndrun, d ) ) {
558       // add a watch onto the newly created .desktop?
559 #if 0
560       char buffer [ FILENAME_MAX ];
561       sprintf ( buffer, "%s/%s", emitdesktoppath, d -> unique_id );
562       pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
563 #endif
564     } else {
565       pnd_log ( pndn_rem, "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
566     }
567
568     // next!
569     d = pnd_box_get_next ( d );
570
571   } // while applist
572
573   return;
574 }
575
576 // returns true if any applications were found
577 unsigned char perform_discoveries ( char *appspath, char *overridespath,              // args to do discovery
578                                     char *emitdesktoppath, char *emiticonpath )       // args to do emitting
579 {
580   pnd_box_handle applist;
581
582   // attempt to auto-discover applications in the given path
583   applist = pnd_disco_search ( appspath, overridespath );
584
585   if ( applist ) {
586     process_discoveries ( applist, emitdesktoppath, emiticonpath );
587   }
588
589   // run a clean up, to remove any dotdesktop files that we didn't
590   // just now create (that seem to have been created by pndnotifyd
591   // previously.) This allows SD eject (or .pnd remove) to remove
592   // an app from the launcher
593   //   NOTE: Could opendir and iterate across all .desktop files,
594   // removing any that have Source= something else, and that the
595   // app name is not in the list found in applist box above. But
596   // a cheesy simple way right now is to just remove .desktop files
597   // that have a last mod time prior to the time we stored above.
598   {
599     DIR *dir;
600
601     if ( ( dir = opendir ( emitdesktoppath ) ) ) {
602       struct dirent *dirent;
603       struct stat dirs;
604       char buffer [ FILENAME_MAX ];
605
606       while ( ( dirent = readdir ( dir ) ) ) {
607
608         // file is a .desktop?
609         if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) {
610           continue;
611         }
612
613         // figure out full path
614         sprintf ( buffer, "%s/%s", emitdesktoppath, dirent -> d_name );
615
616         // file was previously created by libpnd; check Source= line
617         // logic: default to 'yes' (in case we can't open the file for some reason)
618         //        if we can open the file, default to no and look for the source flag we added; if
619         //          that matches then we know its libpnd created, otherwise assume not.
620         unsigned char source_libpnd = 1;
621         {
622           char line [ 256 ];
623           FILE *grep = fopen ( buffer, "r" );
624           if ( grep ) {
625             source_libpnd = 0;
626             while ( fgets ( line, 255, grep ) ) {
627               if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) {
628                 source_libpnd = 2;
629               }
630             } // while
631             fclose ( grep );
632           }
633         }
634         if ( source_libpnd ) {
635 #if 0
636           pnd_log ( pndn_rem,
637                     "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd );
638 #endif
639         } else {
640 #if 0
641           pnd_log ( pndn_rem, "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer );
642 #endif
643           continue; // skip deleting it
644         }
645
646         // file is 'new'?
647         if ( stat ( buffer, &dirs ) == 0 ) {
648           if ( dirs.st_mtime >= createtime ) {
649 #if 0
650             pnd_log ( pndn_rem, "File '%s' seems 'new', so leave it alone.\n", buffer );
651 #endif
652             continue; // skip deleting it
653           }
654         }
655
656         // by this point, the .desktop file must be 'old' and created by pndnotifyd
657         // previously, so can remove it
658         pnd_log ( pndn_rem, "File '%s' seems nolonger relevent; removing it.\n", dirent -> d_name );
659         unlink ( buffer );
660
661       } // while getting filenames from dir
662
663       closedir ( dir );
664     }
665
666   } // purge old .desktop files
667
668   //WARN: MEMORY LEAK HERE
669   pnd_log ( pndn_debug, "pndnotifyd - memory leak here - perform_discoveries()\n" );
670   if ( applist ) {
671     pnd_box_delete ( applist ); // does not free the disco_t contents!
672   }
673
674   return ( 1 );
675 }