6 * Copyright (C) 1987 by the Massachusetts Institute of Technology
9 * Revision 1.7 1987-08-04 01:49:41 wesommer
10 * Rearranged messages.
12 Revision 1.6 87/08/04 01:10:02 wesommer
13 Changes by mike; checked in prior to my hacking.
15 Revision 1.5 87/07/30 14:54:13 wesommer
16 Added debugging code in an attempt to catch a flakey problem.
18 Revision 1.4 87/07/30 00:30:21 wesommer
19 replaced appends = appends+1 with appends = tbs.appends+1
21 Revision 1.3 87/07/30 00:26:11 wesommer
22 Changes by mike prior to "appends" fix.
24 Revision 1.2 87/07/29 16:00:39 wesommer
27 Revision 1.1 87/07/29 15:13:57 wesommer
33 static char *rcsid_qsupport_qc = "$Header$";
37 #include "sms_server.h"
44 /* Specialized Access Routines */
47 ** access_user - verify that client name equals specified login name
49 ** Used by: update_user_shell
50 ** update_finger_by_login
52 ** - since field validation routines are called first, a users_id is
53 ** now in argv[0] instead of the login name. Therefore, we must
54 ** convert the client name to a users_id.
57 access_user(q, argv, cl)
62 register struct krbname *krb;
67 client_name = cl->kname.name;
68 ## repeat retrieve (client_id = users.users_id)
69 ## where users.login = @client_name
70 ## inquire_equel (rowcount = "rowcount")
71 if (rowcount != 1) return(SMS_PERM);
72 if (client_id != *(int *)argv[0]) return(SMS_PERM);
78 ** access_pop - same as access_user plus verifies that a user has only one
79 ** mailbox of type "POP"
88 ** - if q->name = "add_pobox" and type = "POP",
89 ** verify that no POP box already exists for user
94 access_pop(q, argv, cl)
104 if (!bcmp(q->name, "add_pobox", 10) && !bcmp(argv[1], "POP", 4)) {
105 users_id = *(int *)argv[0];
106 mach_id = *(int *)argv[2];
108 ## range of p is pobox
109 ## repeat retrieve (exists = any(p.#box where p.#users_id = @users_id
110 ## and p.type = "POP"
111 ## and p.#mach_id = @mach_id
112 ## and p.#box = @box))
113 if (exists) return(SMS_EXISTS);
116 return(access_user(q, argv, cl));
120 ** access_list - check access for adding or deleting list members
122 ** Inputs: argv[0] - list_id
123 ** cl->krb.name - client name
125 ** - check that client is a member of the access control list
126 ** - OR, if q->shortname == {amtl | dfml} and
127 ** if list.flags & LF_PUBLIC, allow access if client = member
131 access_list(q, argv, cl)
145 list_id = *(int *)argv[0];
146 ## repeat retrieve (acl_id = list.#acl_id, flags = list.#flags)
147 ## where list.#list_id = @list_id
149 /* parse client structure */
150 status = get_client(cl, &client_type, &client_id);
151 if (status != SMS_SUCCESS) return(status);
153 /* if amtl or dmfl and list is public allow client to add or delete self */
154 if (!bcmp("amtl", q->shortname, 4) || !bcmp("dmfl", q->shortname, 4)) {
155 if ((flags & LF_PUBLIC) && !bcmp("USER", argv[1], 4)) {
156 member_id = *(int *)argv[2];
157 if (member_id == client_id) return(SMS_SUCCESS);
161 /* check for client in access control list */
162 exists = find_member(acl_id, client_type, client_id, 0);
163 if (!exists) return(SMS_PERM);
169 ** Setup routine for add_user
171 ** Inputs: argv[0] - login
176 ** - if argv[1] == "#" then set argv[1] = next(uid)
177 ** - if argv[0] == "#" then set argv[0] = "#<uid>"
181 setup_add_user(q, argv, cl, access_check)
183 register char *argv[];
190 if (access_check) return(SMS_SUCCESS);
192 if (!bcmp(argv[1], "#", 2)) {
193 ## range of u is users
194 ## range of v is values
195 ## repeat retrieve (nuid = v.value) where v.name = "uid"
199 ## repeat retrieve (exists = any(u.#uid where u.#uid = @nuid))
201 ## repeat replace v (value = @nuid) where v.name = "uid"
202 sprintf(argv[1], "%d", nuid);
205 if (!bcmp(argv[0], "#", 2)) {
206 sprintf(argv[0], "#%s", argv[1]);
213 ** Setup routine for add_group
217 ** Description: allocate next gid and store in values table
221 setup_add_group(q, argv, cl, access_check)
230 if (access_check) return(SMS_SUCCESS);
232 ## range of g is groups
233 ## range of v is values
234 ## repeat retrieve (ngid = v.value) where v.name = "gid"
238 ## repeat retrieve (exists = any(g.#gid where g.#gid = @ngid))
241 ## repeat replace v (value = @ngid) where v.name = "gid"
246 ** setup_add_filesys - verify existance of referenced file systems
247 ** setup_update_filesys - same, except argv[1..5] --> argv[2..6]
249 ** Inputs: Add Update
250 ** argv[0] - label label
251 ** argv[1] - type new label
252 ** argv[2] - mach_id type
253 ** argv[3] - name mach_id
254 ** argv[4] - mount name
255 ** argv[5] - access mount
260 ** * verify mach_id/name in rvdvirt
261 ** * verify access in {r, x, R, X}
263 ** * extract directory prefix from name
264 ** * verify mach_id/dir in nfsphys
265 ** * verify access in {r, w, R, W}
268 ** SMS_RVD - no such rvd
269 ** SMS_NFS - specified directory not exported
270 ** SMS_FILESYS_ACCESS - invalid filesys access
274 setup_add_filesys(q, argv)
284 mach_id = *(int *)argv[2];
288 if (!bcmp(type, "RVD", 3))
289 return (check_rvd(mach_id, name, access));
290 else if (!bcmp(type, "NFS", 3))
291 return (check_nfs(mach_id, name, access));
296 setup_update_filesys(q, argv)
306 mach_id = *(int *)argv[3];
310 if (!bcmp(type, "RVD", 3))
311 return (check_rvd(mach_id, name, access));
312 else if (!bcmp(type, "NFS", 3))
313 return (check_nfs(mach_id, name, access));
318 ##check_rvd(mach_id, name, access)
326 ## range of rv is rvdvirt
327 ## retrieve (rowcount = any(rv.#name where rv.#mach_id = mach_id and
329 if (rowcount == 0) return(SMS_RVD);
331 caccess = (isupper(*access)) ? tolower(*access) : *access;
332 if (caccess != 'r' && caccess != 'x') return(SMS_FILESYS_ACCESS);
337 ##check_nfs(mach_id, name, access)
348 caccess = (isupper(*access)) ? tolower(*access) : *access;
349 if (caccess != 'r' && caccess != 'w') return(SMS_FILESYS_ACCESS);
351 ## range of np is nfsphys
352 ## retrieve (dir = np.#dir) where np.#mach_id = mach_id
357 if (*cp1++ != *cp2) break;
360 if (*cp2 == 0) return(SMS_SUCCESS);
366 /* Followup Routines */
368 set_user_modtime(q, argv)
375 ## repeat replace u (modtime = "now") where u.#login = @login
379 set_user_modtime_by_id(q, argv)
385 users_id = *(int *)argv[0];
386 ## repeat replace users (modtime = "now") where users.#users_id = @users_id
390 set_list_modtime(q, argv)
397 ## repeat replace list (modtime = "now") where list.name = @list_name
401 set_list_modtime_by_id(q, argv)
407 list_id = *(int *)argv[0];
408 ## repeat replace list (modtime = "now") where list.#list_id = @list_id
412 set_finger_modtime(q, argv)
418 users_id = *(int *)argv[0];
419 ## repeat replace f (modtime = "now") where f.#users_id = @users_id
424 ** set_pop_usage - incr/decr usage count for pop server in serverhosts talbe
427 ** q->name - "add_pobox" or "delete_pobox"
431 ** - incr/decr value field in serverhosts table for pop/mach_id
435 set_pop_usage(q, argv)
441 mach_id = *(int *)argv[2];
442 ## range of sh is serverhosts
444 if (!bcmp(q->name, "add_pobox", 10)) {
445 ## repeat replace sh (value1 = sh.value1 + 1)
446 ## where sh.service = "pop" and sh.#mach_id = @mach_id
447 } else if (!bcmp(q->name, "delete_pobox", 13)) {
448 ## repeat replace sh (value1 = sh.value1 - 1)
449 ## where sh.service = "pop" and sh.#mach_id = @mach_id
456 ** delete_list_members - called after the delete_list query to clean up
459 ** Inputs: argv[0] - list_id
462 ** - foreach string member: decr string refc; ifzero, delete string
463 ** - delete all members entries for this list_id
467 delete_list_members(q, argv)
469 register char *argv[];
475 struct save_queue *sq;
476 struct save_queue *sq_create();
478 list_id = *(int *)argv[0];
481 ## range of m is members
482 ## repeat retrieve (string_id = m.member_id)
483 ## where m.#list_id = @list_id and m.member_type = "STRING"
485 sq_save_data(sq, string_id);
488 while (sq_get_data(sq, &string_id)) {
489 ## range of s is strings
490 ## repeat retrieve (refc = s.#refc) where s.#string_id = @string_id
491 ## inquire_equel (rowcount = "rowcount")
492 if (rowcount == 0) continue;
494 ## repeat delete s where s.#string_id = @string_id
496 ## repeat replace s (#refc = @refc) where s.#string_id = @string_id
501 ## repeat delete m where m.#list_id = @list_id
507 ** grvd_support - Support routine for get_rvd_servers query
510 ** q - grvd query structure
511 ** sq - save_queue struture: contains list of {machine, oper_acl_id,
512 ** admin_acl_id, shutdown_acl_id} records.
513 ** v - validate structure (not used)
514 ** action - action routine
515 ** actarg - action routine argument
518 ** - translate acl_ids to list names
522 grvd_support(q, sq, v, action, actarg)
524 struct save_queue *sq;
533 ## char shutdown[33];
540 ## range of l is list
542 while (sq_get_data(sq, &argv)) {
543 sscanf(argv[0], "%d", &list_id);
544 ## repeat retrieve (oper = l.name) where l.#list_id = @list_id
545 sscanf(argv[1], "%d", &list_id);
546 ## repeat retrieve (admin = l.name) where l.#list_id = @list_id
547 sscanf(argv[2], "%d", &list_id);
548 ## repeat retrieve (shutdown = l.name) where l.#list_id = @list_id
550 (*action)(3, targv, actarg);
561 ** set_next_object_id - set next object id in values table
563 ** Inputs: object - object name in values table
565 ** - called before an APPEND operation to set the next object id to
566 ** be used for the new record
570 set_next_object_id(object)
576 ## range of v is values
577 ## repeat replace v (value = v.value + 1) where v.#name = @name
582 ** get_query_need - check modtime of query's associated table against given
583 ** time and return true if greater (false if not)
586 ** argv[0] - query name
587 ** argv[1] - time to compare against
591 get_query_need(q, argv, action, actarg)
593 register char *argv[];
597 ## char *last_get_time;
601 struct query *get_query_by_name();
603 q1 = get_query_by_name(argv[0]);
605 last_get_time = argv[1];
608 if (q1->type != RETRIEVE || table == (char *)0) return(SMS_NO_MATCH);
610 ## range of tbs is tblstats
611 ## repeat retrieve (need = any(tbs.modtime where tbs.#table = @table and
612 ## tbs.modtime > @last_get_time))
614 result = (need) ? "true" : "false";
615 (*action)(1, &result, actarg);
621 ** add_locker - special query routine for creating a user locker
624 ** argv[0] - users_id
625 ** argv[1] - machine_id
627 ** argv[3] - initial quota
630 ** - get prefix directory (dir) for mount point on specified machine/device
631 ** - create filesys entry (label=<login>, type=NFS, machine=<machine>,
632 ** mount=<dir>/<login>, access=w, acl=dbadmin)
633 ** - increment allocated in nfsphys by quota
634 ** - create nfsquota entry
637 ** - SMS_NFSPHYS - machine/device does not exist in nfsphys
638 ** - SMS_FILESYS_EXISTS - file system already exists
643 register struct query *q;
659 users_id = *(int *)argv[0];
660 mach_id = *(int *)argv[1];
662 sscanf(argv[3], "%d", "a);
664 ## range of u is users
665 ## range of f is filesys
666 ## range of np is nfsphys
667 ## range of tbs is tblstats
670 ## repeat retrieve (login = u.#login) where u.#users_id = @users_id
672 /* get user's acl id */
673 ## repeat retrieve (user_acl = list.list_id) where list.name = @login
675 /* get filesystem directory prefix; give error if machine/device
676 pair not in nfsphys table */
677 printf("np.mach_id = %d and np.device = %s\n", mach_id, device);
679 ## repeat retrieve (dir = np.#dir, allocated = np.#allocated)
680 ## where np.#mach_id = @mach_id and np.#device = device
681 ## inquire_equel (rowcount = "rowcount")
682 if (rowcount == 0) return(SMS_NFSPHYS);
684 /* make sure a filesys with user's name does not already exist */
685 ## repeat retrieve (rowcount = any(f.label where f.label = @login))
686 if (rowcount != 0) return(SMS_FILESYS_EXISTS);
688 /* create a new filesys */
689 sprintf(locker, "%s/%s", dir, login);
690 sprintf(mount, "/mit/%s", login);
691 ## repeat append filesys
692 ## (#label = @login, type = "NFS", #mach_id = @mach_id,
693 ## name = @locker, access = "w", order = 1, #mount = @mount,
694 ## acl_id = @user_acl)
695 ## repeat replace tbs (appends = tbs.appends + 1, modtime = "now")
696 ## where tbs.table = "filesys"
698 /* increment usage count in nfsphys table */
700 ## replace np (#allocated = allocated)
701 ## where np.#mach_id = mach_id and np.#device = device
702 ## repeat replace tbs (updates = tbs.updates + 1, modtime = "now")
703 ## where tbs.table = "nfsphys"
705 /* create nfsquota entry */
706 ## append nfsquota (#users_id = users_id, #mach_id = mach_id,
707 ## #device = device, #quota = quota)
708 ## repeat replace tbs (appends = tbs.appends + 1, modtime = "now")
709 ## where tbs.table = "nfsquota"
715 ** delete_locker - special query routine for deleting a user locker
718 ** argv[0] - users_id
719 ** argv[1] - machine_id
724 ** - delete filesys entry (label=<login>)
725 ** - decrement allocated in nfsphys by quota
726 ** - delete nfsquota entry
729 ** - SMS_FILESYS - no filesys exists for user
733 delete_locker(q, argv)
734 register struct query *q;
735 register char *argv[];
745 users_id = *(int *)argv[0];
746 mach_id = *(int *)argv[1];
748 sscanf(argv[3], "%d", "a);
750 ## range of u is users
751 ## range of f is filesys
752 ## range of np is nfsphys
753 ## range of nq is nfsquota
754 ## range of tbs is tblstats
757 ## repeat retrieve (login = u.#login) where u.#users_id = @users_id
759 /* delete the filesys entry */
760 ## repeat delete f where f.label = @login
761 ## inquire_equel (rowcount = "rowcount")
762 if (rowcount == 0) return(SMS_FILESYS);
763 ## repeat replace tbs (deletes = tbs.deletes + 1, modtime = "now")
764 ## where tbs.table = "filesys"
766 /* decrement usage count in nfsphys table */
767 ## replace np (#allocated = np.#allocated - quota)
768 ## where np.#mach_id = mach_id and np.#device = device
769 ## repeat replace tbs (updates = tbs.updates + 1, modtime = "now")
770 ## where tbs.table = "nfsphys"
772 /* delete nfsquota entry */
773 ## delete nq where nq.#users_id = users_id and nq.#mach_id = mach_id and
774 ## nq.#device = device
775 ## repeat replace tbs (deletes = tbs.deletes + 1, modtime = "now")
776 ## where tbs.table = "nfsquota"
782 ** get_members_of_list - optimized query for retrieval of list members
788 ** - retrieve USER members, then LIST members, then STRING members
792 get_members_of_list(q, argv, action, actarg)
799 ## char member_name[129];
802 list_id = *(int *)argv[0];
804 targv[1] = member_name;
806 ## range of m is members
807 ## repeat retrieve (member_name = users.login)
808 ## where m.#list_id = @list_id and m.member_type = "USER"
809 ## and m.member_id = users.users_id
811 (*action)(2, targv, actarg);
815 ## repeat retrieve (member_name = list.name)
816 ## where m.#list_id = @list_id and m.member_type = "LIST"
817 ## and m.member_id = list.#list_id
819 (*action)(2, targv, actarg);
823 ## repeat retrieve (member_name = strings.string)
824 ## where m.#list_id = @list_id and m.member_type = "STRING"
825 ## and m.member_id = strings.string_id
827 (*action)(2, targv, actarg);
834 ** get_all_poboxes - optimize query for retrieval of all poboxes
837 ** - retrieve LOCAL boxes, then POP boxes, then FOREIGN boxes
841 get_all_poboxes(q, argv, action, actarg)
848 ## char machine[129];
857 ## range of p is pobox
858 ## repeat retrieve (login=users.#login, machine = #machine.name, box=p.#box)
859 ## where p.type = "LOCAL" and p.users_id = users.users_id
860 ## and p.mach_id = #machine.mach_id
862 (*action)(4, targv, actarg);
866 ## repeat retrieve (login=users.#login, machine = #machine.name, box=p.#box)
867 ## where p.type = "POP" and p.users_id = users.users_id
868 ## and p.mach_id = #machine.mach_id
870 (*action)(4, targv, actarg);
873 targv[1] = "FOREIGN";
874 ## repeat retrieve (login=users.#login, machine=strings.string, box=p.#box)
875 ## where p.type = "FOREIGN" and p.users_id = users.users_id
876 ## and p.mach_id = strings.string_id
878 (*action)(4, targv, actarg);
884 /* Validation Routines */
886 validate_row(q, argv, v)
887 register struct query *q;
889 register struct validate *v;
897 /* build where clause */
898 build_qual(v->qual, v->argc, argv, qual);
900 /* setup ingres variables */
905 if (log_flags & LOG_RES)
906 /* tell the logfile what we're doing */
907 com_err(whoami, 0, "validating row: %s", qual);
909 /* look for the record */
910 ## range of rvar is table
911 ## retrieve (rowcount = count(rvar.name where qual))
912 if (rowcount == 0) return(SMS_NO_MATCH);
913 if (rowcount > 1) return(SMS_NOT_UNIQUE);
917 validate_fields(q, argv, vo, n)
919 register char *argv[];
920 register struct valobj *vo;
929 if (log_flags&LOG_RES)
930 com_err(whoami, 0, "validating %s in %s: %s",
931 vo->namefield, vo->table, argv[vo->index]);
932 status = validate_name(argv, vo);
936 if (log_flags&LOG_RES)
937 com_err(whoami, 0, "validating %s in %s: %s",
938 vo->idfield, vo->table, argv[vo->index]);
939 status = validate_id(argv, vo);
943 if (log_flags&LOG_RES)
944 com_err(whoami, 0, "validating %s type: %s",
945 vo->table, argv[vo->index]);
946 status = validate_type(argv, vo);
950 if (log_flags&LOG_RES)
951 com_err(whoami, 0, "validating type-specific data: %s",
953 status = validate_typedata(q, argv, vo);
962 if (status != SMS_EXISTS) return(status);
969 validate_id(argv, vo)
971 register struct valobj *vo;
980 name = argv[vo->index];
982 namefield = vo->namefield;
983 idfield = vo->idfield;
984 ## retrieve (id = table.idfield) where table.namefield = name
985 ## inquire_equel (rowcount = "rowcount")
986 if (rowcount != 1) return(vo->error);
987 *(int *)argv[vo->index] = id;
991 validate_name(argv, vo)
993 register struct valobj *vo;
1000 name = argv[vo->index];
1002 namefield = vo->namefield;
1003 ## retrieve (rowcount = countu(table.namefield
1004 ## where table.namefield = name))
1005 return ((rowcount == 1) ? SMS_EXISTS : vo->error);
1008 validate_type(argv, vo)
1010 register struct valobj *vo;
1017 typename = vo->table;
1018 value = argv[vo->index];
1020 /* uppercase type fields */
1021 for (c = value; *c; c++) if (islower(*c)) *c = toupper(*c);
1023 ## range of a is alias
1024 ## repeat retrieve (rowcount = count(a.trans where a.name = @typename and
1025 ## a.type = "TYPE" and
1026 ## a.trans = @value))
1027 return ((rowcount == 1) ? SMS_EXISTS : vo->error);
1030 /* validate member or type-specific data field */
1032 validate_typedata(q, argv, vo)
1033 register struct query *q;
1034 register char *argv[];
1035 register struct valobj *vo;
1038 ## char *field_type;
1039 ## char data_type[17];
1044 /* get named object */
1045 name = argv[vo->index];
1047 /* get field type string (known to be at index-1) */
1048 field_type = argv[vo->index-1];
1050 /* get corresponding data type associated with field type name */
1051 ## repeat retrieve (data_type = alias.trans)
1052 ## where alias.#name = @field_type and alias.type = "TYPEDATA"
1053 ## inquire_equel (rowcount = "rowcount")
1054 if (rowcount != 1) return(SMS_TYPE);
1056 /* now retrieve the record id corresponding to the named object */
1058 if (!strcmp(data_type, "user")) {
1060 ## repeat retrieve (id = users.users_id) where users.login = @name
1061 ## inquire_equel (rowcount = "rowcount")
1062 if (rowcount != 1) return(SMS_USER);
1064 } else if (!strcmp(data_type, "list")) {
1066 ## repeat retrieve (id = list.list_id) where list.#name = @name
1067 ## inquire_equel (rowcount = "rowcount")
1068 if (rowcount != 1) return(SMS_LIST);
1070 } else if (!strcmp(data_type, "machine")) {
1072 ## repeat retrieve (id = machine.mach_id) where machine.#name = @name
1073 ## inquire_equel (rowcount = "rowcount")
1074 if (rowcount != 1) return(SMS_MACHINE);
1076 } else if (!strcmp(data_type, "string")) {
1078 ## range of s is strings
1079 ## repeat retrieve (id = s.string_id, refc = s.#refc)
1080 ## where s.string = @name
1081 ## inquire_equel (rowcount = "rowcount")
1082 if (rowcount == 0) {
1083 if (q->type != APPEND) return(SMS_STRING);
1084 ## range of v is values
1085 ## retrieve (id = v.value) where v.#name = "strings_id"
1087 ## replace v (value = id) where v.#name = "strings_id"
1088 ## append to strings (string_id = id, string = name, #refc = 1)
1089 } else if (rowcount == 1) {
1090 if (q->type == APPEND || q->type == DELETE) {
1091 refc += (q->type == APPEND) ? 1 : -1;
1093 ## replace s (#refc = refc) where s.string_id = id
1095 ## delete s where s.string_id = id
1103 /* now set value in argv */
1104 *(int *)argv[vo->index] = id;
1106 return (SMS_EXISTS);
1110 translate_ids(q, sq, v, action, actarg)
1111 register struct query *q;
1112 register struct save_queue *sq;
1113 register struct validate *v;
1114 register int (*action)();
1118 ## char *field_type;
1119 ## char data_type[17];
1126 for (i = 0; i < v->objcnt; i++) {
1128 if (vo->type == V_FOLLOWUP) break;
1132 while (sq_get_data(sq, &argv)) {
1136 sscanf(argv[i], "%d", &id);
1138 name = (char *)malloc(129);
1141 /* get field type string (known to be at index-1) */
1142 field_type = argv[vo->index-1];
1144 /* get corresponding data type associated with field type name */
1145 ## repeat retrieve (data_type = alias.trans)
1146 ## where alias.#name = @field_type and alias.type = "TYPEDATA"
1147 ## inquire_equel (rowcount = "rowcount")
1148 if (rowcount != 1) {
1149 sprintf(name, "%d", id);
1150 (*action)(q->vcnt, argv, actarg);
1154 /* retrieve object name */
1156 if (!strcmp(data_type, "user")) {
1158 ## repeat retrieve (name = users.login) where users.users_id = @id
1159 ## inquire_equel (rowcount = "rowcount")
1161 } else if (!strcmp(data_type, "list")) {
1163 ## repeat retrieve (name = list.#name) where list.list_id = @id
1164 ## inquire_equel (rowcount = "rowcount")
1166 } else if (!strcmp(data_type, "machine")) {
1168 ## repeat retrieve (name = machine.#name) where machine.mach_id = @id
1169 ## inquire_equel (rowcount = "rowcount")
1171 } else if (!strcmp(data_type, "string")) {
1173 ## repeat retrieve (name = strings.string)
1174 ## where strings.string_id = @id
1175 ## inquire_equel (rowcount = "rowcount")
1181 /* if there wasn't a corresponding object name, then use the id */
1182 if (rowcount != 1) sprintf(name, "%d", id);
1185 (*action)(q->vcnt, argv, actarg);
1187 /* free saved data */
1188 for (i = 0; i < q->vcnt; i++)
1194 return (SMS_SUCCESS);
1201 * c-continued-statement-offset: 4
1202 * c-brace-offset: -4
1203 * c-argdecl-indent: 4
1204 * c-label-offset: -4