blob: 932c3c213b34e6d7871bbebe5ac39e2cc969a8bf [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);
Bugfix67091cb2022-09-22 15:57:31 +020040public string* print_type(int type);
MG Mud User88f12472016-06-24 23:31:02 +020041
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
Bugfixa75344d2017-06-16 14:04:48 +0200344// loggt einen T_REPORTED_ERR, T_REPORTED_IDEA, T_REPORTED_TYPO,
345// T_REPORTED_MD, T_REPORTED_SYNTAX
MG Mud User88f12472016-06-24 23:31:02 +0200346public string LogReportedError(mapping err)
347{
Zesstra0339e4f2019-11-11 21:38:07 +0100348 //darf nur von Spielershells oder Objekt in /obj/ (z.B. Fehlerteufel oder
349 //vitem_proxy) gerufen werden.
MG Mud User88f12472016-06-24 23:31:02 +0200350 if (extern_call() && !previous_object()
351 || (strstr(load_name(previous_object()),"/std/shells/") == -1
Zesstra0339e4f2019-11-11 21:38:07 +0100352 && strstr(load_name(previous_object()), "/"LIBOBJDIR"/") == -1))
MG Mud User88f12472016-06-24 23:31:02 +0200353 return 0;
354
355 //DEBUG("LogReportedError\n");
356 // DEBUG(sprintf("%O\n",err));
Vanionb4c06532020-03-09 21:54:54 +0100357 string uid = ({string})master()->creator_file(err[F_OBJ]);
MG Mud User88f12472016-06-24 23:31:02 +0200358
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];
Arathorn2e77c0a2016-08-27 14:37:47 +0200374 issue->obj = objectp(err[F_OBJ]) ? object_name(err[F_OBJ]) : err[F_OBJ];
Bugfixf6d9f2a2022-08-31 11:42:00 +0200375 issue->loc = err[F_LINE];
MG Mud User88f12472016-06-24 23:31:02 +0200376 // Normalisieren auf fuehrenden / und kein .c
377 if (err[F_PROG]!="unbekannt")
Zesstraa2ad9712025-03-30 13:37:58 +0200378 issue->prog=err[F_PROG][0] in "/#<" ? err[F_PROG] : "/" + err[F_PROG];
MG Mud User88f12472016-06-24 23:31:02 +0200379 else
380 issue->prog = "unbekannt";
381 issue->titp = getuid(this_interactive() || this_player());
Bugfix6a4979e2021-08-30 21:04:09 +0200382 issue->tienv = object_name(environment(this_interactive() || this_player()));
MG Mud User88f12472016-06-24 23:31:02 +0200383
384 //DEBUG(sprintf("%O\n",issue));
385 issue->hashkey = hash(TLS_HASH_MD5,
386 sprintf("%d%s%s", issue->type, issue->loadname, issue->message));
387
388 // ggf. vorhandenen Fehler suchen - zugegeben: sollte bei von Spielern
389 // gemeldeten Dingen vermutlich nie vorkommen...
390 int oldid = getErrorID(issue->hashkey);
391 if (oldid >= 0)
392 {
393 // ggf. sicherstellen, dass er wieder eroeffnet wird.
394 db_reopen_issue(oldid, "<ErrorD>",
395 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
396 db_countup_issue(oldid);
397 return issue->hashkey;
398 }
399
400 // sonst fuegen wir einen neuen Eintrag hinzu
401 // Spielergemeldete Bugs werden erstmal vor automatischem Loeschen
402 // geschuetzt, bis ein zustaendiger Magier ihn zur Kenntnis nimmt und
403 // entsperrt.
404 issue->locked = 1;
405 issue->locked_by = getuid(TI || PL);
406 issue->locked_time = time();
407
408 // In DB eintragen.
409 issue->id = db_insert_issue(issue);
410
411 lasterror[issue->type]=issue->id;
412
413 // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
414 EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
415 ([F_TYPE: issue->type, F_HASHKEY:issue->hashkey,
416 F_UID:issue->uid, F_ID: issue->id]));
417
418 DEBUG(sprintf("LogReportedError: %s\n",issue->hashkey));
419
420 return issue->hashkey;
421}
422
423//Fehler registrieren
424//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
425//Master gerufen!
426public int LogError(string msg,string prg,string curobj,int line,mixed culprit,
427 int caught)
428{
429 //DEBUG(sprintf("LogError: Prog: %O, Obj: %O,\n",prg,curobj));
430
431 //darf nur vom Master gerufen werden
432 if (!extern_call() ||
433 (previous_object() && previous_object() != master()))
434 return 0;
435
436 struct fullissue_s issue = (<fullissue_s>);
437
Zesstraddddbf72021-05-14 16:52:16 +0200438 //UID bestimmen, curobj is 0 for lwobjects, then the program is used.
439 issue->uid=({string})master()->creator_file(curobj || prg);
Zesstra1c432b72021-07-12 21:18:05 +0200440 //DEBUG(sprintf("LogError: UID: %s\n",issue->uid));
MG Mud User88f12472016-06-24 23:31:02 +0200441
442 //Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
443 if (!stringp(curobj) || !sizeof(curobj))
444 issue->loadname = curobj = "<Unbekannt>";
445 else
446 {
447 //load_name nimmt Strings und Objects und konstruiert den loadname,
448 //wie er sein sollte, wenn das Objekt nicht mehr existiert.
449 issue->loadname=load_name(curobj);
450 }
451 if (!stringp(issue->loadname))
452 {
453 //hier kommt man rein, falls curobj ein 'kaputter' Name ist,
454 //d.h. load_name() 0 liefert.
455 issue->loadname="<Illegal object name>";
456 }
457
458 // Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
459 // ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
460 // nicht gespeichert.
461 if (this_interactive() && IS_LEARNER(this_interactive())
462 && strstr(issue->loadname,WIZARDDIR)==0
bugfixd94d0932020-04-08 11:27:13 +0200463 && ({int})this_interactive()->QueryProp(P_DONT_LOG_ERRORS))
MG Mud User88f12472016-06-24 23:31:02 +0200464 {
465 return 0;
466 }
467
468 // prg und curobj auf fuehrenden / und ohne .c am Ende normieren.
469 if (stringp(prg))
Zesstraa2ad9712025-03-30 13:37:58 +0200470 issue->prog=prg[0] in "/#<" ? prg : "/" + prg;
MG Mud User88f12472016-06-24 23:31:02 +0200471 if (stringp(curobj) && curobj[0]!='/')
472 {
473 curobj="/"+curobj;
474 }
475
476 issue->obj = curobj;
477 issue->loc = line;
478 issue->message = msg;
479 issue->ctime = issue->mtime = issue->atime = time();
480 issue->type = T_RTERROR;
481 issue->count = 1;
482 issue->caught = caught;
483
484 //Hashkey bestimmen: Typ, Name der Blueprint des buggenden Objekts,
485 //Programmname, Zeilennr., Fehlermeldung
486 //TODO: evtl. sha1() statt md5()?
487 issue->hashkey=hash(TLS_HASH_MD5,
488 sprintf("%d%s%s%d%s", T_RTERROR, issue->loadname||"",
489 issue->prog || "", issue->loc,
490 issue->message||"<No error message given.>"));
491 DEBUG(sprintf("LogError: Hashkey: %s", issue->hashkey));
492
493 // ggf. vorhandenen Fehler suchen
494 int oldid = getErrorID(issue->hashkey);
495 if (oldid >= 0)
496 {
497 db_reopen_issue(oldid, "<ErrorD>",
498 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
499 db_countup_issue(oldid);
500 return oldid;
501 }
502
503 //sonst fuegen wir einen neuen Eintrag hinzu
504 //DEBUG(sprintf("LogError: OBJ: %s, BP: %s",curobj,loadname));
505 // Wenn Fehler im HB, Objektnamen ermitteln
506 if (objectp(culprit))
507 issue->hbobj = object_name(culprit);
508
509 //gibt es einen TI/TP? Name mit erfassen
510 mixed tienv;
511 if(objectp(this_interactive()))
512 {
513 issue->titp=getuid(this_interactive());
514 tienv=environment(this_interactive());
515 }
516 else if (objectp(PL) && query_once_interactive(PL))
517 {
518 issue->titp=getuid(PL);
519 tienv=environment(PL);
520 }
521 else if (objectp(PL))
522 {
523 issue->titp=object_name(PL);
524 tienv=environment(PL);
525 }
526 if (objectp(tienv))
527 issue->tienv=object_name(tienv);
528
529 // Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
530 mixed cli;
531 if (pointerp(cli=command_stack()) && sizeof(cli))
532 {
533 issue->verb=cli[0][CMD_VERB];
534 issue->command=cli[0][CMD_TEXT];
535 }
536
537 //stacktrace holen
538 mixed stacktrace;
539 if (caught)
540 stacktrace=debug_info(DINFO_TRACE,DIT_ERROR);
541 else
542 stacktrace=debug_info(DINFO_TRACE,DIT_UNCAUGHT_ERROR);
543 // gueltige Stacktraces haben min. 2 Elemente.
544 // (leerer Trace: ({"No trace."}))
545 if (sizeof(stacktrace) > 1)
546 {
547 int i;
548 issue->stack = allocate(sizeof(stacktrace)-1);
549 // erstes Element ist 0 oder HB-Objekt: kein frame, daher ueberspringen
550 foreach(mixed entry : stacktrace[1..])
551 {
552 // frame->id will be set later by db_insert_issue().
553 struct frame_s frame = (<frame_s> type : entry[TRACE_TYPE],
554 name: entry[TRACE_NAME],
555 prog: entry[TRACE_PROGRAM],
556 obj: entry[TRACE_OBJECT],
557 loc: entry[TRACE_LOC],
558 ticks: entry[TRACE_EVALCOST]);
559 issue->stack[i] = frame;
560 ++i;
561 }
562 }
563
564 issue->id = db_insert_issue(issue);
565
566 lasterror[T_RTERROR]=issue->id;
567
568 // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
569 EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
570 ([ F_TYPE: T_RTERROR, F_HASHKEY: issue->hashkey, F_UID:
571 issue->uid, F_ID: issue->id ]));
572
573// DEBUG(sprintf("LogError: Fehlereintrag:\n%O\n",
574// errors[uid][hashkey]));
575// DEBUG(sprintf("LogError: Verbrauchte Ticks: %d\n",
576// 200000-get_eval_cost()));
577 return issue->id;
578}
579
580//Warnungen registrieren
581//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
582//Master gerufen!
583public int LogWarning(string msg,string prg,string curobj,int line, int in_catch)
584{
585 //DEBUG(sprintf("LogWarning: Prog: %O, Obj: %O,\n",prg,curobj));
586
587 //darf nur vom Master gerufen werden
588 if (!extern_call() ||
589 (previous_object() && previous_object() != master()))
590 return 0;
591
592 struct fullissue_s issue = (<fullissue_s>);
593
Zesstraddddbf72021-05-14 16:52:16 +0200594 //UID bestimmen, curobj is 0 for lwobjects, then the program is used.
595 issue->uid=({string})master()->creator_file(curobj || prg);
MG Mud User88f12472016-06-24 23:31:02 +0200596 //DEBUG(sprintf("LogWarning UID: %s\n",uid));
597
598 //Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
599 if (!stringp(curobj) || !sizeof(curobj))
600 issue->loadname = curobj = "<Unbekannt>";
601 else
602 {
603 //load_name nimmt Strings und Objects und konstruiert den loadname,
604 //wie er sein sollte, wenn das Objekt nicht mehr existiert.
605 issue->loadname=load_name(curobj);
606 }
607
608 if (!stringp(issue->loadname))
609 //hier sollte man reinkommen, falls curobj ein 'kaputter' Name ist,
610 //d.h. load_name() 0 liefert.
611 issue->loadname="<Illegal object name>";
612
Zesstraa2ad9712025-03-30 13:37:58 +0200613 // prg und curobj auf abs. Pfade normalisieren. Dabei Spezialfaelle
614 // beruecksichtigen, z.B. dass prg "#'call_out" sein kann.
MG Mud User88f12472016-06-24 23:31:02 +0200615 if (stringp(prg))
Zesstraa2ad9712025-03-30 13:37:58 +0200616 issue->prog=prg[0] in "/#<" ? prg : "/" + prg;
MG Mud User88f12472016-06-24 23:31:02 +0200617 if (stringp(curobj) && curobj[0]!='/')
618 {
619 curobj="/"+curobj;
620 }
621
622 //DEBUG(sprintf("LogWarning: OBJ: %s, BP: %s\n",curobj,blue));
623
624 // Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
625 // ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
626 // nicht gespeichert.
627 if (this_interactive() && IS_LEARNER(this_interactive())
628 && strstr(issue->loadname,WIZARDDIR)==0
bugfixd94d0932020-04-08 11:27:13 +0200629 && ({int})this_interactive()->QueryProp(P_DONT_LOG_ERRORS)) {
MG Mud User88f12472016-06-24 23:31:02 +0200630 return 0;
631 }
632
633 //Hashkey bestimmen, Typ, Name der Blueprint des buggenden Objekts, Programm
634 //Zeilennr., Warnungsmeldung
635 issue->hashkey=hash(TLS_HASH_MD5,
Zesstra972bdef2022-01-10 11:14:15 +0100636 sprintf("%d%s%s%d%s", T_RTWARN, issue->loadname,
637 issue->prog||"", line, msg));
MG Mud User88f12472016-06-24 23:31:02 +0200638 //DEBUG(sprintf("LogWarning: Hashkey: %s",hashkey));
639
640
641 // ggf. vorhandenen Fehler suchen
642 int oldid = getErrorID(issue->hashkey);
643 if (oldid >= 0)
644 {
645 db_reopen_issue(oldid, "<ErrorD>",
646 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
647 db_countup_issue(oldid);
648 return oldid;
649 }
650
651 //sonst fuegen wir einen neuen Eintrag hinzu
652 // erstmal vervollstaendigen
653 issue->obj = curobj;
654 issue->message = msg;
655 issue->ctime = issue->mtime = issue->atime = time();
656 issue->loc = line;
657 issue->count = 1;
658 issue->type = T_RTWARN;
659 issue->caught = in_catch;
660
661 //gibt es einen TI/TP? Name mit erfassen
662 mixed tienv;
663 if(objectp(this_interactive()))
664 {
665 issue->titp=getuid(this_interactive());
666 tienv=environment(this_interactive());
667 }
668 else if (objectp(PL) && query_once_interactive(PL))
669 {
670 issue->titp=getuid(PL);
671 tienv=environment(PL);
672 }
673 else if (objectp(PL))
674 {
675 issue->titp=object_name(PL);
676 tienv=environment(PL);
677 }
678 if (objectp(tienv))
679 issue->tienv=object_name(tienv);
680
681 // Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
682 mixed cli;
683 if (pointerp(cli=command_stack()) && sizeof(cli))
684 {
685 issue->verb=cli[0][CMD_VERB];
686 issue->command=cli[0][CMD_TEXT];
687 }
688
689 issue->id = db_insert_issue(issue);
690
691 lasterror[T_RTWARN]=issue->id;
692 // Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
693 EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
694 ([F_TYPE: issue->type, F_ID: issue->id,
695 F_UID: issue->uid, F_HASHKEY: issue->hashkey]) );
696
697// DEBUG(sprintf("LogWarning: Warnungseintrag:\n%O\n",
698// warnings[uid][hashkey]));
699// DEBUG(sprintf("LogWarning: Verbrauchte Ticks: %d\n",
700// 200000-get_eval_cost()));
701 return issue->id;
702}
703
704//Warnungen und Fehler beim Kompilieren registrieren
705//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
706//Master gerufen!
707public int LogCompileProblem(string file,string msg,int warn) {
708
709 //DEBUG(sprintf("LogCompileProblem: Prog: %O, Obj: %O,\n",file,msg));
710
711 //darf nur vom Master gerufen werden
712 if (!extern_call() ||
713 (previous_object() && previous_object() != master()))
714 return 0;
715
716 struct fullissue_s issue = (<fullissue_s>);
717
718 //UID bestimmen
Vanionb4c06532020-03-09 21:54:54 +0100719 issue->uid=({string})master()->creator_file(file);
MG Mud User88f12472016-06-24 23:31:02 +0200720 //DEBUG(sprintf("LogCompileProblem UID: %s\n",uid));
721
722 // An File a) fuehrenden / anhaengen und b) endendes .c abschneiden. Macht
723 // beides netterweise load_name().
724 issue->loadname = load_name(file);
725 issue->type = warn ? T_CTWARN : T_CTERROR;
726
727 //loggen wir fuer das File ueberhaupt?
728 if (member(BLACKLIST,explode(issue->loadname,"/")[<1])>=0)
729 return 0;
730
731 //Hashkey bestimmen, in diesem Fall einfach, wir koennen die
732 //Fehlermeldunge selber nehmen.
733 issue->hashkey=hash(TLS_HASH_MD5,sprintf(
734 "%d%s%s",issue->type,issue->loadname, msg));
735 //DEBUG(sprintf("LogCompileProblem: Hashkey: %s",hashkey));
736
737 // ggf. vorhandenen Fehler suchen
738 int oldid = getErrorID(issue->hashkey);
739 if (oldid >= 0)
740 {
741 db_reopen_issue(oldid, "<ErrorD>",
742 "Automatisch wiedereroeffnet wegen erneutem Auftreten.");
743 db_countup_issue(oldid);
744 return oldid;
745 }
746
747 // neuen Eintrag
748 issue->message = msg;
749 issue->count = 1;
750 issue->ctime = issue->mtime = issue->atime = time();
751
752 issue->id = db_insert_issue(issue);
753
754 if (warn) lasterror[T_CTWARN]=issue->id;
755 else lasterror[T_CTERROR]=issue->id;
756
757// DEBUG(sprintf("LogCompileProblem: Eintrag:\n%O\n",
758// (warn ? ctwarnings[uid][hashkey] : cterrors[uid][hashkey])));
759// DEBUG(sprintf("LogCompileProblem: Verbrauchte Ticks: %d\n",
760// 200000-get_eval_cost()));
761 return issue->id;
762}
763
764/* **************** Public Interface ****************** */
765
766//Einen bestimmten Fehler nach Hashkey suchen und als fullissue_s inkl. Notes
767//und Stacktrace liefern.
768struct fullissue_s QueryIssueByHash(string hashkey)
769{
770 struct fullissue_s issue = db_get_issue(0, hashkey);
771 if (structp(issue))
772 return filter_private(issue);
773 return 0;
774}
775//Einen bestimmten Fehler nach ID suchen und als fullissue_s inkl. Notes
776//und Stacktrace liefern.
777struct fullissue_s QueryIssueByID(int issueid)
778{
779 struct fullissue_s issue = db_get_issue(issueid, 0);
780 if (structp(issue))
781 return filter_private(issue);
782 return 0;
783}
784
785// den letzten Eintrag den jeweiligen Typ liefern.
786struct fullissue_s QueryLastIssue(int type)
787{
788 if (!member(lasterror,type))
789 return 0;
790 //einfach den kompletten letzten Eintrag zurueckliefern
791 return(QueryIssueByID(lasterror[type]));
792}
793
794// Liefert alle Issues, deren obj,prog oder loadname gleich <file> ist.
795public struct fullissue_s* QueryIssuesByFile(string file, int type)
796{
797 mixed rows = sl_exec("SELECT * FROM issues "
798 "WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
799 "AND deleted=0 AND resolved=0 AND type=?2"
800 "ORDER BY type,mtime;", file, type);
801 if (pointerp(rows))
802 {
803 struct fullissue_s* ilist = allocate(sizeof(rows));
804 int i;
805 foreach(mixed row : rows)
806 {
807 // Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
808 // die gleiche Reihenfolge wie in der struct haben! Entweder immer
809 // sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
810 struct fullissue_s issue = to_struct( row, (<fullissue_s>) );
811 if (issue->type == T_RTERROR)
812 issue->stack = db_get_stack(issue->id);
813 issue->notes = db_get_notes(issue->id);
814 ilist[i] = filter_private(issue);
815 ++i;
816 }
817 return ilist;
818 }
819 return 0;
820}
821
822// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
823// angebenen <type> und <uid>.
824varargs < <int|string>* >* QueryIssueListByFile(string file)
825{
826 mixed rows = sl_exec("SELECT id,loadname,obj,prog,loc FROM issues "
827 "WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
828 "AND deleted=0 AND resolved=0 "
829 "ORDER BY type,mtime;", file);
830 return rows;
831}
832
833// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
834// angebenen <type> und <uid>.
835varargs < <int|string>* >* QueryIssueListByLoadname(string file, int type)
836{
837 mixed rows;
838 if (type && file)
839 {
840 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
841 "WHERE loadname=?1 AND type=?2 AND deleted=0 "
842 "AND resolved=0 "
843 "ORDER BY type,mtime;", file, type);
844 }
845 else if (type)
846 {
847 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
848 "WHERE type=?1 AND deleted=0 AND resolved=0 "
849 "ORDER BY type,mtime;", type);
850 }
851 else if (file)
852 {
853 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
854 "WHERE loadname=?1 AND deleted=0 AND resolved=0 "
855 "ORDER BY type,mtime;", file);
856 }
857 return rows;
858}
859
860
861// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
862// angebenen <type> und <uid>.
863varargs < <int|string>* >* QueryIssueList(int type, string uid)
864{
865 mixed rows;
866 if (type && uid)
867 {
868 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
869 "WHERE type=?1 AND uid=?2 AND deleted=0 "
870 "AND resolved=0 "
871 "ORDER BY type,rowid;", type,uid);
872 }
873 else if (type)
874 {
875 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
876 "WHERE type=?1 AND deleted=0 AND resolved=0 "
877 "ORDER BY type,rowid;", type);
878 }
879 else if (uid)
880 {
881 rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
882 "WHERE uid=?1 AND deleted=0 AND resolved=0 "
883 "ORDER BY type,rowid;", uid);
884 }
885 return rows;
886}
887
888varargs string* QueryUIDsForType(int type) {
889 //liefert alle UIDs fuer einen Fehlertyp oder fuer alle Fehlertypen
890 string** rows;
891
892 if (type)
893 {
894 rows = sl_exec("SELECT uid FROM issues "
895 "WHERE type=?1 AND deleted=0 AND resvoled=0;", type);
896 }
897 else
898 rows = sl_exec("SELECT uid FROM issues WHERE deleted=0 "
899 "AND resolved=0;");
900
901 return map(rows, function string (string* item)
902 {return item[0];} );
903}
904
905//Wieviele unterschiedliche Fehler in diesem Typ?
906varargs int QueryUniqueIssueCount(int type, string uid)
907{
908 int** rows;
909
910 if (type && uid)
911 {
912 rows = sl_exec("SELECT count(*) FROM issues "
913 "WHERE type=?1 AND uid=?2 AND deleted=0 AND resolved=0;",
914 type, uid);
915 }
916 else if (type)
917 {
918 rows = sl_exec("SELECT count(*) FROM issues "
919 "WHERE type=?1 AND deleted=0 AND resolved=0;",
920 type);
921 }
922 else if (uid)
923 {
924 rows = sl_exec("SELECT count(*) FROM issues "
925 "WHERE uid=?1 AND deleted=0 AND resolved=0;",
926 uid);
927 }
928 else
929 rows = sl_exec("SELECT count(*) FROM issues "
930 "WHERE deleted=0 AND resolved=0;");
931
932 return rows[0][0];
933}
934
935//Einen bestimmten Fehler loeschen
936varargs int ToggleDeleteError(int issueid, string note)
937{
938 mixed rows = sl_exec("SELECT uid,deleted from issues WHERE id=?1;",
939 issueid);
940 if (!pointerp(rows))
941 return -1;
942
943 if (!access_check(rows[0][0], M_DELETE))
944 //zugriff zum Schreiben nicht gestattet
945 return -10;
946
947// sl_exec("BEGIN TRANSACTION;");
948 if (rows[0][1])
949 {
950 // was deleted -> undelete it
951 sl_exec("UPDATE issues SET deleted=0,mtime=?2 WHERE id=?1;",
952 issueid,time());
953 db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
954 txt: sprintf("Loeschmarkierung entfernt. (%s)",
955 note ? note: "<kein Kommentar>")
956 ));
957 }
958 else
959 {
960 // was not deleted -> delete it.
961 sl_exec("UPDATE issues SET deleted=1,mtime=?2 WHERE id=?1;",
962 issueid, time());
963 db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
964 txt: sprintf("Loeschmarkierung gesetzt. (%s)",
965 note ? note: "<kein Kommentar>")
966 ));
967 }
968// sl_exec("COMMIT;");
969 return !rows[0][1];
970}
971
972
973// sperrt den Eintrag oder entsperrt ihn.
974// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
975// wird.
976varargs int LockIssue(int issueid, string note) {
977 return set_lock(issueid, 1, note);
978}
979
980varargs int UnlockIssue(int issueid, string note) {
981 return set_lock(issueid, 0, note);
982}
983
984// einen Fehler als gefixt markieren
985varargs int ResolveIssue(int issueid, string note) {
986 return set_resolution(issueid, 1, note);
987}
988// einen Fehler als nicht gefixt markieren
989varargs int ReOpenIssue(int issueid, string note) {
990 return set_resolution(issueid, 0, note);
991}
992
993varargs int AddNote(int issueid, string note)
994{
995
996 if (!stringp(note) || !sizeof(note))
997 return(-3);
998
999 // existiert die ID in der DB?
1000 struct fullissue_s issue = db_get_issue(issueid,0);
1001 if (!issue)
1002 return -1;
1003
1004 if (!access_check(issue->uid, M_WRITE))
1005 //zugriff zum Schreiben nicht gestattet
1006 return(-10);
1007
1008 return db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
1009 txt: note));
1010}
1011
1012//Einen bestimmten Fehler einer anderen UID zuweisen.
1013//Hashkey ist zwar eindeutig, aber Angabe der
1014//der UID ist deutlich schneller. Weglassen des Typs nochmal langsamer. ;-)
1015//Potentiell also sehr teuer, wenn man UID oder UID+Typ weglaesst.
1016varargs int ReassignIssue(int issueid, string newuid, string note)
1017{
1018 struct fullissue_s issue = db_get_issue(issueid,0);
1019 if (!issue)
1020 return -1;
1021
1022 if (!access_check(issue->uid, M_REASSIGN))
1023 //zugriff zum Schreiben nicht gestattet
1024 return(-10);
1025
1026 return db_reassign_issue(issueid, newuid, note);
1027}
1028
Bugfix67091cb2022-09-22 15:57:31 +02001029varargs int ChangeType(int issueid, int newtype, int oldtype, string note)
1030{
1031 struct fullissue_s issue = db_get_issue(issueid, 0);
1032 if(!issue)
1033 return -1;
1034
1035 if (!access_check(issue->uid, M_CHANGE_TYPE))
1036 {
1037 // Zugriff zum Schreiben nicht gestattet
1038 return(-10);
1039 }
1040
1041 sl_exec(
1042 "UPDATE issues SET type=?1,mtime=?2 WHERE id=?3;",
1043 newtype, time(), issueid);
1044 db_add_note(
1045 (<note_s> id: issueid, time: time(), user: getuid(TI),
1046 txt: sprintf("Fehlertyp von %s auf %s geaendert. (%s)",
1047 print_type(oldtype)[0], print_type(newtype)[0], (note || "<kein Kommentar>"))));
1048 return 1;
1049}
1050
MG Mud User88f12472016-06-24 23:31:02 +02001051/* *********** Eher fuer Debug-Zwecke *********************** */
1052/*
1053mixed QueryAll(int type) {
1054 //das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
1055 //Kopie zurueckgegeben, daher erstmal nur ich...
1056 if (!this_interactive() ||
1057 member(MAINTAINER,getuid(this_interactive()))<0)
1058 return(-1);
1059 if (process_call()) return(-2);
1060 if (!type) return(-3);
1061 return(errors[type]);
1062}
1063
1064mixed QueryResolved()
1065{
1066 //das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
1067 //Kopie zurueckgegeben, daher erstmal nur ich...
1068 if (!this_interactive() ||
1069 member(MAINTAINER,getuid(this_interactive()))<0)
1070 return(-1);
1071 if (process_call()) return(-2);
1072 return(resolved);
1073}
1074*/
1075
1076/* ***************** Internal Stuff ******************** */
1077void create() {
1078 seteuid(getuid(ME));
1079
bugfix4fcca732020-03-23 17:04:14 +01001080 if (sl_open("/"LIBDATADIR"/secure/ARCH/errord.sqlite") != 1)
MG Mud User88f12472016-06-24 23:31:02 +02001081 //if (sl_open("/errord.sqlite") != 1)
1082 {
1083 raise_error("Datenbank konnte nicht geoeffnet werden.\n");
1084 }
1085 sl_exec("PRAGMA foreign_keys = ON; PRAGMA temp_store = 2; ");
1086 //string* res=sl_exec("PRAGMA quick_check(N);");
1087 //if (pointerp(res))
1088 //{
1089 // raise_error("");
1090 //}
1091 //sl_exec("CREATE TABLE issue(id INTEGER,haskey TEXT);");
1092 foreach (string cmd :
1093 explode(read_file("/secure/ARCH/errord.sql.init"),";\n"))
1094 {
1095 if (sizeof(cmd) && cmd != "\n")
1096 {
1097 sl_exec(cmd);
1098 }
1099 }
1100 sl_exec("ANALYZE main;");
1101
1102 lasterror=([]);
1103}
1104
1105string name() {return("<Error-Daemon>");}
1106
1107void save_me(int now) {
1108 if (now)
1109 save_object(SAVEFILE);
1110 else if (find_call_out(#'save_object)==-1) {
1111 call_out(#'save_object, 30, SAVEFILE);
1112 }
1113}
1114
1115varargs int remove(int silent) {
1116 save_me(1);
1117 destruct(ME);
1118 return(1);
1119}
1120
1121
1122// sperrt den Eintrag (lock!=0) oder entsperrt ihn (lock==0).
1123// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
1124// wird.
1125// liefert <0 im Fehlerfall, sonst Array mit Lockdaten
1126private varargs int set_lock(int issueid, int lock, string note)
1127{
1128 struct fullissue_s issue = db_get_issue(issueid,0);
1129 if (!issue)
1130 return -1;
1131
1132 if (!access_check(issue->uid, M_WRITE))
1133 //zugriff zum Schreiben nicht gestattet
1134 return(-10);
1135
1136 return db_set_lock(issueid, lock, note);
1137}
1138
1139//markiert einen Fehler als gefixt, mit 'note' als Bemerkung (res!=0) oder
1140//markiert einen Fehler wieder als nicht-gefixt (resolution==0)
1141//liefert < 0 im Fehlerfall, sonst den neuen Zustand.
1142private varargs int set_resolution(int issueid, int resolution, string note)
1143{
1144 struct fullissue_s issue = db_get_issue(issueid,0);
1145 if (!issue)
1146 return -1;
1147
1148 // Fixen duerfen nur zustaendige
1149 if (resolution
1150 && !access_check(issue->uid, M_FIX))
1151 return -10;
1152 // ggf. Fix zurueckziehen darf jeder mit M_WRITE
1153 if (!resolution &&
1154 !access_check(issue->uid, M_WRITE))
1155 return -10;
1156
1157 int res = db_set_resolution(issueid, resolution, note);
1158
1159 if (res == 1)
1160 {
1161 // Fehler jetzt gefixt.
1162 versende_mail(db_get_issue(issueid,0)); // per Mail verschicken
1163 }
1164
1165 return res;
1166}
1167
1168//ist der Zugriff auf uid erlaubt? Geprueft wird TI (wenn kein TI, auch kein
1169//Schreibzugriff)
1170//mode gibt an, ob lesend oder schreibend
1171private int access_check(string uid, int mode) {
1172
1173 if (mode==M_READ)
1174 return LEARNER_SECURITY; //lesen darf jeder Magier
1175
1176 // In process_string() schonmal gar nicht.
1177 if (process_call()) return(0);
1178 // EM+ duerfen alles loeschen.
1179 if (ARCH_SECURITY) return(1);
1180 // eigene UIDs darf man auch vollumfaenglich bearbeiten.
1181 if (secure_euid()==uid) return(1);
1182
1183 // alles andere wird speziell geprueft
1184 switch(mode)
1185 {
1186 // M_WRITE ist zur zeit eigentlich immer nen Append - das duerfen zur
1187 // Zeit alle ab Vollmagier
1188 case M_WRITE:
1189 return LEARNER_SECURITY;
MG Mud User88f12472016-06-24 23:31:02 +02001190 // Loeschen und Fixen duerfen zur Zeit nur Zustaendige.
1191 case M_DELETE:
1192 case M_REASSIGN:
1193 case M_FIX:
Bugfix67091cb2022-09-22 15:57:31 +02001194 case M_CHANGE_TYPE:
MG Mud User88f12472016-06-24 23:31:02 +02001195 // Master nach UIDs fragen, fuer die der jew. Magier
1196 // zustaendig ist.
Vanionb4c06532020-03-09 21:54:54 +01001197 if (member(({string*})master()->QueryUIDsForWizard(secure_euid()),uid) >= 0)
MG Mud User88f12472016-06-24 23:31:02 +02001198 return 1;
1199
1200 break;
1201 }
1202
1203 return(0); //Fall-through, neinRNER_SECURITY
1204}
1205
1206public string format_stacktrace(struct frame_s* stacktrace) {
1207 string *lines;
1208
1209 if (!pointerp(stacktrace) || !sizeof(stacktrace))
1210 return("");
1211
1212 lines=allocate(sizeof(stacktrace));
1213 int i;
1214 foreach(struct frame_s frame: stacktrace)
1215 {
1216 lines[i]=sprintf("Fun: %.20O in Prog: %.40s\n"
1217 " Zeile: %.8d [%.50s]\n"
1218 " Evalcosts: %d",
1219 frame->name,frame->prog,
1220 frame->loc,frame->obj,frame->ticks);
1221 ++i;
1222 }
1223 return(implode(lines,"\n"));
1224}
1225
1226public string format_notes(struct note_s* notes)
1227{
1228 int i;
1229 string text="";
1230 foreach(struct note_s note: notes)
1231 {
1232 text+=sprintf("Notiz %d von %.10s am %.30s\n%s",
1233 ++i,capitalize(note->user),dtime(note->time),
1234 break_string(note->txt, 78," "));
1235 }
1236 return text;
1237}
1238
1239public string format_error_spieler(struct fullissue_s issue)
1240{
1241 string txt;
1242 string *label;
1243
1244 if (!issue)
1245 return 0;
1246
1247 switch(issue->type)
1248 {
1249 case T_RTERROR:
1250 label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
1251 break;
1252 case T_REPORTED_ERR:
1253 label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
1254 break;
1255 case T_REPORTED_TYPO:
1256 label=({"Typo","Dieser Typo"});
1257 break;
1258 case T_REPORTED_IDEA:
1259 label=({"Idee","Diese Idee"});
1260 break;
1261 case T_REPORTED_MD:
1262 label=({"Fehlendes Detail","Dieses fehlende Detail"});
1263 break;
Bugfixa75344d2017-06-16 14:04:48 +02001264 case T_REPORTED_SYNTAX:
1265 label=({"Syntaxproblem","Dieses Syntaxproblem"});
1266 break;
MG Mud User88f12472016-06-24 23:31:02 +02001267 case T_RTWARN:
1268 label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
1269 break;
1270 case T_CTWARN:
1271 label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
1272 break;
1273 case T_CTERROR:
1274 label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
1275 break;
1276 default: return 0;
1277 }
1278
1279 txt=sprintf( "\nDaten fuer %s mit ID '%s':\n"
1280 "Zeit: %25s\n",
1281 label[0], issue->hashkey,
1282 dtime(issue->ctime)
1283 );
1284
1285 txt+=sprintf("%s",break_string(issue->message,78,
1286 "Meldung: ",BS_INDENT_ONCE));
1287
1288 if (pointerp(issue->notes))
1289 txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
1290
1291 return txt;
1292}
1293
Bugfix67091cb2022-09-22 15:57:31 +02001294public string* print_type(int type)
1295{
1296 string* res;
1297 switch(type)
1298 {
1299 case T_RTERROR:
1300 res = ({"Laufzeitfehler","Dieser Laufzeitfehler"});
1301 break;
1302 case T_REPORTED_ERR:
1303 res = ({"Fehlerhinweis","Dieser Fehlerhinweis"});
1304 break;
1305 case T_REPORTED_TYPO:
1306 res = ({"Typo","Dieser Typo"});
1307 break;
1308 case T_REPORTED_IDEA:
1309 res = ({"Idee","Diese Idee"});
1310 break;
1311 case T_REPORTED_MD:
1312 res = ({"Fehlendes Detail","Dieses fehlende Detail"});
1313 break;
1314 case T_REPORTED_SYNTAX:
1315 res = ({"Syntaxproblem","Dieses Syntaxproblem"});
1316 break;
1317 case T_RTWARN:
1318 res = ({"Laufzeitwarnung","Diese Laufzeitwarnung"});
1319 break;
1320 case T_CTWARN:
1321 res = ({"Ladezeitwarnung","Diese Ladezeitwarnung"});
1322 break;
1323 case T_CTERROR:
1324 res = ({"Ladezeitfehler","Dieser Ladezeitfehler"});
1325 break;
1326 default:
1327 res = 0;
1328 }
1329 return res;
1330}
1331
MG Mud User88f12472016-06-24 23:31:02 +02001332public string format_error(struct fullissue_s issue, int only_essential)
1333{
1334 string txt;
1335 string *label;
1336
1337 if (!issue)
1338 return 0;
1339
Bugfix67091cb2022-09-22 15:57:31 +02001340 label = print_type(issue->type);
1341 if(!label) return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001342
1343 txt=sprintf( "\nDaten fuer %s mit ID %d:\n"
1344 "Hashkey: %s\n"
1345 "Zeit: %25s (Erstmalig: %25s)\n",
1346 label[0], issue->id, issue->hashkey,
1347 dtime(abs(issue->mtime)),dtime(issue->ctime)
1348 );
1349
1350 if (stringp(issue->prog))
1351 txt += sprintf(
1352 "Programm: %.60s\n"
1353 "Zeile: %.60d\n",
1354 issue->prog, issue->loc
1355 );
1356
1357 if (stringp(issue->obj))
1358 txt+=sprintf("Objekt: %.60s\n",
1359 issue->obj);
1360
1361 txt += sprintf("Loadname: %.60s\n"
1362 "UID: %.60s\n",
1363 issue->loadname, issue->uid);
1364
1365 txt+=sprintf("%s",break_string(issue->message,78,
1366 "Meldung: ",BS_INDENT_ONCE));
1367 if (stringp(issue->hbobj))
1368 txt+=sprintf(
1369 "HB-Obj: %.60s\n",issue->hbobj);
1370
1371 if (stringp(issue->titp)) {
1372 txt+=sprintf(
1373 "TI/TP: %.60s\n",issue->titp);
1374 if (stringp(issue->tienv))
1375 txt+=sprintf(
1376 "Environm.: %.60s\n",issue->tienv);
1377 }
1378
1379 if (!stringp(issue->command) ||
1380 !ARCH_SECURITY || process_call())
1381 {
1382 // Kommandoeingabe ist Privatsphaere und darf nicht von jedem einsehbar
1383 // sein.
1384 // in diesem Fall aber zumindest das Verb ausgeben, so vorhanden
1385 if (issue->verb)
1386 txt+=sprintf(
1387 "Verb: %.60s\n",issue->verb);
1388 }
1389 // !process_call() && ARCH_SECURITY erfuellt...
1390 else if (stringp(issue->command))
1391 txt+=sprintf(
1392 "Befehl: %.60s\n",issue->command);
1393
1394 if (issue->caught)
1395 txt+=label[1]+" trat in einem 'catch()' auf.\n";
1396
1397 if (!only_essential)
1398 {
1399 if (issue->deleted)
1400 txt+=label[1]+" wurde als geloescht markiert.\n";
1401
1402 if (issue->locked)
1403 txt+=break_string(
1404 sprintf("%s wurde von %s am %s vor automatischem Loeschen "
1405 "geschuetzt (locked).\n",
1406 label[1],issue->locked_by, dtime(issue->locked_time)),78);
1407 if (issue->resolved)
1408 txt+=label[1]+" wurde als erledigt markiert.\n";
1409 }
1410
1411 txt+=sprintf("%s trat bisher %d Mal auf.\n",
1412 label[1],issue->count);
1413
1414 if (pointerp(issue->stack))
1415 txt+="Stacktrace:\n"+format_stacktrace(issue->stack)+"\n";
1416
1417 if (pointerp(issue->notes))
1418 txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
1419
1420 return txt;
1421}
1422
1423// letzter Aenderung eines Spieler-/Magiersavefiles. Naeherung fuer letzten
1424// Logout ohne das Savefile einzulesen und P_LAST_LOGOUT zu pruefen.
1425private int recent_lastlogout(string nam, int validtime)
1426{
1427 if (!nam || sizeof(nam)<2) return 0;
1428 return file_time("/"LIBSAVEDIR"/" + nam[0..0] + "/" + nam + ".o") >= validtime;
1429}
1430
1431// Versendet Mail an zustaendigen Magier und ggf. Spieler, der den Eintrag
1432// erstellt hat.
1433// ACHTUNG: loescht issue->command.
1434private int versende_mail(struct fullissue_s issue)
1435{
1436 // Versendet eine mail mit dem gefixten Fehler.
1437 mixed *mail;
1438 string text, *empf;
1439 int res = -1;
1440
1441 mail = allocate(9);
1442 mail[MSG_FROM] = "<Fehler-Daemon>";
1443 mail[MSG_SENDER] = getuid(TI);
1444 mail[MSG_BCC] = 0;
1445 mail[MSG_SUBJECT] = sprintf("Fehler/Warnung in %s behoben", issue->loadname);
1446 mail[MSG_DATE] = dtime(time());
1447
1448 // auch wenn ein EM fixt, sollen die Empfaenger nicht automatisch die
1449 // Spielereingabe erhalten, also command loeschen.
1450 issue->command = 0;
1451
1452 // erstmal eine Mail an zustaendige Magier.
Vanionb4c06532020-03-09 21:54:54 +01001453 empf = ({string*})master()->QueryWizardsForUID(issue->uid);
MG Mud User88f12472016-06-24 23:31:02 +02001454 // lang (180 Tage) nicht eingeloggte Magier ausfiltern
1455 empf = filter(empf, #'recent_lastlogout, time() - 15552000);
1456 if (sizeof(empf))
1457 {
1458
1459 text = break_string(
1460 sprintf(STANDARDMAILTEXT,capitalize(getuid(TI)))
1461 +format_error(issue, 1),78,"",BS_LEAVE_MY_LFS);
1462
1463 mail[MSG_RECIPIENT] = empf[0];
1464 if (sizeof(empf)>1)
1465 mail[MSG_CC] = empf[1..];
1466 else
1467 mail[MSG_CC] = 0;
1468 mail[MSG_BODY]=text;
1469 mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
1470
bugfixd94d0932020-04-08 11:27:13 +02001471 if (!sizeof(({string*})"/secure/mailer"->DeliverMail(mail,0)))
MG Mud User88f12472016-06-24 23:31:02 +02001472 res = -1; // an niemanden erfolgreich zugestellt. :-(
1473 else
1474 res = 1;
1475 }
1476
1477 // Bei von Spielern gemeldeten Fehler werden Spieler bei
1478 // Erledigung informiert, wenn deren letzter Logout weniger als 180 Tage her
1479 // ist.
1480 if ( (issue->type &
Bugfixa75344d2017-06-16 14:04:48 +02001481 (T_REPORTED_ERR|T_REPORTED_TYPO|T_REPORTED_IDEA|T_REPORTED_MD|
1482 T_REPORTED_SYNTAX))
MG Mud User88f12472016-06-24 23:31:02 +02001483 && issue->titp
1484 && recent_lastlogout(issue->titp, time() - 15552000) )
1485 {
1486 text = break_string(
1487 sprintf(STANDARDMAILTEXT_ERRORHINT,
1488 capitalize(issue->titp), capitalize(getuid(TI)))
1489 +format_error_spieler(issue), 78,"",BS_LEAVE_MY_LFS);
1490
1491 mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
1492 mail[MSG_RECIPIENT] = issue->titp;
1493 mail[MSG_CC] = 0;
Zesstrac5401ce2021-02-13 20:28:16 +01001494 mail[MSG_SUBJECT] = sprintf("Fehler/Idee/Typo wurde von %s bearbeitet",
MG Mud User88f12472016-06-24 23:31:02 +02001495 getuid(TI));
1496 mail[MSG_BODY] = text;
1497
bugfixd94d0932020-04-08 11:27:13 +02001498 if (!sizeof(({string*})"/secure/mailer"->DeliverMail(mail,0)))
MG Mud User88f12472016-06-24 23:31:02 +02001499 res |= -1;
1500 else
1501 res |= 1;
1502 }
1503
1504 return res;
1505}
1506
1507void reset()
1508{
1509 // geloeschte Issues sofort, gefixte 30 Tage nach letzter Aenderung
1510 // loeschen.
1511 sl_exec("DELETE FROM issues WHERE deleted=1;");
1512 sl_exec("DELETE FROM issues WHERE resolved=1 AND mtime<?1;",
1513 time()-30*24*3600);
1514 set_next_reset(3600*24);
1515}
1516
1517// Nicht jeder Magier muss den ErrorD direkt zerstoeren koennen.
1518public string NotifyDestruct(object caller) {
1519 if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
1520 return "Du darfst den Error-Daemon nicht zerstoeren!\n";
1521 }
1522 return 0;
1523}
1524