PennMUSH Community

root/1.8.3/trunk/src/destroy.c

Revision 1032, 44.0 kB (checked in by shawnw, 1 year ago)

Merged 1.8.3p4 into trunk

Line 
1 /**
2  * \file destroy.c
3  *
4  * \brief Destroying objects and consistency checking.
5  *
6  * This file has two main parts. One part is the functions for destroying
7  * objects and getting objects off of the free list. The major public
8  * functions here are do_destroy(), free_get(), and purge().
9  *
10  * The other part is functions for checking the consistency of the
11  * database, and repairing any inconsistencies that are found. The
12  * major function in this group is dbck().
13  *
14  *
15  * These lengthy comments are by Ralph Melton, December 1995.
16  *
17  * First, a discourse on the theory of how we handle destruction.
18  *
19  * We want to maintain the following invariants:
20  * 1. All destroyed objects are on the free list. (linked through the next
21  *    fields.)
22  * 2. All objects on the free list are destroyed objects.
23  * 3. No undestroyed object has its next, contents, location, or home
24  *    field pointing to a destroyed object.
25  * 4. No object's zone or parent is a destroyed object.
26  * 5. No object's owner is a destroyed object.
27  *
28  * For the sake of efficiency, we allow indirect locks and other locks to
29  * refer to destroyed objects; boolexp.c had better be able to cope with
30  * these.
31  *
32  *
33  * There are three logically distinct parts to destroying an object:
34  *
35  * Part 1: we do all the permissions checks, check for the SAFE flag
36  * and the override switch, and decide that yes, we are going to destroy
37  * this object.
38  *
39  * Part 2: we eliminate all the links from other objects in the database
40  * to this object. This processing may depend on the object's type.
41  *
42  * Part 3: (logically concurrent with part 2, and must happen together
43  * with part 2) Remove any commands the object may have in the queue,
44  * free all the storage associated with the object, set the name to
45  * 'Garbage', and set this object to be a destroyed object, and put it
46  * on the free list. This process is independent of object type.
47  *
48  * Note that phases 2 and 3 do not have to happen immediately after Phase 1.
49  * To allow some delay, we set the object GOING, and then process it on
50  * the check that happens every ten minutes.
51  *
52  */
53
54 #include "config.h"
55
56 #include <ctype.h>
57 #include <limits.h>
58 #include <stdlib.h>
59 #include <string.h>
60
61 #include "copyrite.h"
62 #include "conf.h"
63 #include "mushdb.h"
64 #include "match.h"
65 #include "externs.h"
66 #include "log.h"
67 #include "game.h"
68 #include "extmail.h"
69 #include "malias.h"
70 #include "attrib.h"
71 #include "dbdefs.h"
72 #include "flags.h"
73 #include "lock.h"
74 #include "confmagic.h"
75
76
77
78 dbref first_free = NOTHING;   /**< Object at top of free list */
79
80 static dbref what_to_destroy(dbref player, char *name, int confirm);
81 static void pre_destroy(dbref player, dbref thing);
82 static void free_object(dbref thing);
83 static void empty_contents(dbref thing);
84 static void clear_thing(dbref thing);
85 static void clear_player(dbref thing);
86 static void clear_room(dbref thing);
87 static void clear_exit(dbref thing);
88
89 static void check_fields(void);
90 static void check_connected_rooms(void);
91 static void mark_connected(dbref loc);
92 static void check_connected_marks(void);
93 static void mark_contents(dbref loc);
94 static void check_contents(void);
95 static void check_locations(void);
96 static void check_zones(void);
97 static int attribute_owner_helper
98   (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr,
99    void *args);
100
101 extern void remove_all_obj_chan(dbref thing);
102 extern void chan_chownall(dbref old, dbref new);
103
104 extern struct db_stat_info current_state;
105
106 /** Mark an object */
107 #define SetMarked(x)    Type(x) |= TYPE_MARKED
108 /** Unmark an object */
109 #define ClearMarked(x)  Type(x) &= ~TYPE_MARKED
110
111
112 /* Section I: do_destroy() and related functions. This section is where
113  * the human interface of do_destroy() should largely be determined.
114  */
115
116 /* Methinks it's time to consider human interfaces criteria for destruction
117  * as well as to consider the invariants that need to be maintained.
118  *
119  * My major criteria are these (with no implied ranking, since I haven't
120  * decided how they balance out):
121  *
122  * 1) It's easy to destroy things you intend to destroy.
123  *
124  * 2) It's easy to correct from destroying things that you don't intend
125  * to destroy. This includes both typos and realizing that you didn't mean
126  * to destroy that. This principle requires two sub-principles:
127  *      a) The player gets notified when something 'important' gets
128  *         marked to be destroyed--and gets told .what. is marked.
129  *      b) The actual destruction of the important thing is delayed
130  *         long enough that you can recover from it.
131  *
132  * 3) You can't destroy something you don't have the proper privileges to
133  * destroy. (Obvious, but still worth writing down.)
134  *
135  * To try to achieve a reasonable balance between items 1) and 2), we
136  * have the following design:
137  * Everything is finally destroyed on the second purge after the @destroy
138  * command is done, unless it is set !GOING in the meantime.
139  * @destroying an object while it is set GOING destroys it immediately.
140  *
141  * Let me introduce a little jargon for this discussion:
142  *      pre-destroying an object == setting it GOING, running the @adestroy.
143  *              (Pre-destroying corresponds to phase 1 above.)
144  *      purging an object == actually irrevocably making it vanish.
145  *              (This corresponds to phases 2 and 3 above.)
146  *      undestroying an object == setting it !GOING, etc.
147  *
148  * We would also like to have an @adestroy attribute that contains
149  * code to be executed when the object is destroyed. This is
150  * complicated by the fact that the object is going to be
151  * destroyed. To work around this, we run the @adestroy when the
152  * object is pre-destroyed, not when it's actually purged. This
153  * introduces the possibility that the adestroy may be invoked for
154  * something that is then undestroyed. To compensate for that, we run
155  * the @startup attribute when something is undestroyed.
156  *
157  * Another issue is how to run the @adestroy for objects that are
158  * destroyed as a consequence of other objects being destroyed. For
159  * example, when rooms are destroyed, any exits leading from those
160  * rooms are also destroyed, and when a player is destroyed, !SAFE
161  * objects they own may also be destroyed.
162  *
163  * To handle this, we do the following:
164  * pre-destroying a room pre-destroys all its exits.
165  * pre-destroying a player pre-destroys all the objects that will be purged
166  * when that player is purged.
167  *
168  * This requires the following about undestroys:
169  * undestroying an exit undestroys its source room.
170  * undestroying any object requires undestroying its owner.
171  *
172  * But it also seems to require the following in order to make '@destroy
173  * foo; @undestroy foo' a no-op for all foo:
174  * undestroying a room undestroys all its exits.
175  * undestroying a player undestroys all its GOING things.
176  *
177  * Now, consider this scenario:
178  * Player A owns room #1. Player B owns exit #2, whose source is room #1.
179  * Player B owns thing #3. Player A and player B are both pre-destroyed;
180  * none of the objects are set SAFE. Thing #3 is then undestroyed.
181  *
182  * If you trace through the dependencies, you find that this involves
183  * undestroying all the objects, including both players! Is that what
184  * we want? It seems to me that it would be very surprising in practice.
185  *
186  * To reconcile this, we introduce the following compromise.
187  * undestroying a room undestroys all exits in the room that are not owned
188  *      by a GOING player or set SAFE..
189  * undestroying a player undestroys all objects he owns that are not exits
190  *      in a GOING room that he does not own.
191  *
192  * In this way, the propagation of previous scenario would die out at exit
193  * #2, which would stay GOING. Metaphorically, there are two 'votes' for
194  * its destruction: the destruction of room #1, and the destruction of
195  * player B. Undestroying player B by undestroying thing #3 removes one
196  * of the 'votes' for exit #2's destruction, but there would still be
197  * the vote from room #1. 
198  */
199
200
201 /** Determine what object to destroy and if we're allowed.
202  * Do all matching and permissions checking. Returns the object to be
203  * destroyed if all the permissions checks are successful, otherwise
204  * return NOTHING.
205  */
206 static dbref
207 what_to_destroy(dbref player, char *name, int confirm)
208 {
209   dbref thing;
210
211   thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
212   if (thing == NOTHING)
213     return NOTHING;
214
215   if (IsGarbage(thing)) {
216     notify(player, T("Destroying that again is hardly necessary."));
217     return NOTHING;
218   }
219   if (God(thing)) {
220     notify(player, T("Destroying God would be blasphemous."));
221     return NOTHING;
222   }
223   /* To destroy, you must either:
224    * 1. Control it
225    * 2. Control its source or destination if it's an exit
226    * 3. Be dealing with a dest-ok thing and pass its lock/destroy
227    */
228   if (!controls(player, thing) &&
229       !(IsExit(thing) &&
230         (controls(player, Destination(thing)) ||
231          controls(player, Source(thing)))) &&
232       !(DestOk(thing) && eval_lock(player, thing, Destroy_Lock))) {
233     notify(player, T("Permission denied."));
234     return NOTHING;
235   }
236   if (thing == PLAYER_START || thing == MASTER_ROOM || thing == BASE_ROOM ||
237       thing == DEFAULT_HOME || God(thing)) {
238     notify(player, T("That is too special to be destroyed."));
239     return NOTHING;
240   }
241   if (REALLY_SAFE) {
242     if (Safe(thing) && !DestOk(thing)) {
243       notify(player,
244              T
245              ("That object is set SAFE. You must set it !SAFE before destroying it."));
246       return NOTHING;
247     }
248   } else {                      /* REALLY_SAFE */
249     if (Safe(thing) && !DestOk(thing) && !confirm) {
250       notify(player, T("That object is marked SAFE. Use @nuke to destroy it."));
251       return NOTHING;
252     }
253   }
254   /* check to make sure there's no accidental destruction */
255   if (!confirm && !Owns(player, thing) && !DestOk(thing)) {
256     notify(player,
257            T("That object does not belong to you. Use @nuke to destroy it."));
258     return NOTHING;
259   }
260   /* what kind of thing we are destroying? */
261   switch (Typeof(thing)) {
262   case TYPE_PLAYER:
263     if (!IsPlayer(player)) {
264       notify(player, T("Programs don't kill people; people kill people!"));
265       return NOTHING;
266     }
267     /* The only player a player can own() is themselves...
268      * If they somehow manage to own() another player, they can't
269      * nuke that one either...which seems like a good plan, although
270      * the error message is a bit confusing. -DTC
271      */
272     if (!Wizard(player)) {
273       notify(player, T("Sorry, no suicide allowed."));
274       return NOTHING;
275     }
276     /* Already checked for God(thing), so use Wizard() */
277     if (Wizard(thing) && !God(player)) {
278       notify(player, T("Even you can't do that!"));
279       return NOTHING;
280     }
281     if (Connected(thing)) {
282       notify(player,
283              T("How gruesome. You may not destroy players who are connected."));
284       return NOTHING;
285     }
286     if (!confirm) {
287       notify(player, T("You must use @nuke to destroy a player."));
288       return NOTHING;
289     }
290     break;
291   case TYPE_THING:
292     if (!confirm && Wizard(thing)) {
293       notify(player,
294              T("That object is set WIZARD. You must use @nuke to destroy it."));
295       return NOTHING;
296     }
297     break;
298   case TYPE_ROOM:
299     break;
300   case TYPE_EXIT:
301     break;
302   }
303   return thing;
304
305 }
306
307
308 /** User interface to destroy an object.
309  * \verbatim
310  * This is the top-level function for @destroy.
311  * \endverbatim
312  * \param player the enactor.
313  * \param name name of object to destroy.
314  * \param confirm if 1, called with /override (or nuke).
315  */
316 void
317 do_destroy(dbref player, char *name, int confirm)
318 {
319   dbref thing;
320   thing = what_to_destroy(player, name, confirm);
321   if (!GoodObject(thing))
322     return;
323
324   /* If thing has already been marked for destruction, go ahead and
325    * destroy immediately.
326    */
327   if (Going(thing)) {
328     free_object(thing);
329     notify(player, T("Destroyed."));
330     return;
331   }
332   /* Present informative messages. */
333   if (!REALLY_SAFE && Safe(thing))
334     notify(player,
335            T
336            ("Warning: Target is set SAFE, but scheduling for destruction anyway."));
337   switch (Typeof(thing)) {
338   case TYPE_ROOM:
339     /* wait until dbck */
340     notify_except(Contents(thing), NOTHING,
341                   T("The room shakes and begins to crumble."), 0);
342     if (Owns(player, thing))
343       notify_format(player,
344                     T("You will be rewarded shortly for %s."),
345                     object_header(player, thing));
346     else {
347       notify_format(player,
348                     T
349                     ("The wrecking ball is on its way for %s's %s and its exits."),
350                     Name(Owner(thing)), object_header(player, thing));
351       notify_format(Owner(thing),
352                     T("%s has scheduled your room %s to be destroyed."),
353                     Name(player), object_header(Owner(thing), thing));
354     }
355     break;
356   case TYPE_PLAYER:
357     /* wait until dbck */
358     notify_format(player,
359                   (DESTROY_POSSESSIONS ?
360                    (REALLY_SAFE ?
361                     T
362                     ("%s and all their (non-SAFE) objects are scheduled to be destroyed.")
363                     :
364                     T
365                     ("%s and all their objects are scheduled to be destroyed."))
366                    : T("%s is scheduled to be destroyed.")),
367                   object_header(player, thing));
368     break;
369   case TYPE_THING:
370     if (!Owns(player, thing)) {
371       notify_format(player, T("%s's %s is scheduled to be destroyed."),
372                     Name(Owner(thing)), object_header(player, thing));
373       if (!DestOk(thing))
374         notify_format(Owner(thing),
375                       T("%s has scheduled your %s for destruction."),
376                       Name(player), object_header(Owner(thing), thing));
377     } else {
378       notify_format(player, T("%s is scheduled to be destroyed."),
379                     object_header(player, thing));
380     }
381     break;
382   case TYPE_EXIT:
383     if (!Owns(player, thing)) {
384       notify_format(Owner(thing),
385                     T("%s has scheduled your %s for destruction."),
386                     Name(player), object_header(Owner(thing), thing));
387       notify_format(player,
388                     T("%s's %s is scheduled to be destroyed."),
389                     Name(Owner(thing)), object_header(player, thing));
390     } else
391       notify_format(player,
392                     T("%s is scheduled to be destroyed."),
393                     object_header(player, thing));
394     break;
395   default:
396     do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in do_destroy."));
397     return;
398   }
399
400   pre_destroy(player, thing);
401   return;
402 }
403
404
405 /** Spare an object from slated destruction.
406  * \verbatim
407  * This is the top-level function for @undestroy.
408  * Not undestroy, quite--it's actually 'remove it from its status as about
409  * to be destroyed.'
410  * \endverbatim
411  * \param player the enactor.
412  * \param name name of object to be spared.
413  */
414 void
415 do_undestroy(dbref player, char *name)
416 {
417   dbref thing;
418   thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
419   if (!GoodObject(thing)) {
420     return;
421   }
422   if (!controls(player, thing)) {
423     notify(player, T("Alas, your efforts of mercy are in vain."));
424     return;
425   }
426   if (undestroy(player, thing)) {
427     notify_format(Owner(thing),
428                   T("Your %s has been spared from destruction."),
429                   object_header(Owner(thing), thing));
430     if (player != Owner(thing)) {
431       notify_format(player,
432                     T("%s's %s has been spared from destruction."),
433                     Name(Owner(thing)), object_header(player, thing));
434     }
435   } else {
436     notify(player, T("That can't be undestroyed."));
437   }
438 }
439
440
441
442 /* Section II: Functions that manage the actual work of destroying
443  * Objects.
444  */
445
446 /* Schedule something to be destroyed, run @adestroy, etc. */
447 static void
448 pre_destroy(dbref player, dbref thing)
449 {
450   dbref tmp;
451   if (Going(thing) || IsGarbage(thing)) {
452     /* we've already covered this thing. No need to do so again. */
453     return;
454   }
455   set_flag_internal(thing, "GOING");
456   clear_flag_internal(thing, "GOING_TWICE");
457
458   /* Present informative messages, and do recursive destruction. */
459   switch (Typeof(thing)) {
460   case TYPE_ROOM:
461     DOLIST(tmp, Exits(thing)) {
462       pre_destroy(player, tmp);
463     }
464     break;
465   case TYPE_PLAYER:
466     if (DESTROY_POSSESSIONS) {
467       for (tmp = 0; tmp < db_top; tmp++) {
468         if (Owner(tmp) == thing &&
469             (tmp != thing) && (!REALLY_SAFE || !Safe(thing))) {
470           pre_destroy(player, tmp);
471         }
472       }
473     }
474     break;
475   case TYPE_THING:
476     break;
477   case TYPE_EXIT:
478     /* This is the only case in which we might end up destroying something
479      * whose owner hasn't already been notified. */
480     if ((Owner(thing) != Owner(Source(thing))) && Going(Source(thing))) {
481       if (!Owns(player, thing)) {
482         notify_format(Owner(thing),
483                       T("%s has scheduled your %s for destruction."),
484                       Name(player), object_header(Owner(thing), thing));
485       }
486     }
487     break;
488   default:
489     do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in pre_destroy."));
490     return;
491   }
492
493   if (ADESTROY_ATTR)
494     did_it(player, thing, NULL, NULL, NULL, NULL, "ADESTROY", NOTHING);
495
496   return;
497
498 }
499
500
501 /** Spare an object from destruction.
502  * Not undestroy, quite--it's actually 'remove it from its status as about
503  * to be destroyed.' This is the internal function used in hardcode.
504  * \param player the enactor.
505  * \param thing dbref of object to be spared.
506  * \return 1 successful undestruction.
507  * \return 0 thing is not a valid object to undestroy.
508  */
509 int
510 undestroy(dbref player, dbref thing)
511 {
512   dbref tmp;
513   if (!Going(thing) || IsGarbage(thing)) {
514     return 0;
515   }
516   clear_flag_internal(thing, "GOING");
517   clear_flag_internal(thing, "GOING_TWICE");
518   if (!Halted(thing))
519     (void) queue_attribute_noparent(thing, "STARTUP", thing);
520   /* undestroy owner, if need be. */
521   if (Going(Owner(thing))) {
522     if (Owner(thing) != player) {
523       notify_format(player,
524                     T("%s has been spared from destruction."),
525                     object_header(player, Owner(thing)));
526       notify_format(Owner(thing),
527                     T("You have been spared from destruction by %s."),
528                     Name(player));
529     } else {
530       notify(player, T("You have been spared from destruction."));
531     }
532     (void) undestroy(player, Owner(thing));
533   }
534   switch (Typeof(thing)) {
535   case TYPE_PLAYER:
536     if (DESTROY_POSSESSIONS)
537       /* Undestroy all objects owned by players, except exits that are in
538        * rooms owned by other players that are set GOING, since those will
539        * be purged when the room is purged.
540        */
541       for (tmp = 0; tmp < db_top; tmp++) {
542         if (Owns(thing, tmp) &&
543             (tmp != thing) &&
544             !(IsExit(tmp) && !Owns(thing, Source(tmp)) && Going(Source(tmp)))) {
545           (void) undestroy(player, tmp);
546         }
547       }
548     break;
549   case TYPE_THING:
550     break;
551   case TYPE_EXIT:
552     /* undestroy containing room. */
553     if (Going(Source(thing))) {
554       (void) undestroy(player, Source(thing));
555       notify_format(player,
556                     T("The room %s has been spared from destruction."),
557                     object_header(player, Source(thing)));
558       if (Owner(Source(thing)) != player) {
559         notify_format(Owner(Source(thing)),
560                       T("The room %s has been spared from destruction by %s."),
561                       object_header(Owner(Source(thing)), Source(thing)),
562                       Name(player));
563       }
564     }
565     break;
566   case TYPE_ROOM:
567     /* undestroy exits in this room, except exits that are going to be
568      * destroyed anyway due to a GOING player.
569      */
570     DOLIST(tmp, Exits(thing)) {
571       if (DESTROY_POSSESSIONS ? (!Going(Owner(tmp)) || Safe(tmp)) : 1) {
572         (void) undestroy(player, tmp);
573       }
574     }
575     break;
576   default:
577     do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in un_destroy."));
578     return 0;
579   }
580   return 1;
581 }
582
583
584 /* Does the real work of freeing all the memory and unlinking an object.
585  * This is going to have to be very tightly coupled with the implementation;
586  * if the database format changes, this will likely have to change too.
587  */
588 static void
589 free_object(dbref thing)
590 {
591   dbref i, loc;
592   if (!GoodObject(thing))
593