user2660877
user2660877

Reputation: 13

C: "No such file or directory" on sem_open() with O_CREAT and proper semaphore name

I'm trying to create semaphores in an initialization function in C like that:

void sem_init(int size, sem_t** sem1, sem_t** sem2) {
  char* semname1 = "/somename";
  char* semname2 = "/someothername";

  errno = 0;
  *sem1 = sem_open(semname1, O_CREAT, S_IRUSR|S_IWUSR, 0);
  printf(strerror(errno));

  errno = 0;
  *sem2 = sem_open(semname2, O_CREAT, S_IRUSR|S_IWUSR, size);
  printf(strerror(errno));
}

But even though I set to O_CREAT flag and the names are well-formed, I always get "No such file or directory" as output. The semaphores are created at /dev/shm/...

I don't see any obvious reason for the error to occur. Please help me on what I'm doing wrong.

Upvotes: 0

Views: 4281

Answers (2)

Rachid K.
Rachid K.

Reputation: 5211

As reported in the comments and previous answers, errno is undefined after a service call if the latter did not return in error (e.g. -1 or SEM_FAILED or MAP_FAILED depending on the service). The reason comes from the fact that the service can call several sub-services which may fail temporarily and trigger the setting of errno to some values but this does not make fail the service itself.

As an example, internally sem_open() creates a temporary file in /dev/shm. To make it, it generates a random name thanks to mktemp() service. But the generated name may already exist. So, the creation attempt of the file will make open() return EEXIST. Hence, sem_open() source code makes several attempts generating random file names until the creation succeeds. So, once the creation succeeded, the errno may still contain EEXIST but the returned code from sem_open() is OK (i.e. different than SEM_FAILED). Here is the code snippet which makes all those tries (glibc-2.34, file sysdeps/pthread/sem_open.c):

#define NRETRIES 50
      while (1)
    {
      /* We really want to use mktemp here.  We cannot use mkstemp
         since the file must be opened with a specific mode.  The
         mode cannot later be set since then we cannot apply the
         file create mask.  */
      if (__mktemp (tmpfname) == NULL)
        {
          result = SEM_FAILED;
          goto out;
        }

      /* Open the file.  Make sure we do not overwrite anything.  */
      fd = __open (tmpfname, O_RDWR | O_CREAT | O_EXCL, mode);
      if (fd == -1)
        {
          if (errno == EEXIST)
        {
          if (++retries < NRETRIES)
            {
              /* Restore the six placeholder bytes before the
             null terminator before the next attempt.  */
              memcpy (tmpfname + sizeof (tmpfname) - 7, "XXXXXX", 6);
              continue;
            }

          __set_errno (EAGAIN);
        }

          result = SEM_FAILED;
          goto out;
        }

      /* We got a file.  */
      break;
    }

In the OP's use case, the errno is ENOENT while the service returns a value different than SEM_FAILED. In fact the service realizes that the file to create already exists. Since the O_EXCL flag is not passed, this is considered OK. So, the file is removed and an open attempt is done again to make sure to get ENOENT before re-creating the file. Here is the code snippet part of the same function for this use case:

[...]
    try_again:
      fd = __open (dirname.name,
           (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);

      if (fd == -1)
        {
          /* If we are supposed to create the file try this next.  */
          if ((oflag & O_CREAT) != 0 && errno == ENOENT)
            goto try_create;

          /* Return.  errno is already set.  */
        }
      else
        /* Check whether we already have this semaphore mapped and
           create one if necessary.  */
        result = __sem_check_add_mapping (name, fd, SEM_FAILED);
[...]
      if ((oflag & O_EXCL) == 0 && errno == EEXIST)
        {
          /* Remove the file.  */
          __unlink (tmpfname);

          /* Close the file.  */
          __close (fd);

          goto try_again;
        }
[...]

Upvotes: 0

user3629249
user3629249

Reputation: 16540

as the comments to the OPs question have indicated, the incorrect value in errno is being used. Suggest:

void sem_init(int size, sem_t** sem1, sem_t** sem2) {
    char* semname1 = "/somename";
    char* semname2 = "/someothername";
    sem_t local_sem1;
    sem_t local_sem2;

    if( (local_sem1 = sem_open(semname1, O_CREAT, S_IRUSR|S_IWUSR, 0) ) == SEM_FAILED )
    {
        perror( "sem_open for sem1 failed" );
        exit( EXIT_FAILURE );
    }

    *sem1 = local_sem1;

    if( (local_sem2 = sem_open(semname2, O_CREAT, S_IRUSR|S_IWUSR, 0) ) == SEM_FAILED )
    {
        perror( "sem_open doe awm2 failed" );
        exit( EXIT_FAILURE );
    }

    *sem2 = local_sem2;
}

Upvotes: 4

Related Questions