pndnotifyd: fix some crashes
[pandora-libraries.git] / apps / pndnotifyd.c
index a2ca73f..aa79185 100644 (file)
@@ -15,6 +15,7 @@
 #include <sys/stat.h>  // for umask
 #include <dirent.h>    // for opendir()
 #include <signal.h>    // for sigaction
+#include <errno.h>     // for errno
 
 #include "pnd_conf.h"
 #include "pnd_container.h"
@@ -27,6 +28,8 @@
 #include "pnd_utility.h"
 #include "pnd_desktop.h"
 #include "pnd_logger.h"
+#include "pnd_dbusnotify.h"
+#include "pnd_pndfiles.h"
 
 // this piece of code was simpler once; but need to grow it a bit and in a rush
 // moving all these to globals rather than refactor the code a bit; tsk tsk..
@@ -56,20 +59,27 @@ char *desktop_appspath = NULL;
 char *menu_dotdesktoppath = NULL;
 char *menu_iconpath = NULL;
 char *menu_appspath = NULL;
+char *info_dotdesktoppath = NULL;
 // pnd runscript
 char *run_searchpath; // searchpath to find pnd_run.sh
 char *run_script;     // name of pnd_run.sh script from config
 char *pndrun;         // full path to located pnd_run.sh
 char *pndhup = NULL;  // full path to located pnd_hup.sh
+// default username
+char g_username [ 128 ]; // since we have to wait for login (!!), store username here
 // notifier handle
 pnd_notify_handle nh = 0;
+pnd_dbusnotify_handle dbh = 0;
+unsigned char g_info_p = 0; // spit out info .desktops
 
 // constants
 #define PNDNOTIFYD_LOGLEVEL "pndnotifyd.loglevel"
+#define PNDLOCKNAME "pndnotifyd-disco.lock"
 
 // decl's
 void consume_configuration ( void );
 void setup_notifications ( void );
+void sigint_handler ( int n );
 void sighup_handler ( int n );
 void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *emiticonpath );
 unsigned char perform_discoveries ( char *appspath, char *overridespath,
@@ -108,7 +118,7 @@ int main ( int argc, char *argv[] ) {
       }
 
     } else {
-      printf ( "%s [-d] [##]\n", argv [ 0 ] );
+      printf ( "%s [-d] [-l] [##]\n", argv [ 0 ] );
       printf ( "-d\tDaemon mode; detach from terminal, chdir to /tmp, suppress output. Optional.\n" );
       printf ( "-n\tDo not scan on launch; default is to run a scan for apps when %s is invoked.\n", argv [ 0 ] );
       printf ( "-l#\tLog-it; -l is 0-and-up (or all), and -l2 means 2-and-up (not all); l[0-3] for now. Log goes to /tmp/pndnotifyd.log\n" );
@@ -153,6 +163,8 @@ int main ( int argc, char *argv[] ) {
 
   } // logall
 
+  pnd_log ( pndn_rem, "%s built %s %s", argv [ 0 ], __DATE__, __TIME__ );
+
   pnd_log ( pndn_rem, "log level starting as %u", pnd_log_get_filter() );
 
   pnd_log ( pndn_rem, "Interval between checks is %u seconds\n", interval_secs );
@@ -187,21 +199,20 @@ int main ( int argc, char *argv[] ) {
 
     // umask
     umask ( 022 ); // emitted files can be rwxr-xr-x
-    
+
   } // set up daemon
 
   // wait for a user to be logged in - we should probably get hupped when a user logs in, so we can handle
   // log-out and back in again, with SDs popping in and out between..
   pnd_log ( pndn_rem, "Checking to see if a user is logged in\n" );
-  char tmp_username [ 128 ];
   while ( 1 ) {
-    if ( pnd_check_login ( tmp_username, 127 ) ) {
+    if ( pnd_check_login ( g_username, 127 ) ) {
       break;
     }
     pnd_log ( pndn_debug, "  No one logged in yet .. spinning.\n" );
     sleep ( 2 );
   } // spin
-  pnd_log ( pndn_rem, "Looks like user '%s' is in, continue.\n", tmp_username );
+  pnd_log ( pndn_rem, "Looks like user '%s' is in, continue.\n", g_username );
 
   /* parse configs
    */
@@ -223,6 +234,7 @@ int main ( int argc, char *argv[] ) {
   pnd_log ( pndn_rem, "Apps searchpath is '%s'\n", menu_appspath );
   pnd_log ( pndn_rem, ".desktop files emit to '%s'\n", menu_dotdesktoppath );
   pnd_log ( pndn_rem, ".desktop icon files emit to '%s'\n", menu_iconpath );
+  pnd_log ( pndn_rem, ".desktop info files emit to '%s'\n", info_dotdesktoppath ? info_dotdesktoppath : "n/a" );
 
   /* set up signal handler
    */
@@ -236,6 +248,9 @@ int main ( int argc, char *argv[] ) {
 
   sigaction ( SIGHUP, &siggy, NULL );
 
+  siggy.sa_handler = sigint_handler;
+  sigaction ( SIGINT, &siggy, NULL );
+
   /* set up notifies
    */
 
@@ -245,42 +260,161 @@ int main ( int argc, char *argv[] ) {
   setup_notifications();
   //}
 
+  dbh = pnd_dbusnotify_init();
+
   /* daemon main loop
    */
+  unsigned char watch_inotify = 0, watch_dbus = 0;
+  unsigned char idle = 0;
   while ( 1 ) {
+    int retcode;
+
+    // collect all events to avoid multiple PND rescans
+    do {
+      struct timeval timeout_1s = { 1, 0};
+      int fd_inotify = -1, fd_dbus = -1;
+      struct timeval *timeout = NULL;
+      fd_set rfds, efds;
+      int fd_max = -1;
+
+      if ( dbh )
+        fd_dbus = pnd_dbusnotify_fd ( dbh );
+      if ( nh )
+        fd_inotify = pnd_notify_fd ( nh );
+
+      FD_ZERO ( &rfds );
+      FD_ZERO ( &efds );
+
+      if ( fd_dbus != -1 ) {
+        FD_SET ( fd_dbus, &rfds );
+        FD_SET ( fd_dbus, &efds );
+        if ( fd_dbus > fd_max )
+          fd_max = fd_dbus;
+      }
+
+      if ( fd_inotify != -1 ) {
+        FD_SET ( fd_inotify, &rfds );
+        FD_SET ( fd_inotify, &efds );
+        if ( fd_inotify > fd_max )
+          fd_max = fd_inotify;
+      }
+
+      if ( fd_max == -1 ) {
+        pnd_log ( pndn_error, "ERROR: notify: nothing to watch\n" );
+        retcode = -1;
+        break;
+      }
+
+      // the idle timeout is needed for inotify as some file updates produce several events
+      // with a delay in between (length depends on things like file size and system load), like
+      // - IN_CREATE, IN_CLOSE_WRITE (cp, mv from other fs, etc)
+      // - IN_MOVED_TO, IN_CLOSE_WRITE (PNDManager)
+      // - multiple file copies, deletes, etc
+      timeout = idle ? NULL : &timeout_1s;
+
+      retcode = select ( fd_max + 1, &rfds, NULL, &efds, timeout );
+
+      if ( retcode < 0 ) {
+        pnd_log ( pndn_error, "ERROR: notify: select failed: %d\n", errno );
+        if ( errno == EINTR )
+          retcode = 0;
+
+        break;
+      }
+      idle = ( retcode == 0 );
+
+      if ( dbh && FD_ISSET ( fd_dbus, &efds ) ) {
+        pnd_log ( pndn_error, "ERROR: notify: dbus fd error\n" );
+        pnd_dbusnotify_shutdown ( dbh );
+        dbh = NULL;
+      }
+
+      if ( nh && FD_ISSET ( fd_inotify, &efds ) ) {
+        pnd_log ( pndn_error, "ERROR: notify: inotify fd error\n" );
+        pnd_notify_shutdown ( nh );
+        nh = NULL;
+      }
+
+      if ( dbh && FD_ISSET ( fd_dbus, &rfds ) ) {
+        watch_dbus |= pnd_dbusnotify_rediscover_p ( dbh );
+        if ( watch_dbus && nh ) {
+          // will restart inotify (see below), drop all events
+          pnd_notify_shutdown ( nh );
+          nh = NULL;
+        }
+      }
+
+      if ( nh && FD_ISSET ( fd_inotify, &rfds ) ) {
+        watch_inotify |= pnd_notify_rediscover_p ( nh );
+      }
+    }
+    while ( !idle );
+
+    if ( retcode < 0 ) {
+      break;
+    }
 
     // need to rediscover?
-    if ( scanonlaunch ||
-        pnd_notify_rediscover_p ( nh ) )
-    {
-      createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
+    if ( scanonlaunch || watch_inotify || watch_dbus ) {
+
+      // by this point, the watched directories have notified us that something of relevent
+      // has occurred; we should be clever, but we're not, so just re-brute force the
+      // discovery and spit out .desktop files..
+      pnd_log ( pndn_rem, "------------------------------------------------------\n" );
+
+      pnd_log ( pndn_rem, "System changes detected in dbus or watched paths .. performing re-discover!\n" );
 
       // if this was a forced scan, lets not do that next iteration
       if ( scanonlaunch ) {
+       pnd_log ( pndn_rem, "scan-on-first-launch detected an event\n" );
        scanonlaunch = 0;
       }
 
-      // by this point, the watched directories have notified us that something of relevent
-      // has occurred; we should be clever, but we're not, so just re-brute force the
-      // discovery and spit out .desktop files..
+      if ( watch_inotify ) {
+       pnd_log ( pndn_rem, "inotify detected an event\n" );
+      }
+
+      if ( watch_dbus ) {
+       pnd_log ( pndn_rem, "dbusnotify detected an event\n" );
+      }
+
+      if ( time ( NULL ) - createtime < interval_secs ) {
+       pnd_log ( pndn_rem, "Rediscovery request comes to soon after previous discovery; delaying.\n" );
+       sleep ( interval_secs );
+       idle = 0;
+       continue;
+      }
+
       pnd_log ( pndn_rem, "------------------------------------------------------\n" );
-      pnd_log ( pndn_rem, "Changes within watched paths .. performing re-discover!\n" );
+
+      createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
 
       /* run the discovery
        */
 
-      pnd_log ( pndn_rem, "Scanning desktop paths----------------------------\n" );
+      // do some 'locking'
+      pnd_log ( pndn_rem, "creating lockfile %s", PNDLOCKNAME );
+      if ( ! pnd_lock ( PNDLOCKNAME ) ) {
+       // problem .. well, too bad, we need to do this .. proceed!
+      }
+
+      // scan..
+      pnd_log ( pndn_rem, "  Scanning desktop paths----------------------------\n" );
       if ( ! perform_discoveries ( desktop_appspath, overridespath, desktop_dotdesktoppath, desktop_iconpath ) ) {
-       pnd_log ( pndn_rem, "No applications found in desktop search path\n" );
+       pnd_log ( pndn_rem, "    No applications found in desktop search path\n" );
       }
 
       if ( menu_appspath && menu_dotdesktoppath && menu_iconpath ) {
-       pnd_log ( pndn_rem, "Scanning menu paths----------------------------\n" );
+       pnd_log ( pndn_rem, "  Scanning menu paths----------------------------\n" );
        if ( ! perform_discoveries ( menu_appspath, overridespath, menu_dotdesktoppath, menu_iconpath ) ) {
-         pnd_log ( pndn_rem, "No applications found in menu search path\n" );
+         pnd_log ( pndn_rem, "    No applications found in menu search path\n" );
        }
       }
 
+      // unlock
+      pnd_log ( pndn_rem, "clearing lockfile %s", PNDLOCKNAME );
+      pnd_unlock ( PNDLOCKNAME );
+
       // if we've got a hup script located, lets invoke it
       if ( pndhup ) {
        pnd_log ( pndn_rem, "Invoking hup script '%s'.\n", pndhup );
@@ -290,24 +424,24 @@ int main ( int argc, char *argv[] ) {
       // since its entirely likely new directories have been found (ie: SD with a directory structure was inserted)
       // we should re-apply watches to catch all these new directories; ie: user might use on-device browser to
       // drop in new applications, or use the shell to juggle them around, or any number of activities.
-      //setup_notifications();
+      if ( watch_dbus ) {
+       setup_notifications();
+      }
 
-    } // need to rediscover?
+      watch_inotify = watch_dbus = 0;
 
-    // lets not eat up all the CPU
-    // should use an alarm or select() or something -- I mean really, why aren't I putting interval_secs into
-    // the select() call above in pnd_notify_whatsitcalled()? -- but lets not break this right before release shall we
-    // NOTE: Oh right, I remember now -- inotify will spam when a card is inserted, and it will not be instantaneoous..
-    // the events will dribble in over a second. So this sleep is a lame trick that generally works. I really should
-    // do select(), and then when it returns just spin for a couple seconds slurping up events until no more and a thresh-hold
-    // time is hit, but this will do for now. I suck.
-    sleep ( interval_secs );
+    } // need to rediscover?
 
   } // while
 
   /* shutdown
    */
-  pnd_notify_shutdown ( nh );
+  if ( nh ) {
+    pnd_notify_shutdown ( nh );
+  }
+  if ( dbh ) {
+    pnd_dbusnotify_shutdown ( dbh );
+  }
 
   return ( 0 );
 }
@@ -356,6 +490,7 @@ void consume_configuration ( void ) {
     desktop_dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_DOTDESKTOP_PATH_KEY );
     desktop_iconpath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_ICONS_PATH_KEY );
     desktop_appspath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_SEARCH_KEY );
+    info_dotdesktoppath = pnd_conf_get_as_char ( desktoph, "info.dotdesktoppath" );
   }
 
   if ( ! desktop_dotdesktoppath ) {
@@ -377,6 +512,11 @@ void consume_configuration ( void ) {
     menu_appspath = pnd_conf_get_as_char ( desktoph, PND_MENU_SEARCH_KEY );
   }
 
+  // info
+  if ( desktoph ) {
+    g_info_p = pnd_conf_get_as_int_d ( desktoph, "info.emit_info", 0 );
+  }
+
   /* try to locate a runscript and optional hupscript
    */
 
@@ -448,14 +588,41 @@ void consume_configuration ( void ) {
    * the user is formally logged in
    */
   pnd_log ( pndn_rem, "Setting a default $HOME to non-root user\n" );
-  {
+
+  // first, try to see if known-username maps to a homedir; if so, just use that!
+  // otherwise, pick first non-root homedir and assume .. or we start blaring .desktops
+  // out to all users like idiots
+  unsigned char got_user_homedir = 0;
+
+  if ( g_username [ 0 ] ) {
+    struct stat homedir;
+    char path [ PATH_MAX ];
+
+    sprintf ( path, "/home/%s", g_username );
+
+    // does this made up path exist?
+    if ( stat ( path, &homedir ) == 0 ) {
+
+      // and its a dir?
+      if ( S_ISDIR(homedir.st_mode) ) {
+       pnd_log ( pndn_rem, "  User [%s] matches path [%s], going with '%s'\n", g_username, path, path );
+       setenv ( "HOME", path, 1 /* overwrite */ );
+       got_user_homedir = 1;
+      } // and its a dir?
+
+    } // guessing a homedirname..
+
+  } // got a username?
+
+  // if guessing a path was no good, just try finding one
+  if ( got_user_homedir == 0 ) {
     DIR *dir;
 
     if ( ( dir = opendir ( "/home" ) ) ) {
       struct dirent *dirent;
 
       while ( ( dirent = readdir ( dir ) ) ) {
-       pnd_log ( pndn_rem, "  Found user homedir '%s'\n", dirent -> d_name );
+       pnd_log ( pndn_rem, "  Scanning user homedir '%s'\n", dirent -> d_name );
 
        // file is a .desktop?
        if ( dirent -> d_name [ 0 ] == '.' ) {
@@ -485,6 +652,11 @@ void consume_configuration ( void ) {
   mkdir ( desktop_dotdesktoppath, 0777 );
   mkdir ( desktop_iconpath, 0777 );
 
+  if ( info_dotdesktoppath ) {
+    info_dotdesktoppath = pnd_expand_tilde ( strdup ( info_dotdesktoppath ) );
+    mkdir ( info_dotdesktoppath, 0777 );
+  }
+
   if ( menu_dotdesktoppath ) {
     menu_dotdesktoppath = pnd_expand_tilde ( strdup ( menu_dotdesktoppath ) );
     mkdir ( menu_dotdesktoppath, 0777 );
@@ -531,9 +703,20 @@ void setup_notifications ( void ) {
     pnd_log ( pndn_rem, "Watching path '%s' and its descendents.\n", buffer );
     pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
 
+    //pnd_notify_watch_path ( nh, buffer, 0 /* no recurse */ );
+
   }
   SEARCHPATH_POST
 
+#if 0
+  sleep ( 1 ); // wait for events to trigger?
+
+  // clear out any notifies we just created
+  while ( pnd_notify_rediscover_p ( nh ) ) {
+    usleep ( 100 ); // spin
+  } // while
+#endif
+
   return;
 }
 
@@ -550,6 +733,23 @@ void sighup_handler ( int n ) {
   return;
 }
 
+void sigint_handler ( int n ) {
+
+  pnd_log ( pndn_rem, "---[ SIGINT received ]---\n" );
+
+  if ( dbh ) {
+    pnd_dbusnotify_shutdown ( dbh );
+    dbh = NULL;
+  }
+
+  if ( nh ) {
+    pnd_notify_shutdown ( nh );
+    nh = NULL;
+  }
+
+  return;
+}
+
 // This very recently was inline code; just slight refactor to functionize it so that it can be
 // reused in a couple of places. Simple code with simple design quickly became too large for
 // its simple design; should revisit a lot of these little things..
@@ -563,7 +763,7 @@ void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *
     // check if icon already exists (from a previous extraction say); if so, we needn't
     // do it again
     char existingpath [ FILENAME_MAX ];
-    sprintf ( existingpath, "%s/%s.png", emiticonpath, d -> unique_id );
+    sprintf ( existingpath, "%s/%s.png", emiticonpath, d -> unique_id /*, d -> subapp_number */ );
 
     struct stat dirs;
     if ( stat ( existingpath, &dirs ) == 0 ) {
@@ -581,17 +781,37 @@ void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *
 
       pnd_log ( pndn_debug, "  Icon not already present, so trying to write it! %s\n", existingpath );
 
+      // handle same-path icon override for davec :)
+      char ovrfile [ PATH_MAX ];
+      char *fixpxml;
+      sprintf ( ovrfile, "%s/%s", d -> object_path, d -> object_filename );
+      fixpxml = strcasestr ( ovrfile, PND_PACKAGE_FILEEXT );
+      if ( fixpxml ) {
+       strcpy ( fixpxml, ".png" );
+       fixpxml = NULL;
+       struct stat statbuf;
+       if ( stat ( ovrfile, &statbuf ) == 0 ) {
+         d -> icon = strdup ( ovrfile );
+         fixpxml = ovrfile; // !NULL will be the trigger to skip emittinf desktop from .pnd
+       } // stat
+      } // ovr?
+
       // attempt to create icon files; if successful, alter the disco struct to contain new
       // path, otherwise leave it alone (since it could be a generic icon reference..)
-      if ( pnd_emit_icon ( emiticonpath, d ) ) {
-       // success; fix up icon path to new one..
-       if ( d -> icon ) {
-         free ( d -> icon );
+      if ( fixpxml == NULL ) {
+       // don't have an same-path override icon, so go fetch something from pnd file
+
+       if ( pnd_emit_icon ( emiticonpath, d ) ) {
+         // success; fix up icon path to new one..
+         if ( d -> icon ) {
+           free ( d -> icon );
+         }
+         d -> icon = strdup ( existingpath );
+       } else {
+         pnd_log ( pndn_debug, "  WARN: Couldn't write out icon %s\n", existingpath );
        }
-       d -> icon = strdup ( existingpath );
-      } else {
-       pnd_log ( pndn_debug, "  WARN: Couldn't write out icon %s\n", existingpath );
-      }
+
+      } // got ovr icon already?
 
     } // icon already exists?
 
@@ -607,6 +827,13 @@ void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *
       pnd_log ( pndn_rem, "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
     }
 
+    // info .desktop
+    if ( g_info_p && info_dotdesktoppath ) {
+      if ( ! pnd_emit_dotinfo ( info_dotdesktoppath, pndrun, d ) ) {
+       pnd_log ( pndn_rem, "ERROR: Error creating info .desktop file for app: %s\n", pnd_box_get_key ( d ) );
+      }
+    }
+
     // does this object request any mkdir's?
     if ( d -> mkdir_sp ) {
 
@@ -731,7 +958,7 @@ unsigned char perform_discoveries ( char *appspath, char *overridespath,
                    "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd );
 #endif
        } else {
-#if 1
+#if 0
          pnd_log ( pndn_debug, "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer );
 #endif
          continue; // skip deleting it