1a677fc5c62071dd4b86f161e016193b916c45a4
[vpp.git] / src / svm / ssvm.c
1 /*
2  * Copyright (c) 2015-2019 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <svm/ssvm.h>
16 #include <svm/svm_common.h>
17
18 typedef int (*init_fn) (ssvm_private_t *);
19 typedef void (*delete_fn) (ssvm_private_t *);
20
21 static init_fn server_init_fns[SSVM_N_SEGMENT_TYPES] =
22   { ssvm_server_init_shm, ssvm_server_init_memfd, ssvm_server_init_private };
23 static init_fn client_init_fns[SSVM_N_SEGMENT_TYPES] =
24   { ssvm_client_init_shm, ssvm_client_init_memfd, ssvm_client_init_private };
25 static delete_fn delete_fns[SSVM_N_SEGMENT_TYPES] =
26   { ssvm_delete_shm, ssvm_delete_memfd, ssvm_delete_private };
27
28 int
29 ssvm_server_init_shm (ssvm_private_t * ssvm)
30 {
31   int ssvm_fd;
32   u8 junk = 0, *ssvm_filename;
33   ssvm_shared_header_t *sh;
34   uword page_size, requested_va = 0;
35   void *oldheap;
36
37   if (ssvm->ssvm_size == 0)
38     return SSVM_API_ERROR_NO_SIZE;
39
40   if (CLIB_DEBUG > 1)
41     clib_warning ("[%d] creating segment '%s'", getpid (), ssvm->name);
42
43   ASSERT (vec_c_string_is_terminated (ssvm->name));
44   ssvm_filename = format (0, "/dev/shm/%s%c", ssvm->name, 0);
45   unlink ((char *) ssvm_filename);
46   vec_free (ssvm_filename);
47
48   ssvm_fd = shm_open ((char *) ssvm->name, O_RDWR | O_CREAT | O_EXCL, 0777);
49   if (ssvm_fd < 0)
50     {
51       clib_unix_warning ("create segment '%s'", ssvm->name);
52       return SSVM_API_ERROR_CREATE_FAILURE;
53     }
54
55   if (fchmod (ssvm_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) < 0)
56     clib_unix_warning ("ssvm segment chmod");
57   if (svm_get_root_rp ())
58     {
59       /* TODO: is this really needed? */
60       svm_main_region_t *smr = svm_get_root_rp ()->data_base;
61       if (fchown (ssvm_fd, smr->uid, smr->gid) < 0)
62         clib_unix_warning ("ssvm segment chown");
63     }
64
65   if (lseek (ssvm_fd, ssvm->ssvm_size, SEEK_SET) < 0)
66     {
67       clib_unix_warning ("lseek");
68       close (ssvm_fd);
69       return SSVM_API_ERROR_SET_SIZE;
70     }
71
72   if (write (ssvm_fd, &junk, 1) != 1)
73     {
74       clib_unix_warning ("set ssvm size");
75       close (ssvm_fd);
76       return SSVM_API_ERROR_SET_SIZE;
77     }
78
79   page_size = clib_mem_get_fd_page_size (ssvm_fd);
80   if (ssvm->requested_va)
81     {
82       requested_va = ssvm->requested_va;
83       clib_mem_vm_randomize_va (&requested_va, min_log2 (page_size));
84     }
85
86   sh = clib_mem_vm_map_shared (uword_to_pointer (requested_va, void *),
87                                ssvm->ssvm_size, ssvm_fd, 0,
88                                (char *) ssvm->name);
89   if (sh == CLIB_MEM_VM_MAP_FAILED)
90     {
91       clib_unix_warning ("mmap");
92       close (ssvm_fd);
93       return SSVM_API_ERROR_MMAP;
94     }
95
96   close (ssvm_fd);
97
98   CLIB_MEM_UNPOISON (sh, sizeof (*sh));
99   sh->server_pid = ssvm->my_pid;
100   sh->ssvm_size = ssvm->ssvm_size;
101   sh->ssvm_va = pointer_to_uword (sh);
102   sh->type = SSVM_SEGMENT_SHM;
103   sh->heap = clib_mem_create_heap (((u8 *) sh) + page_size,
104                                    ssvm->ssvm_size - page_size,
105                                    1 /* locked */ , "ssvm server shm");
106
107   oldheap = ssvm_push_heap (sh);
108   sh->name = format (0, "%s", ssvm->name, 0);
109   ssvm_pop_heap (oldheap);
110
111   ssvm->sh = sh;
112   ssvm->my_pid = getpid ();
113   ssvm->is_server = 1;
114
115   /* The application has to set set sh->ready... */
116   return 0;
117 }
118
119 int
120 ssvm_client_init_shm (ssvm_private_t * ssvm)
121 {
122   struct stat stat;
123   int ssvm_fd = -1;
124   ssvm_shared_header_t *sh;
125
126   ASSERT (vec_c_string_is_terminated (ssvm->name));
127   ssvm->is_server = 0;
128
129   while (ssvm->attach_timeout-- > 0)
130     {
131       if (ssvm_fd < 0)
132         ssvm_fd = shm_open ((char *) ssvm->name, O_RDWR, 0777);
133       if (ssvm_fd < 0)
134         {
135           sleep (1);
136           continue;
137         }
138       if (fstat (ssvm_fd, &stat) < 0)
139         {
140           sleep (1);
141           continue;
142         }
143
144       if (stat.st_size > 0)
145         goto map_it;
146     }
147   clib_warning ("client timeout");
148   return SSVM_API_ERROR_CLIENT_TIMEOUT;
149
150 map_it:
151   sh = (void *) mmap (0, MMAP_PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
152                       ssvm_fd, 0);
153   if (sh == MAP_FAILED)
154     {
155       clib_unix_warning ("client research mmap");
156       close (ssvm_fd);
157       return SSVM_API_ERROR_MMAP;
158     }
159
160   while (ssvm->attach_timeout-- > 0)
161     {
162       if (sh->ready)
163         goto re_map_it;
164     }
165   close (ssvm_fd);
166   munmap (sh, MMAP_PAGESIZE);
167   clib_warning ("client timeout 2");
168   return SSVM_API_ERROR_CLIENT_TIMEOUT;
169
170 re_map_it:
171   ssvm->requested_va = sh->ssvm_va;
172   ssvm->ssvm_size = sh->ssvm_size;
173   munmap (sh, MMAP_PAGESIZE);
174
175   sh = ssvm->sh = (void *) mmap ((void *) ssvm->requested_va, ssvm->ssvm_size,
176                                  PROT_READ | PROT_WRITE,
177                                  MAP_SHARED | MAP_FIXED, ssvm_fd, 0);
178
179   if (sh == MAP_FAILED)
180     {
181       clib_unix_warning ("client final mmap");
182       close (ssvm_fd);
183       return SSVM_API_ERROR_MMAP;
184     }
185   sh->client_pid = getpid ();
186   return 0;
187 }
188
189 void
190 ssvm_delete_shm (ssvm_private_t * ssvm)
191 {
192   u8 *fn;
193
194   fn = format (0, "/dev/shm/%s%c", ssvm->name, 0);
195
196   if (CLIB_DEBUG > 1)
197     clib_warning ("[%d] unlinking ssvm (%s) backing file '%s'", getpid (),
198                   ssvm->name, fn);
199
200   /* Throw away the backing file */
201   if (unlink ((char *) fn) < 0)
202     clib_unix_warning ("unlink segment '%s'", ssvm->name);
203
204   vec_free (fn);
205   vec_free (ssvm->name);
206
207   if (ssvm->is_server)
208     clib_mem_vm_unmap (ssvm->sh);
209   else
210     munmap ((void *) ssvm->sh, ssvm->ssvm_size);
211 }
212
213 /**
214  * Initialize memfd segment server
215  */
216 int
217 ssvm_server_init_memfd (ssvm_private_t * memfd)
218 {
219   uword page_size, n_pages;
220   ssvm_shared_header_t *sh;
221   int log2_page_size;
222   void *oldheap;
223
224   if (memfd->ssvm_size == 0)
225     return SSVM_API_ERROR_NO_SIZE;
226
227   ASSERT (vec_c_string_is_terminated (memfd->name));
228
229   memfd->fd = clib_mem_vm_create_fd (CLIB_MEM_PAGE_SZ_DEFAULT,
230                                      (char *) memfd->name);
231
232   if (memfd->fd == CLIB_MEM_ERROR)
233     {
234       clib_unix_warning ("failed to create memfd");
235       return SSVM_API_ERROR_CREATE_FAILURE;
236     }
237
238   log2_page_size = clib_mem_get_fd_log2_page_size (memfd->fd);
239   if (log2_page_size == 0)
240     {
241       clib_unix_warning ("cannot determine page size");
242       return SSVM_API_ERROR_CREATE_FAILURE;
243     }
244
245   n_pages = ((memfd->ssvm_size - 1) >> log2_page_size) + 1;
246
247   if ((ftruncate (memfd->fd, n_pages << log2_page_size)) == -1)
248     {
249       clib_unix_warning ("memfd ftruncate failure");
250       return SSVM_API_ERROR_CREATE_FAILURE;
251     }
252
253   sh = clib_mem_vm_map_shared (uword_to_pointer (memfd->requested_va, void *),
254                                memfd->ssvm_size, memfd->fd, 0,
255                                (char *) memfd->name);
256   if (sh == CLIB_MEM_VM_MAP_FAILED)
257     {
258       clib_unix_warning ("memfd map (fd %d)", memfd->fd);
259       close (memfd->fd);
260       return SSVM_API_ERROR_CREATE_FAILURE;
261     }
262
263   memfd->sh = sh;
264   memfd->my_pid = getpid ();
265   memfd->is_server = 1;
266
267   sh->server_pid = memfd->my_pid;
268   sh->ssvm_size = memfd->ssvm_size;
269   sh->ssvm_va = pointer_to_uword (sh);
270   sh->type = SSVM_SEGMENT_MEMFD;
271
272   page_size = 1ULL << log2_page_size;
273   sh->heap = clib_mem_create_heap (((u8 *) sh) + page_size,
274                                    memfd->ssvm_size - page_size,
275                                    1 /* locked */ , "ssvm server memfd");
276   oldheap = ssvm_push_heap (sh);
277   sh->name = format (0, "%s", memfd->name, 0);
278   ssvm_pop_heap (oldheap);
279
280   /* The application has to set set sh->ready... */
281   return 0;
282 }
283
284 /**
285  * Initialize memfd segment client
286  *
287  * Subtly different than svm_client_init. The caller needs to acquire
288  * a usable file descriptor for the memfd segment e.g. via
289  * vppinfra/socket.c:default_socket_recvmsg
290  */
291 int
292 ssvm_client_init_memfd (ssvm_private_t * memfd)
293 {
294   int mmap_flags = MAP_SHARED;
295   ssvm_shared_header_t *sh;
296   uword page_size;
297
298   memfd->is_server = 0;
299
300   page_size = clib_mem_get_fd_page_size (memfd->fd);
301   if (!page_size)
302     {
303       clib_unix_warning ("page size unknown");
304       return SSVM_API_ERROR_MMAP;
305     }
306
307   if (memfd->requested_va)
308     mmap_flags |= MAP_FIXED;
309
310   /*
311    * Map the segment once, to look at the shared header
312    */
313   sh = (void *) mmap (0, page_size, PROT_READ | PROT_WRITE, MAP_SHARED,
314                       memfd->fd, 0);
315
316   if (sh == MAP_FAILED)
317     {
318       clib_unix_warning ("client research mmap (fd %d)", memfd->fd);
319       close (memfd->fd);
320       return SSVM_API_ERROR_MMAP;
321     }
322
323   memfd->requested_va = sh->ssvm_va;
324   memfd->ssvm_size = sh->ssvm_size;
325   munmap (sh, page_size);
326
327   /*
328    * Remap the segment at the 'right' address
329    */
330   sh = (void *) mmap (uword_to_pointer (memfd->requested_va, void *),
331                       memfd->ssvm_size,
332                       PROT_READ | PROT_WRITE, mmap_flags, memfd->fd, 0);
333
334   if (sh == MAP_FAILED)
335     {
336       clib_unix_warning ("client final mmap");
337       close (memfd->fd);
338       return SSVM_API_ERROR_MMAP;
339     }
340
341   sh->client_pid = getpid ();
342   memfd->sh = sh;
343   return 0;
344 }
345
346 void
347 ssvm_delete_memfd (ssvm_private_t * memfd)
348 {
349   vec_free (memfd->name);
350   if (memfd->is_server)
351     clib_mem_vm_unmap (memfd->sh);
352   else
353     munmap (memfd->sh, memfd->ssvm_size);
354   close (memfd->fd);
355 }
356
357 /**
358  * Initialize segment in a private heap
359  */
360 int
361 ssvm_server_init_private (ssvm_private_t * ssvm)
362 {
363   uword page_size, log2_page_size, rnd_size = 0;
364   ssvm_shared_header_t *sh;
365   clib_mem_heap_t *heap, *oldheap;
366
367   log2_page_size = clib_mem_get_log2_page_size ();
368   if (log2_page_size == 0)
369     {
370       clib_unix_warning ("cannot determine page size");
371       return SSVM_API_ERROR_CREATE_FAILURE;
372     }
373
374   page_size = 1ULL << log2_page_size;
375   rnd_size = clib_max (ssvm->ssvm_size + (page_size - 1), ssvm->ssvm_size);
376   rnd_size &= ~(page_size - 1);
377
378   sh = clib_mem_vm_map (0, rnd_size + page_size, log2_page_size,
379                         (char *) ssvm->name);
380   if (sh == CLIB_MEM_VM_MAP_FAILED)
381     {
382       clib_unix_warning ("private map failed");
383       return SSVM_API_ERROR_CREATE_FAILURE;
384     }
385
386   heap = clib_mem_create_heap ((u8 *) sh + page_size, rnd_size,
387                                1 /* locked */ , "ssvm server private");
388   if (heap == 0)
389     {
390       clib_unix_warning ("heap alloc");
391       return -1;
392     }
393
394   rnd_size = clib_mem_get_heap_free_space (heap);
395
396   ssvm->ssvm_size = rnd_size;
397   ssvm->is_server = 1;
398   ssvm->my_pid = getpid ();
399   ssvm->requested_va = ~0;
400
401   /* First page in allocated memory is set aside for the shared header */
402   ssvm->sh = sh;
403
404   clib_memset (sh, 0, sizeof (*sh));
405   sh->heap = heap;
406   sh->ssvm_size = rnd_size;
407   sh->ssvm_va = pointer_to_uword (sh);
408   sh->type = SSVM_SEGMENT_PRIVATE;
409
410   oldheap = ssvm_push_heap (sh);
411   sh->name = format (0, "%s", ssvm->name, 0);
412   ssvm_pop_heap (oldheap);
413
414   return 0;
415 }
416
417 int
418 ssvm_client_init_private (ssvm_private_t * ssvm)
419 {
420   clib_warning ("BUG: this should not be called!");
421   return -1;
422 }
423
424 void
425 ssvm_delete_private (ssvm_private_t * ssvm)
426 {
427   vec_free (ssvm->name);
428   clib_mem_destroy_heap (ssvm->sh->heap);
429   clib_mem_vm_unmap (ssvm->sh);
430 }
431
432 int
433 ssvm_server_init (ssvm_private_t * ssvm, ssvm_segment_type_t type)
434 {
435   return (server_init_fns[type]) (ssvm);
436 }
437
438 int
439 ssvm_client_init (ssvm_private_t * ssvm, ssvm_segment_type_t type)
440 {
441   return (client_init_fns[type]) (ssvm);
442 }
443
444 void
445 ssvm_delete (ssvm_private_t * ssvm)
446 {
447   delete_fns[ssvm->sh->type] (ssvm);
448 }
449
450 ssvm_segment_type_t
451 ssvm_type (const ssvm_private_t * ssvm)
452 {
453   return ssvm->sh->type;
454 }
455
456 u8 *
457 ssvm_name (const ssvm_private_t * ssvm)
458 {
459   return ssvm->sh->name;
460 }
461
462 /*
463  * fd.io coding-style-patch-verification: ON
464  *
465  * Local Variables:
466  * eval: (c-set-style "gnu")
467  * End:
468  */