]> andersk Git - moira.git/blob - server/qrtn.qc
ec7a0818ec4b451e8ac7160c1d60c9496168d58f
[moira.git] / server / qrtn.qc
1 /*
2  *      $Source$
3  *      $Author$
4  *      $Header$
5  *
6  *      Copyright (C) 1987, 1988 by the Massachusetts Institute of Technology
7  *      For copying and distribution information, please see the file
8  *      <mit-copyright.h>.
9  * 
10  */
11
12 #ifndef lint
13 static char *rcsid_qrtn_qc = "$Header$";
14 #endif lint
15
16 #include <mit-copyright.h>
17 #include "query.h"
18 #include "sms_server.h"
19
20 char *Argv[16];
21
22 int ingres_errno = 0;
23 int sms_errcode = 0;
24 ## int query_timeout = 30;
25 extern char *whoami;
26 extern FILE *journal;
27
28 #define INGRES_BAD_INT 4111
29 #define INGRES_BAD_DATE 4302
30 #define INGRES_DEADLOCK 4700
31 #define INGRES_TIMEOUT 4702
32
33 /*
34  * ingerr: (supposedly) called when Ingres indicates an error.
35  * I have not yet been able to get this to work to intercept a
36  * database open error.
37  */
38
39 static int ingerr(num)
40     int *num;
41 {
42     ingres_errno = *num;
43
44     switch (*num) {
45     case INGRES_BAD_INT:
46         sms_errcode = SMS_INTEGER;
47         break;
48     case INGRES_BAD_DATE:
49         sms_errcode = SMS_DATE;
50         break;
51     case INGRES_DEADLOCK:
52         sms_errcode = SMS_DEADLOCK;
53         com_err(whoami, 0, "INGRES deadlock detected");
54         break;
55     case INGRES_TIMEOUT:
56         sms_errcode = SMS_BUSY;
57         com_err(whoami, 0, "timed out getting lock");
58         break;
59     default:
60         sms_errcode = SMS_INGRES_ERR;
61         com_err(whoami, SMS_INGRES_ERR, " code %d\n", *num);
62         critical_alert("SMS", "SMS server encountered INGRES ERROR %d", *num);
63         return (*num);
64     }
65     return (0);
66 }
67
68 int sms_open_database()
69 {
70     register int i;
71     char *malloc();
72
73     /* initialize local argv */
74     for (i = 0; i < 16; i++)
75         Argv[i] = malloc(ARGLEN);
76
77     IIseterr(ingerr);
78         
79     ingres_errno = 0;
80         
81     /* open the database */
82 ##  ingres sms
83 ##  set lockmode session where level = table, timeout = query_timeout
84 ##  set lockmode on capacls where readlock = shared
85 ##  set lockmode on alias where readlock = shared
86     return ingres_errno;
87 }
88
89 int sms_close_database()
90 {
91 ##  exit
92 }
93
94 sms_check_access(cl, name, argc, argv_ro)
95     client *cl;
96     char *name;
97     int argc;
98     char *argv_ro[];
99 {
100     struct query *q;
101     struct query *get_query_by_name();
102
103     ingres_errno = 0;
104
105     q = get_query_by_name(name, cl->args->sms_version_no);
106     if (q == (struct query *)0)
107         return(SMS_NO_HANDLE);
108
109     return(sms_verify_query(cl, q, argc, argv_ro));
110 }
111
112 sms_process_query(cl, name, argc, argv_ro, action, actarg)
113     client *cl;
114     char *name;
115     int argc;
116     char *argv_ro[];
117     int (*action)();
118     char *actarg;
119 {
120     register struct query *q;
121     register int status;
122     register struct validate *v;
123     char qual[256];
124     char sort[32];
125     char *pqual;
126     char *psort;
127 ##  char *table;
128     struct save_queue *sq;
129     struct query *get_query_by_name();
130     int sq_save_args();
131     struct save_queue *sq_create();
132     char *build_sort();
133
134     ingres_errno = 0;
135
136     /* list queries command */
137     if (!strcmp(name, "_list_queries")) {
138         list_queries(cl->args->sms_version_no, action, actarg);
139         return(SMS_SUCCESS);
140     }
141
142     /* help query command */
143     if (!strcmp(name, "_help")) {
144         if (argc < 1)
145             return(SMS_ARGS);
146         q = get_query_by_name(argv_ro[0], cl->args->sms_version_no);
147         if (q == (struct query *)0) return(SMS_NO_HANDLE);
148         help_query(q, action, actarg);
149         return(SMS_SUCCESS);
150     }
151
152     /* get query structure, return error if named query does not exist */
153     q = get_query_by_name(name, cl->args->sms_version_no);
154     if (q == (struct query *)0) return(SMS_NO_HANDLE);
155     v = q->validate;
156
157     if (q->type != RETRIEVE) {
158 ##      set lockmode session where readlock = exclusive
159 ##      begin transaction
160     }
161
162     /* setup argument vector, verify access and arguments */
163     if ((status = sms_verify_query(cl, q, argc, argv_ro)) != SMS_SUCCESS)
164         goto out;
165
166     /* perform any special query pre-processing */
167     if (v && v->pre_rtn) {
168         status = (*v->pre_rtn)(q, Argv, cl, 0);
169         if (status != SMS_SUCCESS)
170             goto out;
171     }
172
173     switch (q->type) {
174     case RETRIEVE:
175         /* for queries that do not permit wildcarding, check if row
176            uniquely exists */
177         if (v && v->field) {
178             status = validate_row(q, Argv, v);
179             if (status != SMS_EXISTS) break;
180         }
181
182         /* build "where" clause if needed */
183         if (q->qual) {
184             build_qual(q->qual, q->argc, Argv, qual);
185             pqual = qual;
186         } else {
187             pqual = 0;
188         }
189
190         /* build "sort" clause if needed */
191         if (v && v->valobj) {
192             psort = build_sort(v, sort);
193         } else {
194             psort = 0;
195         }
196
197         /* if there is a followup routine, then we must save the results */
198         /* of the first query for use by the followup routine */
199         /* if q->rvar = NULL, perform post_rtn only */
200         if (q->rvar) {
201             if (v && v->post_rtn) {
202                 sq = sq_create();
203                 status = do_retrieve(q, pqual, psort, sq_save_args, sq);
204                 if (status != SMS_SUCCESS) {
205                     sq_destroy(sq);
206                     break;
207                 }
208                 status = (*v->post_rtn)(q, sq, v, action, actarg, cl);
209             } else {
210                 /* normal retrieve */
211                 status = do_retrieve(q, pqual, psort, action, actarg);
212             }
213             if (status != SMS_SUCCESS) break;
214         } else {
215             status = (*v->post_rtn)(q, Argv, cl, action, actarg);
216         }
217
218         break;
219
220     case UPDATE:
221         /* see if row already exists */
222         if (v->field) {
223             status = validate_row(q, Argv, v);
224             if (status != SMS_EXISTS) break;
225         }
226
227         /* build "where" clause and perform update */
228         /* if q->rvar = NULL, perform post_rtn only */
229         if (q->rvar) {
230             build_qual(q->qual, q->argc, Argv, qual);
231             status = do_update(q, &Argv[q->argc], qual, action, actarg);
232             if (status != SMS_SUCCESS) break;
233             table = q->rtable;
234             if (strcmp(q->shortname, "sshi") && strcmp(q->shortname, "ssif")) {
235 ##              repeat replace tblstats (updates = tblstats.updates + 1,
236 ##                                       modtime = "now")
237 ##                  where tblstats.#table = @table
238             }
239         }
240
241         /* execute followup routine (if any) */
242         if (v->post_rtn) status = (*v->post_rtn)(q, Argv, cl);
243
244         break;
245
246     case APPEND:
247         /* see if row already exists */
248         if (v->field) {
249             status = validate_row(q, Argv, v);
250             if (status != SMS_NO_MATCH) break;
251         }
252
253         /* increment id number if necessary */
254         if (v->object_id) {
255             status = set_next_object_id(v->object_id, q->rtable);
256             if (status != SMS_SUCCESS) break;
257         }
258
259         /* build "where" clause if needed */
260         if (q->qual) {
261             build_qual(q->qual, q->argc, Argv, qual);
262             pqual = qual;
263         } else {
264             pqual = 0;
265         }
266
267         /* perform the append */
268         /* if q->rvar = NULL, perform post_rtn only */
269         if (q->rvar) {
270             status = do_append(q, &Argv[q->argc], pqual, action, actarg);
271             if (status != SMS_SUCCESS) break;
272             table = q->rtable;
273 ##          repeat replace tblstats (appends = tblstats.appends + 1,
274 ##                                   modtime = "now")
275 ##              where tblstats.#table = @table
276         }
277         
278         /* execute followup routine */
279         if (v->post_rtn) status = (*v->post_rtn)(q, Argv, cl);
280         break;
281
282     case DELETE:
283         /* see if row already exists */
284         if (v->field) {
285             status = validate_row(q, Argv, v);
286             if (status != SMS_EXISTS) break;
287         }
288
289         /* build "where" clause and perform delete */
290         /* if q->rvar = NULL, perform post_rtn only */
291         if (q->rvar) {
292             build_qual(q->qual, q->argc, Argv, qual);
293             status = do_delete(q, qual, action, actarg);
294             if (status != SMS_SUCCESS) break;
295             table = q->rtable;
296 ##          repeat replace tblstats (deletes = tblstats.deletes + 1,
297 ##                                   modtime = "now")
298 ##              where tblstats.#table = @table
299         }
300
301         /* execute followup routine */
302         if (v->post_rtn) status = (*v->post_rtn)(q, Argv, cl);
303         break;
304
305     }
306
307 out:
308     if (q->type != RETRIEVE) {
309         if (status == SMS_SUCCESS) {
310 ##          end transaction     /* commit to this */
311             if (journal) {
312                 char buf[1024], *bp;
313                 int i;
314                 extern time_t now;
315
316                 fprintf(journal, "%% %s %s %s",
317                         cl->clname, cl->entity, ctime(&now));
318                 fprintf(journal, "%s[%d] ", q->name, cl->args->sms_version_no);
319                 for (i = 0; i < argc; i++) {
320                     if (i != 0) {
321                         putc(' ', journal);
322                     }
323                     requote(buf, argv_ro[i], sizeof(buf));
324                     fputs(buf, journal);
325                 }
326                 putc('\n', journal);
327                 fflush(journal);
328             }
329         } else {
330             if (ingres_errno != INGRES_DEADLOCK) {
331 ##              abort           /* it never happened */
332             }
333         }
334 ##      set lockmode session where readlock = system
335     }
336
337     if (status == SMS_SUCCESS && ingres_errno != 0) {
338         critical_alert("SMS", "Server didn't notice INGRES ERROR %d",
339                        ingres_errno);
340         status = SMS_INTERNAL;
341     }
342
343     if (status != SMS_SUCCESS && log_flags & LOG_RES)
344         com_err(whoami, status, " (Query failed)");
345     /* until all the clients know about SMS_BUSY, convert this error
346      * to one they will recognize.
347      */
348     if (status == SMS_BUSY)
349       status = SMS_DEADLOCK;
350     return(status);
351 }
352
353 build_qual(fmt, argc, argv, qual)
354         char *fmt;
355         int argc;
356         char *argv[];
357         char *qual;
358 {
359     register char *c;
360     register int i;
361     char *args[4];
362     char *index();
363
364     c = fmt;
365     for (i = 0; i < argc; i++) {
366         c = index(c, '%');
367         if (c++ == (char *)0) return(SMS_ARGS);
368         if (*c == 's')
369             args[i] = argv[i];
370         else if (*c == 'd')
371             *(int *)&args[i] = *(int *)argv[i]; /* sigh */
372         else
373             return(SMS_INGRES_ERR);
374     }
375
376     switch (argc) {
377     case 0:
378         strcpy(qual, fmt);
379         break;
380
381     case 1:
382         sprintf(qual, fmt, args[0]);
383         break;
384
385     case 2:
386         sprintf(qual, fmt, args[0], args[1]);
387         break;
388
389     case 3:
390         sprintf(qual, fmt, args[0], args[1], args[2]);
391         break;
392
393     case 4:
394         sprintf(qual, fmt, args[0], args[1], args[2], args[3]);
395         break;
396     }
397     return(SMS_SUCCESS);
398 }
399
400 char *
401 build_sort(v, sort)
402     register struct validate *v;
403     char *sort;
404 {
405     register struct valobj *vo;
406     register int n;
407     char elem[16];
408
409     n = v->objcnt;
410     vo = v->valobj;
411     *sort = 0;
412
413     while (--n >= 0) {
414         if (vo->type == V_SORT) {
415             sprintf(elem, "RET_VAR%d", vo->index + 1);
416             if (*sort) strcat(sort, ", ");
417             strcat(sort, elem);
418         }
419         vo++;
420     }
421
422     return ((*sort) ? sort : 0);
423 }
424
425
426 /* Build arguement vector, verify query and arguments */
427
428 sms_verify_query(cl, q, argc, argv_ro)
429     client *cl;
430     struct query *q;
431     int argc;
432     char *argv_ro[];
433 {
434     register int argreq;
435     register int status;
436     register struct validate *v = q->validate;
437     register int i;
438     register int privileged = 0;
439     int len;
440
441     /* copy the arguments into a local argv that we can modify */
442     if (argc >= QMAXARGS)
443       return(SMS_ARGS);
444     for (i = 0; i < argc; i++) {
445         if ((len = strlen(argv_ro[i])) < ARGLEN)
446             strcpy(Argv[i], argv_ro[i]);
447         else
448             return(SMS_ARG_TOO_LONG);
449         if (Argv[i][len-1] == '\\')
450           return(SMS_BAD_CHAR);
451     }
452
453     /* check initial query access */
454     status = check_query_access(q, Argv, cl);
455     if (status != SMS_SUCCESS && status != SMS_PERM)
456         return(status);
457     if (status == SMS_SUCCESS)
458         privileged++;
459
460     /* check argument count */
461     argreq = q->argc;
462     if (q->type == UPDATE || q->type == APPEND) argreq += q->vcnt;
463     if (argc != argreq) return(SMS_ARGS);
464
465     /* validate arguments */
466     if (v && v->valobj) {
467         status = validate_fields(q, Argv, v->valobj, v->objcnt);
468         if (status != SMS_SUCCESS) return(status);
469     }
470
471     /* perform special query access check */
472     if (!privileged && v && v->acs_rtn) {
473         status = (*v->acs_rtn)(q, Argv, cl);
474         if (status != SMS_SUCCESS && status != SMS_PERM)
475             return(status);
476         if (status == SMS_SUCCESS)
477             privileged++;
478     }
479
480     return(privileged ? SMS_SUCCESS : SMS_PERM);
481 }
482
483 check_query_access(q, argv, cl)
484     struct query *q;
485     char *argv[];
486     client *cl;
487 ##{
488 ##  char *name;
489 ##  int acl_id;
490 ##  int exists;
491 ##  int rowcount;
492 ##  int errorno;
493 ##  static int def_uid;
494     int status;
495     int client_id;
496     char *client_type;
497
498     /* get query access control list */
499     name = q->shortname;
500 ##  repeat retrieve (acl_id = capacls.list_id) where capacls.tag = @name
501 ##  inquire_equel (rowcount = "rowcount", errorno = "errorno")
502     if (errorno != 0) return(SMS_INGRES_ERR);
503     if (rowcount == 0) return(SMS_PERM);
504
505     /* initialize default uid */
506     if (def_uid == 0) {
507 ##      retrieve (def_uid = users.users_id) where users.login = "default"
508     }
509
510     /* check for default access */
511 ##  range of m is imembers
512 ##  repeat retrieve (exists = any(m.#member_id where m.list_id = @acl_id and
513 ##                   m.member_type = "USER" and m.#member_id = def_uid))
514     if (exists) return(SMS_SUCCESS);
515
516     /* parse client name */
517     status = get_client(cl, &client_type, &client_id);
518     if (status != SMS_SUCCESS) return(status);
519
520     /* see if client is in the list (or any of its sub-lists) */
521     exists = find_member("LIST", acl_id, client_type, client_id, 0);
522     return ((exists) ? SMS_SUCCESS : SMS_PERM);
523 ##}
524
525 get_client(cl, client_type, client_id)
526     client *cl;
527     char **client_type;
528     int *client_id;
529 ##{
530     struct krbname *krb;
531 ##  int member_id;
532 ##  char *name;
533 ##  int rowcount;
534
535     if (cl->clname == NULL)
536         return SMS_PERM;
537     
538     /* for now ignore instances */
539     krb = &cl->kname;
540
541     /* if client is from local realm, get users_id */
542     if (!strcmp(krb->realm, krb_realm)) {
543         *client_id = cl->users_id;
544         *client_type = "USER";
545         return(SMS_SUCCESS);
546     }
547
548     /* otherwise use string_id */
549     name = cl->clname;
550 ##  repeat retrieve (member_id = strings.string_id) 
551 ##      where strings.string = @name
552         
553     /* make sure we found a users or string id */
554 ##  inquire_equel (rowcount = "rowcount")
555     if (rowcount == 0) return(SMS_PERM);
556
557     *client_type = "STRING";
558     *client_id = member_id;
559     return(SMS_SUCCESS);
560 ##}
561
562 ##find_member(list_type, list_id, member_type, member_id)
563     char *list_type;
564 ##  int list_id;
565 ##  char *member_type;
566 ##  int member_id;
567 ##{
568 ##  int exists, errorno;
569
570     if (!strcmp(strtrim(list_type), strtrim(member_type)) &&
571         list_id == member_id)
572         return(1);
573
574     /* see if client is a direct member of list */
575 ##  range of m is imembers
576 ##  repeat retrieve (exists = any(m.#member_id where 
577 ##                                m.#list_id = @list_id and
578 ##                                m.#member_type = @member_type and 
579 ##                                m.#member_id = @member_id))
580 ##  inquire_equel(errorno = "errorno")
581     if (errorno == 0)
582       return(exists);
583     else
584       return(0);
585 ##}
586
587
588 do_retrieve(q, pqual, psort, action, actarg)
589     register struct query *q;
590     char *pqual;
591     char *psort;
592     int (*action)();
593     char *actarg;
594 ##{
595 ##  char *rvar;
596 ##  char *rtable;
597 ##  char *cqual;
598 ##  char *csort;
599 ##  int rowcount;
600 ##  int errorno;
601     static char **vaddrs = (char **)NULL;
602
603     if (!vaddrs) {
604         register int i;
605
606         if ((vaddrs = (char **)malloc(sizeof(char *) * QMAXARGS)) == NULL) {
607             com_err(whoami, SMS_NO_MEM, "setting up static argv");
608             exit(1);
609         }
610         for (i = 0; i < QMAXARGS; i++) {
611             if ((vaddrs[i] = malloc(QMAXARGSIZE)) == NULL) {
612                 com_err(whoami, SMS_NO_MEM, "setting up static argv");
613                 exit(1);
614             }
615         }
616     }
617
618     if (q->rvar) {
619         rvar = q->rvar;
620         rtable = q->rtable;
621 ##      range of rvar is rtable
622     }
623
624     if (psort) {
625         csort = psort;
626         if (pqual) {
627             cqual = pqual;
628 ##          retrieve unique (param (q->tlist, vaddrs)) where cqual
629 ##                   sort by csort
630 ##          {
631                  (*action)(q->vcnt, vaddrs, actarg);
632 ##          }
633         } else {
634 ##          retrieve unique (param (q->tlist, vaddrs))
635 ##                   sort by csort
636 ##          {
637                  (*action)(q->vcnt, vaddrs, actarg);
638 ##          }
639         }
640
641     } else {
642         if (pqual) {
643             cqual = pqual;
644 ##          retrieve unique (param (q->tlist, vaddrs)) where cqual
645 ##          {
646                  (*action)(q->vcnt, vaddrs, actarg);
647 ##          }
648         } else {
649 ##          retrieve unique (param (q->tlist, vaddrs))
650 ##          {
651                  (*action)(q->vcnt, vaddrs, actarg);
652 ##          }
653         }
654     }
655
656 ##  inquire_equel (rowcount = "rowcount", errorno = "errorno")
657     if (errorno != 0) return(SMS_INGRES_ERR);
658     return ((rowcount == 0) ? SMS_NO_MATCH : SMS_SUCCESS);
659 ##}
660
661 do_update(q, argv, qual, action, actarg)
662     register struct query *q;
663     char *argv[];
664     char *qual;
665     int (*action)();
666     char *actarg;
667 ##{
668 ##  char *rvar;
669 ##  char *rtable;
670 ##  char *cqual;
671 ##  int errorno;
672       
673     rvar = q->rvar;
674     rtable = q->rtable;
675 ##  range of rvar is rtable
676
677     cqual = qual;
678 ##  replace rvar (param (q->tlist, argv))
679 ##  where cqual
680
681 ##  inquire_equel (errorno = "errorno")
682     if (errorno == INGRES_BAD_INT)
683         return(SMS_INTEGER);
684     else if (errorno != 0)
685         return(SMS_INGRES_ERR);
686     return(SMS_SUCCESS);
687 ##}
688
689 do_append(q, argv, pqual, action, actarg)
690     register struct query *q;
691     char *argv[];
692     char *pqual;
693     int (*action)();
694     char *actarg;
695 ##{
696 ##  char *rvar;
697 ##  char *rtable;
698 ##  char *cqual;
699 ##  int errorno;
700
701     rvar = q->rvar;
702     rtable = q->rtable;
703 ##  range of rvar is rtable
704
705     if (pqual) {
706         cqual = pqual;
707 ##      append to rtable (param (q->tlist, argv)) where cqual
708     } else {
709 ##      append to rtable (param (q->tlist, argv))
710     }
711
712 ##  inquire_equel (errorno = "errorno")
713     if (errorno == INGRES_BAD_INT)
714         return(SMS_INTEGER);
715     else if (errorno != 0)
716         return(SMS_INGRES_ERR);
717     return(SMS_SUCCESS);
718 ##}
719
720 do_delete(q, qual, action, actarg)
721     register struct query *q;
722     char *qual;
723     int (*action)();
724     char *actarg;
725 ##{
726 ##  char *rvar;
727 ##  char *rtable;
728 ##  char *cqual;
729 ##  int errorno;
730
731     rvar = q->rvar;
732     rtable = q->rtable;
733 ##  range of rvar is rtable
734
735     cqual = qual;
736 ##  delete rvar where cqual
737
738 ##  inquire_equel (errorno = "errorno")
739     if (errorno != 0) return(SMS_INGRES_ERR);
740     return(SMS_SUCCESS);
741 ##}
742
743
744 /**
745  ** set_next_object_id - set next object id in values table
746  **
747  ** Inputs: object - object name in values table and in objects
748  **         table - name of table objects are found in
749  **
750  ** - called before an APPEND operation to set the next object id to
751  **   be used for the new record to the next free value
752  **
753  **/
754
755 set_next_object_id(object, table)
756     char *object;
757     char *table;
758 ##{
759 ##  char *name, *tbl;
760 ##  int rowcount, exists, value;
761
762     name = object;
763     tbl = table;
764 ##  range of v is values
765 ##  repeat retrieve (value = v.#value) where v.#name = @name
766 ##  inquire_equel(rowcount = "rowcount")
767     if (rowcount != 1)
768         return(SMS_NO_ID);
769
770 ##  retrieve (exists = any(tbl.name where tbl.name = value))
771 ##  inquire_equel(rowcount = "rowcount")
772     if (rowcount != 1)
773         return(SMS_NO_ID);
774     while (exists) {
775         value++;
776         if (value > MAX_ID_VALUE)
777             value = MIN_ID_VALUE;
778 ##      retrieve (exists = any(tbl.name where tbl.name = value))
779     }
780
781     if (LOG_RES)
782         com_err(whoami, 0, "setting ID %s to %d", name, value);
783 ##  repeat replace v (#value = @value) where v.#name = @name
784     return(SMS_SUCCESS);
785 ##}
786
787
788 /* For now this just checks the argc's.  It should also see that there
789  * are no duplicate names.
790  */
791
792 sanity_check_queries()
793 {
794     register int i;
795     int maxv = 0, maxa = 0;
796 #ifdef MULTIPROTOCOLS
797     extern int QueryCount1, QueryCount2;
798     extern struct query Queries1[], Queries2[];
799 #else
800     extern int QueryCount2;
801     extern struct query Queries2[];
802 #endif MULTIPROTOCOLS
803
804 #define MAX(x,y) ((x) > (y) ? (x) : (y))
805
806 #ifdef MULTIPROTOCOLS
807     for (i = 0; i < QueryCount1; i++) {
808         maxv = MAX(maxv, Queries1[i].vcnt);
809         maxa = MAX(maxa, Queries1[i].argc);
810     }
811 #endif MULTIPROTOCOLS
812     for (i = 0; i < QueryCount2; i++) {
813         maxv = MAX(maxv, Queries2[i].vcnt);
814         maxa = MAX(maxa, Queries2[i].argc);
815     }
816     if (MAX(maxv, maxa) > QMAXARGS) {
817         com_err(whoami, 0, "A query has more args than QMAXARGS");
818         exit(1);
819     }
820 }
This page took 0.086675 seconds and 3 git commands to generate.