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