afs-web-interface-enhancements-20010623
authorJeff Riegel <riegel@almaden.ibm.com>
Sat, 23 Jun 2001 18:01:03 +0000 (18:01 +0000)
committerDerrick Brashear <shadow@dementia.org>
Sat, 23 Jun 2001 18:01:03 +0000 (18:01 +0000)
Changes include:

1) Token caching: Functions to set the current "PAG" (not really a process
   authentication group, but a generic identifier for a user session)
   to a specific value, allowing us to switch tokens between different
   threads of the Web server without reauthenticating.

2) Performance enhancements: The main improvement is that we can check
   whether a directory is a mount point in advance and avoid doing a stat
   or contacting the destination cell.  We've also enabled bulk statting
   with kolya's patch from 11/2000.

3) New UAFS API's to call various pioctl functions, etc.

enclosed in  AFS_WEB_ENHANCEMENTS ifdef

src/afs/UKERNEL/afs_usrops.c
src/afs/UKERNEL/afs_usrops.h
src/afs/UKERNEL/sysincludes.h
src/afs/VNOPS/afs_vnop_access.c
src/afs/VNOPS/afs_vnop_lookup.c
src/afs/afs_osi_pag.c

index 0c1e9eb..cea99e6 100644 (file)
@@ -637,7 +637,7 @@ struct usr_vnode **compvpp;
     struct usr_inode *ip;
     struct usr_vnode *vp;
 
-    usr_assert(followlink == 0);
+    /*usr_assert(followlink == 0);*/
     usr_assert(dirvpp == NULL);
 
     /*
@@ -645,7 +645,7 @@ struct usr_vnode **compvpp;
      */
     if (*fnamep != '/' || uafs_afsPathName(fnamep) != NULL) {
        AFS_GLOCK();
-       code = uafs_LookupName(fnamep, afs_CurrentDir, compvpp, 0);
+       code = uafs_LookupName(fnamep, afs_CurrentDir, compvpp, 0, 0);
        AFS_GUNLOCK();
        return code;
     }
@@ -2174,7 +2174,8 @@ int uafs_LookupName(
     char *path,
     struct usr_vnode *parentVp,
     struct usr_vnode **vpp,
-    int follow)
+    int follow,
+    int no_eval_mtpt)
 {
     int code;
     int linkCount;
@@ -2251,7 +2252,14 @@ int uafs_LookupName(
             * subdirectory since we hold the global lock
             */
            nextVp = NULL;
-           code = afs_lookup(vp, pathP, &nextVp, u.u_cred);
+#ifdef AFS_WEB_ENHANCEMENTS
+            if ((nextPathP != NULL && *nextPathP != '\0') || !no_eval_mtpt)
+              code = afs_lookup(vp, pathP, &nextVp, u.u_cred, 0);
+            else
+              code = afs_lookup(vp, pathP, &nextVp, u.u_cred, AFS_LOOKUP_NOEVAL);
+#else
+            code = afs_lookup(vp, pathP, &nextVp, u.u_cred, 0);
+#endif /* AFS_WEB_ENHANCEMENTS */
            if (code != 0) {
                VN_RELE(vp);
                afs_osi_Free(tmpPath, strlen(path)+1);
@@ -2353,7 +2361,7 @@ int uafs_LookupLink(
     /*
      * Find the target of the symbolic link
      */
-    code = uafs_LookupName(pathP, parentVp, &linkVp, 1);
+    code = uafs_LookupName(pathP, parentVp, &linkVp, 1, 0);
     if (code) {
        afs_osi_Free(pathP, MAX_OSI_PATH+1);
        return code;
@@ -2417,7 +2425,7 @@ int uafs_LookupParent(
     /*
      * look up the parent
      */
-    code = uafs_LookupName(pathP, afs_CurrentDir, &parentP, 1);
+    code = uafs_LookupName(pathP, afs_CurrentDir, &parentP, 1, 0);
     afs_osi_Free(pathP, len);
     if (code != 0) {
        return code;
@@ -2471,7 +2479,7 @@ int uafs_chdir_r(
     int code;
     struct vnode *dirP;
 
-    code = uafs_LookupName(path, afs_CurrentDir, &dirP, 1);
+    code = uafs_LookupName(path, afs_CurrentDir, &dirP, 1, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -2661,7 +2669,7 @@ int uafs_open_r(
            }
        } else {
            fileP = NULL;
-           code = uafs_LookupName(nameP, dirP, &fileP, 1);
+           code = uafs_LookupName(nameP, dirP, &fileP, 1, 0);
            VN_RELE(dirP);
            if (code != 0) {
                errno = code;
@@ -2974,7 +2982,7 @@ int uafs_stat_r(
     int code;
     struct vnode *vp;
 
-    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1);
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -3009,7 +3017,7 @@ int uafs_lstat_r(
     int code;
     struct vnode *vp;
 
-    code = uafs_LookupName(path, afs_CurrentDir, &vp, 0);
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 0, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -3080,7 +3088,7 @@ int uafs_chmod_r(
     struct vnode *vp;
     struct usr_vattr attrs;
 
-    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1);
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -3155,7 +3163,7 @@ int uafs_truncate_r(
     struct vnode *vp;
     struct usr_vattr attrs;
 
-    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1);
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -3367,7 +3375,7 @@ int uafs_link_r(
     /*
      * Look up the existing node.
      */
-    code = uafs_LookupName(existing, afs_CurrentDir, &existP, 1);
+    code = uafs_LookupName(existing, afs_CurrentDir, &existP, 1, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -3508,7 +3516,7 @@ int uafs_readlink_r(
     struct usr_uio uio;
     struct iovec iov[1];
 
-    code = uafs_LookupName(path, afs_CurrentDir, &vp, 0);
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 0, 0);
     if (code != 0) {
        errno = code;
        return -1;
@@ -3808,6 +3816,7 @@ usr_DIR *uafs_opendir_r(
     char *path)
 {
     usr_DIR *dirp;
+    struct usr_vnode *fileP;
     int fd;
 
     /*
@@ -3818,6 +3827,17 @@ usr_DIR *uafs_opendir_r(
        return NULL;
     }
 
+    fileP = afs_FileTable[fd];
+    if (fileP == NULL) {
+     return NULL;
+    }
+
+    if (fileP->v_type != VDIR) {
+      uafs_close_r(fd);
+      errno = ENOTDIR;
+      return NULL;
+    }
+
     /*
      * Set up the directory structures
      */
@@ -4108,4 +4128,186 @@ char *uafs_afsPathName(char *path)
     return NULL;
 }
 
+#ifdef AFS_WEB_ENHANCEMENTS
+/*
+ * uafs_klog_nopag
+ * klog but don't allocate a new pag
+ */
+int uafs_klog_nopag(
+    char *user,
+    char *cell,
+    char *passwd,
+    char **reason)
+{
+    int code;
+    afs_int32 password_expires = -1;
+
+    usr_mutex_lock(&osi_authenticate_lock);
+    code = ka_UserAuthenticateGeneral(
+      KA_USERAUTH_VERSION  /*+KA_USERAUTH_DOSETPAG2*/, user,
+          NULL, cell, passwd, 0, &password_expires,
+          0, reason);
+    usr_mutex_unlock(&osi_authenticate_lock);
+    return code;
+}
+
+/*
+ * uafs_getcellstatus
+ * get the cell status
+ */
+int uafs_getcellstatus(char *cell, afs_int32 *status)
+{
+  int rc;
+  struct afs_ioctl iob;
+
+  iob.in = cell;
+  iob.in_size = strlen(cell)+1;
+  iob.out = 0;
+  iob.out_size = 0;
+
+  rc = call_syscall(AFSCALL_PIOCTL, /*path*/0, _VICEIOCTL(35),
+                      (long)&iob, 0, 0);
+
+  if (rc < 0) {
+    errno = rc;
+    return -1;
+  }
+
+  *status = iob.out;
+  return 0;
+}
+
+/*
+ * uafs_getvolquota
+ * Get quota of volume associated with path
+ */
+int uafs_getvolquota(char *path, afs_int32 *BlocksInUse, afs_int32 *MaxQuota)
+{
+  int rc;
+  struct afs_ioctl iob;
+  VolumeStatus *status;
+  char buf[1024];
+
+  iob.in = 0;
+  iob.in_size = 0;
+  iob.out = buf;
+  iob.out_size = 1024;
+
+  rc = call_syscall(AFSCALL_PIOCTL, path, _VICEIOCTL(4),
+                      (long)&iob, 0, 0);
+
+  if (rc != 0) {
+    errno = rc;
+    return -1;
+  }
+
+  status = (VolumeStatus *) buf;
+  *BlocksInUse = status->BlocksInUse;
+  *MaxQuota = status->MaxQuota;
+  return 0;
+}
+
+/*
+ * uafs_setvolquota
+ * Set quota of volume associated with path
+ */
+int uafs_setvolquota(char *path, afs_int32 MaxQuota)
+{
+  int rc;
+  struct afs_ioctl iob;
+  VolumeStatus *status;
+  char buf[1024];
+
+  iob.in = buf;
+  iob.in_size = 1024;
+  iob.out = 0;
+  iob.out_size = 0;
+
+  memset(buf, 0, sizeof(VolumeStatus));
+  status = (VolumeStatus *) buf;
+  status->MaxQuota = MaxQuota;
+  status->MinQuota = -1;
+
+  rc = call_syscall(AFSCALL_PIOCTL, path, _VICEIOCTL(5),
+                      (long)&iob, 0, 0);
+
+  if (rc != 0) {
+    errno = rc;
+    return -1;
+  }
+
+  return 0;
+}
+
+/*
+ * uafs_statmountpoint
+ * Determine whether a dir. is a mount point or not
+ * return 1 if mount point, 0 if not
+ */
+int uafs_statmountpoint(char *path)
+{
+    int retval;
+    int code;
+    char buf[256];
+
+    AFS_GLOCK();
+    retval = uafs_statmountpoint_r(path);
+    AFS_GUNLOCK();
+    return retval;
+}
+
+int uafs_statmountpoint_r(char *path)
+{
+    int code;
+    struct vnode *vp;
+    struct vcache *avc;
+    struct vrequest treq;
+    int r;
+
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 0, 1);
+    if (code != 0) {
+     errno = code;
+     return -1;
+    }
+
+    avc = (struct vcache *) vp;
+
+    r = avc->mvstat;
+    VN_RELE(vp);
+    return r;
+}
+
+/*
+ * uafs_getRights
+ * Get a list of rights for the current user on path.
+ */
+int uafs_getRights(char *path)
+{
+    int code, rc;
+    struct vnode *vp;
+    int afs_rights;
+
+    AFS_GLOCK();
+    code = uafs_LookupName(path, afs_CurrentDir, &vp, 1, 0);
+    if (code != 0) {
+     errno = code;
+     AFS_GUNLOCK();
+     return -1;
+    }
+
+    afs_rights = PRSFS_READ |
+      PRSFS_WRITE |
+      PRSFS_INSERT |
+      PRSFS_LOOKUP |
+      PRSFS_DELETE |
+      PRSFS_LOCK |
+      PRSFS_ADMINISTER;
+
+    afs_rights = afs_getRights (vp, afs_rights, u.u_cred);
+
+    AFS_GUNLOCK();
+    return afs_rights;
+}
+#endif /* AFS_WEB_ENHANCEMENTS */
+
 #endif /* UKERNEL */
index 9792396..0ef5fe6 100644 (file)
@@ -74,7 +74,8 @@ extern void uafs_RxServerProc(void);
 extern int uafs_LookupLink(struct usr_vnode *vp, struct usr_vnode *parentP,
                           struct usr_vnode **vpp);
 extern int uafs_LookupName(char *path, struct usr_vnode *parentP,
-                          struct usr_vnode **vpp, int follow);
+                          struct usr_vnode **vpp, int follow,
+                           int no_eval_mtpt);
 extern int uafs_LookupParent(char *path, struct usr_vnode **vpp);
 extern int uafs_GetAttr(struct usr_vnode *vp, struct stat *stats);
 
index 367b6e2..7ff6254 100644 (file)
@@ -932,6 +932,8 @@ extern pthread_cond_t usr_sleep_cond;
 #define usr_cond_signal(A)     assert(pthread_cond_signal(A) == 0)
 #define usr_cond_broadcast(A)  assert(pthread_cond_broadcast(A) == 0)
 #define usr_cond_wait(A,B)     pthread_cond_wait(A,B)
+#define usr_cond_timedwait(A,B,C)  pthread_cond_timedwait(A,B,C)
+
 #define usr_thread_create(A,B,C) \
     do { \
        pthread_attr_t attr; \
@@ -1378,4 +1380,6 @@ typedef struct {
 
 extern unsigned short usr_rx_port;
 
+#define AFS_LOOKUP_NOEVAL 1
+
 #endif /* __AFS_SYSINCLUDESH__  so idempotent */
index 2b4de1a..2c3f6af 100644 (file)
@@ -273,3 +273,28 @@ afs_access(OSI_VC_ARG(avc), amode, acred)
     }
 }
 
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+/*
+ * afs_getRights
+ * This function is just an interface to afs_GetAccessBits
+ */
+int afs_getRights(OSI_VC_ARG(avc), arights, acred)
+    OSI_VC_DECL(avc);
+    register afs_int32 arights;
+    struct AFS_UCRED *acred;
+{
+    register afs_int32 code;
+    struct vrequest treq;
+    OSI_VC_CONVERT(avc)
+
+    if (code = afs_InitReq(&treq, acred)) return code;
+
+    code = afs_VerifyVCache(avc, &treq);
+    if (code) {
+      code = afs_CheckCode(code, &treq, 16);
+      return code; 
+    }
+
+    return afs_GetAccessBits(avc, arights, &treq);
+}
+#endif /* defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS) */
index 93980d2..a1f22b2 100644 (file)
@@ -866,8 +866,13 @@ afs_lookup(OSI_VC_ARG(adp), aname, avcp, pnp, flags, rdir, acred)
     int flags;
     struct vnode *rdir;
 #else
+#if defined(UKERNEL)
+afs_lookup(adp, aname, avcp, acred, flags)
+    int flags;
+#else    
 afs_lookup(adp, aname, avcp, acred)
-#endif
+#endif /* UKERNEL */
+#endif /* SUN5 || SGI */
     OSI_VC_DECL(adp);
     struct vcache **avcp;
     char *aname;
@@ -1152,6 +1157,11 @@ afs_lookup(adp, aname, avcp, acred)
        tvc->parentVnode = adp->fid.Fid.Vnode;
        tvc->parentUnique = adp->fid.Fid.Unique;
        tvc->states &= ~CBulkStat;
+
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+        if (!(flags & AFS_LOOKUP_NOEVAL))
+          /* don't eval mount points */
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
        if (tvc->mvstat == 1) {
          /* a mt point, possibly unevaluated */
          struct volume *tvolp;
@@ -1260,6 +1270,14 @@ done:
 
        if (afs_mariner)
          afs_AddMarinerName(aname, tvc); 
+
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+        if (!(flags & AFS_LOOKUP_NOEVAL))
+       /* Here we don't enter the name into the DNLC because we want the
+        evaluated mount dir to be there (the vcache for the mounted volume)
+        rather than the vc of the mount point itself.  we can still find the
+        mount point's vc in the vcache by its fid. */
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
        if (!hit) {
          osi_dnlc_enter (adp, aname, tvc, &versionNo);
        }
index a1b6cec..8c833ea 100644 (file)
@@ -33,7 +33,11 @@ extern int afs_shuttingdown;
 
 /* Exported variables */
 afs_uint32 pag_epoch;
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+afs_uint32 pagCounter = 1;
+#else
 afs_uint32 pagCounter = 0;
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
 
 /* Local variables */
 
@@ -61,14 +65,15 @@ afs_uint32 pagCounter = 0;
  * anyway, so the pag is an alternative handle which is somewhat more
  * secure (although of course not absolutely secure).
 */
+#if !defined(UKERNEL) || !defined(AFS_WEB_ENHANCEMENTS)
 afs_uint32 genpag(void) {
     AFS_STATCNT(genpag);
 #ifdef AFS_LINUX20_ENV
     /* Ensure unique PAG's (mod 200 days) when reloading the client. */
     return (('A' << 24) + ((pag_epoch + pagCounter++) & 0xffffff));
-#else
+#else /* AFS_LINUX20_ENV */
     return (('A' << 24) + (pagCounter++ & 0xffffff));
-#endif
+#endif /* AFS_LINUX20_ENV */
 }
 
 afs_uint32 getpag(void) {
@@ -81,6 +86,30 @@ afs_uint32 getpag(void) {
 #endif
 }
 
+#else
+
+/* Web enhancement: we don't need to restrict pags to 41XXXXXX since
+ * we are not sharing the space with anyone.  So we use the full 32 bits. */
+
+afs_uint32 genpag(void) {
+    AFS_STATCNT(genpag);
+#ifdef AFS_LINUX20_ENV
+    return (pag_epoch + pagCounter++);
+#else
+    return (pagCounter++);
+#endif /* AFS_LINUX20_ENV */
+}
+
+afs_uint32 getpag(void) {
+    AFS_STATCNT(getpag);
+#ifdef AFS_LINUX20_ENV
+    /* Ensure unique PAG's (mod 200 days) when reloading the client. */
+    return (pag_epoch + pagCounter);
+#else
+    return (pagCounter);
+#endif
+}
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
 
 /* used to require 10 seconds between each setpag to guarantee that
  * PAGs never wrap - which would be a security hole.  If we presume
@@ -182,6 +211,115 @@ afs_setpag (void)
 #endif
 }
 
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+/*
+ * afs_setpag_val
+ * This function is like setpag but sets the current thread's pag id to a
+ * caller-provided value instead of calling genpag().  This implements a
+ * form of token caching since the caller can recall a particular pag value
+ * for the thread to restore tokens, rather than reauthenticating.
+ */
+int
+#if    defined(AFS_SUN5_ENV)
+afs_setpag_val (struct AFS_UCRED **credpp, int pagval)
+#elif  defined(AFS_OSF_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_FBSD_ENV)
+afs_setpag_val (struct proc *p, void *args, int *retval, int pagval)
+#else
+afs_setpag_val (int pagval) 
+#endif
+{
+    int code = 0;
+
+#if defined(AFS_SGI53_ENV) && defined(MP)
+    /* This is our first chance to get the global lock. */
+    AFS_GLOCK();
+#endif /* defined(AFS_SGI53_ENV) && defined(MP) */    
+
+    AFS_STATCNT(afs_setpag);
+#ifdef AFS_SUN5_ENV
+    if (!afs_suser(*credpp))
+#else
+    if (!afs_suser())
+#endif
+    {
+       while (osi_Time() - pag_epoch < pagCounter ) {
+           afs_osi_Wait(1000, (struct afs_osi_WaitHandle *) 0, 0);
+       }       
+    }
+
+#if    defined(AFS_SUN5_ENV)
+    code = AddPag(pagval, credpp);
+#elif  defined(AFS_OSF_ENV) || defined(AFS_FBSD_ENV)
+    code = AddPag(p, pagval, &p->p_rcred);
+#elif  defined(AFS_AIX41_ENV)
+    {
+       struct ucred *credp;
+       struct ucred *credp0;
+       
+       credp = crref();
+       credp0 = credp;
+       code = AddPag(pagval, &credp);
+       /* If AddPag() didn't make a new cred, then free our cred ref */
+       if (credp == credp0) {
+           crfree(credp);
+       }
+    }
+#elif  defined(AFS_HPUX110_ENV)
+    {
+       struct ucred *credp = p_cred(u.u_procp);
+       code = AddPag(pagval, &credp);
+    }
+#elif  defined(AFS_SGI_ENV)
+    {
+       cred_t *credp;
+       credp = OSI_GET_CURRENT_CRED();
+       code = AddPag(pagval, &credp);
+    }
+#elif  defined(AFS_LINUX20_ENV)
+    {
+       struct AFS_UCRED *credp = crref();
+       code = AddPag(pagval, &credp);
+       crfree(credp);
+    }
+#elif defined(AFS_DARWIN_ENV)  || defined(AFS_FBSD_ENV)
+    {
+       struct ucred *credp=crdup(p->p_cred->pc_ucred);
+       code=AddPag(p, pagval, &credp);
+       crfree(credp);
+    }
+#else
+    code = AddPag(pagval, &u.u_cred);
+#endif
+
+    afs_Trace1(afs_iclSetp, CM_TRACE_SETPAG, ICL_TYPE_INT32, code);
+#if    defined(AFS_SUN5_ENV) || defined(AFS_SGI_ENV) || defined(AFS_OSF_ENV) || defined(AFS_LINUX20_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_FBSD_ENV)
+#if defined(AFS_SGI53_ENV) && defined(MP)
+    AFS_GUNLOCK();
+#endif /* defined(AFS_SGI53_ENV) && defined(MP) */    
+    return (code);
+#else
+    if (!getuerror())
+       setuerror(code);
+    return (code);
+#endif
+}
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
+
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+int afs_getpag_val()
+{
+  int pagvalue;
+  struct AFS_UCRED *credp = u.u_cred;
+  int gidset0, gidset1;
+
+  gidset0 = credp->cr_groups[0];
+  gidset1 = credp->cr_groups[1];
+  pagvalue=afs_get_pag_from_groups(gidset0, gidset1);
+  return pagvalue;
+}
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
+
+
 #if defined(AFS_OSF_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_FBSD_ENV)
 int AddPag(struct proc *p, afs_int32 aval, struct AFS_UCRED **credpp)
 #else  /* AFS_OSF_ENV || AFS_FBSD_ENV */
@@ -244,11 +382,15 @@ afs_uint32 afs_get_pag_from_groups(gid_t g0a, gid_t g1a)
        h = (g0 >> 14);
        h = (g1 >> 14) + h + h + h;
        ret = ((h << 28) | l);
+#if defined(UKERNEL) && defined(AFS_WEB_ENHANCEMENTS)
+       return ret;
+#else
        /* Additional testing */
        if (((ret >> 24) & 0xff) == 'A')
            return ret;
        else
            return NOPAG;
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
     }
     return NOPAG;
 }
@@ -260,7 +402,9 @@ void afs_get_groups_from_pag(afs_uint32 pag, gid_t *g0p, gid_t *g1p)
 
 
     AFS_STATCNT(afs_get_groups_from_pag);
+#if !defined(UKERNEL) || !defined(AFS_WEB_ENHANCEMENTS)
     pag &= 0x7fffffff;
+#endif /* UKERNEL && AFS_WEB_ENHANCEMENTS */
     g0 = 0x3fff & (pag >> 14);
     g1 = 0x3fff & pag;
     g0 |= ((pag >> 28) / 3) << 14;