blob: bb4ac10c94b8c2b1f4c53989ba2c0dbc863999f8 [file] [log] [blame]
Zesstrad771f4e2025-06-27 19:39:13 +02001/* MorgenGrauen MUDlib
2 /p/daemon/errord.c
3 speichert Fehler und Warnungen
4 Autor: Zesstra
5 $Id: errord.c 9439 2016-01-20 09:48:28Z Zesstra $
6 ggf. Changelog:
7*/
8
9#pragma strict_types,save_types,rtt_checks
10#pragma no_clone
11#pragma no_shadow
12#pragma no_inherit
13#pragma pedantic
14#pragma range_check
15#pragma warn_deprecated
16
17#include <config.h>
18#include <wizlevels.h>
19#include <defines.h>
20#include <debug_info.h>
21#include <commands.h>
22#include <wizlevels.h>
23#include <mail.h>
24#include <tls.h>
25#include <events.h>
26
27inherit "/secure/errord-structs";
28
29#define __NEED_IMPLEMENTATION__
30#include "errord.h"
31#undef __NEED_IMPLEMENTATION__
32
33#define SAVEFILE (__DIR__+"ARCH/errord")
34
35#define TI this_interactive()
36
37private int access_check(string uid,int mode);
38private varargs int set_lock(int issueid, int lock, string note);
39private varargs int set_resolution(int issueid, int resolution, string note);
40public string* print_type(int type);
41
42private int versende_mail(struct fullissue_s fehler);
43
44nosave mapping lasterror; // die letzen 5 jeder Art.
45
46
47/* ******************* Helfer **************************** */
48
49public int getErrorID(string hashkey)
50{
51 int** row=sl_exec("SELECT id from issues WHERE hashkey=?1;",
52 hashkey);
53 //DEBUG(sprintf("getEID: %s: %O\n",hashkey,row));
54 if (pointerp(row))
55 {
56 return row[0][0];
57 }
58 return -1;
59}
60
61// note->id muss auf einen Eintrag in issues verweisen, es erfolgt keine
62// Pruefung.
63int db_add_note(struct note_s note)
64{
65 sl_exec("INSERT INTO notes(issueid,time,user,txt) "
66 "VALUES(?1,?2,?3,?4);",
67 to_array(note)...);
68 return sl_insert_id();
69}
70
71private struct frame_s* db_get_stack(int issueid)
72{
73 mixed rows = sl_exec("SELECT * FROM stacktraces WHERE issueid=?1 "
74 "ORDER BY rowid;", issueid);
75 if (pointerp(rows))
76 {
77 struct frame_s* stack = allocate(sizeof(rows));
78 int i;
79 foreach(mixed row : rows)
80 {
81 stack[i] = to_struct(row, (<frame_s>));
82 ++i;
83 }
84 return stack;
85 }
86 return 0;
87}
88
89private struct note_s* db_get_notes(int issueid)
90{
91 mixed rows = sl_exec("SELECT * FROM notes WHERE issueid=?1 "
92 "ORDER BY rowid;", issueid);
93 if (pointerp(rows))
94 {
95 struct note_s* notes = allocate(sizeof(rows));
96 int i;
97 foreach(mixed row : rows)
98 {
99 notes[i] = to_struct(row, (<note_s>));
100 ++i;
101 }
102 return notes;
103 }
104 return 0;
105}
106
107// einen durch id oder hashkey bezeichneten Eintrag als fullissue_s liefern.
108private struct fullissue_s db_get_issue(int issueid, string hashkey)
109{
110 mixed rows = sl_exec("SELECT * FROM issues WHERE id=?1 OR hashkey=?2;",
111 issueid, hashkey);
112 if (pointerp(rows))
113 {
114 // Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
115 // die gleiche Reihenfolge wie in der struct haben! Entweder immer
116 // sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
117 struct fullissue_s issue = to_struct( rows[0], (<fullissue_s>) );
118 if (issue->type == T_RTERROR)
119 issue->stack = db_get_stack(issue->id);
120 issue->notes = db_get_notes(issue->id);
121 return issue;
122 }
123 return 0;
124}
125
126private struct fullissue_s filter_private(struct fullissue_s issue)
127{
128 //momentan wird F_CLI, also die Spielereingabe vor dem Fehler
129 //ausgefiltert, wenn TI kein EM oder man in process_string() ist.
130
131 //Wenn EM und nicht in process_string() oder die Spielereingabe gar nicht
132 //im Fehlereintrag drinsteht: ungefiltert zurueck
133 if (!issue->command ||
134 (!process_call() && ARCH_SECURITY) )
135 return issue;
136
137 //sonst F_CLI rausfiltern, also Kopie und in der Kopie aendern.
138 issue->command="Bitte EM fragen";
139 return issue;
140}
141
142// setzt oder loescht die Loeschsperre.
143// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
144// Rueckgabe: -1, wenn Issue nicht existiert, -2 wenn bereits resolved, -3
145// wenn keine Aenderung noetig, sonst den neuen Sperrzustand
146int db_set_lock(int issueid, int lockstate, string note)
147{
148 int** rows = sl_exec("SELECT locked,resolved FROM issues WHERE id=?1;",
149 issueid);
150 if (!rows)
151 return -1; // nicht vorhanden.
152
153 if (rows[0][1])
154 return -2; // bereits resolved -> Sperre nicht moeglich.
155
156
157 if (lockstate && !rows[0][0])
158 {
159 // Sperren
160// sl_exec("BEGIN TRANSACTION;");
161 sl_exec("UPDATE issues SET locked=1,locked_by=?2,locked_time=?3,mtime=?3 "
162 "WHERE id=?1;",
163 issueid, getuid(TI), time());
164 db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
165 txt: sprintf("Lock gesetzt: %s",
166 note ? note : "<kein Kommentar>")) );
167// sl_exec("COMMIT;");
168 return 1;
169 }
170 else if (!lockstate && rows[0][0])
171 {
172 // entsperren
173// sl_exec("BEGIN TRANSACTION;");
174 sl_exec("UPDATE issues SET locked=0,locked_by=0,locked_time=0,mtime=?2 "
175 "WHERE id=?1;", issueid, time());
176 db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
177 txt: sprintf("Lock geloescht: %s",
178 note ? note : "<kein Kommentar>")) );
179// sl_exec("COMMIT;");
180 return 0;
181 }
182 // nix aendern.
183 return -3;
184}
185
186// markiert ein Issue als gefixt oder nicht gefixt.
187// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
188// Rueckgabe: -1, wenn Issue nicht existiert, -3 wenn keine Aenderung noetig,
189// sonst den neuen Sperrzustand
190int db_set_resolution(int issueid, int resolved, string note)
191{
192 int** rows = sl_exec("SELECT resolved FROM issues WHERE id=?1;",
193 issueid);
194 if (!rows)
195 return -1; // nicht vorhanden.
196
197 if (resolved && !rows[0][0])
198 {
199 // Als gefixt markieren.
200// sl_exec("BEGIN TRANSACTION;");
201 sl_exec("UPDATE issues SET resolved=1,resolver=?2,mtime=?3 "
202 "WHERE id=?1;",
203 issueid, getuid(TI),time());
204 db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
205 txt: sprintf("Fehler gefixt: %s",
206 note ? note : "<kein Kommentar>")) );
207// sl_exec("COMMIT;");
208 return 1;
209 }
210 else if (!resolved && rows[0][0])
211 {
212 // als nicht gefixt markieren.
213// sl_exec("BEGIN TRANSACTION;");
214 sl_exec("UPDATE issues SET resolved=0,resolver=0,mtime=?2 "
215 "WHERE id=?1;", issueid, time());
216 db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
217 txt: sprintf("Fix zurueckgezogen: %s",
218 note ? note : "<kein Kommentar>")) );
219// sl_exec("COMMIT;");
220 return 0;
221 }
222 // nix aendern.
223 return -3;
224
225}
226
227// Transferiert ein Issue zu einer neuen zustaendigen UID
228// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
229// Rueckgabe: -1, wenn Issue nicht existiert, -3 wenn keine Aenderung noetig,
230// 1, wenn erfolgreich neu zugewiesen
231int db_reassign_issue(int issueid, string newuid, string note)
232{
233 string** rows = sl_exec("SELECT uid FROM issues WHERE id=?1;",
234 issueid);
235 if (!rows)
236 return -1; // nicht vorhanden.
237
238 if (!stringp(newuid))
239 return(-2);
240
241 if (newuid != rows[0][0])
242 {
243// sl_exec("BEGIN TRANSACTION;");
244 sl_exec("UPDATE issues SET uid=?2,mtime=?3 WHERE id=?1;",
245 issueid, newuid,time());
246 db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
247 txt: sprintf("Fehler von %s an %s uebertragen. (%s)",
248 rows[0][0], newuid,
249 note ? note : "<kein Kommentar>")) );
250// sl_exec("COMMIT;");
251 return 1;
252 }
253
254 return -3;
255}
256
257// inkrementiert count und aktualisiert mtime, atime.
258// Ausserdem wird ggf. das Loeschflag genullt - ein erneut aufgetretener
259// Fehler sollte anschliessend nicht mehr geloescht sein. Geloeste
260// (resolved) Eintraege werden NICHT auf ungeloest gesetzt. Vermutlich trat
261// der Fehler in einem alten Objekte auf...
262// Issue muss in der DB existieren.
263int db_countup_issue(int issueid)
264{
265 sl_exec("UPDATE issues SET count=count+1,mtime=?2,atime=?2,deleted=0 WHERE id=?1;",
266 issueid,time());
267 return 1;
268}
269
270// Das Issue wird ggf. ent-loescht und als nicht resvolved markiert.
271// Sind pl und msg != 0, wird eine Notiz angehaengt.
272// aktualisiert mtime, atime.
273// Issue muss in der DB existieren.
274int db_reopen_issue(int issueid, string pl, string msg)
275{
276 int** row=sl_exec("SELECT deleted,resolved from issues WHERE id=?1;",
277 issueid);
278 if (pointerp(row)
279 && (row[0][0] || row[0][1]) )
280 {
281// sl_exec("BEGIN TRANSACTION;");
282 if (pl && msg)
283 {
284 db_add_note( (<note_s> id: issueid,
285 time: time(),
286 user: pl,
287 txt: msg) );
288 }
289 sl_exec("UPDATE issues SET "
290 "deleted=0,resolved=0,resolver=0,mtime=?2,atime=?2 WHERE id=?1;",
291 issueid,time());
292// sl_exec("COMMIT;");
293 }
294 return 1;
295}
296
297int db_insert_issue(struct fullissue_s issue)
298{
299 //DEBUG(sprintf("db_insert: %O\n", issue));
300
301 mixed row=sl_exec("SELECT id from issues WHERE hashkey=?1;",
302 issue->hashkey);
303 //DEBUG(sprintf("insert: %s: %O\n",issue->hashkey,row));
304 if (pointerp(row))
305 {
306 issue->id=row[0][0];
307 return db_countup_issue(issue->id);
308 }
309// sl_exec("BEGIN TRANSACTION;");
310 sl_exec("INSERT INTO issues(hashkey,uid,type,mtime,ctime,atime,count,"
311 "deleted,resolved,locked,locked_by,locked_time,resolver,message,"
312 "loadname,obj,prog,loc,titp,tienv,hbobj,caught,command,verb) "
313 "VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,"
314 "?16,?17,?18,?19,?20,?21,?22,?23,?24);",
315 (to_array(issue)[1..24])...);
316 issue->id=sl_insert_id();
317
318 if (pointerp(issue->stack))
319 {
320 foreach(struct frame_s entry : issue->stack)
321 {
322 entry->id = issue->id;
323 sl_exec("INSERT INTO stacktraces(issueid,type,name,prog,obj,loc,ticks) "
324 "VALUES(?1,?2,?3,?4,?5,?6,?7);",
325 to_array(entry)...);
326 }
327 }
328 if (pointerp(issue->notes))
329 {
330 foreach(struct note_s entry : issue->notes)
331 {
332 entry->id = issue->id;
333 sl_exec("INSERT INTO notes(issueid,time,user,txt) "
334 "VALUES(?1,?2,?3,?4);",
335 to_array(entry)...);
336 }
337 }
338// sl_exec("COMMIT;");
339
340 return issue->id;
341}
342
343
344// loggt einen T_REPORTED_ERR, T_REPORTED_IDEA, T_REPORTED_TYPO,
345// T_REPORTED_MD, T_REPORTED_SYNTAX
346public string LogReportedError(mapping err)
347{
348 //darf nur von Spielershells oder Objekt in /obj/ (z.B. Fehlerteufel oder
349 //vitem_proxy) gerufen werden.
350 if (extern_call() && !previous_object()
351 || (strstr(load_name(previous_object()),"/std/shells/") == -1
352 && strstr(load_name(previous_object()), "/"LIBOBJDIR"/") == -1))
353 return 0;
354
355 //DEBUG("LogReportedError\n");
356 // DEBUG(sprintf("%O\n",err));
357 string uid = ({string})master()->creator_file(err[F_OBJ]);
358
359 // default-Typ
360 if (!member(err, F_TYPE)) err[F_TYPE] = T_REPORTED_ERR;
361
362 // div. Keys duerfen nicht gesetzt sein.
363 err -= ([F_STATE, F_READSTAMP, F_CAUGHT, F_STACK, F_CLI, F_VERB,
364 F_LOCK, F_RESOLVER, F_NOTES]);
365
366 // Errormapping in issue-struct umwandeln und befuellen.
367 struct fullissue_s issue = (<fullissue_s>);
368 issue->type = err[F_TYPE];
369 issue->uid = uid;
370 issue->mtime = issue->ctime = issue->atime = time();
371 issue->count=1;
372 issue->loadname = load_name(err[F_OBJ]);
373 issue->message = err[F_MSG];
374 issue->obj = objectp(err[F_OBJ]) ? object_name(err[F_OBJ]) : err[F_OBJ];
375 issue->loc = err[F_LINE];
376 // Normalisieren auf fuehrenden / und kein .c
377<<<<<<< HEAD
378 if (err[F_PROG]!="unbekannt")
379 issue->prog=err[F_PROG][0] in "/#<" ? err[F_PROG] : "/" + err[F_PROG];
380 else
381 issue->prog = "unbekannt";
382=======
383 if (stringp(err[F_PROG]))
384 issue->prog = load_name(err[F_PROG]);
385>>>>>>> a811db8d (Fehlendes Programm als 0 speichern)
386 issue->titp = getuid(this_interactive() || this_player());
387 issue->tienv = object_name(environment(this_interactive() || this_player()));
388
389 //DEBUG(sprintf("%O\n",issue));
390 issue->hashkey = hash(TLS_HASH_MD5,
391 sprintf("%d%s%s", issue->type, issue->loadname, issue->message));
392
393 // ggf. vorhandenen Fehler suchen - zugegeben: sollte bei von Spielern
394 // gemeldeten Dingen vermutlich nie vorkommen...
395 int oldid = getErrorID(issue->hashkey);
396 if (oldid >= 0)
397 {
398 // ggf. sicherstellen, dass er wieder eroeffnet wird.
399 db_reopen_issue(oldid, "<ErrorD>",
400 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
401 db_countup_issue(oldid);
402 return issue->hashkey;
403 }
404
405 // sonst fuegen wir einen neuen Eintrag hinzu
406 // Spielergemeldete Bugs werden erstmal vor automatischem Loeschen
407 // geschuetzt, bis ein zustaendiger Magier ihn zur Kenntnis nimmt und
408 // entsperrt.
409 issue->locked = 1;
410 issue->locked_by = getuid(TI || PL);
411 issue->locked_time = time();
412
413 // In DB eintragen.
414 issue->id = db_insert_issue(issue);
415
416 lasterror[issue->type]=issue->id;
417
418 // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
419 EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
420 ([F_TYPE: issue->type, F_HASHKEY:issue->hashkey,
421 F_UID:issue->uid, F_ID: issue->id]));
422
423 DEBUG(sprintf("LogReportedError: %s\n",issue->hashkey));
424
425 return issue->hashkey;
426}
427
428//Fehler registrieren
429//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
430//Master gerufen!
431public int LogError(string msg,string prg,string curobj,int line,mixed culprit,
432 int caught)
433{
434 //DEBUG(sprintf("LogError: Prog: %O, Obj: %O,\n",prg,curobj));
435
436 //darf nur vom Master gerufen werden
437 if (!extern_call() ||
438 (previous_object() && previous_object() != master()))
439 return 0;
440
441 struct fullissue_s issue = (<fullissue_s>);
442
443 //UID bestimmen, curobj is 0 for lwobjects, then the program is used.
444 issue->uid=({string})master()->creator_file(curobj || prg);
445 //DEBUG(sprintf("LogError: UID: %s\n",issue->uid));
446
447 //Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
448 if (!stringp(curobj) || !sizeof(curobj))
449 issue->loadname = curobj = "<Unbekannt>";
450 else
451 {
452 //load_name nimmt Strings und Objects und konstruiert den loadname,
453 //wie er sein sollte, wenn das Objekt nicht mehr existiert.
454 issue->loadname=load_name(curobj);
455 }
456 if (!stringp(issue->loadname))
457 {
458 //hier kommt man rein, falls curobj ein 'kaputter' Name ist,
459 //d.h. load_name() 0 liefert.
460 issue->loadname="<Illegal object name>";
461 }
462
463 // Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
464 // ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
465 // nicht gespeichert.
466 if (this_interactive() && IS_LEARNER(this_interactive())
467 && strstr(issue->loadname,WIZARDDIR)==0
468 && ({int})this_interactive()->QueryProp(P_DONT_LOG_ERRORS))
469 {
470 return 0;
471 }
472
473 // prg und curobj auf fuehrenden / und ohne .c am Ende normieren.
474 if (stringp(prg))
475 issue->prog=prg[0] in "/#<" ? prg : "/" + prg;
476 if (stringp(curobj) && curobj[0]!='/')
477 {
478 curobj="/"+curobj;
479 }
480
481 issue->obj = curobj;
482 issue->loc = line;
483 issue->message = msg;
484 issue->ctime = issue->mtime = issue->atime = time();
485 issue->type = T_RTERROR;
486 issue->count = 1;
487 issue->caught = caught;
488
489 //Hashkey bestimmen: Typ, Name der Blueprint des buggenden Objekts,
490 //Programmname, Zeilennr., Fehlermeldung
491 //TODO: evtl. sha1() statt md5()?
492 issue->hashkey=hash(TLS_HASH_MD5,
493 sprintf("%d%s%s%d%s", T_RTERROR, issue->loadname||"",
494 issue->prog || "", issue->loc,
495 issue->message||"<No error message given.>"));
496 DEBUG(sprintf("LogError: Hashkey: %s", issue->hashkey));
497
498 // ggf. vorhandenen Fehler suchen
499 int oldid = getErrorID(issue->hashkey);
500 if (oldid >= 0)
501 {
502 db_reopen_issue(oldid, "<ErrorD>",
503 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
504 db_countup_issue(oldid);
505 return oldid;
506 }
507
508 //sonst fuegen wir einen neuen Eintrag hinzu
509 //DEBUG(sprintf("LogError: OBJ: %s, BP: %s",curobj,loadname));
510 // Wenn Fehler im HB, Objektnamen ermitteln
511 if (objectp(culprit))
512 issue->hbobj = object_name(culprit);
513
514 //gibt es einen TI/TP? Name mit erfassen
515 mixed tienv;
516 if(objectp(this_interactive()))
517 {
518 issue->titp=getuid(this_interactive());
519 tienv=environment(this_interactive());
520 }
521 else if (objectp(PL) && query_once_interactive(PL))
522 {
523 issue->titp=getuid(PL);
524 tienv=environment(PL);
525 }
526 else if (objectp(PL))
527 {
528 issue->titp=object_name(PL);
529 tienv=environment(PL);
530 }
531 if (objectp(tienv))
532 issue->tienv=object_name(tienv);
533
534 // Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
535 mixed cli;
536 if (pointerp(cli=command_stack()) && sizeof(cli))
537 {
538 issue->verb=cli[0][CMD_VERB];
539 issue->command=cli[0][CMD_TEXT];
540 }
541
542 //stacktrace holen
543 mixed stacktrace;
544 if (caught)
545 stacktrace=debug_info(DINFO_TRACE,DIT_ERROR);
546 else
547 stacktrace=debug_info(DINFO_TRACE,DIT_UNCAUGHT_ERROR);
548 // gueltige Stacktraces haben min. 2 Elemente.
549 // (leerer Trace: ({"No trace."}))
550 if (sizeof(stacktrace) > 1)
551 {
552 int i;
553 issue->stack = allocate(sizeof(stacktrace)-1);
554 // erstes Element ist 0 oder HB-Objekt: kein frame, daher ueberspringen
555 foreach(mixed entry : stacktrace[1..])
556 {
557 // frame->id will be set later by db_insert_issue().
558 struct frame_s frame = (<frame_s> type : entry[TRACE_TYPE],
559 name: entry[TRACE_NAME],
560 prog: entry[TRACE_PROGRAM],
561 obj: entry[TRACE_OBJECT],
562 loc: entry[TRACE_LOC],
563 ticks: entry[TRACE_EVALCOST]);
564 issue->stack[i] = frame;
565 ++i;
566 }
567 }
568
569 issue->id = db_insert_issue(issue);
570
571 lasterror[T_RTERROR]=issue->id;
572
573 // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
574 EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
575 ([ F_TYPE: T_RTERROR, F_HASHKEY: issue->hashkey, F_UID:
576 issue->uid, F_ID: issue->id ]));
577
578// DEBUG(sprintf("LogError: Fehlereintrag:\n%O\n",
579// errors[uid][hashkey]));
580// DEBUG(sprintf("LogError: Verbrauchte Ticks: %d\n",
581// 200000-get_eval_cost()));
582 return issue->id;
583}
584
585//Warnungen registrieren
586//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
587//Master gerufen!
588public int LogWarning(string msg,string prg,string curobj,int line, int in_catch)
589{
590 //DEBUG(sprintf("LogWarning: Prog: %O, Obj: %O,\n",prg,curobj));
591
592 //darf nur vom Master gerufen werden
593 if (!extern_call() ||
594 (previous_object() && previous_object() != master()))
595 return 0;
596
597 struct fullissue_s issue = (<fullissue_s>);
598
599 //UID bestimmen, curobj is 0 for lwobjects, then the program is used.
600 issue->uid=({string})master()->creator_file(curobj || prg);
601 //DEBUG(sprintf("LogWarning UID: %s\n",uid));
602
603 //Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
604 if (!stringp(curobj) || !sizeof(curobj))
605 issue->loadname = curobj = "<Unbekannt>";
606 else
607 {
608 //load_name nimmt Strings und Objects und konstruiert den loadname,
609 //wie er sein sollte, wenn das Objekt nicht mehr existiert.
610 issue->loadname=load_name(curobj);
611 }
612
613 if (!stringp(issue->loadname))
614 //hier sollte man reinkommen, falls curobj ein 'kaputter' Name ist,
615 //d.h. load_name() 0 liefert.
616 issue->loadname="<Illegal object name>";
617
618 // prg und curobj auf abs. Pfade normalisieren. Dabei Spezialfaelle
619 // beruecksichtigen, z.B. dass prg "#'call_out" sein kann.
620 if (stringp(prg))
621 issue->prog=prg[0] in "/#<" ? prg : "/" + prg;
622 if (stringp(curobj) && curobj[0]!='/')
623 {
624 curobj="/"+curobj;
625 }
626
627 //DEBUG(sprintf("LogWarning: OBJ: %s, BP: %s\n",curobj,blue));
628
629 // Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
630 // ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
631 // nicht gespeichert.
632 if (this_interactive() && IS_LEARNER(this_interactive())
633 && strstr(issue->loadname,WIZARDDIR)==0
634 && ({int})this_interactive()->QueryProp(P_DONT_LOG_ERRORS)) {
635 return 0;
636 }
637
638 //Hashkey bestimmen, Typ, Name der Blueprint des buggenden Objekts, Programm
639 //Zeilennr., Warnungsmeldung
640 issue->hashkey=hash(TLS_HASH_MD5,
641 sprintf("%d%s%s%d%s", T_RTWARN, issue->loadname,
642 issue->prog||"", line, msg));
643 //DEBUG(sprintf("LogWarning: Hashkey: %s",hashkey));
644
645
646 // ggf. vorhandenen Fehler suchen
647 int oldid = getErrorID(issue->hashkey);
648 if (oldid >= 0)
649 {
650 db_reopen_issue(oldid, "<ErrorD>",
651 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
652 db_countup_issue(oldid);
653 return oldid;
654 }
655
656 //sonst fuegen wir einen neuen Eintrag hinzu
657 // erstmal vervollstaendigen
658 issue->obj = curobj;
659 issue->message = msg;
660 issue->ctime = issue->mtime = issue->atime = time();
661 issue->loc = line;
662 issue->count = 1;
663 issue->type = T_RTWARN;
664 issue->caught = in_catch;
665
666 //gibt es einen TI/TP? Name mit erfassen
667 mixed tienv;
668 if(objectp(this_interactive()))
669 {
670 issue->titp=getuid(this_interactive());
671 tienv=environment(this_interactive());
672 }
673 else if (objectp(PL) && query_once_interactive(PL))
674 {
675 issue->titp=getuid(PL);
676 tienv=environment(PL);
677 }
678 else if (objectp(PL))
679 {
680 issue->titp=object_name(PL);
681 tienv=environment(PL);
682 }
683 if (objectp(tienv))
684 issue->tienv=object_name(tienv);
685
686 // Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
687 mixed cli;
688 if (pointerp(cli=command_stack()) && sizeof(cli))
689 {
690 issue->verb=cli[0][CMD_VERB];
691 issue->command=cli[0][CMD_TEXT];
692 }
693
694 issue->id = db_insert_issue(issue);
695
696 lasterror[T_RTWARN]=issue->id;
697 // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
698 EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
699 ([F_TYPE: issue->type, F_ID: issue->id,
700 F_UID: issue->uid, F_HASHKEY: issue->hashkey]) );
701
702// DEBUG(sprintf("LogWarning: Warnungseintrag:\n%O\n",
703// warnings[uid][hashkey]));
704// DEBUG(sprintf("LogWarning: Verbrauchte Ticks: %d\n",
705// 200000-get_eval_cost()));
706 return issue->id;
707}
708
709//Warnungen und Fehler beim Kompilieren registrieren
710//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
711//Master gerufen!
712public int LogCompileProblem(string file,string msg,int warn) {
713
714 //DEBUG(sprintf("LogCompileProblem: Prog: %O, Obj: %O,\n",file,msg));
715
716 //darf nur vom Master gerufen werden
717 if (!extern_call() ||
718 (previous_object() && previous_object() != master()))
719 return 0;
720
721 struct fullissue_s issue = (<fullissue_s>);
722
723 //UID bestimmen
724 issue->uid=({string})master()->creator_file(file);
725 //DEBUG(sprintf("LogCompileProblem UID: %s\n",uid));
726
727 // An File a) fuehrenden / anhaengen und b) endendes .c abschneiden. Macht
728 // beides netterweise load_name().
729 issue->loadname = load_name(file);
730 issue->type = warn ? T_CTWARN : T_CTERROR;
731
732 //loggen wir fuer das File ueberhaupt?
733 if (member(BLACKLIST,explode(issue->loadname,"/")[<1])>=0)
734 return 0;
735
736 //Hashkey bestimmen, in diesem Fall einfach, wir koennen die
737 //Fehlermeldunge selber nehmen.
738 issue->hashkey=hash(TLS_HASH_MD5,sprintf(
739 "%d%s%s",issue->type,issue->loadname, msg));
740 //DEBUG(sprintf("LogCompileProblem: Hashkey: %s",hashkey));
741
742 // ggf. vorhandenen Fehler suchen
743 int oldid = getErrorID(issue->hashkey);
744 if (oldid >= 0)
745 {
746 db_reopen_issue(oldid, "<ErrorD>",
747 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
748 db_countup_issue(oldid);
749 return oldid;
750 }
751
752 // neuen Eintrag
753 issue->message = msg;
754 issue->count = 1;
755 issue->ctime = issue->mtime = issue->atime = time();
756
757 issue->id = db_insert_issue(issue);
758
759 if (warn) lasterror[T_CTWARN]=issue->id;
760 else lasterror[T_CTERROR]=issue->id;
761
762// DEBUG(sprintf("LogCompileProblem: Eintrag:\n%O\n",
763// (warn ? ctwarnings[uid][hashkey] : cterrors[uid][hashkey])));
764// DEBUG(sprintf("LogCompileProblem: Verbrauchte Ticks: %d\n",
765// 200000-get_eval_cost()));
766 return issue->id;
767}
768
769/* **************** Public Interface ****************** */
770
771//Einen bestimmten Fehler nach Hashkey suchen und als fullissue_s inkl. Notes
772//und Stacktrace liefern.
773struct fullissue_s QueryIssueByHash(string hashkey)
774{
775 struct fullissue_s issue = db_get_issue(0, hashkey);
776 if (structp(issue))
777 return filter_private(issue);
778 return 0;
779}
780//Einen bestimmten Fehler nach ID suchen und als fullissue_s inkl. Notes
781//und Stacktrace liefern.
782struct fullissue_s QueryIssueByID(int issueid)
783{
784 struct fullissue_s issue = db_get_issue(issueid, 0);
785 if (structp(issue))
786 return filter_private(issue);
787 return 0;
788}
789
790// den letzten Eintrag den jeweiligen Typ liefern.
791struct fullissue_s QueryLastIssue(int type)
792{
793 if (!member(lasterror,type))
794 return 0;
795 //einfach den kompletten letzten Eintrag zurueckliefern
796 return(QueryIssueByID(lasterror[type]));
797}
798
799// Liefert alle Issues, deren obj,prog oder loadname gleich <file> ist.
800public struct fullissue_s* QueryIssuesByFile(string file, int type)
801{
802 mixed rows = sl_exec("SELECT * FROM issues "
803 "WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
804 "AND deleted=0 AND resolved=0 AND type=?2"
805 "ORDER BY type,mtime;", file, type);
806 if (pointerp(rows))
807 {
808 struct fullissue_s* ilist = allocate(sizeof(rows));
809 int i;
810 foreach(mixed row : rows)
811 {
812 // Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
813 // die gleiche Reihenfolge wie in der struct haben! Entweder immer
814 // sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
815 struct fullissue_s issue = to_struct( row, (<fullissue_s>) );
816 if (issue->type == T_RTERROR)
817 issue->stack = db_get_stack(issue->id);
818 issue->notes = db_get_notes(issue->id);
819 ilist[i] = filter_private(issue);
820 ++i;
821 }
822 return ilist;
823 }
824 return 0;
825}
826
827// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
828// angebenen <type> und <uid>.
829varargs < <int|string>* >* QueryIssueListByFile(string file)
830{
831 mixed rows = sl_exec("SELECT id,loadname,obj,prog,loc FROM issues "
832 "WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
833 "AND deleted=0 AND resolved=0 "
834 "ORDER BY type,mtime;", file);
835 return rows;
836}
837
838// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
839// angebenen <type> und <uid>.
840varargs < <int|string>* >* QueryIssueListByLoadname(string file, int type)
841{
842 mixed rows;
843 if (type && file)
844 {
845 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
846 "WHERE loadname=?1 AND type=?2 AND deleted=0 "
847 "AND resolved=0 "
848 "ORDER BY type,mtime;", file, type);
849 }
850 else if (type)
851 {
852 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
853 "WHERE type=?1 AND deleted=0 AND resolved=0 "
854 "ORDER BY type,mtime;", type);
855 }
856 else if (file)
857 {
858 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
859 "WHERE loadname=?1 AND deleted=0 AND resolved=0 "
860 "ORDER BY type,mtime;", file);
861 }
862 return rows;
863}
864
865
866// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
867// angebenen <type> und <uid>.
868varargs < <int|string>* >* QueryIssueList(int type, string uid)
869{
870 mixed rows;
871 if (type && uid)
872 {
873 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
874 "WHERE type=?1 AND uid=?2 AND deleted=0 "
875 "AND resolved=0 "
876 "ORDER BY type,rowid;", type,uid);
877 }
878 else if (type)
879 {
880 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
881 "WHERE type=?1 AND deleted=0 AND resolved=0 "
882 "ORDER BY type,rowid;", type);
883 }
884 else if (uid)
885 {
886 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
887 "WHERE uid=?1 AND deleted=0 AND resolved=0 "
888 "ORDER BY type,rowid;", uid);
889 }
890 return rows;
891}
892
893varargs string* QueryUIDsForType(int type) {
894 //liefert alle UIDs fuer einen Fehlertyp oder fuer alle Fehlertypen
895 string** rows;
896
897 if (type)
898 {
899 rows = sl_exec("SELECT uid FROM issues "
900 "WHERE type=?1 AND deleted=0 AND resvoled=0;", type);
901 }
902 else
903 rows = sl_exec("SELECT uid FROM issues WHERE deleted=0 "
904 "AND resolved=0;");
905
906 return map(rows, function string (string* item)
907 {return item[0];} );
908}
909
910//Wieviele unterschiedliche Fehler in diesem Typ?
911varargs int QueryUniqueIssueCount(int type, string uid)
912{
913 int** rows;
914
915 if (type && uid)
916 {
917 rows = sl_exec("SELECT count(*) FROM issues "
918 "WHERE type=?1 AND uid=?2 AND deleted=0 AND resolved=0;",
919 type, uid);
920 }
921 else if (type)
922 {
923 rows = sl_exec("SELECT count(*) FROM issues "
924 "WHERE type=?1 AND deleted=0 AND resolved=0;",
925 type);
926 }
927 else if (uid)
928 {
929 rows = sl_exec("SELECT count(*) FROM issues "
930 "WHERE uid=?1 AND deleted=0 AND resolved=0;",
931 uid);
932 }
933 else
934 rows = sl_exec("SELECT count(*) FROM issues "
935 "WHERE deleted=0 AND resolved=0;");
936
937 return rows[0][0];
938}
939
940//Einen bestimmten Fehler loeschen
941varargs int ToggleDeleteError(int issueid, string note)
942{
943 mixed rows = sl_exec("SELECT uid,deleted from issues WHERE id=?1;",
944 issueid);
945 if (!pointerp(rows))
946 return -1;
947
948 if (!access_check(rows[0][0], M_DELETE))
949 //zugriff zum Schreiben nicht gestattet
950 return -10;
951
952// sl_exec("BEGIN TRANSACTION;");
953 if (rows[0][1])
954 {
955 // was deleted -> undelete it
956 sl_exec("UPDATE issues SET deleted=0,mtime=?2 WHERE id=?1;",
957 issueid,time());
958 db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
959 txt: sprintf("Loeschmarkierung entfernt. (%s)",
960 note ? note: "<kein Kommentar>")
961 ));
962 }
963 else
964 {
965 // was not deleted -> delete it.
966 sl_exec("UPDATE issues SET deleted=1,mtime=?2 WHERE id=?1;",
967 issueid, time());
968 db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
969 txt: sprintf("Loeschmarkierung gesetzt. (%s)",
970 note ? note: "<kein Kommentar>")
971 ));
972 }
973// sl_exec("COMMIT;");
974 return !rows[0][1];
975}
976
977
978// sperrt den Eintrag oder entsperrt ihn.
979// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
980// wird.
981varargs int LockIssue(int issueid, string note) {
982 return set_lock(issueid, 1, note);
983}
984
985varargs int UnlockIssue(int issueid, string note) {
986 return set_lock(issueid, 0, note);
987}
988
989// einen Fehler als gefixt markieren
990varargs int ResolveIssue(int issueid, string note) {
991 return set_resolution(issueid, 1, note);
992}
993// einen Fehler als nicht gefixt markieren
994varargs int ReOpenIssue(int issueid, string note) {
995 return set_resolution(issueid, 0, note);
996}
997
998varargs int AddNote(int issueid, string note)
999{
1000
1001 if (!stringp(note) || !sizeof(note))
1002 return(-3);
1003
1004 // existiert die ID in der DB?
1005 struct fullissue_s issue = db_get_issue(issueid,0);
1006 if (!issue)
1007 return -1;
1008
1009 if (!access_check(issue->uid, M_WRITE))
1010 //zugriff zum Schreiben nicht gestattet
1011 return(-10);
1012
1013 return db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
1014 txt: note));
1015}
1016
1017//Einen bestimmten Fehler einer anderen UID zuweisen.
1018//Hashkey ist zwar eindeutig, aber Angabe der
1019//der UID ist deutlich schneller. Weglassen des Typs nochmal langsamer. ;-)
1020//Potentiell also sehr teuer, wenn man UID oder UID+Typ weglaesst.
1021varargs int ReassignIssue(int issueid, string newuid, string note)
1022{
1023 struct fullissue_s issue = db_get_issue(issueid,0);
1024 if (!issue)
1025 return -1;
1026
1027 if (!access_check(issue->uid, M_REASSIGN))
1028 //zugriff zum Schreiben nicht gestattet
1029 return(-10);
1030
1031 return db_reassign_issue(issueid, newuid, note);
1032}
1033
1034varargs int ChangeType(int issueid, int newtype, int oldtype, string note)
1035{
1036 struct fullissue_s issue = db_get_issue(issueid, 0);
1037 if(!issue)
1038 return -1;
1039
1040 if (!access_check(issue->uid, M_CHANGE_TYPE))
1041 {
1042 // Zugriff zum Schreiben nicht gestattet
1043 return(-10);
1044 }
1045
1046 sl_exec(
1047 "UPDATE issues SET type=?1,mtime=?2 WHERE id=?3;",
1048 newtype, time(), issueid);
1049 db_add_note(
1050 (<note_s> id: issueid, time: time(), user: getuid(TI),
1051 txt: sprintf("Fehlertyp von %s auf %s geaendert. (%s)",
1052 print_type(oldtype)[0], print_type(newtype)[0], (note || "<kein Kommentar>"))));
1053 return 1;
1054}
1055
1056/* *********** Eher fuer Debug-Zwecke *********************** */
1057/*
1058mixed QueryAll(int type) {
1059 //das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
1060 //Kopie zurueckgegeben, daher erstmal nur ich...
1061 if (!this_interactive() ||
1062 member(MAINTAINER,getuid(this_interactive()))<0)
1063 return(-1);
1064 if (process_call()) return(-2);
1065 if (!type) return(-3);
1066 return(errors[type]);
1067}
1068
1069mixed QueryResolved()
1070{
1071 //das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
1072 //Kopie zurueckgegeben, daher erstmal nur ich...
1073 if (!this_interactive() ||
1074 member(MAINTAINER,getuid(this_interactive()))<0)
1075 return(-1);
1076 if (process_call()) return(-2);
1077 return(resolved);
1078}
1079*/
1080
1081/* ***************** Internal Stuff ******************** */
1082void create() {
1083 seteuid(getuid(ME));
1084
1085 if (sl_open("/"LIBDATADIR"/secure/ARCH/errord.sqlite") != 1)
1086 //if (sl_open("/errord.sqlite") != 1)
1087 {
1088 raise_error("Datenbank konnte nicht geoeffnet werden.\n");
1089 }
1090 sl_exec("PRAGMA foreign_keys = ON; PRAGMA temp_store = 2; ");
1091 //string* res=sl_exec("PRAGMA quick_check(N);");
1092 //if (pointerp(res))
1093 //{
1094 // raise_error("");
1095 //}
1096 //sl_exec("CREATE TABLE issue(id INTEGER,haskey TEXT);");
1097 foreach (string cmd :
1098 explode(read_file("/secure/ARCH/errord.sql.init"),";\n"))
1099 {
1100 if (sizeof(cmd) && cmd != "\n")
1101 {
1102 sl_exec(cmd);
1103 }
1104 }
1105 sl_exec("ANALYZE main;");
1106
1107 lasterror=([]);
1108}
1109
1110string name() {return("<Error-Daemon>");}
1111
1112void save_me(int now) {
1113 if (now)
1114 save_object(SAVEFILE);
1115 else if (find_call_out(#'save_object)==-1) {
1116 call_out(#'save_object, 30, SAVEFILE);
1117 }
1118}
1119
1120varargs int remove(int silent) {
1121 save_me(1);
1122 destruct(ME);
1123 return(1);
1124}
1125
1126
1127// sperrt den Eintrag (lock!=0) oder entsperrt ihn (lock==0).
1128// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
1129// wird.
1130// liefert <0 im Fehlerfall, sonst Array mit Lockdaten
1131private varargs int set_lock(int issueid, int lock, string note)
1132{
1133 struct fullissue_s issue = db_get_issue(issueid,0);
1134 if (!issue)
1135 return -1;
1136
1137 if (!access_check(issue->uid, M_WRITE))
1138 //zugriff zum Schreiben nicht gestattet
1139 return(-10);
1140
1141 return db_set_lock(issueid, lock, note);
1142}
1143
1144//markiert einen Fehler als gefixt, mit 'note' als Bemerkung (res!=0) oder
1145//markiert einen Fehler wieder als nicht-gefixt (resolution==0)
1146//liefert < 0 im Fehlerfall, sonst den neuen Zustand.
1147private varargs int set_resolution(int issueid, int resolution, string note)
1148{
1149 struct fullissue_s issue = db_get_issue(issueid,0);
1150 if (!issue)
1151 return -1;
1152
1153 // Fixen duerfen nur zustaendige
1154 if (resolution
1155 && !access_check(issue->uid, M_FIX))
1156 return -10;
1157 // ggf. Fix zurueckziehen darf jeder mit M_WRITE
1158 if (!resolution &&
1159 !access_check(issue->uid, M_WRITE))
1160 return -10;
1161
1162 int res = db_set_resolution(issueid, resolution, note);
1163
1164 if (res == 1)
1165 {
1166 // Fehler jetzt gefixt.
1167 versende_mail(db_get_issue(issueid,0)); // per Mail verschicken
1168 }
1169
1170 return res;
1171}
1172
1173//ist der Zugriff auf uid erlaubt? Geprueft wird TI (wenn kein TI, auch kein
1174//Schreibzugriff)
1175//mode gibt an, ob lesend oder schreibend
1176private int access_check(string uid, int mode) {
1177
1178 if (mode==M_READ)
1179 return LEARNER_SECURITY; //lesen darf jeder Magier
1180
1181 // In process_string() schonmal gar nicht.
1182 if (process_call()) return(0);
1183 // EM+ duerfen alles loeschen.
1184 if (ARCH_SECURITY) return(1);
1185 // eigene UIDs darf man auch vollumfaenglich bearbeiten.
1186 if (secure_euid()==uid) return(1);
1187
1188 // alles andere wird speziell geprueft
1189 switch(mode)
1190 {
1191 // M_WRITE ist zur zeit eigentlich immer nen Append - das duerfen zur
1192 // Zeit alle ab Vollmagier
1193 case M_WRITE:
1194 return LEARNER_SECURITY;
1195 // Loeschen und Fixen duerfen zur Zeit nur Zustaendige.
1196 case M_DELETE:
1197 case M_REASSIGN:
1198 case M_FIX:
1199 case M_CHANGE_TYPE:
1200 // Master nach UIDs fragen, fuer die der jew. Magier
1201 // zustaendig ist.
1202 if (member(({string*})master()->QueryUIDsForWizard(secure_euid()),uid) >= 0)
1203 return 1;
1204
1205 break;
1206 }
1207
1208 return(0); //Fall-through, neinRNER_SECURITY
1209}
1210
1211public string format_stacktrace(struct frame_s* stacktrace) {
1212 string *lines;
1213
1214 if (!pointerp(stacktrace) || !sizeof(stacktrace))
1215 return("");
1216
1217 lines=allocate(sizeof(stacktrace));
1218 int i;
1219 foreach(struct frame_s frame: stacktrace)
1220 {
1221 lines[i]=sprintf("Fun: %.20O in Prog: %.40s\n"
1222 " Zeile: %.8d [%.50s]\n"
1223 " Evalcosts: %d",
1224 frame->name,frame->prog,
1225 frame->loc,frame->obj,frame->ticks);
1226 ++i;
1227 }
1228 return(implode(lines,"\n"));
1229}
1230
1231public string format_notes(struct note_s* notes)
1232{
1233 int i;
1234 string text="";
1235 foreach(struct note_s note: notes)
1236 {
1237 text+=sprintf("Notiz %d von %.10s am %.30s\n%s",
1238 ++i,capitalize(note->user),dtime(note->time),
1239 break_string(note->txt, 78," "));
1240 }
1241 return text;
1242}
1243
1244public string format_error_spieler(struct fullissue_s issue)
1245{
1246 string txt;
1247 string *label;
1248
1249 if (!issue)
1250 return 0;
1251
1252 switch(issue->type)
1253 {
1254 case T_RTERROR:
1255 label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
1256 break;
1257 case T_REPORTED_ERR:
1258 label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
1259 break;
1260 case T_REPORTED_TYPO:
1261 label=({"Typo","Dieser Typo"});
1262 break;
1263 case T_REPORTED_IDEA:
1264 label=({"Idee","Diese Idee"});
1265 break;
1266 case T_REPORTED_MD:
1267 label=({"Fehlendes Detail","Dieses fehlende Detail"});
1268 break;
1269 case T_REPORTED_SYNTAX:
1270 label=({"Syntaxproblem","Dieses Syntaxproblem"});
1271 break;
1272 case T_RTWARN:
1273 label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
1274 break;
1275 case T_CTWARN:
1276 label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
1277 break;
1278 case T_CTERROR:
1279 label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
1280 break;
1281 default: return 0;
1282 }
1283
1284 txt=sprintf( "\nDaten fuer %s mit ID '%s':\n"
1285 "Zeit: %25s\n",
1286 label[0], issue->hashkey,
1287 dtime(issue->ctime)
1288 );
1289
1290 txt+=sprintf("%s",break_string(issue->message,78,
1291 "Meldung: ",BS_INDENT_ONCE));
1292
1293 if (pointerp(issue->notes))
1294 txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
1295
1296 return txt;
1297}
1298
1299public string* print_type(int type)
1300{
1301 string* res;
1302 switch(type)
1303 {
1304 case T_RTERROR:
1305 res = ({"Laufzeitfehler","Dieser Laufzeitfehler"});
1306 break;
1307 case T_REPORTED_ERR:
1308 res = ({"Fehlerhinweis","Dieser Fehlerhinweis"});
1309 break;
1310 case T_REPORTED_TYPO:
1311 res = ({"Typo","Dieser Typo"});
1312 break;
1313 case T_REPORTED_IDEA:
1314 res = ({"Idee","Diese Idee"});
1315 break;
1316 case T_REPORTED_MD:
1317 res = ({"Fehlendes Detail","Dieses fehlende Detail"});
1318 break;
1319 case T_REPORTED_SYNTAX:
1320 res = ({"Syntaxproblem","Dieses Syntaxproblem"});
1321 break;
1322 case T_RTWARN:
1323 res = ({"Laufzeitwarnung","Diese Laufzeitwarnung"});
1324 break;
1325 case T_CTWARN:
1326 res = ({"Ladezeitwarnung","Diese Ladezeitwarnung"});
1327 break;
1328 case T_CTERROR:
1329 res = ({"Ladezeitfehler","Dieser Ladezeitfehler"});
1330 break;
1331 default:
1332 res = 0;
1333 }
1334 return res;
1335}
1336
1337public string format_error(struct fullissue_s issue, int only_essential)
1338{
1339 string txt;
1340 string *label;
1341
1342 if (!issue)
1343 return 0;
1344
1345 label = print_type(issue->type);
1346 if(!label) return 0;
1347
1348 txt=sprintf( "\nDaten fuer %s mit ID %d:\n"
1349 "Hashkey: %s\n"
1350 "Zeit: %25s (Erstmalig: %25s)\n",
1351 label[0], issue->id, issue->hashkey,
1352 dtime(abs(issue->mtime)),dtime(issue->ctime)
1353 );
1354
1355 if (stringp(issue->prog))
1356 txt += sprintf(
1357 "Programm: %.60s\n"
1358 "Zeile: %.60d\n",
1359 issue->prog, issue->loc
1360 );
1361
1362 if (stringp(issue->obj))
1363 txt+=sprintf("Objekt: %.60s\n",
1364 issue->obj);
1365
1366 txt += sprintf("Loadname: %.60s\n"
1367 "UID: %.60s\n",
1368 issue->loadname, issue->uid);
1369
1370 txt+=sprintf("%s",break_string(issue->message,78,
1371 "Meldung: ",BS_INDENT_ONCE));
1372 if (stringp(issue->hbobj))
1373 txt+=sprintf(
1374 "HB-Obj: %.60s\n",issue->hbobj);
1375
1376 if (stringp(issue->titp)) {
1377 txt+=sprintf(
1378 "TI/TP: %.60s\n",issue->titp);
1379 if (stringp(issue->tienv))
1380 txt+=sprintf(
1381 "Environm.: %.60s\n",issue->tienv);
1382 }
1383
1384 if (!stringp(issue->command) ||
1385 !ARCH_SECURITY || process_call())
1386 {
1387 // Kommandoeingabe ist Privatsphaere und darf nicht von jedem einsehbar
1388 // sein.
1389 // in diesem Fall aber zumindest das Verb ausgeben, so vorhanden
1390 if (issue->verb)
1391 txt+=sprintf(
1392 "Verb: %.60s\n",issue->verb);
1393 }
1394 // !process_call() && ARCH_SECURITY erfuellt...
1395 else if (stringp(issue->command))
1396 txt+=sprintf(
1397 "Befehl: %.60s\n",issue->command);
1398
1399 if (issue->caught)
1400 txt+=label[1]+" trat in einem 'catch()' auf.\n";
1401
1402 if (!only_essential)
1403 {
1404 if (issue->deleted)
1405 txt+=label[1]+" wurde als geloescht markiert.\n";
1406
1407 if (issue->locked)
1408 txt+=break_string(
1409 sprintf("%s wurde von %s am %s vor automatischem Loeschen "
1410 "geschuetzt (locked).\n",
1411 label[1],issue->locked_by, dtime(issue->locked_time)),78);
1412 if (issue->resolved)
1413 txt+=label[1]+" wurde als erledigt markiert.\n";
1414 }
1415
1416 txt+=sprintf("%s trat bisher %d Mal auf.\n",
1417 label[1],issue->count);
1418
1419 if (pointerp(issue->stack))
1420 txt+="Stacktrace:\n"+format_stacktrace(issue->stack)+"\n";
1421
1422 if (pointerp(issue->notes))
1423 txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
1424
1425 return txt;
1426}
1427
1428// letzter Aenderung eines Spieler-/Magiersavefiles. Naeherung fuer letzten
1429// Logout ohne das Savefile einzulesen und P_LAST_LOGOUT zu pruefen.
1430private int recent_lastlogout(string nam, int validtime)
1431{
1432 if (!nam || sizeof(nam)<2) return 0;
1433 return file_time("/"LIBSAVEDIR"/" + nam[0..0] + "/" + nam + ".o") >= validtime;
1434}
1435
1436// Versendet Mail an zustaendigen Magier und ggf. Spieler, der den Eintrag
1437// erstellt hat.
1438// ACHTUNG: loescht issue->command.
1439private int versende_mail(struct fullissue_s issue)
1440{
1441 // Versendet eine mail mit dem gefixten Fehler.
1442 mixed *mail;
1443 string text, *empf;
1444 int res = -1;
1445
1446 mail = allocate(9);
1447 mail[MSG_FROM] = "<Fehler-Daemon>";
1448 mail[MSG_SENDER] = getuid(TI);
1449 mail[MSG_BCC] = 0;
1450 mail[MSG_SUBJECT] = sprintf("Fehler/Warnung in %s behoben", issue->loadname);
1451 mail[MSG_DATE] = dtime(time());
1452
1453 // auch wenn ein EM fixt, sollen die Empfaenger nicht automatisch die
1454 // Spielereingabe erhalten, also command loeschen.
1455 issue->command = 0;
1456
1457 // erstmal eine Mail an zustaendige Magier.
1458 empf = ({string*})master()->QueryWizardsForUID(issue->uid);
1459 // lang (180 Tage) nicht eingeloggte Magier ausfiltern
1460 empf = filter(empf, #'recent_lastlogout, time() - 15552000);
1461 if (sizeof(empf))
1462 {
1463
1464 text = break_string(
1465 sprintf(STANDARDMAILTEXT,capitalize(getuid(TI)))
1466 +format_error(issue, 1),78,"",BS_LEAVE_MY_LFS);
1467
1468 mail[MSG_RECIPIENT] = empf[0];
1469 if (sizeof(empf)>1)
1470 mail[MSG_CC] = empf[1..];
1471 else
1472 mail[MSG_CC] = 0;
1473 mail[MSG_BODY]=text;
1474 mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
1475
1476 if (!sizeof(({string*})"/secure/mailer"->DeliverMail(mail,0)))
1477 res = -1; // an niemanden erfolgreich zugestellt. :-(
1478 else
1479 res = 1;
1480 }
1481
1482 // Bei von Spielern gemeldeten Fehler werden Spieler bei
1483 // Erledigung informiert, wenn deren letzter Logout weniger als 180 Tage her
1484 // ist.
1485 if ( (issue->type &
1486 (T_REPORTED_ERR|T_REPORTED_TYPO|T_REPORTED_IDEA|T_REPORTED_MD|
1487 T_REPORTED_SYNTAX))
1488 && issue->titp
1489 && recent_lastlogout(issue->titp, time() - 15552000) )
1490 {
1491 text = break_string(
1492 sprintf(STANDARDMAILTEXT_ERRORHINT,
1493 capitalize(issue->titp), capitalize(getuid(TI)))
1494 +format_error_spieler(issue), 78,"",BS_LEAVE_MY_LFS);
1495
1496 mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
1497 mail[MSG_RECIPIENT] = issue->titp;
1498 mail[MSG_CC] = 0;
1499 mail[MSG_SUBJECT] = sprintf("Fehler/Idee/Typo wurde von %s bearbeitet",
1500 getuid(TI));
1501 mail[MSG_BODY] = text;
1502
1503 if (!sizeof(({string*})"/secure/mailer"->DeliverMail(mail,0)))
1504 res |= -1;
1505 else
1506 res |= 1;
1507 }
1508
1509 return res;
1510}
1511
1512void reset()
1513{
1514 // geloeschte Issues sofort, gefixte 30 Tage nach letzter Aenderung
1515 // loeschen.
1516 sl_exec("DELETE FROM issues WHERE deleted=1;");
1517 sl_exec("DELETE FROM issues WHERE resolved=1 AND mtime<?1;",
1518 time()-30*24*3600);
1519 set_next_reset(3600*24);
1520}
1521
1522// Nicht jeder Magier muss den ErrorD direkt zerstoeren koennen.
1523public string NotifyDestruct(object caller) {
1524 if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
1525 return "Du darfst den Error-Daemon nicht zerstoeren!\n";
1526 }
1527 return 0;
1528}
1529