1  /* Basic tests for Linux SYSV shared memory extensions.
       2     Copyright (C) 2020-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  #include <sys/ipc.h>
      20  #include <sys/shm.h>
      21  #include <errno.h>
      22  #include <stdlib.h>
      23  #include <stdbool.h>
      24  #include <stdio.h>
      25  #include <unistd.h>
      26  #include <inttypes.h>
      27  #include <limits.h>
      28  
      29  #include <support/check.h>
      30  #include <support/temp_file.h>
      31  
      32  #define SHM_MODE 0644
      33  
      34  /* These are for the temporary file we generate.  */
      35  static char *name;
      36  static int shmid;
      37  static long int pgsz;
      38  
      39  static void
      40  remove_shm (void)
      41  {
      42    /* Enforce message queue removal in case of early test failure.
      43       Ignore error since the shm may already have being removed.  */
      44    shmctl (shmid, IPC_RMID, NULL);
      45  }
      46  
      47  static void
      48  do_prepare (int argc, char *argv[])
      49  {
      50    TEST_VERIFY_EXIT (create_temp_file ("tst-sysvshm.", &name) != -1);
      51  }
      52  
      53  #define PREPARE do_prepare
      54  
      55  struct test_shminfo
      56  {
      57    __syscall_ulong_t shmall;
      58    __syscall_ulong_t shmmax;
      59    __syscall_ulong_t shmmni;
      60  };
      61  
      62  /* It tries to obtain some system-wide SysV shared memory information from
      63     /proc to check against IPC_INFO/SHM_INFO.  The /proc only returns the
      64     tunables value of SHMALL, SHMMAX, and SHMMNI.  */
      65  
      66  static uint64_t
      67  read_proc_file (const char *file)
      68  {
      69    FILE *f = fopen (file, "r");
      70    if (f == NULL)
      71      FAIL_UNSUPPORTED ("/proc is not mounted or %s is not available", file);
      72  
      73    /* Handle 32-bit binaries running on 64-bit kernels.  */
      74    uint64_t v;
      75    int r = fscanf (f, "%" SCNu64, &v);
      76    TEST_VERIFY_EXIT (r == 1);
      77  
      78    fclose (f);
      79    return v;
      80  }
      81  
      82  
      83  /* Check if the message queue with IDX (index into the kernel's internal
      84     array) matches the one with KEY.  The CMD is either SHM_STAT or
      85     SHM_STAT_ANY.  */
      86  
      87  static bool
      88  check_shminfo (int idx, key_t key, int cmd)
      89  {
      90    struct shmid_ds shminfo;
      91    int sid = shmctl (idx, cmd, &shminfo);
      92    /* Ignore unused array slot returned by the kernel or information from
      93       unknown message queue.  */
      94    if ((sid == -1 && errno == EINVAL) || sid != shmid)
      95      return false;
      96  
      97    if (sid == -1)
      98      FAIL_EXIT1 ("shmctl with %s failed: %m",
      99  		cmd == SHM_STAT ? "SHM_STAT" : "SHM_STAT_ANY");
     100  
     101    TEST_COMPARE (shminfo.shm_perm.__key, key);
     102    TEST_COMPARE (shminfo.shm_perm.mode, SHM_MODE);
     103    TEST_COMPARE (shminfo.shm_segsz, pgsz);
     104  
     105    return true;
     106  }
     107  
     108  static int
     109  do_test (void)
     110  {
     111    atexit (remove_shm);
     112  
     113    pgsz = sysconf (_SC_PAGESIZE);
     114    if (pgsz == -1)
     115      FAIL_EXIT1 ("sysconf (_SC_PAGESIZE) failed: %m");
     116  
     117    key_t key = ftok (name, 'G');
     118    if (key == -1)
     119      FAIL_EXIT1 ("ftok failed: %m");
     120  
     121    shmid = shmget (key, pgsz, IPC_CREAT | IPC_EXCL | SHM_MODE);
     122    if (shmid == -1)
     123      FAIL_EXIT1 ("shmget failed: %m");
     124  
     125    /* It does not check shmmax because kernel clamp its value to INT_MAX for:
     126  
     127       1. Compat symbols with IPC_64, i.e, 32-bit binaries running on 64-bit
     128          kernels.
     129  
     130       2. Default symbol without IPC_64 (defined as IPC_OLD within Linux) and
     131          glibc always use IPC_64 for 32-bit ABIs (to support 64-bit time_t).
     132          It means that 32-bit binaries running on 32-bit kernels will not see
     133          shmmax being clamped.
     134  
     135       And finding out whether the compat symbol is used would require checking
     136       the underlying kernel against the current ABI.  The shmall and shmmni
     137       already provided enough coverage.  */
     138  
     139    struct test_shminfo tipcinfo;
     140    tipcinfo.shmall = read_proc_file ("/proc/sys/kernel/shmall");
     141    tipcinfo.shmmni = read_proc_file ("/proc/sys/kernel/shmmni");
     142  
     143    int shmidx;
     144  
     145    /* Note: SHM_INFO does not return a shminfo, but rather a 'struct shm_info'.
     146       It is tricky to verify its values since the syscall returns system wide
     147       resources consumed by shared memory.  The shmctl implementation handles
     148       SHM_INFO as IPC_INFO, so the IPC_INFO test should validate SHM_INFO as
     149       well.  */
     150  
     151    {
     152      struct shminfo ipcinfo;
     153      shmidx = shmctl (shmid, IPC_INFO, (struct shmid_ds *) &ipcinfo);
     154      if (shmidx == -1)
     155        FAIL_EXIT1 ("shmctl with IPC_INFO failed: %m");
     156  
     157      TEST_COMPARE (ipcinfo.shmall, tipcinfo.shmall);
     158      TEST_COMPARE (ipcinfo.shmmni, tipcinfo.shmmni);
     159    }
     160  
     161    /* We check if the created shared memory shows in the global list.  */
     162    bool found = false;
     163    for (int i = 0; i <= shmidx; i++)
     164      {
     165        /* We can't tell apart if SHM_STAT_ANY is not supported (kernel older
     166  	 than 4.17) or if the index used is invalid.  So it just check if
     167  	 value returned from a valid call matches the created message
     168  	 queue.  */
     169        check_shminfo (i, key, SHM_STAT_ANY);
     170  
     171        if (check_shminfo (i, key, SHM_STAT))
     172  	{
     173  	  found = true;
     174  	  break;
     175  	}
     176      }
     177  
     178    if (!found)
     179      FAIL_EXIT1 ("shmctl with SHM_STAT/SHM_STAT_ANY could not find the "
     180  		"created shared memory");
     181  
     182    if (shmctl (shmid, IPC_RMID, NULL) == -1)
     183      FAIL_EXIT1 ("shmctl failed");
     184  
     185    return 0;
     186  }
     187  
     188  #include <support/test-driver.c>