exec change



Hi,

Owen didn't like that searching the path kept you from passing in an
environment (because there's no execvpe()), so I cut-and-pasted
and slightly modified the execvp() implementation from GNU libc
adding the ability to pass in an environment.

I was going to do it with g_find_in_path(), so I implemented that too,
in gutils.c, and this uses g_file_test() in a new file gfileutils.c.
g_find_program_in_path() is separate from g_find_in_path() because
searching PATH requires some special steps, g_find_in_path
(g_getenv(PATH), program) is not correct. Anyway, I didn't end up
using these in gexec.c but I guess they're useful still.

Havoc

gutils.c:


gchar*
g_find_in_path (const gchar *path_list,
                const gchar *filename)
{
  gchar **paths;
  gchar **p;
  gchar *f;
  
  paths = g_strsplit (path_list, ":", -1);
  
  p = paths;
  while (*p)
    {
      f = g_strconcat (*p, "/", filename, NULL);

      if (g_file_test (f, G_FILE_TEST_IS_EXECUTABLE))
        {
          g_strfreev (paths);
          return f;
        }

      g_free (f);

      ++p;
    }

  g_strfreev (paths);
  return NULL;
}

/* Based on execvp() from GNU Libc.
 * Some of this code is cut-and-pasted into gexec.c
 */

static gchar*
my_strchrnul (const gchar *str, gchar c)
{
  gchar *p = (gchar*)str;
  while (*p && (*p != c))
    ++p;

  return p;
}

gchar*
g_find_program_in_path (const gchar *file)
{
  gchar *path, *p, *name, *freeme;
  size_t len;
  size_t pathlen;
  
  path = g_getenv ("PATH");
  if (path == NULL)
    {
      /* There is no `PATH' in the environment.  The default
       * search path in libc is the current directory followed by
       * the path `confstr' returns for `_CS_PATH'.
       */
      
      /* In GLib we put . last, for security, and don't use the
       * unportable confstr(); UNIX98 does not actually specify
       * what to search if PATH is unset. POSIX may, dunno.
       */
      
      path = "/bin:/usr/bin:.";
    }
  
  len = strlen (file) + 1;
  pathlen = strlen (path);
  freeme = name = g_malloc (pathlen + len + 1);
  
  /* Copy the file name at the top, including '\0'  */
  memcpy (name + pathlen + 1, file, len);
  name = name + pathlen;
  /* And add the slash before the filename  */
  *name = '/';
  
  p = path;
  do
    {
      char *startp;

      path = p;
      p = my_strchrnul (path, ':');

      if (p == path)
        /* Two adjacent colons, or a colon at the beginning or the end
         * of `PATH' means to search the current directory.
         */
        startp = name + 1;
      else
        startp = memcpy (name - (p - path), path, p - path);

      if (g_file_test (startp, G_FILE_TEST_IS_EXECUTABLE))
        {
          gchar *ret;
          ret = g_strdup (startp);
          g_free (freeme);
          return ret;
        }
    }
  while (*p++ != '\0');
  
  g_free (freeme);

  return NULL;
}

gfileutils.c:

gboolean
g_file_test (const gchar *filename,
             GFileTest test)
{
  struct stat s;

  if (test & G_FILE_TEST_EXISTS)
    return (access (filename, F_OK) == 0);
  else if (test & G_FILE_TEST_IS_EXECUTABLE)
    return (access (filename, X_OK) == 0);
  else
    {
      if (stat (filename, &s) < 0)
        return FALSE;
      
      if ((test & G_FILE_TEST_IS_DIR) &&
          S_ISDIR (s.st_mode))
        return TRUE;
      else if ((test & G_FILE_TEST_IS_SYMLINK) &&
               S_ISLNK (s.st_mode))
        return TRUE;
      else
        return FALSE;
    }
}

gexec.c:


/* Based on execvp from GNU C Library */

static void
script_execute (const gchar *file,
                gchar      **argv,
                gchar      **envp,
                gboolean     search_path)
{
  /* Count the arguments.  */
  int argc = 0;
  while (argv[argc])
    ++argc;
  
  /* Construct an argument list for the shell.  */
  {
    gchar **new_argv;

    new_argv = g_new0 (gchar*, argc + 1);
    
    new_argv[0] = (char *) "/bin/sh";
    new_argv[1] = (char *) file;
    while (argc > 1)
      {
	new_argv[argc] = argv[argc - 1];
	--argc;
      }

    /* Execute the shell. */
    if (envp)
      execve (new_argv[0], new_argv, envp);
    else
      execv (new_argv[0], new_argv);
    
    g_free (new_argv);
  }
}

static gchar*
my_strchrnul (const gchar *str, gchar c)
{
  gchar *p = (gchar*) str;
  while (*p && (*p != c))
    ++p;

  return p;
}

static gint
g_execute (const gchar *file,
           gchar      **argv,
           gchar      **envp,
           gboolean     search_path)
{
  if (*file == '\0')
    {
      /* We check the simple case first. */
      errno = ENOENT;
      return -1;
    }

  if (!search_path || strchr (file, '/') != NULL)
    {
      /* Don't search when it contains a slash. */
      if (envp)
        execve (file, argv, envp);
      else
        execv (file, argv);
      
      if (errno == ENOEXEC)
	script_execute (file, argv, envp, FALSE);
    }
  else
    {
      gboolean got_eacces = 0;
      char *path, *p, *name, *freeme;
      size_t len;
      size_t pathlen;

      path = g_getenv ("PATH");
      if (path == NULL)
	{
	  /* There is no `PATH' in the environment.  The default
	   * search path in libc is the current directory followed by
	   * the path `confstr' returns for `_CS_PATH'.
           */

          /* In GLib we put . last, for security, and don't use the
           * unportable confstr(); UNIX98 does not actually specify
           * what to search if PATH is unset. POSIX may, dunno.
           */
          
          path = "/bin:/usr/bin:.";
	}

      len = strlen (file) + 1;
      pathlen = strlen (path);
      freeme = name = g_malloc (pathlen + len + 1);
      
      /* Copy the file name at the top, including '\0'  */
      memcpy (name + pathlen + 1, file, len);
      name = name + pathlen;
      /* And add the slash before the filename  */
      *name = '/';

      p = path;
      do
	{
	  char *startp;

	  path = p;
	  p = my_strchrnul (path, ':');

	  if (p == path)
	    /* Two adjacent colons, or a colon at the beginning or the end
             * of `PATH' means to search the current directory.
             */
	    startp = name + 1;
	  else
	    startp = memcpy (name - (p - path), path, p - path);

	  /* Try to execute this name.  If it works, execv will not return.  */
          if (envp)
            execve (startp, argv, envp);
          else
            execv (startp, argv);
          
	  if (errno == ENOEXEC)
	    script_execute (startp, argv, envp, search_path);

	  switch (errno)
	    {
	    case EACCES:
	      /* Record the we got a `Permission denied' error.  If we end
               * up finding no executable we can use, we want to diagnose
               * that we did find one but were denied access.
               */
	      got_eacces = TRUE;

              /* FALL THRU */
              
	    case ENOENT:
#ifdef ESTALE
	    case ESTALE:
#endif
#ifdef ENOTDIR
	    case ENOTDIR:
#endif
	      /* Those errors indicate the file is missing or not executable
               * by us, in which case we want to just try the next path
               * directory.
               */
	      break;

	    default:
	      /* Some other error means we found an executable file, but
               * something went wrong executing it; return the error to our
               * caller.
               */
              g_free (freeme);
	      return -1;
	    }
	}
      while (*p++ != '\0');

      /* We tried every element and none of them worked.  */
      if (got_eacces)
	/* At least one failure was due to permissions, so report that
         * error.
         */
        errno = EACCES;

      g_free (freeme);
    }

  /* Return the error from the last attempt (probably ENOENT).  */
  return -1;
}






[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]