blob: a17639b715e7069ef54ae9b758676353e76d858c [file] [log] [blame]
/* MorgenGrauen MUDlib
/p/daemon/errord.c
speichert Fehler und Warnungen
Autor: Zesstra
$Id: errord.c 9439 2016-01-20 09:48:28Z Zesstra $
ggf. Changelog:
*/
#pragma strict_types,save_types,rtt_checks
#pragma no_clone
#pragma no_shadow
#pragma no_inherit
#pragma pedantic
#pragma range_check
#pragma warn_deprecated
#include <config.h>
#include <wizlevels.h>
#include <defines.h>
#include <debug_info.h>
#include <commands.h>
#include <wizlevels.h>
#include <mail.h>
#include <tls.h>
#include <events.h>
inherit "/secure/errord-structs";
#define __NEED_IMPLEMENTATION__
#include "errord.h"
#undef __NEED_IMPLEMENTATION__
#define SAVEFILE (__DIR__+"ARCH/errord")
#define TI this_interactive()
private int access_check(string uid,int mode);
private varargs int set_lock(int issueid, int lock, string note);
private varargs int set_resolution(int issueid, int resolution, string note);
private int versende_mail(struct fullissue_s fehler);
nosave mapping lasterror; // die letzen 5 jeder Art.
/* ******************* Helfer **************************** */
public int getErrorID(string hashkey)
{
int** row=sl_exec("SELECT id from issues WHERE hashkey=?1;",
hashkey);
//DEBUG(sprintf("getEID: %s: %O\n",hashkey,row));
if (pointerp(row))
{
return row[0][0];
}
return -1;
}
// note->id muss auf einen Eintrag in issues verweisen, es erfolgt keine
// Pruefung.
int db_add_note(struct note_s note)
{
sl_exec("INSERT INTO notes(issueid,time,user,txt) "
"VALUES(?1,?2,?3,?4);",
to_array(note)...);
return sl_insert_id();
}
private struct frame_s* db_get_stack(int issueid)
{
mixed rows = sl_exec("SELECT * FROM stacktraces WHERE issueid=?1 "
"ORDER BY rowid;", issueid);
if (pointerp(rows))
{
struct frame_s* stack = allocate(sizeof(rows));
int i;
foreach(mixed row : rows)
{
stack[i] = to_struct(row, (<frame_s>));
++i;
}
return stack;
}
return 0;
}
private struct note_s* db_get_notes(int issueid)
{
mixed rows = sl_exec("SELECT * FROM notes WHERE issueid=?1 "
"ORDER BY rowid;", issueid);
if (pointerp(rows))
{
struct note_s* notes = allocate(sizeof(rows));
int i;
foreach(mixed row : rows)
{
notes[i] = to_struct(row, (<note_s>));
++i;
}
return notes;
}
return 0;
}
// einen durch id oder hashkey bezeichneten Eintrag als fullissue_s liefern.
private struct fullissue_s db_get_issue(int issueid, string hashkey)
{
mixed rows = sl_exec("SELECT * FROM issues WHERE id=?1 OR hashkey=?2;",
issueid, hashkey);
if (pointerp(rows))
{
// Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
// die gleiche Reihenfolge wie in der struct haben! Entweder immer
// sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
struct fullissue_s issue = to_struct( rows[0], (<fullissue_s>) );
if (issue->type == T_RTERROR)
issue->stack = db_get_stack(issue->id);
issue->notes = db_get_notes(issue->id);
return issue;
}
return 0;
}
private struct fullissue_s filter_private(struct fullissue_s issue)
{
//momentan wird F_CLI, also die Spielereingabe vor dem Fehler
//ausgefiltert, wenn TI kein EM oder man in process_string() ist.
//Wenn EM und nicht in process_string() oder die Spielereingabe gar nicht
//im Fehlereintrag drinsteht: ungefiltert zurueck
if (!issue->command ||
(!process_call() && ARCH_SECURITY) )
return issue;
//sonst F_CLI rausfiltern, also Kopie und in der Kopie aendern.
issue->command="Bitte EM fragen";
return issue;
}
// setzt oder loescht die Loeschsperre.
// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
// Rueckgabe: -1, wenn Issue nicht existiert, -2 wenn bereits resolved, -3
// wenn keine Aenderung noetig, sonst den neuen Sperrzustand
int db_set_lock(int issueid, int lockstate, string note)
{
int** rows = sl_exec("SELECT locked,resolved FROM issues WHERE id=?1;",
issueid);
if (!rows)
return -1; // nicht vorhanden.
if (rows[0][1])
return -2; // bereits resolved -> Sperre nicht moeglich.
if (lockstate && !rows[0][0])
{
// Sperren
// sl_exec("BEGIN TRANSACTION;");
sl_exec("UPDATE issues SET locked=1,locked_by=?2,locked_time=?3,mtime=?3 "
"WHERE id=?1;",
issueid, getuid(TI), time());
db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Lock gesetzt: %s",
note ? note : "<kein Kommentar>")) );
// sl_exec("COMMIT;");
return 1;
}
else if (!lockstate && rows[0][0])
{
// entsperren
// sl_exec("BEGIN TRANSACTION;");
sl_exec("UPDATE issues SET locked=0,locked_by=0,locked_time=0,mtime=?2 "
"WHERE id=?1;", issueid, time());
db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Lock geloescht: %s",
note ? note : "<kein Kommentar>")) );
// sl_exec("COMMIT;");
return 0;
}
// nix aendern.
return -3;
}
// markiert ein Issue als gefixt oder nicht gefixt.
// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
// Rueckgabe: -1, wenn Issue nicht existiert, -3 wenn keine Aenderung noetig,
// sonst den neuen Sperrzustand
int db_set_resolution(int issueid, int resolved, string note)
{
int** rows = sl_exec("SELECT resolved FROM issues WHERE id=?1;",
issueid);
if (!rows)
return -1; // nicht vorhanden.
if (resolved && !rows[0][0])
{
// Als gefixt markieren.
// sl_exec("BEGIN TRANSACTION;");
sl_exec("UPDATE issues SET resolved=1,resolver=?2,mtime=?3 "
"WHERE id=?1;",
issueid, getuid(TI),time());
db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Fehler gefixt: %s",
note ? note : "<kein Kommentar>")) );
// sl_exec("COMMIT;");
return 1;
}
else if (!resolved && rows[0][0])
{
// als nicht gefixt markieren.
// sl_exec("BEGIN TRANSACTION;");
sl_exec("UPDATE issues SET resolved=0,resolver=0,mtime=?2 "
"WHERE id=?1;", issueid, time());
db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Fix zurueckgezogen: %s",
note ? note : "<kein Kommentar>")) );
// sl_exec("COMMIT;");
return 0;
}
// nix aendern.
return -3;
}
// Transferiert ein Issue zu einer neuen zustaendigen UID
// Prueft, ob <issueid> existiert und aendert den Zustand nur, wenn noetig.
// Rueckgabe: -1, wenn Issue nicht existiert, -3 wenn keine Aenderung noetig,
// 1, wenn erfolgreich neu zugewiesen
int db_reassign_issue(int issueid, string newuid, string note)
{
string** rows = sl_exec("SELECT uid FROM issues WHERE id=?1;",
issueid);
if (!rows)
return -1; // nicht vorhanden.
if (!stringp(newuid))
return(-2);
if (newuid != rows[0][0])
{
// sl_exec("BEGIN TRANSACTION;");
sl_exec("UPDATE issues SET uid=?2,mtime=?3 WHERE id=?1;",
issueid, newuid,time());
db_add_note( (<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Fehler von %s an %s uebertragen. (%s)",
rows[0][0], newuid,
note ? note : "<kein Kommentar>")) );
// sl_exec("COMMIT;");
return 1;
}
return -3;
}
// inkrementiert count und aktualisiert mtime, atime.
// Ausserdem wird ggf. das Loeschflag genullt - ein erneut aufgetretener
// Fehler sollte anschliessend nicht mehr geloescht sein. Geloeste
// (resolved) Eintraege werden NICHT auf ungeloest gesetzt. Vermutlich trat
// der Fehler in einem alten Objekte auf...
// Issue muss in der DB existieren.
int db_countup_issue(int issueid)
{
sl_exec("UPDATE issues SET count=count+1,mtime=?2,atime=?2,deleted=0 WHERE id=?1;",
issueid,time());
return 1;
}
// Das Issue wird ggf. ent-loescht und als nicht resvolved markiert.
// Sind pl und msg != 0, wird eine Notiz angehaengt.
// aktualisiert mtime, atime.
// Issue muss in der DB existieren.
int db_reopen_issue(int issueid, string pl, string msg)
{
int** row=sl_exec("SELECT deleted,resolved from issues WHERE id=?1;",
issueid);
if (pointerp(row)
&& (row[0][0] || row[0][1]) )
{
// sl_exec("BEGIN TRANSACTION;");
if (pl && msg)
{
db_add_note( (<note_s> id: issueid,
time: time(),
user: pl,
txt: msg) );
}
sl_exec("UPDATE issues SET "
"deleted=0,resolved=0,resolver=0,mtime=?2,atime=?2 WHERE id=?1;",
issueid,time());
// sl_exec("COMMIT;");
}
return 1;
}
int db_insert_issue(struct fullissue_s issue)
{
//DEBUG(sprintf("db_insert: %O\n", issue));
mixed row=sl_exec("SELECT id from issues WHERE hashkey=?1;",
issue->hashkey);
//DEBUG(sprintf("insert: %s: %O\n",issue->hashkey,row));
if (pointerp(row))
{
issue->id=row[0][0];
return db_countup_issue(issue->id);
}
// sl_exec("BEGIN TRANSACTION;");
sl_exec("INSERT INTO issues(hashkey,uid,type,mtime,ctime,atime,count,"
"deleted,resolved,locked,locked_by,locked_time,resolver,message,"
"loadname,obj,prog,loc,titp,tienv,hbobj,caught,command,verb) "
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,"
"?16,?17,?18,?19,?20,?21,?22,?23,?24);",
(to_array(issue)[1..24])...);
issue->id=sl_insert_id();
if (pointerp(issue->stack))
{
foreach(struct frame_s entry : issue->stack)
{
entry->id = issue->id;
sl_exec("INSERT INTO stacktraces(issueid,type,name,prog,obj,loc,ticks) "
"VALUES(?1,?2,?3,?4,?5,?6,?7);",
to_array(entry)...);
}
}
if (pointerp(issue->notes))
{
foreach(struct note_s entry : issue->notes)
{
entry->id = issue->id;
sl_exec("INSERT INTO notes(issueid,time,user,txt) "
"VALUES(?1,?2,?3,?4);",
to_array(entry)...);
}
}
// sl_exec("COMMIT;");
return issue->id;
}
// loggt einen T_REPORTED_ERR, T_REPORTED_IDEA, T_REPORTED_TYPO,
// T_REPORTED_MD, T_REPORTED_SYNTAX
public string LogReportedError(mapping err)
{
//darf nur von Spielershells oder Objekt in /obj/ (z.B. Fehlerteufel oder
//vitem_proxy) gerufen werden.
if (extern_call() && !previous_object()
|| (strstr(load_name(previous_object()),"/std/shells/") == -1
&& strstr(load_name(previous_object()), "/"LIBOBJDIR"/") == -1))
return 0;
//DEBUG("LogReportedError\n");
// DEBUG(sprintf("%O\n",err));
string uid = ({string})master()->creator_file(err[F_OBJ]);
// default-Typ
if (!member(err, F_TYPE)) err[F_TYPE] = T_REPORTED_ERR;
// div. Keys duerfen nicht gesetzt sein.
err -= ([F_STATE, F_READSTAMP, F_CAUGHT, F_STACK, F_CLI, F_VERB,
F_LOCK, F_RESOLVER, F_NOTES]);
// Errormapping in issue-struct umwandeln und befuellen.
struct fullissue_s issue = (<fullissue_s>);
issue->type = err[F_TYPE];
issue->uid = uid;
issue->mtime = issue->ctime = issue->atime = time();
issue->count=1;
issue->loadname = load_name(err[F_OBJ]);
issue->message = err[F_MSG];
issue->obj = objectp(err[F_OBJ]) ? object_name(err[F_OBJ]) : err[F_OBJ];
// Normalisieren auf fuehrenden / und kein .c
if (err[F_PROG]!="unbekannt")
issue->prog = load_name(err[F_PROG]);
else
issue->prog = "unbekannt";
issue->titp = getuid(this_interactive() || this_player());
if (objectp(err[F_OBJ]))
issue->tienv = object_name(environment(err[F_OBJ]));
//DEBUG(sprintf("%O\n",issue));
issue->hashkey = hash(TLS_HASH_MD5,
sprintf("%d%s%s", issue->type, issue->loadname, issue->message));
// ggf. vorhandenen Fehler suchen - zugegeben: sollte bei von Spielern
// gemeldeten Dingen vermutlich nie vorkommen...
int oldid = getErrorID(issue->hashkey);
if (oldid >= 0)
{
// ggf. sicherstellen, dass er wieder eroeffnet wird.
db_reopen_issue(oldid, "<ErrorD>",
"Automatisch wiedereroeffnet wegen erneutem Auftreten.");
db_countup_issue(oldid);
return issue->hashkey;
}
// sonst fuegen wir einen neuen Eintrag hinzu
// Spielergemeldete Bugs werden erstmal vor automatischem Loeschen
// geschuetzt, bis ein zustaendiger Magier ihn zur Kenntnis nimmt und
// entsperrt.
issue->locked = 1;
issue->locked_by = getuid(TI || PL);
issue->locked_time = time();
// In DB eintragen.
issue->id = db_insert_issue(issue);
lasterror[issue->type]=issue->id;
// Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
([F_TYPE: issue->type, F_HASHKEY:issue->hashkey,
F_UID:issue->uid, F_ID: issue->id]));
DEBUG(sprintf("LogReportedError: %s\n",issue->hashkey));
return issue->hashkey;
}
//Fehler registrieren
//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
//Master gerufen!
public int LogError(string msg,string prg,string curobj,int line,mixed culprit,
int caught)
{
//DEBUG(sprintf("LogError: Prog: %O, Obj: %O,\n",prg,curobj));
//darf nur vom Master gerufen werden
if (!extern_call() ||
(previous_object() && previous_object() != master()))
return 0;
struct fullissue_s issue = (<fullissue_s>);
//UID bestimmen
issue->uid=({string})master()->creator_file(curobj);
//DEBUG(sprintf("LogError: UID: %s\n",uid));
//Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
if (!stringp(curobj) || !sizeof(curobj))
issue->loadname = curobj = "<Unbekannt>";
else
{
//load_name nimmt Strings und Objects und konstruiert den loadname,
//wie er sein sollte, wenn das Objekt nicht mehr existiert.
issue->loadname=load_name(curobj);
}
if (!stringp(issue->loadname))
{
//hier kommt man rein, falls curobj ein 'kaputter' Name ist,
//d.h. load_name() 0 liefert.
issue->loadname="<Illegal object name>";
}
// Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
// ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
// nicht gespeichert.
if (this_interactive() && IS_LEARNER(this_interactive())
&& strstr(issue->loadname,WIZARDDIR)==0
&& ({int})this_interactive()->QueryProp(P_DONT_LOG_ERRORS))
{
return 0;
}
// prg und curobj auf fuehrenden / und ohne .c am Ende normieren.
if (stringp(prg))
issue->prog = load_name(prg);
if (stringp(curobj) && curobj[0]!='/')
{
curobj="/"+curobj;
}
issue->obj = curobj;
issue->loc = line;
issue->message = msg;
issue->ctime = issue->mtime = issue->atime = time();
issue->type = T_RTERROR;
issue->count = 1;
issue->caught = caught;
//Hashkey bestimmen: Typ, Name der Blueprint des buggenden Objekts,
//Programmname, Zeilennr., Fehlermeldung
//TODO: evtl. sha1() statt md5()?
issue->hashkey=hash(TLS_HASH_MD5,
sprintf("%d%s%s%d%s", T_RTERROR, issue->loadname||"",
issue->prog || "", issue->loc,
issue->message||"<No error message given.>"));
DEBUG(sprintf("LogError: Hashkey: %s", issue->hashkey));
// ggf. vorhandenen Fehler suchen
int oldid = getErrorID(issue->hashkey);
if (oldid >= 0)
{
db_reopen_issue(oldid, "<ErrorD>",
"Automatisch wiedereroeffnet wegen erneutem Auftreten.");
db_countup_issue(oldid);
return oldid;
}
//sonst fuegen wir einen neuen Eintrag hinzu
//DEBUG(sprintf("LogError: OBJ: %s, BP: %s",curobj,loadname));
// Wenn Fehler im HB, Objektnamen ermitteln
if (objectp(culprit))
issue->hbobj = object_name(culprit);
//gibt es einen TI/TP? Name mit erfassen
mixed tienv;
if(objectp(this_interactive()))
{
issue->titp=getuid(this_interactive());
tienv=environment(this_interactive());
}
else if (objectp(PL) && query_once_interactive(PL))
{
issue->titp=getuid(PL);
tienv=environment(PL);
}
else if (objectp(PL))
{
issue->titp=object_name(PL);
tienv=environment(PL);
}
if (objectp(tienv))
issue->tienv=object_name(tienv);
// Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
mixed cli;
if (pointerp(cli=command_stack()) && sizeof(cli))
{
issue->verb=cli[0][CMD_VERB];
issue->command=cli[0][CMD_TEXT];
}
//stacktrace holen
mixed stacktrace;
if (caught)
stacktrace=debug_info(DINFO_TRACE,DIT_ERROR);
else
stacktrace=debug_info(DINFO_TRACE,DIT_UNCAUGHT_ERROR);
// gueltige Stacktraces haben min. 2 Elemente.
// (leerer Trace: ({"No trace."}))
if (sizeof(stacktrace) > 1)
{
int i;
issue->stack = allocate(sizeof(stacktrace)-1);
// erstes Element ist 0 oder HB-Objekt: kein frame, daher ueberspringen
foreach(mixed entry : stacktrace[1..])
{
// frame->id will be set later by db_insert_issue().
struct frame_s frame = (<frame_s> type : entry[TRACE_TYPE],
name: entry[TRACE_NAME],
prog: entry[TRACE_PROGRAM],
obj: entry[TRACE_OBJECT],
loc: entry[TRACE_LOC],
ticks: entry[TRACE_EVALCOST]);
issue->stack[i] = frame;
++i;
}
}
issue->id = db_insert_issue(issue);
lasterror[T_RTERROR]=issue->id;
// Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
([ F_TYPE: T_RTERROR, F_HASHKEY: issue->hashkey, F_UID:
issue->uid, F_ID: issue->id ]));
// DEBUG(sprintf("LogError: Fehlereintrag:\n%O\n",
// errors[uid][hashkey]));
// DEBUG(sprintf("LogError: Verbrauchte Ticks: %d\n",
// 200000-get_eval_cost()));
return issue->id;
}
//Warnungen registrieren
//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
//Master gerufen!
public int LogWarning(string msg,string prg,string curobj,int line, int in_catch)
{
//DEBUG(sprintf("LogWarning: Prog: %O, Obj: %O,\n",prg,curobj));
//darf nur vom Master gerufen werden
if (!extern_call() ||
(previous_object() && previous_object() != master()))
return 0;
struct fullissue_s issue = (<fullissue_s>);
//UID bestimmen
issue->uid=({string})master()->creator_file(curobj);
//DEBUG(sprintf("LogWarning UID: %s\n",uid));
//Loadname (besser als BP, falls rename_object() benutzt wurde) bestimmen
if (!stringp(curobj) || !sizeof(curobj))
issue->loadname = curobj = "<Unbekannt>";
else
{
//load_name nimmt Strings und Objects und konstruiert den loadname,
//wie er sein sollte, wenn das Objekt nicht mehr existiert.
issue->loadname=load_name(curobj);
}
if (!stringp(issue->loadname))
//hier sollte man reinkommen, falls curobj ein 'kaputter' Name ist,
//d.h. load_name() 0 liefert.
issue->loadname="<Illegal object name>";
// prg und curobj auf abs. Pfade normalisieren.
if (stringp(prg))
issue->prog=load_name(prg);
if (stringp(curobj) && curobj[0]!='/')
{
curobj="/"+curobj;
}
//DEBUG(sprintf("LogWarning: OBJ: %s, BP: %s\n",curobj,blue));
// Wenn curobj in /players/ liegt, es einen TI gibt, welcher ein Magier
// ist und dieser die Prop P_DONT_LOG_ERRORS gesetzt hat, wird der FEhler
// nicht gespeichert.
if (this_interactive() && IS_LEARNER(this_interactive())
&& strstr(issue->loadname,WIZARDDIR)==0
&& ({int})this_interactive()->QueryProp(P_DONT_LOG_ERRORS)) {
return 0;
}
//Hashkey bestimmen, Typ, Name der Blueprint des buggenden Objekts, Programm
//Zeilennr., Warnungsmeldung
issue->hashkey=hash(TLS_HASH_MD5,
sprintf("%d%s%s%d%s", T_RTWARN, issue->loadname, issue->prog, line,
msg));
//DEBUG(sprintf("LogWarning: Hashkey: %s",hashkey));
// ggf. vorhandenen Fehler suchen
int oldid = getErrorID(issue->hashkey);
if (oldid >= 0)
{
db_reopen_issue(oldid, "<ErrorD>",
"Automatisch wiedereroeffnet wegen erneutem Auftreten.");
db_countup_issue(oldid);
return oldid;
}
//sonst fuegen wir einen neuen Eintrag hinzu
// erstmal vervollstaendigen
issue->obj = curobj;
issue->message = msg;
issue->ctime = issue->mtime = issue->atime = time();
issue->loc = line;
issue->count = 1;
issue->type = T_RTWARN;
issue->caught = in_catch;
//gibt es einen TI/TP? Name mit erfassen
mixed tienv;
if(objectp(this_interactive()))
{
issue->titp=getuid(this_interactive());
tienv=environment(this_interactive());
}
else if (objectp(PL) && query_once_interactive(PL))
{
issue->titp=getuid(PL);
tienv=environment(PL);
}
else if (objectp(PL))
{
issue->titp=object_name(PL);
tienv=environment(PL);
}
if (objectp(tienv))
issue->tienv=object_name(tienv);
// Mal schauen, ob der Commandstack auch was fuer uns hat. ;-)
mixed cli;
if (pointerp(cli=command_stack()) && sizeof(cli))
{
issue->verb=cli[0][CMD_VERB];
issue->command=cli[0][CMD_TEXT];
}
issue->id = db_insert_issue(issue);
lasterror[T_RTWARN]=issue->id;
// Event triggern, aber nur eine Teilmenge der Daten direkt mitliefern.
EVENTD->TriggerEvent(EVT_LIB_NEW_ERROR,
([F_TYPE: issue->type, F_ID: issue->id,
F_UID: issue->uid, F_HASHKEY: issue->hashkey]) );
// DEBUG(sprintf("LogWarning: Warnungseintrag:\n%O\n",
// warnings[uid][hashkey]));
// DEBUG(sprintf("LogWarning: Verbrauchte Ticks: %d\n",
// 200000-get_eval_cost()));
return issue->id;
}
//Warnungen und Fehler beim Kompilieren registrieren
//Diese Funktion darf nicht mehr als 200k Ticks verbrauchen und wird nur vom
//Master gerufen!
public int LogCompileProblem(string file,string msg,int warn) {
//DEBUG(sprintf("LogCompileProblem: Prog: %O, Obj: %O,\n",file,msg));
//darf nur vom Master gerufen werden
if (!extern_call() ||
(previous_object() && previous_object() != master()))
return 0;
struct fullissue_s issue = (<fullissue_s>);
//UID bestimmen
issue->uid=({string})master()->creator_file(file);
//DEBUG(sprintf("LogCompileProblem UID: %s\n",uid));
// An File a) fuehrenden / anhaengen und b) endendes .c abschneiden. Macht
// beides netterweise load_name().
issue->loadname = load_name(file);
issue->type = warn ? T_CTWARN : T_CTERROR;
//loggen wir fuer das File ueberhaupt?
if (member(BLACKLIST,explode(issue->loadname,"/")[<1])>=0)
return 0;
//Hashkey bestimmen, in diesem Fall einfach, wir koennen die
//Fehlermeldunge selber nehmen.
issue->hashkey=hash(TLS_HASH_MD5,sprintf(
"%d%s%s",issue->type,issue->loadname, msg));
//DEBUG(sprintf("LogCompileProblem: Hashkey: %s",hashkey));
// ggf. vorhandenen Fehler suchen
int oldid = getErrorID(issue->hashkey);
if (oldid >= 0)
{
db_reopen_issue(oldid, "<ErrorD>",
"Automatisch wiedereroeffnet wegen erneutem Auftreten.");
db_countup_issue(oldid);
return oldid;
}
// neuen Eintrag
issue->message = msg;
issue->count = 1;
issue->ctime = issue->mtime = issue->atime = time();
issue->id = db_insert_issue(issue);
if (warn) lasterror[T_CTWARN]=issue->id;
else lasterror[T_CTERROR]=issue->id;
// DEBUG(sprintf("LogCompileProblem: Eintrag:\n%O\n",
// (warn ? ctwarnings[uid][hashkey] : cterrors[uid][hashkey])));
// DEBUG(sprintf("LogCompileProblem: Verbrauchte Ticks: %d\n",
// 200000-get_eval_cost()));
return issue->id;
}
/* **************** Public Interface ****************** */
//Einen bestimmten Fehler nach Hashkey suchen und als fullissue_s inkl. Notes
//und Stacktrace liefern.
struct fullissue_s QueryIssueByHash(string hashkey)
{
struct fullissue_s issue = db_get_issue(0, hashkey);
if (structp(issue))
return filter_private(issue);
return 0;
}
//Einen bestimmten Fehler nach ID suchen und als fullissue_s inkl. Notes
//und Stacktrace liefern.
struct fullissue_s QueryIssueByID(int issueid)
{
struct fullissue_s issue = db_get_issue(issueid, 0);
if (structp(issue))
return filter_private(issue);
return 0;
}
// den letzten Eintrag den jeweiligen Typ liefern.
struct fullissue_s QueryLastIssue(int type)
{
if (!member(lasterror,type))
return 0;
//einfach den kompletten letzten Eintrag zurueckliefern
return(QueryIssueByID(lasterror[type]));
}
// Liefert alle Issues, deren obj,prog oder loadname gleich <file> ist.
public struct fullissue_s* QueryIssuesByFile(string file, int type)
{
mixed rows = sl_exec("SELECT * FROM issues "
"WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
"AND deleted=0 AND resolved=0 AND type=?2"
"ORDER BY type,mtime;", file, type);
if (pointerp(rows))
{
struct fullissue_s* ilist = allocate(sizeof(rows));
int i;
foreach(mixed row : rows)
{
// Einfachster Weg - funktioniert aber nur, solange die Felder in der DB
// die gleiche Reihenfolge wie in der struct haben! Entweder immer
// sicherstellen oder Ergebnisreihenfolge oben im select festlegen!
struct fullissue_s issue = to_struct( row, (<fullissue_s>) );
if (issue->type == T_RTERROR)
issue->stack = db_get_stack(issue->id);
issue->notes = db_get_notes(issue->id);
ilist[i] = filter_private(issue);
++i;
}
return ilist;
}
return 0;
}
// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
// angebenen <type> und <uid>.
varargs < <int|string>* >* QueryIssueListByFile(string file)
{
mixed rows = sl_exec("SELECT id,loadname,obj,prog,loc FROM issues "
"WHERE (loadname=?1 OR prog=?1 OR obj=?1) "
"AND deleted=0 AND resolved=0 "
"ORDER BY type,mtime;", file);
return rows;
}
// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
// angebenen <type> und <uid>.
varargs < <int|string>* >* QueryIssueListByLoadname(string file, int type)
{
mixed rows;
if (type && file)
{
rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
"WHERE loadname=?1 AND type=?2 AND deleted=0 "
"AND resolved=0 "
"ORDER BY type,mtime;", file, type);
}
else if (type)
{
rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
"WHERE type=?1 AND deleted=0 AND resolved=0 "
"ORDER BY type,mtime;", type);
}
else if (file)
{
rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
"WHERE loadname=?1 AND deleted=0 AND resolved=0 "
"ORDER BY type,mtime;", file);
}
return rows;
}
// Liefert eine Liste von allen IDs, Loadnames, UIDs und Typen fuer die
// angebenen <type> und <uid>.
varargs < <int|string>* >* QueryIssueList(int type, string uid)
{
mixed rows;
if (type && uid)
{
rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
"WHERE type=?1 AND uid=?2 AND deleted=0 "
"AND resolved=0 "
"ORDER BY type,rowid;", type,uid);
}
else if (type)
{
rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
"WHERE type=?1 AND deleted=0 AND resolved=0 "
"ORDER BY type,rowid;", type);
}
else if (uid)
{
rows = sl_exec("SELECT id,loadname,uid,type FROM issues "
"WHERE uid=?1 AND deleted=0 AND resolved=0 "
"ORDER BY type,rowid;", uid);
}
return rows;
}
varargs string* QueryUIDsForType(int type) {
//liefert alle UIDs fuer einen Fehlertyp oder fuer alle Fehlertypen
string** rows;
if (type)
{
rows = sl_exec("SELECT uid FROM issues "
"WHERE type=?1 AND deleted=0 AND resvoled=0;", type);
}
else
rows = sl_exec("SELECT uid FROM issues WHERE deleted=0 "
"AND resolved=0;");
return map(rows, function string (string* item)
{return item[0];} );
}
//Wieviele unterschiedliche Fehler in diesem Typ?
varargs int QueryUniqueIssueCount(int type, string uid)
{
int** rows;
if (type && uid)
{
rows = sl_exec("SELECT count(*) FROM issues "
"WHERE type=?1 AND uid=?2 AND deleted=0 AND resolved=0;",
type, uid);
}
else if (type)
{
rows = sl_exec("SELECT count(*) FROM issues "
"WHERE type=?1 AND deleted=0 AND resolved=0;",
type);
}
else if (uid)
{
rows = sl_exec("SELECT count(*) FROM issues "
"WHERE uid=?1 AND deleted=0 AND resolved=0;",
uid);
}
else
rows = sl_exec("SELECT count(*) FROM issues "
"WHERE deleted=0 AND resolved=0;");
return rows[0][0];
}
//Einen bestimmten Fehler loeschen
varargs int ToggleDeleteError(int issueid, string note)
{
mixed rows = sl_exec("SELECT uid,deleted from issues WHERE id=?1;",
issueid);
if (!pointerp(rows))
return -1;
if (!access_check(rows[0][0], M_DELETE))
//zugriff zum Schreiben nicht gestattet
return -10;
// sl_exec("BEGIN TRANSACTION;");
if (rows[0][1])
{
// was deleted -> undelete it
sl_exec("UPDATE issues SET deleted=0,mtime=?2 WHERE id=?1;",
issueid,time());
db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Loeschmarkierung entfernt. (%s)",
note ? note: "<kein Kommentar>")
));
}
else
{
// was not deleted -> delete it.
sl_exec("UPDATE issues SET deleted=1,mtime=?2 WHERE id=?1;",
issueid, time());
db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
txt: sprintf("Loeschmarkierung gesetzt. (%s)",
note ? note: "<kein Kommentar>")
));
}
// sl_exec("COMMIT;");
return !rows[0][1];
}
// sperrt den Eintrag oder entsperrt ihn.
// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
// wird.
varargs int LockIssue(int issueid, string note) {
return set_lock(issueid, 1, note);
}
varargs int UnlockIssue(int issueid, string note) {
return set_lock(issueid, 0, note);
}
// einen Fehler als gefixt markieren
varargs int ResolveIssue(int issueid, string note) {
return set_resolution(issueid, 1, note);
}
// einen Fehler als nicht gefixt markieren
varargs int ReOpenIssue(int issueid, string note) {
return set_resolution(issueid, 0, note);
}
varargs int AddNote(int issueid, string note)
{
if (!stringp(note) || !sizeof(note))
return(-3);
// existiert die ID in der DB?
struct fullissue_s issue = db_get_issue(issueid,0);
if (!issue)
return -1;
if (!access_check(issue->uid, M_WRITE))
//zugriff zum Schreiben nicht gestattet
return(-10);
return db_add_note((<note_s> id: issueid, time: time(), user: getuid(TI),
txt: note));
}
//Einen bestimmten Fehler einer anderen UID zuweisen.
//Hashkey ist zwar eindeutig, aber Angabe der
//der UID ist deutlich schneller. Weglassen des Typs nochmal langsamer. ;-)
//Potentiell also sehr teuer, wenn man UID oder UID+Typ weglaesst.
varargs int ReassignIssue(int issueid, string newuid, string note)
{
struct fullissue_s issue = db_get_issue(issueid,0);
if (!issue)
return -1;
if (!access_check(issue->uid, M_REASSIGN))
//zugriff zum Schreiben nicht gestattet
return(-10);
return db_reassign_issue(issueid, newuid, note);
}
/* *********** Eher fuer Debug-Zwecke *********************** */
/*
mixed QueryAll(int type) {
//das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
//Kopie zurueckgegeben, daher erstmal nur ich...
if (!this_interactive() ||
member(MAINTAINER,getuid(this_interactive()))<0)
return(-1);
if (process_call()) return(-2);
if (!type) return(-3);
return(errors[type]);
}
mixed QueryResolved()
{
//das koennte ein sehr sehr grosses Mapping sein, ausserdem wird keine
//Kopie zurueckgegeben, daher erstmal nur ich...
if (!this_interactive() ||
member(MAINTAINER,getuid(this_interactive()))<0)
return(-1);
if (process_call()) return(-2);
return(resolved);
}
*/
/* ***************** Internal Stuff ******************** */
void create() {
seteuid(getuid(ME));
if (sl_open("/"LIBDATADIR"/secure/ARCH/errord.sqlite") != 1)
//if (sl_open("/errord.sqlite") != 1)
{
raise_error("Datenbank konnte nicht geoeffnet werden.\n");
}
sl_exec("PRAGMA foreign_keys = ON; PRAGMA temp_store = 2; ");
//string* res=sl_exec("PRAGMA quick_check(N);");
//if (pointerp(res))
//{
// raise_error("");
//}
//sl_exec("CREATE TABLE issue(id INTEGER,haskey TEXT);");
foreach (string cmd :
explode(read_file("/secure/ARCH/errord.sql.init"),";\n"))
{
if (sizeof(cmd) && cmd != "\n")
{
sl_exec(cmd);
}
}
sl_exec("ANALYZE main;");
lasterror=([]);
}
string name() {return("<Error-Daemon>");}
void save_me(int now) {
if (now)
save_object(SAVEFILE);
else if (find_call_out(#'save_object)==-1) {
call_out(#'save_object, 30, SAVEFILE);
}
}
varargs int remove(int silent) {
save_me(1);
destruct(ME);
return(1);
}
// sperrt den Eintrag (lock!=0) oder entsperrt ihn (lock==0).
// Sperre heisst hier, dass der Fehler vom Expire nicht automatisch geloescht
// wird.
// liefert <0 im Fehlerfall, sonst Array mit Lockdaten
private varargs int set_lock(int issueid, int lock, string note)
{
struct fullissue_s issue = db_get_issue(issueid,0);
if (!issue)
return -1;
if (!access_check(issue->uid, M_WRITE))
//zugriff zum Schreiben nicht gestattet
return(-10);
return db_set_lock(issueid, lock, note);
}
//markiert einen Fehler als gefixt, mit 'note' als Bemerkung (res!=0) oder
//markiert einen Fehler wieder als nicht-gefixt (resolution==0)
//liefert < 0 im Fehlerfall, sonst den neuen Zustand.
private varargs int set_resolution(int issueid, int resolution, string note)
{
struct fullissue_s issue = db_get_issue(issueid,0);
if (!issue)
return -1;
// Fixen duerfen nur zustaendige
if (resolution
&& !access_check(issue->uid, M_FIX))
return -10;
// ggf. Fix zurueckziehen darf jeder mit M_WRITE
if (!resolution &&
!access_check(issue->uid, M_WRITE))
return -10;
int res = db_set_resolution(issueid, resolution, note);
if (res == 1)
{
// Fehler jetzt gefixt.
versende_mail(db_get_issue(issueid,0)); // per Mail verschicken
}
return res;
}
//ist der Zugriff auf uid erlaubt? Geprueft wird TI (wenn kein TI, auch kein
//Schreibzugriff)
//mode gibt an, ob lesend oder schreibend
private int access_check(string uid, int mode) {
if (mode==M_READ)
return LEARNER_SECURITY; //lesen darf jeder Magier
// In process_string() schonmal gar nicht.
if (process_call()) return(0);
// EM+ duerfen alles loeschen.
if (ARCH_SECURITY) return(1);
// eigene UIDs darf man auch vollumfaenglich bearbeiten.
if (secure_euid()==uid) return(1);
// alles andere wird speziell geprueft
switch(mode)
{
// M_WRITE ist zur zeit eigentlich immer nen Append - das duerfen zur
// Zeit alle ab Vollmagier
case M_WRITE:
return LEARNER_SECURITY;
break;
// Loeschen und Fixen duerfen zur Zeit nur Zustaendige.
case M_DELETE:
case M_REASSIGN:
case M_FIX:
// Master nach UIDs fragen, fuer die der jew. Magier
// zustaendig ist.
if (member(({string*})master()->QueryUIDsForWizard(secure_euid()),uid) >= 0)
return 1;
break;
}
return(0); //Fall-through, neinRNER_SECURITY
}
public string format_stacktrace(struct frame_s* stacktrace) {
string *lines;
if (!pointerp(stacktrace) || !sizeof(stacktrace))
return("");
lines=allocate(sizeof(stacktrace));
int i;
foreach(struct frame_s frame: stacktrace)
{
lines[i]=sprintf("Fun: %.20O in Prog: %.40s\n"
" Zeile: %.8d [%.50s]\n"
" Evalcosts: %d",
frame->name,frame->prog,
frame->loc,frame->obj,frame->ticks);
++i;
}
return(implode(lines,"\n"));
}
public string format_notes(struct note_s* notes)
{
int i;
string text="";
foreach(struct note_s note: notes)
{
text+=sprintf("Notiz %d von %.10s am %.30s\n%s",
++i,capitalize(note->user),dtime(note->time),
break_string(note->txt, 78," "));
}
return text;
}
public string format_error_spieler(struct fullissue_s issue)
{
string txt;
string *label;
if (!issue)
return 0;
switch(issue->type)
{
case T_RTERROR:
label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
break;
case T_REPORTED_ERR:
label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
break;
case T_REPORTED_TYPO:
label=({"Typo","Dieser Typo"});
break;
case T_REPORTED_IDEA:
label=({"Idee","Diese Idee"});
break;
case T_REPORTED_MD:
label=({"Fehlendes Detail","Dieses fehlende Detail"});
break;
case T_REPORTED_SYNTAX:
label=({"Syntaxproblem","Dieses Syntaxproblem"});
break;
case T_RTWARN:
label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
break;
case T_CTWARN:
label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
break;
case T_CTERROR:
label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
break;
default: return 0;
}
txt=sprintf( "\nDaten fuer %s mit ID '%s':\n"
"Zeit: %25s\n",
label[0], issue->hashkey,
dtime(issue->ctime)
);
txt+=sprintf("%s",break_string(issue->message,78,
"Meldung: ",BS_INDENT_ONCE));
if (pointerp(issue->notes))
txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
return txt;
}
public string format_error(struct fullissue_s issue, int only_essential)
{
string txt;
string *label;
if (!issue)
return 0;
switch(issue->type)
{
case T_RTERROR:
label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
break;
case T_REPORTED_ERR:
label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
break;
case T_REPORTED_TYPO:
label=({"Typo","Dieser Typo"});
break;
case T_REPORTED_IDEA:
label=({"Idee","Diese Idee"});
break;
case T_REPORTED_MD:
label=({"Fehlendes Detail","Dieses fehlende Detail"});
break;
case T_REPORTED_SYNTAX:
label=({"Syntaxproblem","Dieses Syntaxproblem"});
break;
case T_RTWARN:
label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
break;
case T_CTWARN:
label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
break;
case T_CTERROR:
label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
break;
default: return 0;
}
txt=sprintf( "\nDaten fuer %s mit ID %d:\n"
"Hashkey: %s\n"
"Zeit: %25s (Erstmalig: %25s)\n",
label[0], issue->id, issue->hashkey,
dtime(abs(issue->mtime)),dtime(issue->ctime)
);
if (stringp(issue->prog))
txt += sprintf(
"Programm: %.60s\n"
"Zeile: %.60d\n",
issue->prog, issue->loc
);
if (stringp(issue->obj))
txt+=sprintf("Objekt: %.60s\n",
issue->obj);
txt += sprintf("Loadname: %.60s\n"
"UID: %.60s\n",
issue->loadname, issue->uid);
txt+=sprintf("%s",break_string(issue->message,78,
"Meldung: ",BS_INDENT_ONCE));
if (stringp(issue->hbobj))
txt+=sprintf(
"HB-Obj: %.60s\n",issue->hbobj);
if (stringp(issue->titp)) {
txt+=sprintf(
"TI/TP: %.60s\n",issue->titp);
if (stringp(issue->tienv))
txt+=sprintf(
"Environm.: %.60s\n",issue->tienv);
}
if (!stringp(issue->command) ||
!ARCH_SECURITY || process_call())
{
// Kommandoeingabe ist Privatsphaere und darf nicht von jedem einsehbar
// sein.
// in diesem Fall aber zumindest das Verb ausgeben, so vorhanden
if (issue->verb)
txt+=sprintf(
"Verb: %.60s\n",issue->verb);
}
// !process_call() && ARCH_SECURITY erfuellt...
else if (stringp(issue->command))
txt+=sprintf(
"Befehl: %.60s\n",issue->command);
if (issue->caught)
txt+=label[1]+" trat in einem 'catch()' auf.\n";
if (!only_essential)
{
if (issue->deleted)
txt+=label[1]+" wurde als geloescht markiert.\n";
if (issue->locked)
txt+=break_string(
sprintf("%s wurde von %s am %s vor automatischem Loeschen "
"geschuetzt (locked).\n",
label[1],issue->locked_by, dtime(issue->locked_time)),78);
if (issue->resolved)
txt+=label[1]+" wurde als erledigt markiert.\n";
}
txt+=sprintf("%s trat bisher %d Mal auf.\n",
label[1],issue->count);
if (pointerp(issue->stack))
txt+="Stacktrace:\n"+format_stacktrace(issue->stack)+"\n";
if (pointerp(issue->notes))
txt+="Bemerkungen:\n"+format_notes(issue->notes)+"\n";
return txt;
}
// letzter Aenderung eines Spieler-/Magiersavefiles. Naeherung fuer letzten
// Logout ohne das Savefile einzulesen und P_LAST_LOGOUT zu pruefen.
private int recent_lastlogout(string nam, int validtime)
{
if (!nam || sizeof(nam)<2) return 0;
return file_time("/"LIBSAVEDIR"/" + nam[0..0] + "/" + nam + ".o") >= validtime;
}
// Versendet Mail an zustaendigen Magier und ggf. Spieler, der den Eintrag
// erstellt hat.
// ACHTUNG: loescht issue->command.
private int versende_mail(struct fullissue_s issue)
{
// Versendet eine mail mit dem gefixten Fehler.
mixed *mail;
string text, *empf;
int res = -1;
mail = allocate(9);
mail[MSG_FROM] = "<Fehler-Daemon>";
mail[MSG_SENDER] = getuid(TI);
mail[MSG_BCC] = 0;
mail[MSG_SUBJECT] = sprintf("Fehler/Warnung in %s behoben", issue->loadname);
mail[MSG_DATE] = dtime(time());
// auch wenn ein EM fixt, sollen die Empfaenger nicht automatisch die
// Spielereingabe erhalten, also command loeschen.
issue->command = 0;
// erstmal eine Mail an zustaendige Magier.
empf = ({string*})master()->QueryWizardsForUID(issue->uid);
// lang (180 Tage) nicht eingeloggte Magier ausfiltern
empf = filter(empf, #'recent_lastlogout, time() - 15552000);
if (sizeof(empf))
{
text = break_string(
sprintf(STANDARDMAILTEXT,capitalize(getuid(TI)))
+format_error(issue, 1),78,"",BS_LEAVE_MY_LFS);
mail[MSG_RECIPIENT] = empf[0];
if (sizeof(empf)>1)
mail[MSG_CC] = empf[1..];
else
mail[MSG_CC] = 0;
mail[MSG_BODY]=text;
mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
if (!sizeof(({string*})"/secure/mailer"->DeliverMail(mail,0)))
res = -1; // an niemanden erfolgreich zugestellt. :-(
else
res = 1;
}
// Bei von Spielern gemeldeten Fehler werden Spieler bei
// Erledigung informiert, wenn deren letzter Logout weniger als 180 Tage her
// ist.
if ( (issue->type &
(T_REPORTED_ERR|T_REPORTED_TYPO|T_REPORTED_IDEA|T_REPORTED_MD|
T_REPORTED_SYNTAX))
&& issue->titp
&& recent_lastlogout(issue->titp, time() - 15552000) )
{
text = break_string(
sprintf(STANDARDMAILTEXT_ERRORHINT,
capitalize(issue->titp), capitalize(getuid(TI)))
+format_error_spieler(issue), 78,"",BS_LEAVE_MY_LFS);
mail[MSG_ID]=sprintf(MUDNAME": %d.%d",time(),random(__INT_MAX__));
mail[MSG_RECIPIENT] = issue->titp;
mail[MSG_CC] = 0;
mail[MSG_SUBJECT] = sprintf("Fehler/Idee/Typo wurde von %s behoben",
getuid(TI));
mail[MSG_BODY] = text;
if (!sizeof(({string*})"/secure/mailer"->DeliverMail(mail,0)))
res |= -1;
else
res |= 1;
}
return res;
}
void reset()
{
// geloeschte Issues sofort, gefixte 30 Tage nach letzter Aenderung
// loeschen.
sl_exec("DELETE FROM issues WHERE deleted=1;");
sl_exec("DELETE FROM issues WHERE resolved=1 AND mtime<?1;",
time()-30*24*3600);
set_next_reset(3600*24);
}
// Nicht jeder Magier muss den ErrorD direkt zerstoeren koennen.
public string NotifyDestruct(object caller) {
if( (caller!=this_object() && !ARCH_SECURITY) || process_call() ) {
return "Du darfst den Error-Daemon nicht zerstoeren!\n";
}
return 0;
}