PennMUSH Community

root/1.8.3/trunk/src/cque.c

Revision 1167, 36.9 kB (checked in by shawnw, 8 months ago)

Merge devel into trunk for p6 release

Line 
1 /**
2  * \file cque.c
3  *
4  * \brief Queue for PennMUSH.
5  *
6  *
7  */
8
9 #include "copyrite.h"
10 #include "config.h"
11
12 #include <ctype.h>
13 #include <fcntl.h>
14 #include <limits.h>
15 #include <signal.h>
16 #include <string.h>
17 #ifdef I_SYS_TIME
18 #include <sys/time.h>
19 #ifdef TIME_WITH_SYS_TIME
20 #include <time.h>
21 #endif
22 #else
23 #include <time.h>
24 #endif
25
26 #include "conf.h"
27 #include "command.h"
28 #include "mushdb.h"
29 #include "match.h"
30 #include "externs.h"
31 #include "parse.h"
32 #include "strtree.h"
33 #include "mymalloc.h"
34 #include "game.h"
35 #include "attrib.h"
36 #include "flags.h"
37 #include "dbdefs.h"
38 #include "log.h"
39 #include "confmagic.h"
40
41
42 EVAL_CONTEXT global_eval_context;
43
44 /** A queue entry.
45  * This structure reprsents a queue entry on a linked list of queue
46  * entries (a queue). It is used for all of the queues.
47  */
48 typedef struct bque {
49   struct bque *next;                    /**< pointer to next entry on queue */
50   dbref player;                 /**< player who will do command */
51   dbref queued;                 /**< object whose QUEUE gets incremented for this command */
52   dbref cause;                  /**< player causing command (for %N) */
53   dbref sem;                    /**< semaphore object to block on */
54   char *semattr;                /**< semaphore attribute to block on */
55   time_t left;                  /**< seconds left until execution */
56   char *env[10];                /**< environment, from wild match */
57   char *rval[NUMQ];             /**< environment, from setq() */
58   char *comm;                   /**< command to be executed */
59 } BQUE;
60
61 slab *bque_slab = NULL; /**< slab for 'struct bque' allocations */
62
63 static BQUE *qfirst = NULL, *qlast = NULL, *qwait = NULL;
64 static BQUE *qlfirst = NULL, *qllast = NULL;
65 static BQUE *qsemfirst = NULL, *qsemlast = NULL;
66
67 static int add_to_generic(dbref player, int am, const char *name,
68                           uint32_t flags);
69 static int add_to(dbref player, int am);
70 static int add_to_sem(dbref player, int am, const char *name);
71 static int queue_limit(dbref player);
72 void free_qentry(BQUE *point);
73 static int pay_queue(dbref player, const char *command);
74 void wait_que(dbref player, int waituntil, char *command,
75               dbref cause, dbref sem, const char *semattr, int until);
76 int que_next(void);
77
78 static void show_queue(dbref player, dbref victim, int q_type,
79                        int q_quiet, int q_all, BQUE *q_ptr, int *tot, int *self,
80                        int *del);
81 static void do_raw_restart(dbref victim);
82 static int waitable_attr(dbref thing, const char *atr);
83 static void shutdown_a_queue(BQUE **head, BQUE **tail);
84
85 extern sig_atomic_t cpu_time_limit_hit; /**< Have we used too much CPU? */
86
87 /** Attribute flags to be set or checked on attributes to be used
88  * as semaphores.
89  */
90 #define SEMAPHORE_FLAGS (AF_LOCKED | AF_PRIVATE | AF_NOCOPY | AF_NODUMP)
91
92 /** Queue initializtion function. Must  be called before anything
93  * is added to the queue.
94  */
95 void
96 init_queue(void)
97 {
98   bque_slab = slab_create("command queues", sizeof(BQUE));
99   slab_set_opt(bque_slab, SLAB_ALLOC_BEST_FIT, 1);
100 }
101
102 /* Returns true if the attribute on thing can be used as a semaphore.
103  * atr should be given in UPPERCASE.
104  */
105 static int
106 waitable_attr(dbref thing, const char *atr)
107 {
108   ATTR *a;
109   if (!atr || !*atr)
110     return 0;
111   a = atr_get_noparent(thing, atr);
112   if (!a) {                     /* Attribute isn't set */
113     a = atr_match(atr);
114     if (!a)                     /* It's not a built in attribute */
115       return 1;
116     return !strcmp(AL_NAME(a), "SEMAPHORE");    /* Only allow SEMAPHORE for now */
117   } else {                      /* Attribute is set. Check for proper owner and flags and value */
118     if ((AL_CREATOR(a) == GOD) && (AL_FLAGS(a) == SEMAPHORE_FLAGS)) {
119       char *v = atr_value(a);
120       if (!*v || is_integer(v))
121         return 1;
122       else
123         return 0;
124     } else {
125       return 0;
126     }
127   }
128   return 0;                     /* Not reached */
129 }
130
131 static int
132 add_to_generic(dbref player, int am, const char *name, uint32_t flags)
133 {
134   int num = 0;
135   ATTR *a;
136   char buff[MAX_COMMAND_LEN];
137   a = atr_get_noparent(player, name);
138   if (a)
139     num = parse_integer(atr_value(a));
140   num += am;
141   /* We set the attribute's value to 0 even if we're going to clear
142    * it later, because clearing it may fail (perhaps someone's also
143    * foolishly using it as a branch in an attribute tree)
144    */
145   snprintf(buff, sizeof buff, "%d", num);
146   (void) atr_add(player, name, buff, GOD, flags);
147   if (!num) {
148     (void) atr_clr(player, name, GOD);
149   }
150   return num;
151 }
152
153 static int
154 add_to(dbref player, int am)
155 {
156   return add_to_generic(player, am, "QUEUE", NOTHING);
157 }
158
159 static int
160 add_to_sem(dbref player, int am, const char *name)
161 {
162   return add_to_generic(player, am, name ? name : "SEMAPHORE", SEMAPHORE_FLAGS);
163 }
164
165 static int
166 queue_limit(dbref player)
167 {
168   /* returns 1 if player has exceeded his queue limit, and always
169      increments QUEUE by one. */
170   int nlimit;
171
172   nlimit = add_to(player, 1);
173   if (HugeQueue(player))
174     return nlimit > (QUEUE_QUOTA + db_top);
175   else
176     return nlimit > QUEUE_QUOTA;
177 }
178
179 /** Free a queue entry.
180  * \param point queue entry to free.
181  */
182 void
183 free_qentry(BQUE *point)
184 {
185   int a;
186   for (a = 0; a < 10; a++)
187     if (point->env[a]) {
188       mush_free((Malloc_t) point->env[a], "bqueue_env");
189     }
190   for (a = 0; a < NUMQ; a++)
191     if (point->rval[a]) {
192       mush_free((Malloc_t) point->rval[a], "bqueue_rval");
193     }
194   if (point->semattr)
195     mush_free((Malloc_t) point->semattr, "bqueue_semattr");
196   if (point->comm)
197     mush_free((Malloc_t) point->comm, "bqueue_comm");
198   slab_free(bque_slab, point);
199 }
200
201 static int
202 pay_queue(dbref player, const char *command)
203 {
204   int estcost;
205   estcost =
206     QUEUE_COST +
207     (QUEUE_LOSS ? ((get_random_long(0, QUEUE_LOSS - 1) == 0) ? 1 : 0) : 0);
208   if (!quiet_payfor(player, estcost)) {
209     notify(Owner(player), T("Not enough money to queue command."));
210     return 0;
211   }
212   if (!NoPay(player) && (estcost != QUEUE_COST) && Track_Money(Owner(player))) {
213     char *preserve_wnxt[10];
214     char *preserve_rnxt[NUMQ];
215     char *val_wnxt[10];
216     char *val_rnxt[NUMQ];
217     char *preserves[10];
218     char *preserveq[NUMQ];
219     save_global_nxt("pay_queue_save", preserve_wnxt, preserve_rnxt, val_wnxt,
220                     val_rnxt);
221     save_global_regs("pay_queue_save", preserveq);
222     save_global_env("pay_queue_save", preserves);
223     notify_format(Owner(player),
224                   T("GAME: Object %s(%s) lost a %s to queue loss."),
225                   Name(player), unparse_dbref(player), MONEY);
226     restore_global_regs("pay_queue_save", preserveq);
227     restore_global_env("pay_queue_save", preserves);
228     restore_global_nxt("pay_queue_save", preserve_wnxt, preserve_rnxt, val_wnxt,
229                        val_rnxt);
230   }
231   if (queue_limit(QUEUE_PER_OWNER ? Owner(player) : player)) {
232     notify_format(Owner(player),
233                   T("Runaway object: %s(%s). Commands halted."),
234                   Name(player), unparse_dbref(player));
235     do_log(LT_TRACE, player, player, T("Runaway object %s executing: %s"),
236            unparse_dbref(player), command);
237     /* Refund the queue costs */
238     giveto(player, QUEUE_COST);
239     add_to(QUEUE_PER_OWNER ? Owner(player) : player, -1);
240     /* wipe out that object's queue and set it HALT */
241     do_halt(Owner(player), "", player);
242     set_flag_internal(player, "HALT");
243     return 0;
244   }
245   return 1;
246 }
247
248 /** Add a new entry onto the player or object command queues.
249  * This function adds a new entry to the back of the player or
250  * object command queues (depending on whether the call was
251  * caused by a player or an object).
252  * \param player the enactor for the queued command.
253  * \param command the command to enqueue.
254  * \param cause the player or object causing the command to be queued.
255  */
256 void
257 parse_que(dbref player, const char *command, dbref cause)
258 {
259   int a;
260   BQUE *tmp;
261   if (!IsPlayer(player) && (Halted(player)))
262     return;
263   if (!pay_queue(player, command))      /* make sure player can afford to do it */
264     return;
265   tmp = slab_malloc(bque_slab, NULL);
266   tmp->comm = mush_strdup(command, "bqueue_comm");
267   tmp->semattr = NULL;
268   tmp->player = player;
269   tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player;
270   tmp->next = NULL;
271   tmp->left = 0;
272   tmp->cause = cause;
273   for (a = 0; a < 10; a++)
274     if (!global_eval_context.wnxt[a])
275       tmp->env[a] = NULL;
276     else {
277       tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
278     }
279   for (a = 0; a < NUMQ; a++)
280     if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
281       tmp->rval[a] = NULL;
282     else {
283       tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
284     }
285   if (IsPlayer(cause)) {
286     if (qlast) {
287       qlast->next = tmp;
288       qlast = tmp;
289     } else
290       qlast = qfirst = tmp;
291   } else {
292     if (qllast) {
293       qllast->next = tmp;
294       qllast = tmp;
295     } else
296       qllast = qlfirst = tmp;
297   }
298 }
299
300
301 /** Enqueue the action part of an attribute.
302  * This function is a front-end to parse_que() that takes an attribute,
303  * removes ^....: or $....: from its value, and queues what's left.
304  * \param executor object containing the attribute.
305  * \param atrname attribute name.
306  * \param enactor the enactor.
307  * \param noparent if true, parents of executor are not checked for atrname.
308  * \retval 0 failure.
309  * \retval 1 success.
310  */
311 int
312 queue_attribute_base(dbref executor, const char *atrname, dbref enactor,
313                      int noparent)
314 {
315   ATTR *a;
316
317   a = queue_attribute_getatr(executor, atrname, noparent);
318   if (!a)
319     return 0;
320   queue_attribute_useatr(executor, a, enactor);
321   return 1;
322 }
323
324 ATTR *
325 queue_attribute_getatr(dbref executor, const char *atrname, int noparent)
326 {
327   return (noparent ? atr_get_noparent(executor, strupper(atrname)) :
328           atr_get(executor, strupper(atrname)));
329 }
330
331 int
332 queue_attribute_useatr(dbref executor, ATTR *a, dbref enactor)
333 {
334   char *start, *command;
335   start = safe_atr_value(a);
336   command = start;
337   /* Trim off $-command or ^-command prefix */
338   if (*command == '$' || *command == '^') {
339     do {
340       command = strchr(command + 1, ':');
341     } while (command && command[-1] == '\\');
342     if (!command)
343       /* Oops, had '$' or '^', but no ':' */
344       command = start;
345     else
346       /* Skip the ':' */
347       command++;
348   }
349   parse_que(executor, command, enactor);
350   free(start);
351   return 1;
352 }
353
354
355 /** Queue an entry on the wait or semaphore queues.
356  * This function creates and adds a queue entry to the wait queue
357  * or the semaphore queue. Wait queue entries are sorted by when
358  * they're due to expire; semaphore queue entries are just added
359  * to the back of the queue.
360  * \param player the enqueuing object.
361  * \param waittill time to wait, or 0.
362  * \param command command to enqueue.
363  * \param cause object that caused command to be enqueued.
364  * \param sem object to serve as a semaphore, or NOTHING.
365  * \param semattr attribute to serve as a semaphore, or NULL (to use SEMAPHORE).
366  * \param until 1 if we wait until an absolute time.
367  */
368 void
369 wait_que(dbref player, int waittill, char *command, dbref cause, dbref sem,
370          const char *semattr, int until)
371 {
372   BQUE *tmp;
373   int a;
374   if (waittill == 0) {
375     if (sem != NOTHING)
376       add_to_sem(sem, -1, semattr);
377     parse_que(player, command, cause);
378     return;
379   }
380   if (!pay_queue(player, command))      /* make sure player can afford to do it */
381     return;
382   tmp = slab_malloc(bque_slab, NULL);
383   tmp->comm = mush_strdup(command, "bqueue_comm");
384   tmp->player = player;
385   tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player;
386   tmp->cause = cause;
387   tmp->semattr = NULL;
388   tmp->next = NULL;
389   for (a = 0; a < 10; a++) {
390     if (!global_eval_context.wnxt[a])
391       tmp->env[a] = NULL;
392     else {
393       tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
394     }
395   }
396   for (a = 0; a < NUMQ; a++) {
397     if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
398       tmp->rval[a] = NULL;
399     else {
400       tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
401     }
402   }
403
404   if (until) {
405     tmp->left = (time_t) waittill;
406   } else {
407     if (waittill >= 0)
408       tmp->left = mudtime + waittill;
409     else
410       tmp->left = 0;            /* semaphore wait without a timeout */
411   }
412   tmp->sem = sem;
413   if (sem == NOTHING) {
414     /* No semaphore, put on normal wait queue, sorted by time */
415     BQUE *point, *trail = NULL;
416
417     for (point = qwait;
418          point && (point->left <= tmp->left); point = point->next)
419       trail = point;
420
421     tmp->next = point;
422     if (trail != NULL)
423       trail->next = tmp;
424     else
425       qwait = tmp;
426   } else {
427
428     /* Put it on the end of the semaphore queue */
429     tmp->semattr =
430       mush_strdup(semattr ? semattr : "SEMAPHORE", "bqueue_semattr");
431     if (qsemlast != NULL) {
432       qsemlast->next = tmp;
433       qsemlast = tmp;
434     } else {
435       qsemfirst = qsemlast = tmp;
436     }
437   }
438 }
439
440 /** Once-a-second check for queued commands.
441  * This function is called every second to check for commands
442  * on the wait queue or semaphore queue, and to move a command
443  * off the low priority object queue and onto the normal priority
444  * player queue.
445  */
446 void
447 do_second(void)
448 {
449   BQUE *trail = NULL, *point, *next;
450   /* move contents of low priority queue onto end of normal one
451    * this helps to keep objects from getting out of control since
452    * its effects on other objects happen only after one second
453    * this should allow @halt to be typed before getting blown away
454    * by scrolling text.
455    */
456   if (qlfirst) {
457     if (qlast)
458       qlast->next = qlfirst;
459     else
460       qfirst = qlfirst;
461     qlast = qllast;
462     qllast = qlfirst = NULL;
463   }
464   /* check regular wait queue */
465
466   while (qwait && qwait->left <= mudtime) {
467     point = qwait;
468     qwait = point->next;
469     point->next = NULL;
470     point->left = 0;
471     if (IsPlayer(point->cause)) {
472       if (qlast) {
473         qlast->next = point;
474         qlast = point;
475       } else
476         qlast = qfirst = point;
477     } else {
478       if (qllast) {
479         qllast->next = point;
480         qllast = point;
481       } else
482         qllast = qlfirst = point;
483     }
484   }
485
486   /* check for semaphore timeouts */
487
488   for (point = qsemfirst, trail = NULL; point; point = next) {
489     if (point->left == 0 || point->left > mudtime) {
490       next = (trail = point)->next;
491       continue;                 /* skip non-timed and those that haven't gone off yet */
492     }
493     if (trail != NULL)
494       trail->next = next = point->next;
495     else
496       qsemfirst = next = point->next;
497     if (point == qsemlast)
498       qsemlast = trail;
499     add_to_sem(point->sem, -1, point->semattr);
500     point->sem = NOTHING;
501     point->next = NULL;
502     if (IsPlayer(point->cause)) {
503       if (qlast) {
504         qlast->next = point;
505         qlast = point;
506       } else
507         qlast = qfirst = point;
508     } else {
509       if (qllast) {
510         qllast->next = point;
511         qllast = point