[1768] in Moira

home help back first fref pref prev next nref lref last post

Re: proposed moira schema changes and queries for

daemon@ATHENA.MIT.EDU (Qing Dong)
Tue Apr 24 11:50:06 2001

Message-Id: <200104241550.LAA12964@melbourne-city-street.mit.edu>
Date: Tue, 24 Apr 2001 11:45:14 -0400
To: Garry Zacheiss <zacheiss@mit.edu>
From: Qing Dong <dongq@MIT.EDU>
Cc: moiradev@mit.edu
In-Reply-To: <200104240500.BAA182243@oliver.mit.edu>
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"

Garry, thanks for the suggestion. Here are the new diffs for the modified
files. 

Qing

Index: mr_server.h
===================================================================
RCS file:
\\dongq-afs\all\athena.mit.edu\astaff\project\moiradev\repository/moira/serv
er/mr_server.h,v
retrieving revision 1.55
diff -c -r1.55 mr_server.h
*** mr_server.h	2000/09/25 22:48:48	1.55
--- mr_server.h	2001/04/24 15:07:35
***************
*** 152,157 ****
--- 152,158 ----
  int access_snt(struct query *q, char *argv[], client *cl);
  int access_printer(struct query *q, char *argv[], client *cl);
  int access_zephyr(struct query *q, char *argv[], client *cl);
+ int access_container(struct query *q, char *argv[], client *cl);
  
  /* prototypes from qfollow.pc */
  int followup_fix_modby(struct query *q, struct save_queue *sq,
***************
*** 187,192 ****
--- 188,196 ----
  int followup_gpsv(struct query *q, struct save_queue *sq, struct validate
*v,
  		  int (*action)(int, char **, void *), void *actarg,
  		  client *cl);
+ int followup_gcon(struct query *q, struct save_queue *sq, struct validate
*v,
+ 		  int (*action)(int, char **, void *), void *actarg,
+ 		  client *cl);
  
  int followup_ausr(struct query *q, char *argv[], client *cl);
  int followup_aqot(struct query *q, char *argv[], client *cl);
***************
*** 231,236 ****
--- 235,241 ----
  int setup_uhha(struct query *q, char *argv[], client *cl);
  int setup_aprn(struct query *q, char *argv[], client *cl);
  int setup_dpsv(struct query *q, char *argv[], client *cl);
+ int setup_dcon(struct query *q, char *argv[], client *cl);
  
  /* prototypes from qsupport.pc */
  int set_pobox(struct query *q, char *argv[], client *cl);
***************
*** 240,245 ****
--- 245,251 ----
  int tag_member_of_list(struct query *q, char *argv[], client *cl);
  int register_user(struct query *q, char *argv[], client *cl);
  int do_user_reservation(struct query *q, char *argv[], client *cl);
+ int update_container(struct query *q, char *argv[], client *cl);
  
  int get_ace_use(struct query *q, char **argv, client *cl,
  		int (*action)(int, char *[], void *), void *actarg);
***************
*** 263,268 ****
--- 269,281 ----
  int get_user_by_reservation(struct query *q, char **argv, client *cl,
  			    int (*action)(int, char *[], void *),
  			    void *actarg);
+ int get_machines_of_container(struct query *q, char **argv, client *cl,
+ 			    int (*action)(int, char *[], void *),
+ 			    void *actarg);
+ int get_subcontainers_of_container(struct query *q, char **argv, client *cl,
+ 			    int (*action)(int, char *[], void *),
+ 			    void *actarg);
+ 
  
  /* prototypes from qvalidate.pc */
  int validate_fields(struct query *q, char *argv[], struct valobj *vo, int
n);


Index: qsupport.pc
===================================================================
RCS file:
\\dongq-afs\all\athena.mit.edu\astaff\project\moiradev\repository/moira/serv
er/qsupport.pc,v
retrieving revision 2.32
diff -c -r2.32 qsupport.pc
*** qsupport.pc	2001/04/01 05:31:20	2.32
--- qsupport.pc	2001/04/24 15:08:39
***************
*** 1851,1853 ****
--- 1851,2073 ----
  
    return MR_SUCCESS;
  }
+ 
+ int update_container(struct query *q, char *argv[], client *cl)
+ {
+   EXEC SQL BEGIN DECLARE SECTION;
+   int cnt_id, acl_id, memacl_id, who;
+   char cname[CONTAINERS_CNAME_SIZE], newchildcname[CONTAINERS_CNAME_SIZE];
+   char* newcname, *entity, *description, *location, *contact, *acl_type,
*memacl_type;
+   EXEC SQL END DECLARE SECTION;
+   char* tmpchar;
+   int cnt, childid;
+   char childcname[CONTAINERS_CNAME_SIZE];
+ 
+   cnt_id = *(int *)argv[0];
+   newcname = argv[1];
+   description = argv[2];
+   location = argv[3];
+   contact = argv[4];
+   acl_type = argv[5];
+   acl_id = *(int *)argv[6];
+   memacl_type = argv[7];
+   memacl_id = *(int *)argv[8];
+   entity = cl->entity;
+   who = cl->client_id;
+ 
+   EXEC SQL SELECT cname INTO :cname
+     FROM containers
+     WHERE cnt_id = :cnt_id; 
+ 
+   /* if we are renaming the container */
+   if (strcmp(cname, newcname))
+   {
+     /* make sure that only the name part of the cname has been changed */
+     tmpchar = strrchr(cname, '/');
+     /* not a top-level name */
+     if (tmpchar)
+     {
+       cnt = tmpchar - cname + 1;
+       /* the parent part of the old and new name should be identical */
+       if (strncmp(cname, newcname, cnt))
+         return MR_NEW_CONTAINER_NAME;
+     }
+     /* top level name, new name should be a top level name too */
+     else
+     {
+       if (strrchr(newcname, '/'))
+         return MR_NEW_CONTAINER_NAME;
+     }
+ 
+     /* update the cname for this container */
+     EXEC SQL UPDATE containers
+       SET cname = :newcname
+     WHERE cnt_id = :cnt_id;
+ 
+     if (dbms_errno)
+       return mr_errcode;
+ 
+     /* get cnames for its child containers */
+     EXEC SQL DECLARE csr_ucon CURSOR FOR
+       SELECT cname, cnt_id FROM container WHERE cname LIKE :cname || '/'
|| '%';
+   
+     EXEC SQL OPEN csr_ucon;
+     if (dbms_errno)
+       return mr_errcode;
+ 
+     while (1)
+     {
+       EXEC SQL FETCH csr_ucon INTO :childcname, :childid;
+       if (sqlca.sqlcode)
+ 	      break;
+       
+       /* concatenate the new parent cname with the existing sub-container
name
+        * we get the sub-containers new cname */
+       tmpchar = childcname + strlen(cname);
+       strcpy(newchildcname, newcname);
+       strcat(newchildcname, tmpchar);
+ 
+       /* update the cname */
+       EXEC SQL UPDATE containers
+         SET cname = :newchildcname, modtime = SYSDATE, modby = :who,
modwith = :entity
+       WHERE cnt_id = :childid;
+ 
+       if (sqlca.sqlcode)
+         break;
+     }
+   
+     EXEC SQL CLOSE csr_gubr; 
+     if (dbms_errno)
+       return mr_errcode;
+   }
+ 
+   /* update the remaining fields */
+   EXEC SQL UPDATE containers 
+     SET description = NVL(:description, CHR(0)), location =
NVL(:location, CHR(0)), 
+       contact = NVL(:contact, CHR(0)), acl_type = :acl_type, acl_id =
:acl_id, 
+       memacl_type = :memacl_type, memacl_id = :memacl_id, 
+       modtime = SYSDATE, modby = :who, modwith = :entity
+     WHERE cnt_id = :cnt_id;
+ 
+   if (dbms_errno)
+     return mr_errcode;
+     
+   return MR_SUCCESS;
+ }
+ 
+ int get_machines_of_container(struct query *q, char *argv[], client *cl,
+ 			int (*action)(int, char *[], void *), void *actarg)
+ {
+   EXEC SQL BEGIN DECLARE SECTION;
+   int cnt_id, isrecursive;
+   char machinename[MACHINE_NAME_SIZE],
containercname[CONTAINERS_CNAME_SIZE];
+   char *qs;
+   EXEC SQL END DECLARE SECTION;
+ 
+   char querystring[512], tmp [256];
+   char *rargv[2];
+   int found = 0;
+   
+   rargv[0] = machinename;
+   rargv[1] = containercname;
+ 
+   cnt_id = *(int *)argv[0];
+   isrecursive = *(int *)argv[1];
+ 
+   /* get the container name */
+   
+   EXEC SQL SELECT cname INTO :containercname
+     FROM containers
+     WHERE cnt_id = :cnt_id; 
+ 
+   strcpy(querystring, "SELECT a.name, b.cname FROM machine a, containers
b, mcntmap c ");
+   strcat(querystring, "WHERE a.mach_id = c.mach_id AND b.cnt_id =
c.cnt_id ");
+   
+   if (!isrecursive)
+     sprintf(tmp, "AND b.cnt_id = %d ", cnt_id);
+   else
+     sprintf(tmp, "AND (b.cnt_id = %d OR b.cname LIKE '%s/%%') ", cnt_id,
containercname);
+ 
+   strcat(querystring, tmp);
+   strcat(querystring, "ORDER BY b.cname, a.name");
+ 
+   qs = querystring;
+ 
+   EXEC SQL PREPARE stmt FROM :qs;
+   if (sqlca.sqlcode)
+     return MR_INTERNAL;
+   EXEC SQL DECLARE curs_gmnm CURSOR FOR stmt;
+   EXEC SQL OPEN curs_gmnm;
+   
+    while (1)
+ 	{
+ 	  EXEC SQL FETCH curs_gmnm INTO :machinename, :containercname;
+ 	  if (sqlca.sqlcode)
+ 	    break;
+ 	  (*action)(2, rargv, actarg);
+ 	  found++;
+ 	}
+ 
+   EXEC SQL CLOSE curs_gmnm;
+   if (!found)
+     return MR_NO_MATCH;
+   return MR_SUCCESS;
+ }
+ 
+ int get_subcontainers_of_container(struct query *q, char *argv[], client
*cl,
+ 			int (*action)(int, char *[], void *), void *actarg)
+ {
+   EXEC SQL BEGIN DECLARE SECTION;
+   int cnt_id, isrecursive;
+   char containercname[CONTAINERS_CNAME_SIZE],
subcontainercname[CONTAINERS_CNAME_SIZE];
+   char *qs;
+   EXEC SQL END DECLARE SECTION;
+ 
+   char querystring[512], tmp [256];
+   char *rargv[1];
+   int found = 0;
+   
+   rargv[0] = subcontainercname;
+ 
+   cnt_id = *(int *)argv[0];
+   isrecursive = *(int *)argv[1];
+ 
+   /* get the container name */
+   
+   EXEC SQL SELECT cname INTO :containercname
+     FROM containers
+     WHERE cnt_id = :cnt_id; 
+ 
+   strcpy(querystring, "SELECT cname FROM containers ");
+   
+   if (!isrecursive)
+     sprintf(tmp, "WHERE cname LIKE '%s/%%' and cname NOT LIKE '%s/%%/%%'
", containercname, 
+         containercname);
+   else
+     sprintf(tmp, "WHERE cname LIKE '%s/%%' ", containercname);
+ 
+   strcat(querystring, tmp);
+   strcat(querystring, "ORDER BY cname");
+ 
+   qs = querystring;
+ 
+   EXEC SQL PREPARE stmt FROM :qs;
+   if (sqlca.sqlcode)
+     return MR_INTERNAL;
+   EXEC SQL DECLARE curs_gsoc CURSOR FOR stmt;
+   EXEC SQL OPEN curs_gsoc;
+   
+    while (1)
+ 	{
+ 	  EXEC SQL FETCH curs_gsoc INTO :subcontainercname;
+ 	  if (sqlca.sqlcode)
+ 	    break;
+ 	  (*action)(1, rargv, actarg);
+ 	  found++;
+ 	}
+ 
+   EXEC SQL CLOSE curs_gsoc;
+   if (!found)
+     return MR_NO_MATCH;
+   return MR_SUCCESS;
+ }


Index: qsetup.pc
===================================================================
RCS file:
\\dongq-afs\all\athena.mit.edu\astaff\project\moiradev\repository/moira/serv
er/qsetup.pc,v
retrieving revision 2.42
diff -c -r2.42 qsetup.pc
*** qsetup.pc	2000/12/19 07:32:46	2.42
--- qsetup.pc	2001/04/24 15:18:35
***************
*** 222,227 ****
--- 222,231 ----
    EXEC SQL DELETE FROM mcmap WHERE mach_id = :id;
    if (dbms_errno)
      return mr_errcode;
+ 
+   EXEC SQL DELETE FROM mcntmap WHERE mach_id = :id;
+   if (dbms_errno)
+     return mr_errcode;
    return MR_SUCCESS;
  }
  
***************
*** 1254,1259 ****
--- 1258,1296 ----
    if (cnt > 0)
      return MR_IN_USE;
  
+   return MR_SUCCESS;
+ }
+ 
+ int setup_dcon(struct query *q, char *argv[], client *cl)
+ {
+   EXEC SQL BEGIN DECLARE SECTION;
+   int id, cnt;
+   char containercname[CONTAINERS_CNAME_SIZE];
+   EXEC SQL END DECLARE SECTION;
+ 
+   id = *(int *)argv[0];
+   /* check to see if there are machines in this container */
+   EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM mcntmap
+     WHERE cnt_id = :id;
+   if (cnt > 0)
+     return MR_IN_USE;
+ 
+   /* check to see if there are subcontainers in this container */
+ 
+   /* get the container name */
+   
+   EXEC SQL SELECT cname INTO :containercname
+     FROM containers
+     WHERE cnt_id = :id; 
+ 
+   EXEC SQL SELECT COUNT(cnt_id) INTO :cnt FROM containers
+     WHERE cname LIKE :containercname || '/' || '%';
+ 
+   if (cnt > 0)
+     return MR_IN_USE;
+ 
+   if (dbms_errno)
+     return mr_errcode;
    return MR_SUCCESS;
  }
  

Index: queries2.c
===================================================================
RCS file:
\\dongq-afs\all\athena.mit.edu\astaff\project\moiradev\repository/moira/serv
er/queries2.c,v
retrieving revision 2.82
diff -c -r2.82 queries2.c
*** queries2.c	2000/12/19 07:32:47	2.82
--- queries2.c	2001/04/24 15:19:02
***************
*** 42,47 ****
--- 42,51 ----
    {V_ID, 0, FILESYS_TABLE, "label", "filsys_id", MR_FILESYS},
  };
  
+ static struct valobj VOcon0[] = {
+ 	{V_ID, 0, CONTAINERS_TABLE, "cname", "cnt_id", MR_CONTAINER},
+ };
+ 
  static struct valobj VOnum0[] = {
    {V_NUM, 0},
  };
***************
*** 3107,3114 ****
  static char *gdds_fields[] = {
    "sid",
  };
  
- 
  /* Generalized Query Definitions */
  
  /* Multiple versions of the same query MUST be listed in ascending
--- 3111,3274 ----
  static char *gdds_fields[] = {
    "sid",
  };
+ 
+ static char *gcon_fields[] = {
+   "cname",
+   "cname", "description", "location", "contact",
+   "ace_type", "ace_name", "memace_type", "memace_name", "modtime",
"modby", "modwith",
+ };
+ 
+ static struct validate gcon_validate = {
+   0,
+   0,
+   0,
+   0,
+   0,
+   0,
+   0,
+   0,
+   followup_gcon,
+ };
+ 
+ static char *acon_fields[] = {
+   "cname", "description", "location", "contact",
+   "ace_type", "ace_name", "memace_type", "memace_name",
+ };
+ 
+ static struct valobj acon_valobj[] = {
+   {V_CHAR, 0, CONTAINERS_TABLE, "cname"},
+   {V_LEN, 1, CONTAINERS_TABLE, "description"},
+   {V_CHAR, 2, CONTAINERS_TABLE, "location"},
+   {V_CHAR, 3, CONTAINERS_TABLE, "contact"},
+   {V_TYPE, 4, 0, "ace_type", 0, MR_ACE},
+   {V_TYPEDATA, 5, 0, 0, 0, MR_ACE},
+   {V_TYPE, 6, 0, "ace_type", 0, MR_ACE},
+   {V_TYPEDATA, 7, 0, 0, 0, MR_ACE},
+ };
+ 
+ static struct validate acon_validate =
+ {
+   acon_valobj,
+   8,
+   "cname",
+   "cname = '%s'",
+   1,
+   "cnt_id",
+   0,
+   prefetch_value,
+   set_modtime,
+ };
+ 
+ static char *ucon_fields[] = {
+   "cname",
+   "newcname", "description", "location", "contact",
+   "ace_type", "ace_name", "memace_type", "memace_name",
+ };
+ 
+ static struct valobj ucon_valobj[] = {
+   {V_ID, 0, CONTAINERS_TABLE, "cname", "cnt_id", MR_CONTAINER},
+   {V_RENAME, 1, CONTAINERS_TABLE, "cname", "cnt_id", MR_NOT_UNIQUE},
+   {V_LEN, 2, CONTAINERS_TABLE, "description"},
+   {V_CHAR, 3, CONTAINERS_TABLE, "location"},
+   {V_CHAR, 4, CONTAINERS_TABLE, "contact"},
+   {V_TYPE, 5, 0, "ace_type", 0, MR_ACE},
+   {V_TYPEDATA, 6, 0, 0, 0, MR_ACE},
+   {V_TYPE, 7, 0, "ace_type", 0, MR_ACE},
+   {V_TYPEDATA, 8, 0, 0, 0, MR_ACE},
+ };
+ 
+ static struct validate ucon_validate =
+ {
+   ucon_valobj,
+   9,
+   0,
+   0,
+   0,
+   0,
+   access_container,
+   0,
+   update_container,
+ };
+ 
+ static char *dcon_fields[] = {
+   "cname",
+ };
+ 
+ static struct validate dcon_validate =
+ {
+   VOcon0,
+   1,
+   0,
+   0,
+   0,
+   0,
+   0,
+   setup_dcon,
+   0,
+ };
+ 
+ static char *amcn_fields[] = {
+   "machine", "container",
+ };
+ 
+ static struct valobj amcn_valobj[] =	/* ADD_MACHINE_TO_CONTAINER */
+ {					/* DELETE_MACHINE_FROM_CONTAINER */
+   {V_ID, 0, MACHINE_TABLE, "name", "mach_id", MR_MACHINE},
+   {V_ID, 1, CONTAINERS_TABLE, "cname", "cnt_id", MR_CONTAINER},
+ };
+ 
+ static struct validate amcn_validate = /* for amtn and dmfn */
+ {
+   amcn_valobj,
+   2,
+   "mach_id",
+   "mach_id = %d and cnt_id = %d",
+   2,
+   0,
+   access_container,
+   0,
+   set_mach_modtime_by_id,
+ };
+ 
+ static char *gmoc_fields[] = {
+   "container",
+   "isrecursive",
+ 	"machine",
+   "container",
+ };
+ 
+ static struct validate gmoc_validate = 
+ {
+   VOcon0,
+   1,
+   0,
+   0,
+   0,
+   0,
+   0,
+   0,
+   get_machines_of_container,
+ };
+ 
+ static char *gsoc_fields[] = {
+   "container",
+   "isrecursive",
+ 	"subcontainer",
+ };
+ 
+ static struct validate gsoc_validate = 
+ {
+   VOcon0,
+   1,
+   0,
+   0,
+   0,
+   0,
+   0,
+   0,
+   get_subcontainers_of_container,
+ };
  
  /* Generalized Query Definitions */
  
  /* Multiple versions of the same query MUST be listed in ascending
***************
*** 6424,6429 ****
--- 6584,6742 ----
      0,
      NULL,
      NULL,
+   },
+ 
+   {
+     /* Q_GCON - GET_CONTAINER, v7 */
+     "get_container",
+     "gcon",
+     7,
+     RETRIEVE,
+     "c",
+     CONTAINERS_TABLE,
+     "cname, description, location, contact, acl_type, acl_id,
memacl_type, memacl_id, TO_CHAR(modtime, 'DD-mon-YYYY HH24:MI:SS'), modby,
modwith FROM containers",
+     gcon_fields,
+     11,
+     "cname = '%s' AND cnt_id != 0",
+     1,
+     NULL,
+     &gcon_validate,
+   },
+ 
+   {
+     /* Q_ACON - ADD_CONTAINER, v7 */ /* uses prefetch_value() for cnt_id */
+     "add_container",
+     "acon",
+     7,
+     APPEND,
+     "c",
+     CONTAINERS_TABLE,
+     "INTO containers (cname, description, location, contact, acl_type,
acl_id, memacl_type, memacl_id, cnt_id) VALUES ('%s', NVL('%s', CHR(0)),
NVL('%s', CHR(0)), NVL('%s', CHR(0)), '%s', %d, '%s', %d, %s)",
+     acon_fields,
+     8,
+     0,
+     0,
+     NULL,
+     &acon_validate,
+   },
+ 
+   {
+     /* Q_UCON - UPDATE_CONTAINER, v7 */
+     "update_container",
+     "ucon",
+     7,
+     UPDATE,
+     0,
+     CONTAINERS_TABLE,
+     0,
+     ucon_fields,
+     8,
+     0,
+     0,
+     NULL,
+     &ucon_validate,
+   },
+ 
+   {
+     /* Q_DCON - DELETE_CONTAINER, v7 */
+     "delete_container",
+     "dcon",
+     7,
+     DELETE,
+     "c",
+     CONTAINERS_TABLE,
+     NULL,
+     dcon_fields,
+     0,
+     "cnt_id = %d",
+     1,
+     NULL,
+     &dcon_validate,
+   },
+ 
+   {
+     /* Q_AMCN - ADD_MACHINE_TO_CONTAINER, v7 */
+     "add_machine_to_container",
+     "amcn",
+     7,
+     APPEND,
+     "mcn",
+     MCNTMAP_TABLE,
+     "INTO mcntmap (mach_id, cnt_id) VALUES (%d, %d)",
+     amcn_fields,
+     2,
+     0,
+     0,
+     NULL,
+     &amcn_validate,
+   },
+ 
+   {
+     /* Q_DMCN - DELETE_MACHINE_FROM_CONTAINER, v7 */
+     "delete_machine_from_container",
+     "dmcn",
+     7,
+     DELETE,
+     "mcn",
+     MCNTMAP_TABLE,
+     0,
+     amcn_fields,
+     0,
+     "mach_id = %d AND cnt_id = %d",
+     2,
+     NULL,
+     &amcn_validate,
+   },
+ 
+   {
+     /* Q_GMNM - GET_MACHINE_TO_CONTAINER_MAP, v7 */
+     "get_machine_to_container_map",
+     "gmnm",
+     7,
+     RETRIEVE,
+     "mcn",
+     MCNTMAP_TABLE,
+     "c.cname FROM machine m, containers c, mcntmap mcn",
+     amcn_fields,
+     1,
+     "m.name = UPPER('%s') AND mcn.cnt_id = c.cnt_id AND mcn.mach_id =
m.mach_id",
+     1,
+     NULL,
+     NULL,
+   },
+ 
+   {
+     /* Q_GMOC - GET_MACHINES_OF_CONTAINER, v7 */
+     "get_machines_of_container",
+     "gmoc",
+     7,
+     RETRIEVE,
+     NULL,
+     MCNTMAP_TABLE,
+     NULL,
+     gmoc_fields,
+     2,
+     NULL,
+     0,
+     NULL,
+     &gmoc_validate,
+   },
+ 
+   {
+     /* Q_GSOC - GET_SUBCONTAINERS_OF_CONTAINER, v7 */
+     "get_subcontainers_of_container",
+     "gsoc",
+     7,
+     RETRIEVE,
+     NULL,
+     CONTAINERS_TABLE,
+     NULL,
+     gsoc_fields,
+     1,
+     NULL,
+     0,
+     NULL,
+     &gsoc_validate,
    },
  
  };


At 01:00 AM 4/24/2001 -0400, Garry Zacheiss wrote:
>	Hi Qing,
>
>	A read through this looks good.  I'd like to ask for one
>behavior change, though; currently, as you have things implemented,
>deleting a container will remove all the machines from the container
>without prompting and then delete the container.  This seems like it
>could be a nasty user interface if someone typos a name.  I'd much
>rather see the query return MR_IN_USE if there are machines still in a
>container, and only allow you to delete empty containers.  This is how
>the deletion of cluster is currently handled.  In the "moira" client, we
>check if the query returned MR_IN_USE, and display a list of all the
>machines in the cluster, and then ask the user if they'd like to remove
>them all and then proceed.  I'd like to see similar behavior for the
>delete_container operation.
>
>Garry
> 


home help back first fref pref prev next nref lref last post