4

I have tried:

char *path = realpath(getenv("_"), NULL);
*(strrchr(path, '/')+1) = '\0';

That works, but if my executable is invoked by a parent process, then the path of the parent process is shown.

I have googled a lot but I wasn't able to find any properly working solution.

/proc is no option.

Thomas
  • 3,074
  • 1
  • 25
  • 39
  • Have you tried [sysctl?](http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/sysctl.3?query=sysctl&sec=3) – P.P Jul 18 '15 at 20:12
  • @BlueMoon: Yes, I have tried to get `kinfo_proc` via `sysctl()`, but `kinfo_proc->p_comm` only contains the executable name (i.e.: `a.out`). – Thomas Jul 18 '15 at 20:22

2 Answers2

2

After some experimenting, I think I have a working solution...

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <limits.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include <sys/stat.h>

const char *getExecutablePath(char *epath) {
  int mib[4];
  char **argv;
  size_t len;
  const char *comm;
  int ok = 0;

  mib[0] = CTL_KERN;
  mib[1] = KERN_PROC_ARGS;
  mib[2] = getpid();
  mib[3] = KERN_PROC_ARGV;

  if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0)
    abort();

  if (!(argv = malloc(len)))
    abort();

  if (sysctl(mib, 4, argv, &len, NULL, 0) < 0)
    abort();

  comm = argv[0];

  if (*comm == '/' || *comm == '.') {
    if (realpath(comm, epath))
      ok = 1;
  } else {
    char *sp;
    char *xpath = strdup(getenv("PATH"));
    char *path = strtok_r(xpath, ":", &sp);
    struct stat st;

    if (!xpath)
      abort();

    while (path) {
      snprintf(epath, PATH_MAX, "%s/%s", path, comm);

      if (!stat(epath, &st) && (st.st_mode & S_IXUSR)) {
        ok = 1;
        break;
      }

      path = strtok_r(NULL, ":", &sp);
    }

    free(xpath);
  }

  if (ok)
    *strrchr(epath, '/') = '\0';

  free(argv);
  return ok ? epath : NULL;
}

int main(void) {
  char path[PATH_MAX];

  if (getExecutablePath(path))
    puts(path);

  return 0;
}
Thomas
  • 3,074
  • 1
  • 25
  • 39
1

Sadly there is no way to get the full path of the executed file in OpenBSD.

The first argument by convention points to the file name, not to the full path, and you can't be sure the parent process will follow that convention.

The following command, called exe, illustrates this.

#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/wait.h>

int exe( const char * path, const char * fakepath )
{
    pid_t pid = vfork( ) ;
    if( pid == 0 )
    {
        /* Child process */
        int fd ;
        const int TO = getdtablesize( ) ;
        for( fd = 0 ; fd <= TO ; fd++ )
        {
            close( fd );
        }
        open( "/dev/tty", O_RDONLY ) ;
        open( "/dev/tty", O_WRONLY ) ;
        open( "/dev/tty", O_WRONLY ) ;
        const char *arguments[2] = { fakepath, NULL } ;
        execvp( path, (char**)arguments ) ;
        perror( "exe" ) ;
        exit( 1 ) ;
    }
    else if( pid > 0 )
    {
        /* Parent process */
        int status = 0 ;
        if( waitpid( pid, &status, 0 ) != -1 )
        {
            if( WIFEXITED( status ) )
            {
                return WEXITSTATUS( status ) ;
            }
            else
            {
                printf( "exe: child process failed\n" ) ;
                return 1 ;
            }
        }
    }
    perror( "exe" ) ;
    return 1 ;
}

int main( int argc, char * argv[] )
{
    if( argc != 3 )
    {
        printf( "Usage: exe program /fake/first/program/argument\n" ) ;
        return 1 ;
    }
    return exe( argv[1], argv[2] ) ;
}

Now you can execute a program passing an arbitraty first argument, like this:

exe program /fake/first/program/argument

An user asked Theo de Raadt to implement what is necessary to be able to get the full path of the executed file in this thread

I guess we could stick 'path' into an auxiliary vector value and have ld.so do the realpath() call if $ORIGIN is used? It would be that or have the kernel store the whole path for the life of the process for obtaining with sysctl(). Right now it only stores the last component of the (resolved) original path in p_comm.

And he replied:

Quite expensive, for such a small need.

  • BSD sucks. What we need is almost always access to the directory node of the executable so that we can use "openat" calls to load exe relative files. Then a directory with a running program could move while execution is going on. – Lothar Feb 27 '17 at 19:17