Code review: Locate exe file in *nix

Hi all. Those of you in the know, please review my code.

Due to the "If you could create your own language..." thread
( http://www.cplusplus.com/forum/lounge/28380/ )
I am spending a little time on my little language, aliae.

Part of the startup code for the shell ("alsh") must determine the executable's full path. This is so that cool things can happen -- such as making a self-contained executable (like a Tcl StarKit or compiled mzscheme program).

On Windows, of course, this is easy. On Unix, however, there is no guaranteed way to make it work. The best method is to try the proc filesystem, and failing that to use the argv[0] given when the program first starts (the find_executable() function must be called first in main()).

Here are my platform files... (For completeness I include the Windows file also.) Alas, I haven't yet been able to get my Linux working (I need to go buy some CD-Rs), so I haven't been able to properly compile the *nix version... so there might be some typos in there ATM. (Sorry.) I also know that I need a few variable name adjustments in the *nix code...

Please critique.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// platform.hpp

//          Copyright Michael Thomas Greer 2010
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt )

#pragma once
#ifndef PLATFORM_HPP
#define PLATFORM_HPP

#include <string>

namespace platform
  {

  std::string find_executable( const std::string& argv0 );
  bool        isatty( unsigned fd );
  
  }

#endif

// end platform.hpp 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// platform.cpp

//          Copyright Michael Thomas Greer 2010
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt )

#include "platform.hpp"

#if defined(__WIN32__)

  #include "windows/platform.cpp"

#elif defined(__unix__)

  #include "unix/platform.cpp"

#else

  #error Alas, there is no support for your platform. Please add it!

#endif

// end platform.cpp 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// unix/platform.cpp (POSIX)

//          Copyright Michael Thomas Greer 2010
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt )

#ifndef PLATFORM_HPP
  #error You need to include ../platform.cpp instead of this file!
#endif

#include <cstdio>       // FILENAME_MAX, sprintf()
#include <cstdlib>      // realpath()
#include <sys/types.h>  // required by stat()
#include <sys/stat.h>   // stat()
#include <unistd.h>     // readlink(), getcwd()

// Legacy junk required for realpath()
#include <sys/param.h>
#include <limits.h>

// Useful C++ stuff
#include <sstream>

namespace platform
  {

 //--------------------------------------------------------------------------
  // Make sure FILENAME_MAX is defined, and that it is at least PATH_MAX
  #ifndef FILENAME_MAX
    #define FILENAME_MAX 4096
  #endif

  #if defined( PATH_MAX )
    #if PATH_MAX > FILENAME_MAX
      #undef  FILENAME_MAX
      #define FILENAME_MAX PATH_MAX
    #endif
  #endif

 //--------------------------------------------------------------------------
  std::string find_executable__validate( const std::string& filename )
    {
    // function
    //   This helper function validates a filename.
    //
    // returns
    //   the normalized path -- if filename exists and is executable
    //   an empty string     -- otherwise
    //
    using namespace std;
    char buffer[ FILENAME_MAX ];

    if (realpath( filename.c_str(), buffer ))
      {
      struct stat status;
      if ((stat( buffer, &status ) == 0)
      &&  (S_ISREG( status.st_mode ))
      &&  (status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
        return buffer;
      }
    return "";
    }

 //--------------------------------------------------------------------------
  std::string find_executable( const std::string& argv0 )
    {
    using namespace std;

    char buffer[ FILENAME_MAX ];

    #if (_POSIX_VERSION >= 200112L) || defined(linux) || defined(__linux)

      // The nice method using the proc filesystem
      // Conforming to one of:
      //   POSIX 2001.1
      //   Linux versions 2.2 or later

      char linkname[ 32 ];
      sprintf( linkname, "/proc/%u/exe", getpid() );

      long /*ssize_t*/ len = readlink( linkname, buffer, FILENAME_MAX );
      if (len >= 0)
        {
        if (len >= FILENAME_MAX) buffer[ FILENAME_MAX - 1 ] = '\0';
        return buffer;
        }

      // Fooey. Fall-through to the not so nice method

    #endif

    // The not so nice method.
    // It relies upon argv[ 0 ] being set to something reasonable...
    // (And assumes that the CWD has not changed since the program began)

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    // Not valid
    if (argv0.empty())
      return "";

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    // If it begins with '/' then we assume an absolute path
    if (argv0[ 0 ] == '/')
      return find_executable__validate( argv0 );

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    // Else if it has a '/' in it then we assume a relative path
    if (getcwd( buffer, FILENAME_MAX ))
      {
      string path = buffer;
      path += '/';
      path += argv0;
      return find_executable__validate( path );
      }

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    // Alas, we have no choice now but to search the $PATH for argv[ 0 ]
    const char* cpath = getenv( "PATH" );
    if (!cpath) cpath = ".:/bin:/usr/bin";  //The sh shell default, apparently

    // For each path...
    istringstream paths( cpath );
    string path;
    while (getline( paths, path, ':' ))
      {
      // Get the realpath of an existing file
      path += '/';
      path += argv0;
      path  = find_executable__validate( path );
      if (!path.empty()) return path;
      }

    // No path was found to match argv[ 0 ]
    return "";
    }

 //--------------------------------------------------------------------------
  bool isatty( unsigned fd )
    {
    static int filenos[] = { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO };
    if (fd > 2) return false;
    return ::isatty( filenos[ fd ] );
    }

  }

// end platform.cpp 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// windows/platform.cpp (Windows)

//          Copyright Michael Thomas Greer 2010
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt )

#ifndef PLATFORM_HPP
  #error You need to include ../platform.cpp instead of this file!
#endif

#include <algorithm>
#include <windows.h>

namespace platform
  {

  //--------------------------------------------------------------------------
  std::string find_executable( const std::string& argv0 )
    {
    WCHAR widename[ MAX_PATH ];
    char  ansiname[ MAX_PATH + MAX_PATH ];

    if (GetModuleFileNameW( NULL, widename, MAX_PATH ) == 0)
      {
      GetModuleFileNameA( NULL, ansiname, sizeof( ansiname ) );
      MultiByteToWideChar( CP_ACP, 0, ansiname, -1, widename, MAX_PATH );
      }

    WideCharToMultiByte( CP_UTF8, 0, widename, -1, ansiname, sizeof( ansiname ), NULL, NULL );

    std::string name = ansiname;
    std::replace( name.begin(), name.end(), '\\', '/' );

    return name;
    }

  //--------------------------------------------------------------------------
  bool isatty( unsigned fd )
    {
    static DWORD handles[] = {
      STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
      };
    DWORD foo;
    if (fd > 2) return false;
    return GetConsoleMode( GetStdHandle( handles[ fd ] ), &foo ) != 0;
    }

  }

// end platform.cpp 

Thank you for your time.

[edit] Fixed line 104. Thanks Albatross!
Last edited on
I haven't yet tested your code, however I can tell you that you have an error in the Unix implementation on line 104. Did you mean argv0[0]?

EDIT1: After testing and fixing that bug, the code works for valid addresses on a UNIX system up to all the specifications. Starting the second test...
EDIT2: After deleting the nice method for the UNIX search and re-compiling the code, it still works for a valid address.

Note: Testing was done using cd (address) and then ./programname.

-Albatross
Last edited on
Thank you for your quick help Albatross. I didn't expect such swift awesomeness.

I am also interested for information on older or unusual systems (running *nix or Windows, of course) from those of you in the know.
Topic archived. No new replies allowed.