6 * Copyright (C) 1987 by the Massachusetts Institute of Technology
9 * Revision 1.6 1987-08-04 01:10:02 wesommer
10 * Changes by mike; checked in prior to my hacking.
12 Revision 1.5 87/07/30 14:54:13 wesommer
13 Added debugging code in an attempt to catch a flakey problem.
15 Revision 1.4 87/07/30 00:30:21 wesommer
16 replaced appends = appends+1 with appends = tbs.appends+1
18 Revision 1.3 87/07/30 00:26:11 wesommer
19 Changes by mike prior to "appends" fix.
21 Revision 1.2 87/07/29 16:00:39 wesommer
24 Revision 1.1 87/07/29 15:13:57 wesommer
30 static char *rcsid_qsupport_qc = "$Header$";
34 #include "sms_server.h"
41 /* Specialized Access Routines */
44 ** access_user - verify that client name equals specified login name
46 ** Used by: update_user_shell
47 ** update_finger_by_login
49 ** - since field validation routines are called first, a users_id is
50 ** now in argv[0] instead of the login name. Therefore, we must
51 ** convert the client name to a users_id.
54 access_user(q, argv, cl)
59 register struct krbname *krb;
64 client_name = cl->kname.name;
65 ## repeat retrieve (client_id = users.users_id)
66 ## where users.login = @client_name
67 ## inquire_equel (rowcount = "rowcount")
68 if (rowcount != 1) return(SMS_PERM);
69 if (client_id != *(int *)argv[0]) return(SMS_PERM);
75 ** access_pop - same as access_user plus verifies that a user has only one
76 ** mailbox of type "POP"
85 ** - if q->name = "add_pobox" and type = "POP",
86 ** verify that no POP box already exists for user
91 access_pop(q, argv, cl)
101 if (!bcmp(q->name, "add_pobox", 10) && !bcmp(argv[1], "POP", 4)) {
102 users_id = *(int *)argv[0];
103 mach_id = *(int *)argv[2];
105 ## range of p is pobox
106 ## repeat retrieve (exists = any(p.#box where p.#users_id = @users_id
107 ## and p.type = "POP"
108 ## and p.#mach_id = @mach_id
109 ## and p.#box = @box))
110 if (exists) return(SMS_EXISTS);
113 return(access_user(q, argv, cl));
117 ** access_list - check access for adding or deleting list members
119 ** Inputs: argv[0] - list_id
120 ** cl->krb.name - client name
122 ** - check that client is a member of the access control list
123 ** - OR, if q->shortname == {amtl | dfml} and
124 ** if list.flags & LF_PUBLIC, allow access if client = member
128 access_list(q, argv, cl)
142 list_id = *(int *)argv[0];
143 ## repeat retrieve (acl_id = list.#acl_id, flags = list.#flags)
144 ## where list.#list_id = @list_id
146 /* parse client structure */
147 status = get_client(cl, &client_type, &client_id);
148 if (status != SMS_SUCCESS) return(status);
150 /* if amtl or dmfl and list is public allow client to add or delete self */
151 if (!bcmp("amtl", q->shortname, 4) || !bcmp("dmfl", q->shortname, 4)) {
152 if ((flags & LF_PUBLIC) && !bcmp("USER", argv[1], 4)) {
153 member_id = *(int *)argv[2];
154 if (member_id == client_id) return(SMS_SUCCESS);
158 /* check for client in access control list */
159 exists = find_member(acl_id, client_type, client_id, 0);
160 if (!exists) return(SMS_PERM);
166 ** Setup routine for add_user
168 ** Inputs: argv[0] - login
173 ** - if argv[1] == "#" then set argv[1] = next(uid)
174 ** - if argv[0] == "#" then set argv[0] = "#<uid>"
178 setup_add_user(q, argv, cl, access_check)
180 register char *argv[];
187 if (access_check) return(SMS_SUCCESS);
189 if (!bcmp(argv[1], "#", 2)) {
190 ## range of u is users
191 ## range of v is values
192 ## repeat retrieve (nuid = v.value) where v.name = "uid"
196 ## repeat retrieve (exists = any(u.#uid where u.#uid = @nuid))
198 ## repeat replace v (value = @nuid) where v.name = "uid"
199 sprintf(argv[1], "%d", nuid);
202 if (!bcmp(argv[0], "#", 2)) {
203 sprintf(argv[0], "#%s", argv[1]);
210 ** Setup routine for add_group
214 ** Description: allocate next gid and store in values table
218 setup_add_group(q, argv, cl, access_check)
227 if (access_check) return(SMS_SUCCESS);
229 ## range of g is groups
230 ## range of v is values
231 ## repeat retrieve (ngid = v.value) where v.name = "gid"
235 ## repeat retrieve (exists = any(g.#gid where g.#gid = @ngid))
238 ## repeat replace v (value = @ngid) where v.name = "gid"
243 ** setup_add_filesys - verify existance of referenced file systems
244 ** setup_update_filesys - same, except argv[1..5] --> argv[2..6]
246 ** Inputs: Add Update
247 ** argv[0] - label label
248 ** argv[1] - type new label
249 ** argv[2] - mach_id type
250 ** argv[3] - name mach_id
251 ** argv[4] - mount name
252 ** argv[5] - access mount
257 ** * verify mach_id/name in rvdvirt
258 ** * verify access in {r, x, R, X}
260 ** * extract directory prefix from name
261 ** * verify mach_id/dir in nfsphys
262 ** * verify access in {r, w, R, W}
265 ** SMS_RVD - no such rvd
266 ** SMS_NFS - specified directory not exported
267 ** SMS_FILESYS_ACCESS - invalid filesys access
271 setup_add_filesys(q, argv)
281 mach_id = *(int *)argv[2];
285 if (!bcmp(type, "RVD", 3))
286 return (check_rvd(mach_id, name, access));
287 else if (!bcmp(type, "NFS", 3))
288 return (check_nfs(mach_id, name, access));
293 setup_update_filesys(q, argv)
303 mach_id = *(int *)argv[3];
307 if (!bcmp(type, "RVD", 3))
308 return (check_rvd(mach_id, name, access));
309 else if (!bcmp(type, "NFS", 3))
310 return (check_nfs(mach_id, name, access));
315 ##check_rvd(mach_id, name, access)
323 ## range of rv is rvdvirt
324 ## retrieve (rowcount = any(rv.#name where rv.#mach_id = mach_id and
326 if (rowcount == 0) return(SMS_RVD);
328 caccess = (isupper(*access)) ? tolower(*access) : *access;
329 if (caccess != 'r' && caccess != 'x') return(SMS_FILESYS_ACCESS);
334 ##check_nfs(mach_id, name, access)
345 caccess = (isupper(*access)) ? tolower(*access) : *access;
346 if (caccess != 'r' && caccess != 'w') return(SMS_FILESYS_ACCESS);
348 ## range of np is nfsphys
349 ## retrieve (dir = np.#dir) where np.#mach_id = mach_id
354 if (*cp1++ != *cp2) break;
357 if (*cp2 == 0) return(SMS_SUCCESS);
363 /* Followup Routines */
365 set_user_modtime(q, argv)
372 ## repeat replace u (modtime = "now") where u.#login = @login
376 set_user_modtime_by_id(q, argv)
382 users_id = *(int *)argv[0];
383 ## repeat replace users (modtime = "now") where users.#users_id = @users_id
387 set_list_modtime(q, argv)
394 ## repeat replace list (modtime = "now") where list.name = @list_name
398 set_list_modtime_by_id(q, argv)
404 list_id = *(int *)argv[0];
405 ## repeat replace list (modtime = "now") where list.#list_id = @list_id
409 set_finger_modtime(q, argv)
415 users_id = *(int *)argv[0];
416 ## repeat replace f (modtime = "now") where f.#users_id = @users_id
421 ** set_pop_usage - incr/decr usage count for pop server in serverhosts talbe
424 ** q->name - "add_pobox" or "delete_pobox"
428 ** - incr/decr value field in serverhosts table for pop/mach_id
432 set_pop_usage(q, argv)
438 mach_id = *(int *)argv[2];
439 ## range of sh is serverhosts
441 if (!bcmp(q->name, "add_pobox", 10)) {
442 ## repeat replace sh (value1 = sh.value1 + 1)
443 ## where sh.service = "pop" and sh.#mach_id = @mach_id
444 } else if (!bcmp(q->name, "delete_pobox", 13)) {
445 ## repeat replace sh (value1 = sh.value1 - 1)
446 ## where sh.service = "pop" and sh.#mach_id = @mach_id
453 ** delete_list_members - called after the delete_list query to clean up
456 ** Inputs: argv[0] - list_id
459 ** - foreach string member: decr string refc; ifzero, delete string
460 ** - delete all members entries for this list_id
464 delete_list_members(q, argv)
466 register char *argv[];
472 struct save_queue *sq;
473 struct save_queue *sq_create();
475 list_id = *(int *)argv[0];
478 ## range of m is members
479 ## repeat retrieve (string_id = m.member_id)
480 ## where m.#list_id = @list_id and m.member_type = "STRING"
482 sq_save_data(sq, string_id);
485 while (sq_get_data(sq, &string_id)) {
486 ## range of s is strings
487 ## repeat retrieve (refc = s.#refc) where s.#string_id = @string_id
488 ## inquire_equel (rowcount = "rowcount")
489 if (rowcount == 0) continue;
491 ## repeat delete s where s.#string_id = @string_id
493 ## repeat replace s (#refc = @refc) where s.#string_id = @string_id
498 ## repeat delete m where m.#list_id = @list_id
504 ** grvd_support - Support routine for get_rvd_servers query
507 ** q - grvd query structure
508 ** sq - save_queue struture: contains list of {machine, oper_acl_id,
509 ** admin_acl_id, shutdown_acl_id} records.
510 ** v - validate structure (not used)
511 ** action - action routine
512 ** actarg - action routine argument
515 ** - translate acl_ids to list names
519 grvd_support(q, sq, v, action, actarg)
521 struct save_queue *sq;
530 ## char shutdown[33];
537 ## range of l is list
539 while (sq_get_data(sq, &argv)) {
540 sscanf(argv[0], "%d", &list_id);
541 ## repeat retrieve (oper = l.name) where l.#list_id = @list_id
542 sscanf(argv[1], "%d", &list_id);
543 ## repeat retrieve (admin = l.name) where l.#list_id = @list_id
544 sscanf(argv[2], "%d", &list_id);
545 ## repeat retrieve (shutdown = l.name) where l.#list_id = @list_id
547 (*action)(3, targv, actarg);
558 ** set_next_object_id - set next object id in values table
560 ** Inputs: object - object name in values table
562 ** - called before an APPEND operation to set the next object id to
563 ** be used for the new record
567 set_next_object_id(object)
573 ## range of v is values
574 ## repeat replace v (value = v.value + 1) where v.#name = @name
579 ** get_query_need - check modtime of query's associated table against given
580 ** time and return true if greater (false if not)
583 ** argv[0] - query name
584 ** argv[1] - time to compare against
588 get_query_need(q, argv, action, actarg)
590 register char *argv[];
594 ## char *last_get_time;
598 struct query *get_query_by_name();
600 q1 = get_query_by_name(argv[0]);
602 last_get_time = argv[1];
605 if (q1->type != RETRIEVE || table == (char *)0) return(SMS_NO_MATCH);
607 ## range of tbs is tblstats
608 ## repeat retrieve (need = any(tbs.modtime where tbs.#table = @table and
609 ## tbs.modtime > @last_get_time))
611 result = (need) ? "true" : "false";
612 (*action)(1, &result, actarg);
618 ** add_locker - special query routine for creating a user locker
621 ** argv[0] - users_id
622 ** argv[1] - machine_id
624 ** argv[3] - initial quota
627 ** - get prefix directory (dir) for mount point on specified machine/device
628 ** - create filesys entry (label=<login>, type=NFS, machine=<machine>,
629 ** mount=<dir>/<login>, access=w, acl=dbadmin)
630 ** - increment allocated in nfsphys by quota
631 ** - create nfsquota entry
634 ** - SMS_NFSPHYS - machine/device does not exist in nfsphys
635 ** - SMS_FILESYS_EXISTS - file system already exists
640 register struct query *q;
656 users_id = *(int *)argv[0];
657 mach_id = *(int *)argv[1];
659 sscanf(argv[3], "%d", "a);
661 ## range of u is users
662 ## range of f is filesys
663 ## range of np is nfsphys
664 ## range of tbs is tblstats
667 ## repeat retrieve (login = u.#login) where u.#users_id = @users_id
669 /* get user's acl id */
670 ## repeat retrieve (user_acl = list.list_id) where list.name = @login
672 /* get filesystem directory prefix; give error if machine/device
673 pair not in nfsphys table */
674 printf("np.mach_id = %d and np.device = %s\n", mach_id, device);
676 ## repeat retrieve (dir = np.#dir, allocated = np.#allocated)
677 ## where np.#mach_id = @mach_id and np.#device = device
678 ## inquire_equel (rowcount = "rowcount")
679 if (rowcount == 0) return(SMS_NFSPHYS);
681 /* make sure a filesys with user's name does not already exist */
682 ## repeat retrieve (rowcount = any(f.label where f.label = @login))
683 if (rowcount != 0) return(SMS_FILESYS_EXISTS);
685 /* create a new filesys */
686 sprintf(locker, "%s/%s", dir, login);
687 sprintf(mount, "/mit/%s", login);
688 ## repeat append filesys
689 ## (#label = @login, type = "NFS", #mach_id = @mach_id,
690 ## name = @locker, access = "w", order = 1, #mount = @mount,
691 ## acl_id = @user_acl)
692 ## repeat replace tbs (appends = tbs.appends + 1, modtime = "now")
693 ## where tbs.table = "filesys"
695 /* increment usage count in nfsphys table */
697 ## replace np (#allocated = allocated)
698 ## where np.#mach_id = mach_id and np.#device = device
699 ## repeat replace tbs (updates = tbs.updates + 1, modtime = "now")
700 ## where tbs.table = "nfsphys"
702 /* create nfsquota entry */
703 ## append nfsquota (#users_id = users_id, #mach_id = mach_id,
704 ## #device = device, #quota = quota)
705 ## repeat replace tbs (appends = tbs.appends + 1, modtime = "now")
706 ## where tbs.table = "nfsquota"
712 ** delete_locker - special query routine for deleting a user locker
715 ** argv[0] - users_id
716 ** argv[1] - machine_id
721 ** - delete filesys entry (label=<login>)
722 ** - decrement allocated in nfsphys by quota
723 ** - delete nfsquota entry
726 ** - SMS_FILESYS - no filesys exists for user
730 delete_locker(q, argv)
731 register struct query *q;
732 register char *argv[];
742 users_id = *(int *)argv[0];
743 mach_id = *(int *)argv[1];
745 sscanf(argv[3], "%d", "a);
747 ## range of u is users
748 ## range of f is filesys
749 ## range of np is nfsphys
750 ## range of nq is nfsquota
751 ## range of tbs is tblstats
754 ## repeat retrieve (login = u.#login) where u.#users_id = @users_id
756 /* delete the filesys entry */
757 ## repeat delete f where f.label = @login
758 ## inquire_equel (rowcount = "rowcount")
759 if (rowcount == 0) return(SMS_FILESYS);
760 ## repeat replace tbs (deletes = tbs.deletes + 1, modtime = "now")
761 ## where tbs.table = "filesys"
763 /* decrement usage count in nfsphys table */
764 ## replace np (#allocated = np.#allocated - quota)
765 ## where np.#mach_id = mach_id and np.#device = device
766 ## repeat replace tbs (updates = tbs.updates + 1, modtime = "now")
767 ## where tbs.table = "nfsphys"
769 /* delete nfsquota entry */
770 ## delete nq where nq.#users_id = users_id and nq.#mach_id = mach_id and
771 ## nq.#device = device
772 ## repeat replace tbs (deletes = tbs.deletes + 1, modtime = "now")
773 ## where tbs.table = "nfsquota"
779 ** get_members_of_list - optimized query for retrieval of list members
785 ** - retrieve USER members, then LIST members, then STRING members
789 get_members_of_list(q, argv, action, actarg)
796 ## char member_name[129];
799 list_id = *(int *)argv[0];
801 targv[1] = member_name;
803 ## range of m is members
804 ## repeat retrieve (member_name = users.login)
805 ## where m.#list_id = @list_id and m.member_type = "USER"
806 ## and m.member_id = users.users_id
808 (*action)(2, targv, actarg);
812 ## repeat retrieve (member_name = list.name)
813 ## where m.#list_id = @list_id and m.member_type = "LIST"
814 ## and m.member_id = list.#list_id
816 (*action)(2, targv, actarg);
820 ## repeat retrieve (member_name = strings.string)
821 ## where m.#list_id = @list_id and m.member_type = "STRING"
822 ## and m.member_id = strings.string_id
824 (*action)(2, targv, actarg);
831 ** get_all_poboxes - optimize query for retrieval of all poboxes
834 ** - retrieve LOCAL boxes, then POP boxes, then FOREIGN boxes
838 get_all_poboxes(q, argv, action, actarg)
845 ## char machine[129];
854 ## range of p is pobox
855 ## repeat retrieve (login=users.#login, machine = #machine.name, box=p.#box)
856 ## where p.type = "LOCAL" and p.users_id = users.users_id
857 ## and p.mach_id = #machine.mach_id
859 (*action)(4, targv, actarg);
863 ## repeat retrieve (login=users.#login, machine = #machine.name, box=p.#box)
864 ## where p.type = "POP" and p.users_id = users.users_id
865 ## and p.mach_id = #machine.mach_id
867 (*action)(4, targv, actarg);
870 targv[1] = "FOREIGN";
871 ## repeat retrieve (login=users.#login, machine=strings.string, box=p.#box)
872 ## where p.type = "FOREIGN" and p.users_id = users.users_id
873 ## and p.mach_id = strings.string_id
875 (*action)(4, targv, actarg);
881 /* Validation Routines */
883 validate_row(q, argv, v)
884 register struct query *q;
886 register struct validate *v;
894 /* build where clause */
895 build_qual(v->qual, v->argc, argv, qual);
897 /* setup ingres variables */
902 /* tell the logfile what we're doing */
903 com_err(whoami, 0, "validating row");
904 com_err(whoami, 0, qual);
906 /* look for the record */
907 ## range of rvar is table
908 ## retrieve (rowcount = count(rvar.name where qual))
909 com_err(whoami, 0, "row validated");
910 if (rowcount == 0) return(SMS_NO_MATCH);
911 if (rowcount > 1) return(SMS_NOT_UNIQUE);
915 validate_fields(q, argv, vo, n)
917 register char *argv[];
918 register struct valobj *vo;
927 sprintf(buf, "validating %s in %s: %s",
928 vo->namefield, vo->table, argv[vo->index]);
929 com_err(whoami, 0, buf);
930 status = validate_name(argv, vo);
934 sprintf(buf, "validating %s in %s: %s",
935 vo->idfield, vo->table, argv[vo->index]);
936 com_err(whoami, 0, buf);
937 status = validate_id(argv, vo);
941 sprintf(buf, "validating %s type: %s",
942 vo->table, argv[vo->index]);
943 com_err(whoami, 0, buf);
944 status = validate_type(argv, vo);
948 sprintf(buf, "validating type-specific data: %s",
950 com_err(whoami, 0, buf);
951 status = validate_typedata(q, argv, vo);
960 if (status != SMS_EXISTS) return(status);
967 validate_id(argv, vo)
969 register struct valobj *vo;
978 name = argv[vo->index];
980 namefield = vo->namefield;
981 idfield = vo->idfield;
982 ## retrieve (id = table.idfield) where table.namefield = name
983 ## inquire_equel (rowcount = "rowcount")
984 if (rowcount != 1) return(vo->error);
985 *(int *)argv[vo->index] = id;
989 validate_name(argv, vo)
991 register struct valobj *vo;
998 name = argv[vo->index];
1000 namefield = vo->namefield;
1001 ## retrieve (rowcount = countu(table.namefield
1002 ## where table.namefield = name))
1003 return ((rowcount == 1) ? SMS_EXISTS : vo->error);
1006 validate_type(argv, vo)
1008 register struct valobj *vo;
1015 typename = vo->table;
1016 value = argv[vo->index];
1018 /* uppercase type fields */
1019 for (c = value; *c; c++) if (islower(*c)) *c = toupper(*c);
1021 ## range of a is alias
1022 ## repeat retrieve (rowcount = count(a.trans where a.name = @typename and
1023 ## a.type = "TYPE" and
1024 ## a.trans = @value))
1025 return ((rowcount == 1) ? SMS_EXISTS : vo->error);
1028 /* validate member or type-specific data field */
1030 validate_typedata(q, argv, vo)
1031 register struct query *q;
1032 register char *argv[];
1033 register struct valobj *vo;
1036 ## char *field_type;
1037 ## char data_type[17];
1042 /* get named object */
1043 name = argv[vo->index];
1045 /* get field type string (known to be at index-1) */
1046 field_type = argv[vo->index-1];
1048 /* get corresponding data type associated with field type name */
1049 ## repeat retrieve (data_type = alias.trans)
1050 ## where alias.#name = @field_type and alias.type = "TYPEDATA"
1051 ## inquire_equel (rowcount = "rowcount")
1052 if (rowcount != 1) return(SMS_TYPE);
1054 /* now retrieve the record id corresponding to the named object */
1056 if (!strcmp(data_type, "user")) {
1058 ## repeat retrieve (id = users.users_id) where users.login = @name
1059 ## inquire_equel (rowcount = "rowcount")
1060 if (rowcount != 1) return(SMS_USER);
1062 } else if (!strcmp(data_type, "list")) {
1064 ## repeat retrieve (id = list.list_id) where list.#name = @name
1065 ## inquire_equel (rowcount = "rowcount")
1066 if (rowcount != 1) return(SMS_LIST);
1068 } else if (!strcmp(data_type, "machine")) {
1070 ## repeat retrieve (id = machine.mach_id) where machine.#name = @name
1071 ## inquire_equel (rowcount = "rowcount")
1072 if (rowcount != 1) return(SMS_MACHINE);
1074 } else if (!strcmp(data_type, "string")) {
1076 ## range of s is strings
1077 ## repeat retrieve (id = s.string_id, refc = s.#refc)
1078 ## where s.string = @name
1079 ## inquire_equel (rowcount = "rowcount")
1080 if (rowcount == 0) {
1081 if (q->type != APPEND) return(SMS_STRING);
1082 ## range of v is values
1083 ## retrieve (id = v.value) where v.#name = "strings_id"
1085 ## replace v (value = id) where v.#name = "strings_id"
1086 ## append to strings (string_id = id, string = name, #refc = 1)
1087 } else if (rowcount == 1) {
1088 if (q->type == APPEND || q->type == DELETE) {
1089 refc += (q->type == APPEND) ? 1 : -1;
1091 ## replace s (#refc = refc) where s.string_id = id
1093 ## delete s where s.string_id = id
1101 /* now set value in argv */
1102 *(int *)argv[vo->index] = id;
1104 return (SMS_EXISTS);
1108 translate_ids(q, sq, v, action, actarg)
1109 register struct query *q;
1110 register struct save_queue *sq;
1111 register struct validate *v;
1112 register int (*action)();
1116 ## char *field_type;
1117 ## char data_type[17];
1124 for (i = 0; i < v->objcnt; i++) {
1126 if (vo->type == V_FOLLOWUP) break;
1130 while (sq_get_data(sq, &argv)) {
1134 sscanf(argv[i], "%d", &id);
1136 name = (char *)malloc(129);
1139 /* get field type string (known to be at index-1) */
1140 field_type = argv[vo->index-1];
1142 /* get corresponding data type associated with field type name */
1143 ## repeat retrieve (data_type = alias.trans)
1144 ## where alias.#name = @field_type and alias.type = "TYPEDATA"
1145 ## inquire_equel (rowcount = "rowcount")
1146 if (rowcount != 1) {
1147 sprintf(name, "%d", id);
1148 (*action)(q->vcnt, argv, actarg);
1152 /* retrieve object name */
1154 if (!strcmp(data_type, "user")) {
1156 ## repeat retrieve (name = users.login) where users.users_id = @id
1157 ## inquire_equel (rowcount = "rowcount")
1159 } else if (!strcmp(data_type, "list")) {
1161 ## repeat retrieve (name = list.#name) where list.list_id = @id
1162 ## inquire_equel (rowcount = "rowcount")
1164 } else if (!strcmp(data_type, "machine")) {
1166 ## repeat retrieve (name = machine.#name) where machine.mach_id = @id
1167 ## inquire_equel (rowcount = "rowcount")
1169 } else if (!strcmp(data_type, "string")) {
1171 ## repeat retrieve (name = strings.string)
1172 ## where strings.string_id = @id
1173 ## inquire_equel (rowcount = "rowcount")
1179 /* if there wasn't a corresponding object name, then use the id */
1180 if (rowcount != 1) sprintf(name, "%d", id);
1183 (*action)(q->vcnt, argv, actarg);
1185 /* free saved data */
1186 for (i = 0; i < q->vcnt; i++)
1192 return (SMS_SUCCESS);
1199 * c-continued-statement-offset: 4
1200 * c-brace-offset: -4
1201 * c-argdecl-indent: 4
1202 * c-label-offset: -4