1 /* Copyright (C) 2002-2022 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3 
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8 
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with the GNU C Library; if not, see
16    <https://www.gnu.org/licenses/>.  */
17 
18 #include <fcntl.h>
19 #include <semaphore.h>
20 #include <stdarg.h>
21 #include <unistd.h>
22 #include <sys/mman.h>
23 #include "semaphoreP.h"
24 #include <shm-directory.h>
25 #include <sem_routines.h>
26 #include <futex-internal.h>
27 #include <libc-lock.h>
28 
29 #if !PTHREAD_IN_LIBC
30 /* The private names are not exported from libc.  */
31 # define __link link
32 # define __unlink unlink
33 #endif
34 
35 sem_t *
__sem_open(const char * name,int oflag,...)36 __sem_open (const char *name, int oflag, ...)
37 {
38   int fd;
39   sem_t *result;
40 
41   /* Check that shared futexes are supported.  */
42   int err = futex_supports_pshared (PTHREAD_PROCESS_SHARED);
43   if (err != 0)
44     {
45       __set_errno (err);
46       return SEM_FAILED;
47     }
48 
49   struct shmdir_name dirname;
50   if (__shm_get_name (&dirname, name, true) != 0)
51     {
52       __set_errno (EINVAL);
53       return SEM_FAILED;
54     }
55 
56   /* Disable asynchronous cancellation.  */
57 #ifdef __libc_ptf_call
58   int state;
59   __libc_ptf_call (__pthread_setcancelstate,
60                    (PTHREAD_CANCEL_DISABLE, &state), 0);
61 #endif
62 
63   /* If the semaphore object has to exist simply open it.  */
64   if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
65     {
66     try_again:
67       fd = __open (dirname.name,
68 		   (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);
69 
70       if (fd == -1)
71 	{
72 	  /* If we are supposed to create the file try this next.  */
73 	  if ((oflag & O_CREAT) != 0 && errno == ENOENT)
74 	    goto try_create;
75 
76 	  /* Return.  errno is already set.  */
77 	}
78       else
79 	/* Check whether we already have this semaphore mapped and
80 	   create one if necessary.  */
81 	result = __sem_check_add_mapping (name, fd, SEM_FAILED);
82     }
83   else
84     {
85       /* We have to open a temporary file first since it must have the
86 	 correct form before we can start using it.  */
87       mode_t mode;
88       unsigned int value;
89       va_list ap;
90 
91     try_create:
92       va_start (ap, oflag);
93 
94       mode = va_arg (ap, mode_t);
95       value = va_arg (ap, unsigned int);
96 
97       va_end (ap);
98 
99       if (value > SEM_VALUE_MAX)
100 	{
101 	  __set_errno (EINVAL);
102 	  result = SEM_FAILED;
103 	  goto out;
104 	}
105 
106       /* Create the initial file content.  */
107       union
108       {
109 	sem_t initsem;
110 	struct new_sem newsem;
111       } sem;
112 
113       __new_sem_open_init (&sem.newsem, value);
114 
115       /* Initialize the remaining bytes as well.  */
116       memset ((char *) &sem.initsem + sizeof (struct new_sem), '\0',
117 	      sizeof (sem_t) - sizeof (struct new_sem));
118 
119       char tmpfname[] = SHMDIR "sem.XXXXXX";
120       int retries = 0;
121 #define NRETRIES 50
122       while (1)
123 	{
124 	  /* We really want to use mktemp here.  We cannot use mkstemp
125 	     since the file must be opened with a specific mode.  The
126 	     mode cannot later be set since then we cannot apply the
127 	     file create mask.  */
128 	  if (__mktemp (tmpfname) == NULL)
129 	    {
130 	      result = SEM_FAILED;
131 	      goto out;
132 	    }
133 
134 	  /* Open the file.  Make sure we do not overwrite anything.  */
135 	  fd = __open (tmpfname, O_RDWR | O_CREAT | O_EXCL, mode);
136 	  if (fd == -1)
137 	    {
138 	      if (errno == EEXIST)
139 		{
140 		  if (++retries < NRETRIES)
141 		    {
142 		      /* Restore the six placeholder bytes before the
143 			 null terminator before the next attempt.  */
144 		      memcpy (tmpfname + sizeof (tmpfname) - 7, "XXXXXX", 6);
145 		      continue;
146 		    }
147 
148 		  __set_errno (EAGAIN);
149 		}
150 
151 	      result = SEM_FAILED;
152 	      goto out;
153 	    }
154 
155 	  /* We got a file.  */
156 	  break;
157 	}
158 
159       if (TEMP_FAILURE_RETRY (write (fd, &sem.initsem, sizeof (sem_t)))
160 	  == sizeof (sem_t)
161 	  /* Map the sem_t structure from the file.  */
162 	  && (result = (sem_t *) __mmap (NULL, sizeof (sem_t),
163 					 PROT_READ | PROT_WRITE, MAP_SHARED,
164 					 fd, 0)) != MAP_FAILED)
165 	{
166 	  /* Create the file.  Don't overwrite an existing file.  */
167 	  if (__link (tmpfname, dirname.name) != 0)
168 	    {
169 	      /* Undo the mapping.  */
170 	      __munmap (result, sizeof (sem_t));
171 
172 	      /* Reinitialize 'result'.  */
173 	      result = SEM_FAILED;
174 
175 	      /* This failed.  If O_EXCL is not set and the problem was
176 		 that the file exists, try again.  */
177 	      if ((oflag & O_EXCL) == 0 && errno == EEXIST)
178 		{
179 		  /* Remove the file.  */
180 		  __unlink (tmpfname);
181 
182 		  /* Close the file.  */
183 		  __close (fd);
184 
185 		  goto try_again;
186 		}
187 	    }
188 	  else
189 	    /* Insert the mapping into the search tree.  This also
190 	       determines whether another thread sneaked by and already
191 	       added such a mapping despite the fact that we created it.  */
192 	    result = __sem_check_add_mapping (name, fd, result);
193 	}
194 
195       /* Now remove the temporary name.  This should never fail.  If
196 	 it fails we leak a file name.  Better fix the kernel.  */
197       __unlink (tmpfname);
198     }
199 
200   /* Map the mmap error to the error we need.  */
201   if (MAP_FAILED != (void *) SEM_FAILED && result == MAP_FAILED)
202     result = SEM_FAILED;
203 
204   /* We don't need the file descriptor anymore.  */
205   if (fd != -1)
206     {
207       /* Do not disturb errno.  */
208       int save = errno;
209       __close (fd);
210       errno = save;
211     }
212 
213 out:
214 #ifdef __libc_ptf_call
215   __libc_ptf_call (__pthread_setcancelstate, (state, NULL), 0);
216 #endif
217 
218   return result;
219 }
220 #if PTHREAD_IN_LIBC
221 versioned_symbol (libc, __sem_open, sem_open, GLIBC_2_34);
222 # if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_1_1, GLIBC_2_34)
223 compat_symbol (libpthread, __sem_open, sem_open, GLIBC_2_1_1);
224 # endif
225 #else /* !PTHREAD_IN_LIBC */
226 strong_alias (__sem_open, sem_open)
227 #endif
228