Pregunta

I'm porting a Linux C++03 application to Darwin OS X and have some code that reads the symbolic link at /proc/self/exe to determine the directory in which the executable running is located.

How can I compute the directory of the current executable running on Macintosh Darwin OS X Mavericks in C++?

Here is my existing code that works on Linux:

bool
resolveBinaryLocation(string &binaryDirname)
{
  // Read the symbolic link '/proc/self/exe'.
  const char *linkName = "/proc/self/exe";
  const size_t bufSize = PATH_MAX + 1;
  char dirNameBuffer[bufSize];
  const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));

  if (ret == -1) {
    // Permission denied (We must be inetd with this app run as other than root).
    return false;
  }

  dirNameBuffer[ret] = 0; // Terminate the string with a NULL character.

  binaryDirname = dirNameBuffer;

  // Erase the name of the executable:
  string::size_type last = binaryDirname.size() - 1;
  string::size_type idx  = binaryDirname.rfind(DSI_PATH_CHAR, last);

  // Add one to keep the trailing directory separator.
  binaryDirname.erase(idx + 1);

  return true;
}
¿Fue útil?

Solución

Here is what I settled upon as a solution:

bool
resolveBinaryLocation(string &binaryDirname)
{
  const size_t bufSize = PATH_MAX + 1;
  char dirNameBuffer[bufSize];

#ifdef __APPLE__
  uint32_t size = bufSize;

  if (_NSGetExecutablePath(dirNameBuffer, &size) != 0) {
    // Buffer size is too small.
    return false;
  }
#else // not __APPLE__
  // Read the symbolic link '/proc/self/exe'.
  const char *linkName = "/proc/self/exe";
  const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));

  if (ret == -1) {
    // Permission denied (We must be inetd with this app run as other than root).
    return false;
  }
    
  dirNameBuffer[ret] = 0; // Terminate the string with a NULL character.
#endif // else not __APPLE__

  binaryDirname = dirNameBuffer;

  // Erase the name of the executable:
  string::size_type last = binaryDirname.size() - 1;
  string::size_type idx  = binaryDirname.rfind(DSI_PATH_CHAR, last);

  // Add one to keep the trailing directory separator.
  binaryDirname.erase(idx + 1);

  return true;
}

Otros consejos

It is worth pointing out that /proc/self/exe and _NSGetExecutablePath are non-equivalent. If the executable is deleted or replaced while running, opening /proc/self/exe will still open the running version of the executable, while opening _NSGetExecutablePath will either fail (if deleted) or open the new version instead

If it is important to get a macOS equivalent to /proc/self/exe which works even when the executable is deleted/replaced, one option is to open an FD to _NSGetExecutablePath during startup and keep it open. Suppose that FD is 3, then /dev/fd/3 will behave mostly like Linux /proc/self/exe, including the same behaviour when the executable is deleted/replaced. (One difference is you can self-exec using /proc/self/exe on Linux, whereas macOS lacks fexecve and apparently won't let you exec anything in /dev/fd.)

Theoretically, there is a possibility for a race condition here: between starting and opening itself, the executable could be replaced by a new version or deleted. One might decide that such a race condition is so rare there is no need to worry about it; but if one wants to handle it, I have two suggestions:

  • If at startup, opening the path returned by _NSGetExecutablePath() fails (the executable has been deleted while starting, or some other odd condition) – just abort with an error message
  • Check the LC_UUID of the executable you open is the same as your own – abort with an error message if they are different (means the executable replaced with new version at startup race condition has occurred). If aborting isn't an option, you could try to exec the new version instead.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top