1 /******************************************************************************
2   Copyright (c) 1995, 1996 Xerox Corporation.  All rights reserved.
3   Portions of this code were written by Stephen White, aka ghond.
4   Use and copying of this software and preparation of derivative works based
5   upon this software are permitted.  Any distribution of this software or
6   derivative works must comply with all applicable United States export
7   control laws.  This software is made available AS IS, and Xerox Corporation
8   makes no warranty about the software, its performance or its conformity to
9   any specification.  Any person obtaining a copy of this software is requested
10   to send their name and post office or electronic mail address to:
11     Pavel Curtis
12     Xerox PARC
13     3333 Coyote Hill Rd.
14     Palo Alto, CA 94304
15     Pavel@Xerox.Com
16  *****************************************************************************/
17 
18 /*****************************************************************************
19  * Routines for manipulating verbs on DB objects
20  *****************************************************************************/
21 
22 #include <ctype.h>
23 #include "my-stdlib.h"
24 #include "my-string.h"
25 
26 #include "config.h"
27 #include "db.h"
28 #include "db_private.h"
29 #include "db_tune.h"
30 #include "list.h"
31 #include "log.h"
32 #include "parse_cmd.h"
33 #include "program.h"
34 #include "storage.h"
35 #include "utils.h"
36 
37 
38 /*********** Prepositions ***********/
39 
40 #define MAXPPHRASE 3		/* max. number of words in a prepositional phrase */
41 
42 /* NOTE: New prepositional phrases should only be added to this list at the
43  * end, never in the middle, and no entries should every be removed from this
44  * list; the list indices are stored raw in the DB file.
45  */
46 static const char *prep_list[] =
47 {
48     "with/using",
49     "at/to",
50     "in front of",
51     "in/inside/into",
52     "on top of/on/onto/upon",
53     "out of/from inside/from",
54     "over",
55     "through",
56     "under/underneath/beneath",
57     "behind",
58     "beside",
59     "for/about",
60     "is",
61     "as",
62     "off/off of",
63 };
64 
65 #define NPREPS Arraysize(prep_list)
66 
67 typedef struct pt_entry {
68     int nwords;
69     char *words[MAXPPHRASE];
70     struct pt_entry *next;
71 } pt_entry;
72 
73 struct pt_entry *prep_table[NPREPS];
74 
75 void
dbpriv_build_prep_table(void)76 dbpriv_build_prep_table(void)
77 {
78     int i, j;
79     int nwords;
80     char **words;
81     char cprep[100];
82     const char *p;
83     char *t;
84     pt_entry *current_alias, **prev;
85 
86     for (i = 0; i < NPREPS; i++) {
87 	p = prep_list[i];
88 	prev = &prep_table[i];
89 	while (*p) {
90 	    t = cprep;
91 	    if (*p == '/')
92 		p++;
93 	    while (*p && *p != '/')
94 		*t++ = *p++;
95 	    *t = '\0';
96 
97 	    /* This call to PARSE_INTO_WORDS() is the reason that this function
98 	     * is called from DB_INITIALIZE() instead of from the first call to
99 	     * DB_FIND_PREP(), below.  You see, PARSE_INTO_WORDS() isn't
100 	     * re-entrant, and DB_FIND_PREP() is called from code that's still
101 	     * using the results of a previous call to PARSE_INTO_WORDS()...
102 	     */
103 	    words = parse_into_words(cprep, &nwords);
104 
105 	    current_alias = mymalloc(sizeof(struct pt_entry), M_PREP);
106 	    current_alias->nwords = nwords;
107 	    current_alias->next = 0;
108 	    for (j = 0; j < nwords; j++)
109 		current_alias->words[j] = str_dup(words[j]);
110 	    *prev = current_alias;
111 	    prev = &current_alias->next;
112 	}
113     }
114 }
115 
116 db_prep_spec
db_find_prep(int argc,char * argv[],int * first,int * last)117 db_find_prep(int argc, char *argv[], int *first, int *last)
118 {
119     pt_entry *alias;
120     int i, j, k;
121     int exact_match = (first == 0 || last == 0);
122 
123     for (i = 0; i < argc; i++) {
124 	for (j = 0; j < NPREPS; j++) {
125 	    for (alias = prep_table[j]; alias; alias = alias->next) {
126 		if (i + alias->nwords <= argc) {
127 		    for (k = 0; k < alias->nwords; k++) {
128 			if (mystrcasecmp(argv[i + k], alias->words[k]))
129 			    break;
130 		    }
131 		    if (k == alias->nwords
132 			&& (!exact_match || i + k == argc)) {
133 			if (!exact_match) {
134 			    *first = i;
135 			    *last = i + alias->nwords - 1;
136 			}
137 			return (db_prep_spec) j;
138 		    }
139 		}
140 	    }
141 	}
142 	if (exact_match)
143 	    break;
144     }
145     return PREP_NONE;
146 }
147 
148 db_prep_spec
db_match_prep(const char * prepname)149 db_match_prep(const char *prepname)
150 {
151     db_prep_spec prep;
152     int argc;
153     char *ptr;
154     char **argv;
155     char *s, first;
156 
157     s = str_dup(prepname);
158     first = s[0];
159     if (first == '#')
160 	first = (++s)[0];
161     prep = strtol(s, &ptr, 10);
162     if (*ptr == '\0') {
163 	free_str(s);
164 	if (!isdigit(first) || prep >= NPREPS)
165 	    return PREP_NONE;
166 	else
167 	    return prep;
168     }
169     if ((ptr = strchr(s, '/')) != '\0')
170 	*ptr = '\0';
171 
172     argv = parse_into_words(s, &argc);
173     prep = db_find_prep(argc, argv, 0, 0);
174     free_str(s);
175     return prep;
176 }
177 
178 const char *
db_unparse_prep(db_prep_spec prep)179 db_unparse_prep(db_prep_spec prep)
180 {
181     if (prep == PREP_NONE)
182 	return "none";
183     else if (prep == PREP_ANY)
184 	return "any";
185     else
186 	return prep_list[prep];
187 }
188 
189 
190 /*********** Verbs ***********/
191 
192 #define DOBJSHIFT  4
193 #define IOBJSHIFT  6
194 #define OBJMASK    0x3
195 #define PERMMASK   0xF
196 
197 void
db_add_verb(Objid oid,const char * vnames,Objid owner,unsigned flags,db_arg_spec dobj,db_prep_spec prep,db_arg_spec iobj)198 db_add_verb(Objid oid, const char *vnames, Objid owner, unsigned flags,
199 	    db_arg_spec dobj, db_prep_spec prep, db_arg_spec iobj)
200 {
201     Object *o = dbpriv_find_object(oid);
202     Verbdef *v, *newv;
203 
204     db_priv_affected_callable_verb_lookup();
205 
206     newv = mymalloc(sizeof(Verbdef), M_VERBDEF);
207     newv->name = vnames;
208     newv->owner = owner;
209     newv->perms = flags | (dobj << DOBJSHIFT) | (iobj << IOBJSHIFT);
210     newv->prep = prep;
211     newv->next = 0;
212     newv->program = 0;
213     if (o->verbdefs) {
214 	for (v = o->verbdefs; v->next; v = v->next);
215 	v->next = newv;
216     } else
217 	o->verbdefs = newv;
218 }
219 
220 static Verbdef *
find_verbdef_by_name(Object * o,const char * vname,int check_x_bit)221 find_verbdef_by_name(Object * o, const char *vname, int check_x_bit)
222 {
223     Verbdef *v;
224 
225     for (v = o->verbdefs; v; v = v->next)
226 	if (verbcasecmp(v->name, vname)
227 	    && (!check_x_bit || (v->perms & VF_EXEC)))
228 	    break;
229 
230     return v;
231 }
232 
233 int
db_count_verbs(Objid oid)234 db_count_verbs(Objid oid)
235 {
236     int count = 0;
237     Object *o = dbpriv_find_object(oid);
238     Verbdef *v;
239 
240     for (v = o->verbdefs; v; v = v->next)
241 	count++;
242 
243     return count;
244 }
245 
246 int
db_for_all_verbs(Objid oid,int (* func)(void * data,const char * vname),void * data)247 db_for_all_verbs(Objid oid,
248 		 int (*func) (void *data, const char *vname),
249 		 void *data)
250 {
251     Object *o = dbpriv_find_object(oid);
252     Verbdef *v;
253 
254     for (v = o->verbdefs; v; v = v->next)
255 	if ((*func) (data, v->name))
256 	    return 1;
257 
258     return 0;
259 }
260 
261 typedef struct {		/* Non-null db_verb_handles point to these */
262     Objid definer;
263     Verbdef *verbdef;
264 } handle;
265 
266 void
db_delete_verb(db_verb_handle vh)267 db_delete_verb(db_verb_handle vh)
268 {
269     handle *h = (handle *) vh.ptr;
270     Objid oid = h->definer;
271     Verbdef *v = h->verbdef;
272     Object *o = dbpriv_find_object(oid);
273     Verbdef *vv;
274 
275     db_priv_affected_callable_verb_lookup();
276 
277     vv = o->verbdefs;
278     if (vv == v)
279 	o->verbdefs = v->next;
280     else {
281 	while (vv->next != v)
282 	    vv = vv->next;
283 	vv->next = v->next;
284     }
285 
286     if (v->program)
287 	free_program(v->program);
288     if (v->name)
289 	free_str(v->name);
290     myfree(v, M_VERBDEF);
291 }
292 
293 db_verb_handle
db_find_command_verb(Objid oid,const char * verb,db_arg_spec dobj,unsigned prep,db_arg_spec iobj)294 db_find_command_verb(Objid oid, const char *verb,
295 		     db_arg_spec dobj, unsigned prep, db_arg_spec iobj)
296 {
297     Object *o;
298     Verbdef *v;
299     static handle h;
300     db_verb_handle vh;
301 
302     for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent))
303 	for (v = o->verbdefs; v; v = v->next) {
304 	    db_arg_spec vdobj = (v->perms >> DOBJSHIFT) & OBJMASK;
305 	    db_arg_spec viobj = (v->perms >> IOBJSHIFT) & OBJMASK;
306 
307 	    if (verbcasecmp(v->name, verb)
308 		&& (vdobj == ASPEC_ANY || vdobj == dobj)
309 		&& (v->prep == PREP_ANY || v->prep == prep)
310 		&& (viobj == ASPEC_ANY || viobj == iobj)) {
311 		h.definer = o->id;
312 		h.verbdef = v;
313 		vh.ptr = &h;
314 
315 		return vh;
316 	    }
317 	}
318 
319     vh.ptr = 0;
320 
321     return vh;
322 }
323 
324 #ifdef VERB_CACHE
325 int db_verb_generation = 0;
326 
327 int verbcache_hit = 0;
328 int verbcache_neg_hit = 0;
329 int verbcache_miss = 0;
330 
331 typedef struct vc_entry vc_entry;
332 
333 struct vc_entry {
334     unsigned int hash;
335 #ifdef RONG
336     int generation;
337 #endif
338     Objid oid_key;		/* Note that we proceed up the parent tree
339 				   until we hit an object with verbs on it */
340     char *verbname;
341     handle h;
342     struct vc_entry *next;
343 };
344 
345 static vc_entry **vc_table = NULL;
346 static int vc_size = 0;
347 
348 #define DEFAULT_VC_SIZE 7507
349 
350 void
db_priv_affected_callable_verb_lookup(void)351 db_priv_affected_callable_verb_lookup(void)
352 {
353     int i;
354     vc_entry *vc, *vc_next;
355 
356     if (vc_table == NULL)
357 	return;
358 
359     db_verb_generation++;
360 
361     for (i = 0; i < vc_size; i++) {
362 	vc = vc_table[i];
363 	while (vc) {
364 	    vc_next = vc->next;
365 	    free_str(vc->verbname);
366 	    myfree(vc, M_VC_ENTRY);
367 	    vc = vc_next;
368 	}
369 	vc_table[i] = NULL;
370     }
371 }
372 
373 static void
make_vc_table(int size)374 make_vc_table(int size)
375 {
376     int i;
377 
378     vc_size = size;
379     vc_table = mymalloc(size * sizeof(vc_entry *), M_VC_TABLE);
380     for (i = 0; i < size; i++) {
381 	vc_table[i] = NULL;
382     }
383 }
384 
385 #define VC_CACHE_STATS_MAX 16
386 
387 Var
db_verb_cache_stats(void)388 db_verb_cache_stats(void)
389 {
390     int i, depth, histogram[VC_CACHE_STATS_MAX + 1];
391     vc_entry *vc;
392     Var v, vv;
393 
394     for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++) {
395 	histogram[i] = 0;
396     }
397 
398     for (i = 0; i < vc_size; i++) {
399 	depth = 0;
400 	for (vc = vc_table[i]; vc; vc = vc->next)
401 	    depth++;
402 	if (depth > VC_CACHE_STATS_MAX)
403 	    depth = VC_CACHE_STATS_MAX;
404 	histogram[depth]++;
405     }
406 
407     v = new_list(5);
408     v.v.list[1].type = TYPE_INT;
409     v.v.list[1].v.num = verbcache_hit;
410     v.v.list[2].type = TYPE_INT;
411     v.v.list[2].v.num = verbcache_neg_hit;
412     v.v.list[3].type = TYPE_INT;
413     v.v.list[3].v.num = verbcache_miss;
414     v.v.list[4].type = TYPE_INT;
415     v.v.list[4].v.num = db_verb_generation;
416     vv = (v.v.list[5] = new_list(VC_CACHE_STATS_MAX + 1));
417     for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++) {
418 	vv.v.list[i + 1].type = TYPE_INT;
419 	vv.v.list[i + 1].v.num = histogram[i];
420     }
421     return v;
422 }
423 
424 void
db_log_cache_stats(void)425 db_log_cache_stats(void)
426 {
427     int i, depth, histogram[VC_CACHE_STATS_MAX + 1];
428     vc_entry *vc;
429 
430     for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++) {
431 	histogram[i] = 0;
432     }
433 
434     for (i = 0; i < vc_size; i++) {
435 	depth = 0;
436 	for (vc = vc_table[i]; vc; vc = vc->next)
437 	    depth++;
438 	if (depth > VC_CACHE_STATS_MAX)
439 	    depth = VC_CACHE_STATS_MAX;
440 	histogram[depth]++;
441     }
442 
443     oklog("Verb cache stat summary: %d hits, %d misses, %d generations\n",
444 	  verbcache_hit, verbcache_miss, db_verb_generation);
445     oklog("Depth   Count\n");
446     for (i = 0; i < VC_CACHE_STATS_MAX + 1; i++)
447 	oklog("%-5d   %-5d\n", i, histogram[i]);
448     oklog("---\n");
449 }
450 
451 #endif
452 
453 db_verb_handle
db_find_callable_verb(Objid oid,const char * verb)454 db_find_callable_verb(Objid oid, const char *verb)
455 {
456     Object *o;
457     Verbdef *v;
458 #ifdef VERB_CACHE
459     vc_entry *new_vc;
460 #else
461     static handle h;
462 #endif
463     db_verb_handle vh;
464 
465 #ifdef VERB_CACHE
466     unsigned int hash, bucket;
467     Objid first_parent_with_verbs = oid;
468     vc_entry *vc;
469 
470     if (vc_table == NULL)
471 	make_vc_table(DEFAULT_VC_SIZE);
472 
473     for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent)) {
474 	if (o->verbdefs != NULL)
475 	    break;
476     }
477 
478     if (o) {
479 	first_parent_with_verbs = o->id;
480     } else {
481 	first_parent_with_verbs = NOTHING;
482     }
483 
484     hash = str_hash(verb) ^ (~first_parent_with_verbs);		/* ewww, but who cares */
485     bucket = hash % vc_size;
486 
487     for (vc = vc_table[bucket]; vc; vc = vc->next) {
488 	if (hash == vc->hash
489 	    && first_parent_with_verbs == vc->oid_key
490 	    && !mystrcasecmp(verb, vc->verbname)) {
491 	    /* we haaave a winnaaah */
492 	    if (vc->h.verbdef) {
493 		verbcache_hit++;
494 		vh.ptr = &vc->h;
495 	    } else {
496 		verbcache_neg_hit++;
497 		vh.ptr = 0;
498 	    }
499 	    return vh;
500 	}
501     }
502 
503     /* A swing and a miss. */
504     verbcache_miss++;
505 #else
506     o = dbpriv_find_object(oid);
507 #endif
508 
509 #ifdef VERB_CACHE
510     /*
511      * Add the entry to the verbcache whether we find it or not.  This means
512      * we do "negative caching", keeping track of failed lookups so that
513      * repeated failures hit the cache instead of going through a lookup.
514      */
515     new_vc = mymalloc(sizeof(vc_entry), M_VC_ENTRY);
516 
517     new_vc->hash = hash;
518     new_vc->oid_key = first_parent_with_verbs;
519     new_vc->verbname = str_dup(verb);
520     new_vc->h.verbdef = NULL;
521     new_vc->next = vc_table[bucket];
522     vc_table[bucket] = new_vc;
523 #endif
524 
525     for ( /* from above */ ; o; o = dbpriv_find_object(o->parent))
526 	if ((v = find_verbdef_by_name(o, verb, 1)) != 0) {
527 #ifdef VERB_CACHE
528 	    new_vc->h.definer = o->id;
529 	    new_vc->h.verbdef = v;
530 	    vh.ptr = &new_vc->h;
531 #else
532 	    h.definer = o->id;
533 	    h.verbdef = v;
534 	    vh.ptr = &h;
535 #endif
536 	    return vh;
537 	}
538     /*
539      * note that the verbcache has cleared h.verbdef, so it defaults to a
540      * "miss" cache if the for loop doesn't win
541      */
542     vh.ptr = 0;
543 
544     return vh;
545 }
546 
547 db_verb_handle
db_find_defined_verb(Objid oid,const char * vname,int allow_numbers)548 db_find_defined_verb(Objid oid, const char *vname, int allow_numbers)
549 {
550     Object *o = dbpriv_find_object(oid);
551     Verbdef *v;
552     char *p;
553     int num, i;
554     static handle h;
555     db_verb_handle vh;
556 
557     if (!allow_numbers ||
558 	(num = strtol(vname, &p, 10),
559 	 (isspace(*vname) || *p != '\0')))
560 	num = -1;
561 
562     for (i = 0, v = o->verbdefs; v; v = v->next, i++)
563 	if (i == num || verbcasecmp(v->name, vname))
564 	    break;
565 
566     if (v) {
567 	h.definer = o->id;
568 	h.verbdef = v;
569 	vh.ptr = &h;
570 
571 	return vh;
572     }
573     vh.ptr = 0;
574 
575     return vh;
576 }
577 
578 db_verb_handle
db_find_indexed_verb(Objid oid,unsigned index)579 db_find_indexed_verb(Objid oid, unsigned index)
580 {
581     Object *o = dbpriv_find_object(oid);
582     Verbdef *v;
583     unsigned i;
584     static handle h;
585     db_verb_handle vh;
586 
587     for (v = o->verbdefs, i = 0; v; v = v->next)
588 	if (++i == index) {
589 	    h.definer = o->id;
590 	    h.verbdef = v;
591 	    vh.ptr = &h;
592 
593 	    return vh;
594 	}
595     vh.ptr = 0;
596 
597     return vh;
598 }
599 
600 Objid
db_verb_definer(db_verb_handle vh)601 db_verb_definer(db_verb_handle vh)
602 {
603     handle *h = (handle *) vh.ptr;
604 
605     if (h)
606 	return h->definer;
607 
608     panic("DB_VERB_DEFINER: Null handle!");
609     return 0;
610 }
611 
612 const char *
db_verb_names(db_verb_handle vh)613 db_verb_names(db_verb_handle vh)
614 {
615     handle *h = (handle *) vh.ptr;
616 
617     if (h)
618 	return h->verbdef->name;
619 
620     panic("DB_VERB_NAMES: Null handle!");
621     return 0;
622 }
623 
624 void
db_set_verb_names(db_verb_handle vh,const char * names)625 db_set_verb_names(db_verb_handle vh, const char *names)
626 {
627     handle *h = (handle *) vh.ptr;
628 
629     db_priv_affected_callable_verb_lookup();
630 
631     if (h) {
632 	if (h->verbdef->name)
633 	    free_str(h->verbdef->name);
634 	h->verbdef->name = names;
635     } else
636 	panic("DB_SET_VERB_NAMES: Null handle!");
637 }
638 
639 Objid
db_verb_owner(db_verb_handle vh)640 db_verb_owner(db_verb_handle vh)
641 {
642     handle *h = (handle *) vh.ptr;
643 
644     if (h)
645 	return h->verbdef->owner;
646 
647     panic("DB_VERB_OWNER: Null handle!");
648     return 0;
649 }
650 
651 void
db_set_verb_owner(db_verb_handle vh,Objid owner)652 db_set_verb_owner(db_verb_handle vh, Objid owner)
653 {
654     handle *h = (handle *) vh.ptr;
655 
656     if (h)
657 	h->verbdef->owner = owner;
658     else
659 	panic("DB_SET_VERB_OWNER: Null handle!");
660 }
661 
662 unsigned
db_verb_flags(db_verb_handle vh)663 db_verb_flags(db_verb_handle vh)
664 {
665     handle *h = (handle *) vh.ptr;
666 
667     if (h)
668 	return h->verbdef->perms & PERMMASK;
669 
670     panic("DB_VERB_FLAGS: Null handle!");
671     return 0;
672 }
673 
674 void
db_set_verb_flags(db_verb_handle vh,unsigned flags)675 db_set_verb_flags(db_verb_handle vh, unsigned flags)
676 {
677     handle *h = (handle *) vh.ptr;
678 
679     db_priv_affected_callable_verb_lookup();
680 
681     if (h) {
682 	h->verbdef->perms &= ~PERMMASK;
683 	h->verbdef->perms |= flags;
684     } else
685 	panic("DB_SET_VERB_FLAGS: Null handle!");
686 }
687 
688 Program *
db_verb_program(db_verb_handle vh)689 db_verb_program(db_verb_handle vh)
690 {
691     handle *h = (handle *) vh.ptr;
692 
693     if (h) {
694 	Program *p = h->verbdef->program;
695 
696 	return p ? p : null_program();
697     }
698     panic("DB_VERB_PROGRAM: Null handle!");
699     return 0;
700 }
701 
702 void
db_set_verb_program(db_verb_handle vh,Program * program)703 db_set_verb_program(db_verb_handle vh, Program * program)
704 {
705     handle *h = (handle *) vh.ptr;
706 
707     /* Not necessary, since this was only here to cope with nonprogrammed verbs, and that turns out to be handled properly in modern servers. */
708 
709     /* db_priv_affected_callable_verb_lookup(); */
710 
711     if (h) {
712 	if (h->verbdef->program)
713 	    free_program(h->verbdef->program);
714 	h->verbdef->program = program;
715     } else
716 	panic("DB_SET_VERB_PROGRAM: Null handle!");
717 }
718 
719 void
db_verb_arg_specs(db_verb_handle vh,db_arg_spec * dobj,db_prep_spec * prep,db_arg_spec * iobj)720 db_verb_arg_specs(db_verb_handle vh,
721 	     db_arg_spec * dobj, db_prep_spec * prep, db_arg_spec * iobj)
722 {
723     handle *h = (handle *) vh.ptr;
724 
725     if (h) {
726 	*dobj = (h->verbdef->perms >> DOBJSHIFT) & OBJMASK;
727 	*prep = h->verbdef->prep;
728 	*iobj = (h->verbdef->perms >> IOBJSHIFT) & OBJMASK;
729     } else
730 	panic("DB_VERB_ARG_SPECS: Null handle!");
731 }
732 
733 void
db_set_verb_arg_specs(db_verb_handle vh,db_arg_spec dobj,db_prep_spec prep,db_arg_spec iobj)734 db_set_verb_arg_specs(db_verb_handle vh,
735 		   db_arg_spec dobj, db_prep_spec prep, db_arg_spec iobj)
736 {
737     handle *h = (handle *) vh.ptr;
738 
739     db_priv_affected_callable_verb_lookup();
740 
741     if (h) {
742 	h->verbdef->perms = ((h->verbdef->perms & PERMMASK)
743 			     | (dobj << DOBJSHIFT)
744 			     | (iobj << IOBJSHIFT));
745 	h->verbdef->prep = prep;
746     } else
747 	panic("DB_SET_VERB_ARG_SPECS: Null handle!");
748 }
749 
750 int
db_verb_allows(db_verb_handle h,Objid progr,db_verb_flag flag)751 db_verb_allows(db_verb_handle h, Objid progr, db_verb_flag flag)
752 {
753     return ((db_verb_flags(h) & flag)
754 	    || progr == db_verb_owner(h)
755 	    || is_wizard(progr));
756 }
757 
758 
759 char rcsid_db_verbs[] = "$Id: db_verbs.c,v 1.5 1998/12/14 13:17:39 nop Exp $";
760 
761 /*
762  * $Log: db_verbs.c,v $
763  * Revision 1.5  1998/12/14 13:17:39  nop
764  * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
765  *
766  * Revision 1.4  1997/09/07 23:58:37  nop
767  * Bump up cache size to 7507, since lambdamoo has been running with that
768  * for months.
769  *
770  * Revision 1.3  1997/07/07 03:24:53  nop
771  * Merge UNSAFE_OPTS (r5) after extensive testing.
772  *
773  * Revision 1.2.2.5  1997/07/07 01:41:20  nop
774  * set_verb_code() doesn't really need a verb generation bump.
775  *
776  * Revision 1.2.2.4  1997/06/05 08:38:37  bjj
777  * Tweak nop's verbcache by moving an actual handle into vc_entry to avoid the
778  * copy after the lookup.  Also, keep verbs that aren't found in the cache so
779  * repeated calls to nonexistant verbs benefit from caching as well (need to
780  * watch the new field in verb_cache_stats()[2] to see how often this is useful).
781  *
782  * Revision 1.2.2.3  1997/05/29 11:56:21  nop
783  * Added Jason Maltzen's builtin to return a list version of cache stats.
784  *
785  * Revision 1.2.2.2  1997/03/22 22:54:36  bjj
786  * Tiny tweak to db_find_callable_verb to avoid recomputing the first parent
787  * with verbdefs.  Also hit it with indent, which made these diffs bigger than
788  * they should have been...
789  *
790  * Revision 1.2.2.1  1997/03/20 07:26:03  nop
791  * First pass at the new verb cache.  Some ugly code inside.
792  *
793  * Revision 1.2  1997/03/03 04:18:31  nop
794  * GNU Indent normalization
795  *
796  * Revision 1.1.1.1  1997/03/03 03:44:59  nop
797  * LambdaMOO 1.8.0p5
798  *
799  * Revision 2.4  1996/05/12  21:32:23  pavel
800  * Changed db_add_verb() not to bump the reference count of the given
801  * verb-names string.  Release 1.8.0p5.
802  *
803  * Revision 2.3  1996/02/08  07:17:40  pavel
804  * Updated copyright notice for 1996.  Release 1.8.0beta1.
805  *
806  * Revision 2.2  1995/12/28  00:29:03  pavel
807  * Broke out the building of the preposition table into a separate function,
808  * called during initialization, to avoid an storage-overwriting bug.  Changed
809  * db_delete_verb() to unbundle how the verb is found.  Changed
810  * db_find_defined_verb() to allow suppressing old numeric-string behavior.
811  * Release 1.8.0alpha3.
812  *
813  * Revision 2.1  1995/12/11  08:11:07  pavel
814  * Made verb programs never be NULL any more.  Release 1.8.0alpha2.
815  *
816  * Revision 2.0  1995/11/30  04:21:33  pavel
817  * New baseline version, corresponding to release 1.8.0alpha1.
818  *
819  * Revision 1.1  1995/11/30  04:21:20  pavel
820  * Initial revision
821  */
822