SOLARIS: fix for AFS_PAG_ONEGROUP_ENV for Solaris 11
[openafs.git] / src / afs / SOLARIS / osi_groups.c
index bba3882..4cbb62f 100644 (file)
  * setpag
  *
  */
-#include "../afs/param.h"
-#include "../afs/sysincludes.h"
-#include "../afs/afsincludes.h"
-#include "../afs/afs_stats.h"  /* statistics */
+
+#include <afsconfig.h>
+#include "afs/param.h"
+
+#include <unistd.h>
+#ifdef AFS_SUN510_ENV
+#include <sys/cred.h>
+#endif
+
+
+#include "afs/sysincludes.h"
+#include "afsincludes.h"
+#include "afs/afs_stats.h"     /* statistics */
 
 
 static int
-afs_getgroups(
-    struct cred *cred,
-    gid_t *gidset);
+  afs_getgroups(struct cred *cred, gid_t * gidset);
 
 static int
-afs_setgroups(
-    struct cred **cred,
-    int ngroups,
-    gid_t *gidset,
-    int change_parent);
+  afs_setgroups(struct cred **cred, int ngroups, gid_t * gidset,
+               int change_parent);
 
 
-#if    defined(AFS_SUN55_ENV)
 int
 afs_xsetgroups(uap, rvp)
-u_int uap; /* this is gidsetsize */
-gid_t *rvp; /* this is gidset */
-#else 
-struct setgroupsa {
-    u_int gidsetsize;
-    gid_t *gidset;
-};
-
-afs_xsetgroups(uap, rvp) 
-    struct setgroupsa *uap;
-    rval_t *rvp;
-#endif
+     u_int uap;                        /* this is gidsetsize */
+     gid_t *rvp;               /* this is gidset */
 {
     int code = 0;
     struct vrequest treq;
@@ -56,7 +49,8 @@ afs_xsetgroups(uap, rvp)
     AFS_GLOCK();
     code = afs_InitReq(&treq, proc->p_cred);
     AFS_GUNLOCK();
-    if (code) return code;
+    if (code)
+       return code;
     code = setgroups(uap, rvp);
 
     /* Note that if there is a pag already in the new groups we don't
@@ -73,48 +67,138 @@ afs_xsetgroups(uap, rvp)
     return code;
 }
 
+#ifdef AFS_PAG_ONEGROUP_ENV
+/**
+ * Take a PAG, and put it into the given array of gids.
+ *
+ * @param[in] pagvalue     The numeric id for the PAG to assign (must not be -1)
+ * @param[in] gidset       An array of gids
+ * @param[inout] a_ngroups How many entries in 'gidset' have valid gids
+ * @param[in] gidset_sz    The number of bytes allocated for 'gidset'
+ *
+ * @return error code
+ */
+static int
+pag_to_gidset(afs_uint32 pagvalue, gid_t *gidset, int *a_ngroups,
+              size_t gidset_sz)
+{
+    int i;
+    gid_t *gidslot = NULL;
+    int ngroups = *a_ngroups;
+
+    osi_Assert(pagvalue != -1);
+
+    /* See if we already have a PAG gid */
+    for (i = 0; i < ngroups; i++) {
+        if (((gidset[i] >> 24) & 0xff) == 'A') {
+            gidslot = &gidset[i];
+            break;
+        }
+    }
+
+    if (gidslot == NULL) {
+        /* If we don't already have a PAG, grow the groups list by one, and put
+         * our PAG in the new empty slot. */
+        if ((sizeof(gidset[0])) * (ngroups + 1) > gidset_sz) {
+            return E2BIG;
+        }
+        ngroups += 1;
+        gidslot = &gidset[ngroups-1];
+    }
+
+    /*
+     * For newer Solaris releases (Solaris 11), we cannot control the order of
+     * the supplemental groups list of a process, so we can't store PAG gids as
+     * the first two gids anymore. To make finding a PAG gid easier to find,
+     * just use a single gid to represent a PAG, and just store the PAG id
+     * itself in there, like is currently done on Linux. Note that our PAG ids
+     * all start with the byte 0x41 ('A'), so we should not collide with
+     * anything. GIDs with the highest bit set are special (used for Windows
+     * SID mapping), but anything under that range should be fine.
+     */
+    *gidslot = pagvalue;
+
+    *a_ngroups = ngroups;
+
+    return 0;
+}
+#else
+/* For earlier Solaris releases, convert a PAG number into two gids, and store
+ * those gids as the first groups in the supplemental group list. */
+static int
+pag_to_gidset(afs_uint32 pagvalue, gid_t *gidset, int *a_ngroups,
+              size_t gidset_sz)
+{
+    int j;
+    int ngroups = *a_ngroups;
+
+    osi_Assert(pagvalue != -1);
+
+    if (afs_get_pag_from_groups(gidset[0], gidset[1]) == NOPAG) {
+       /* We will have to shift grouplist to make room for pag */
+       if ((sizeof(gidset[0])) * (ngroups + 2) > gidset_sz) {
+           return E2BIG;
+       }
+       for (j = ngroups - 1; j >= 0; j--) {
+           gidset[j + 2] = gidset[j];
+       }
+       ngroups += 2;
+    }
+    afs_get_groups_from_pag(pagvalue, &gidset[0], &gidset[1]);
+
+    *a_ngroups = ngroups;
+
+    return 0;
+}
+#endif
+
 int
 setpag(cred, pagvalue, newpag, change_parent)
-    struct cred **cred;
-    afs_uint32 pagvalue;
-    afs_uint32 *newpag;
-    afs_uint32 change_parent;
+     struct cred **cred;
+     afs_uint32 pagvalue;
+     afs_uint32 *newpag;
+     afs_uint32 change_parent;
 {
     gid_t *gidset;
     int ngroups, code;
-    int j;
+    size_t gidset_sz;
 
     AFS_STATCNT(setpag);
 
-    gidset = (gid_t *) osi_AllocSmallSpace(AFS_SMALLOCSIZ);
+    /* Derive gidset size from running kernel's ngroups_max;
+     * default 16, but configurable up to 32 (Sol10) or
+     * 1024 (Sol11).
+     */
+    gidset_sz = sizeof(gidset[0]) * ngroups_max;
+
+    /* must use osi_Alloc, osi_AllocSmallSpace may not be enough. */
+    gidset = osi_Alloc(gidset_sz);
+
+    pagvalue = (pagvalue == -1 ? genpag() : pagvalue);
+
+    mutex_enter(&curproc->p_crlock);
     ngroups = afs_getgroups(*cred, gidset);
 
-    if (afs_get_pag_from_groups(gidset[0], gidset[1]) == NOPAG) {
-       /* We will have to shift grouplist to make room for pag */
-       if ((sizeof gidset[0])*(ngroups + 2) > AFS_SMALLOCSIZ) {
-           osi_FreeSmallSpace((char *)gidset);
-           return (E2BIG);
-       }
-       for (j = ngroups -1; j >= 0; j--) {
-           gidset[j+2] = gidset[j];
-       }
-       ngroups += 2;
-    }
-    *newpag = (pagvalue == -1 ? genpag(): pagvalue);
-    afs_get_groups_from_pag(*newpag, &gidset[0], &gidset[1]);
-    if (code = afs_setgroups(cred, ngroups, gidset, change_parent)) {
-       osi_FreeSmallSpace((char *)gidset);
-       return (code);
+    code = pag_to_gidset(pagvalue, gidset, &ngroups, gidset_sz);
+    if (code != 0) {
+        mutex_exit(&curproc->p_crlock);
+        goto done;
     }
-    osi_FreeSmallSpace((char *)gidset);
+
+    *newpag = pagvalue;
+
+    /* afs_setgroups will release curproc->p_crlock */
+    /* exit action is same regardless of code */
+    code = afs_setgroups(cred, ngroups, gidset, change_parent);
+
+ done:
+    osi_Free((char *)gidset, gidset_sz);
     return code;
 }
 
 
 static int
-afs_getgroups(
-    struct cred *cred,
-    gid_t *gidset)
+afs_getgroups(struct cred *cred, gid_t * gidset)
 {
     int ngrps, savengrps;
     gid_t *gp;
@@ -122,39 +206,72 @@ afs_getgroups(
     AFS_STATCNT(afs_getgroups);
 
     gidset[0] = gidset[1] = 0;
+#if defined(AFS_SUN510_ENV)
+    savengrps = ngrps = crgetngroups(cred);
+    gp = crgetgroups(cred);
+#else
     savengrps = ngrps = cred->cr_ngroups;
     gp = cred->cr_groups;
+#endif
     while (ngrps--)
-       *gidset++ = *gp++;   
+       *gidset++ = *gp++;
     return savengrps;
 }
 
 
 
 static int
-afs_setgroups(
-    struct cred **cred,
-    int ngroups,
-    gid_t *gidset,
-    int change_parent)
+afs_setgroups(struct cred **cred, int ngroups, gid_t * gidset,
+             int change_parent)
 {
-    int ngrps;
-    int i;
+#ifndef AFS_PAG_ONEGROUP_ENV
     gid_t *gp;
+#endif
 
     AFS_STATCNT(afs_setgroups);
 
-    if (ngroups > ngroups_max)
+    if (ngroups > ngroups_max) {
+       mutex_exit(&curproc->p_crlock);
        return EINVAL;
+    }
     if (!change_parent)
        *cred = (struct cred *)crcopy(*cred);
+
+#ifdef AFS_PAG_ONEGROUP_ENV
+    crsetgroups(*cred, ngroups, gidset);
+#else
+# if defined(AFS_SUN510_ENV)
+    crsetgroups(*cred, ngroups, gidset);
+    gp = crgetgroups(*cred);
+# else
     (*cred)->cr_ngroups = ngroups;
     gp = (*cred)->cr_groups;
+# endif
     while (ngroups--)
        *gp++ = *gidset++;
-    if (!change_parent) {
-       struct proc *proc = ttoproc(curthread);
-       crset(proc, *cred); /* broadcast to all threads */
-    }
+#endif /* !AFS_PAG_ONEGROUP_ENV */
+
+    mutex_exit(&curproc->p_crlock);
+    if (!change_parent)
+       crset(curproc, *cred);  /* broadcast to all threads */
     return (0);
 }
+
+#ifdef AFS_PAG_ONEGROUP_ENV
+afs_int32
+osi_get_group_pag(struct cred *cred) {
+    gid_t *gidset;
+    int ngroups;
+    int i;
+
+    gidset = crgetgroups(cred);
+    ngroups = crgetngroups(cred);
+
+    for (i = 0; i < ngroups; i++) {
+        if (((gidset[i] >> 24) & 0xff) == 'A') {
+            return gidset[i];
+        }
+    }
+    return NOPAG;
+}
+#endif