Question

I have a GNU/Linux application with uses a number of shared memory objects. It could, potentially, be run a number of times on the same system. To keep things tidy, I first create a directory in /dev/shm for each of the set of shared memory objects.

The problem is that on newer GNU/Linux distributions, I no longer seem to be able create these in a sub-directory of /dev/shm.

The following is a minimal C program with illustrates what I'm talking about:

/*****************************************************************************
* shm_minimal.c
*
* Test shm_open()
*
* Expect to create shared memory file in:
*  /dev/shm/
*  └── my_dir
*      └── shm_name
*
* NOTE: Only visible on filesystem during execution.  I try to be nice, and
*       clean up after myself.
*
* Compile with:
*   $ gcc -lrt shm_minimal.c -o shm_minimal
*
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>


int main(int argc, const char* argv[]) {
  int shm_fd = -1;

  char* shm_dir = "/dev/shm/my_dir";
  char* shm_file = "/my_dir/shm_name";      /* does NOT work */
  //char* shm_file = "/my_dir_shm_name";    /* works */

  // Create directory in /dev/shm
  mkdir(shm_dir, 0777);

  // make shared memory segment
  shm_fd = shm_open(shm_file, O_RDWR | O_CREAT, 0600);

  if (-1 == shm_fd) {

    switch (errno) {
    case EINVAL:
      /* Confirmed on:
       *  kernel v3.14, GNU libc v2.19  (ArchLinux)
       *  kernel v3.13, GNU libc v2.19  (Ubuntu 14.04 Beta 2)
       */
      perror("FAIL - EINVAL");
      return 1;

    default:
      printf("Some other problem not being tested\n");
      return 2;
    }

  } else {
    /* Confirmed on:
     *  kernel v3.8, GNU libc v2.17    (Mint 15)
     *  kernel v3.2, GNU libc v2.15    (Xubuntu 12.04 LTS)
     *  kernel v3.1, GNU libc v2.13    (Debian 6.0)
     *  kernel v2.6.32, GNU libc v2.12 (RHEL 6.4)
     */
    printf("Success !!!\n");
  }

  // clean up
  close(shm_fd);
  shm_unlink(shm_file);
  rmdir(shm_dir);
  return 0;
}


/* vi: set ts=2 sw=2 ai expandtab:
 */

When I run this program on a fairly new distribution, the call to shm_open() returns -1, and errno is set to EINVAL. However, when I run on something a little older, it creates the shared memory object in /dev/shm/my_dir as expected.

For the larger application, the solution is simple. I can use a common prefix instead of a directory.

If you could help enlighten me to this apparent change in behavior it would be very helpful. I suspect someone else out there might be trying to do something similar.

Was it helpful?

Solution

So it turns out the issue stems from how GNU libc validates the shared memory name. Specifically, the shared memory object MUST now be at the root of the shmfs mount point.

This was changed in glibc git commit b20de2c3d9 as the result of bug BZ #16274.

Specifically, the change is the line:

if (name[0] == '\0' || namelen > NAME_MAX || strchr (name, '/') != NULL)

Which now disallows '/' from anywhere in the filename (not counting leading '/')

OTHER TIPS

If you have a third party tool that was broken by this shm_open change, a brilliant coworker found a workaround : preload a library that overrides the shm_open call and swaps slashes for underscores. It does the same for shm_unlink as well, so the application can properly free shared memory when needed.

deslash_shm.cc :

#include <dlfcn.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <algorithm>
#include <string>

// function used in place of the standard shm_open() function
extern "C" int shm_open(const char *name, int oflag, mode_t mode)
{
    // keep a function pointer to the real shm_open() function
    static int (*real_open)(const char *, int, mode_t) = NULL;
    // the first time in, ask the dynamic linker to find the real shm_open() function
    if (!real_open) real_open = (int (*)(const char *, int, mode_t)) dlsym(RTLD_NEXT,"shm_open");

    // take the name we were given and replace all slashes with underscores instead
    std::string n = name;
    std::replace(n.begin(), n.end(), '/', '_');

    // call the real open function with the patched path name
    return real_open(n.c_str(), oflag, mode);
}

// function used in place of the standard shm_unlink() function
extern "C" int shm_unlink(const char *name)
{
    // keep a function pointer to the real shm_unlink() function
    static int (*real_unlink)(const char *) = NULL;
    // the first time in, ask the dynamic linker to find the real shm_unlink() function
    if (!real_unlink) real_unlink = (int (*)(const char *)) dlsym(RTLD_NEXT, "shm_unlink");

    // take the name we were given and replace all slashes with underscores instead
    std::string n = name;
    std::replace(n.begin(), n.end(), '/', '_');

    // call the real unlink function with the patched path name
    return real_unlink(n.c_str());
}

To compile this file:

c++ -fPIC -shared -o deslash_shm.so deslash_shm.cc -ldl

And preload it before starting a process that tries to use non-standard slash characters in shm_open:

in bash:

export LD_PRELOAD=/path/to/deslash_shm.so

in tcsh:

setenv LD_PRELOAD /path/to/deslash_shm.so
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top