blob: d5b4777115b261a1b936a88dff8292d164557496 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// fileview.c
4//
5// $Id: fileview.c 9142 2015-02-04 22:17:29Z Zesstra $
Zesstraefad7be2018-11-06 23:18:30 +01006#pragma strict_types, rtt_checks
Zesstra0d64cca2020-03-09 21:03:56 +01007#pragma range_check
MG Mud User88f12472016-06-24 23:31:02 +02008#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +02009
10#include <ansi.h>
11#include <player/base.h>
12#include <wizlevels.h>
13#include <shells.h>
14#include <daemon/mand.h>
15#include <udp.h>
Zesstraefad7be2018-11-06 23:18:30 +010016#include <files.h>
Zesstrac70bf582019-11-26 00:43:17 +010017#include <rtlimits.h>
Zesstraefad7be2018-11-06 23:18:30 +010018
MG Mud User88f12472016-06-24 23:31:02 +020019#define NEED_PROTOTYPES
20#include <magier.h>
21#include <thing/properties.h>
22#include <player.h>
23
Zesstraa2bb5bf2021-04-20 17:45:22 +020024struct lsformats {
25 mapping templates;
26 mapping colormap;
27};
28private nosave struct lsformats lstemplates;
MG Mud User88f12472016-06-24 23:31:02 +020029private nosave mapping oldman_result;
30
31// ###################
32//######################### INITIALISIERUNG #############################
33// ###################
34
35mixed _query_localcmds()
36{
37 return ({({"ls","_ls",0,LEARNER_LVL}),
38 ({"more","_more",0,LEARNER_LVL}),
39 ({"cat","_cat",0,LEARNER_LVL}),
40 ({"head","_cat",0,LEARNER_LVL}),
41 ({"tail","_cat",0,LEARNER_LVL}),
42 ({"man","_man",0,LEARNER_LVL}),
43 ({"rman","_rman",0,LEARNER_LVL}),
44 ({"showprops","_showprops",0,WIZARD_LVL}),
45 ({"grep","_grep",0,LEARNER_LVL})});
46}
47
48
49// ######
50//################################ LS ###################################
51// ######
52
53//
54// _ls: Der ls-Befehl: Verzeichnisse und Dateien anzeigen
55// cmdline: Kommandozeile
56//
57
Zesstraa2bb5bf2021-04-20 17:45:22 +020058// Helfer fuer map() zum extrahieren benutzerdefinierte Farben aus P_VARIABLES
MG Mud User88f12472016-06-24 23:31:02 +020059private string _get_color(string color)
60{
61 return COLORS[lower_case(color)];
62}
63
Zesstraa2bb5bf2021-04-20 17:45:22 +020064// Ermittelt Farbtags und Formattemplates mit Platzhaltern fuer ls-Ausgaben
65// und bereitet beides fuer die Nutzung durch terminal_colours() vor.
MG Mud User88f12472016-06-24 23:31:02 +020066private void SetColorstrings()
67{
Zesstraa2bb5bf2021-04-20 17:45:22 +020068 mapping vars = QueryProp(P_VARIABLES);
69 // 2nd value in templates is the additional amount of space the templates
70 // itself needs in addition to the value inserted later in place of %s.
71 lstemplates = (<lsformats> templates: m_allocate(3,2),
72 colormap:m_allocate(4,1) );
73 // 0-Key ist der default, wenn ein Platzhalter nicht gefunden wird. Wird
74 // z.B. fuer Ende-Tags benutzt.
75 lstemplates.colormap[0]=ANSI_NORMAL;
Bugfix9518dd92025-04-09 00:13:36 +020076 // Variablen fuer Markierungszeichen bei P_NO_ASCII_ART
77 string obj = "";
78 string vc = "";
79 if(QueryProp(P_NO_ASCII_ART))
80 {
81 obj = "*";
82 vc = "+";
83 }
Zesstraa2bb5bf2021-04-20 17:45:22 +020084 // Template fuer die Ausgabe und die Defaults fuer die Farbplatzhalter je
85 // nach Terminal einstellen.
Arathornb3051452021-05-13 21:13:03 +020086 switch(QueryProp(P_TTY))
MG Mud User88f12472016-06-24 23:31:02 +020087 {
88 case "vt100":
Zesstraa2bb5bf2021-04-20 17:45:22 +020089 lstemplates.templates[DIR] = "%^LS_DIR_START%^%s/%^LS_DIR_END%^";
90 lstemplates.templates[DIR, 1] = 1;
Bugfix9518dd92025-04-09 00:13:36 +020091 lstemplates.templates[OBJ] =
92 "%^LS_OBJ_START%^%s" + obj + "%^LS_OBJ_END%^";
93 lstemplates.templates[OBJ, 1] = sizeof(obj);
94 lstemplates.templates[VC] = "%^LS_VC_START%^%s" + vc + "%^LS_VC_END%^";
95 lstemplates.templates[VC, 1] = sizeof(vc);
Zesstraa2bb5bf2021-04-20 17:45:22 +020096 lstemplates.colormap["LS_DIR_START"]=ANSI_BOLD;
97 lstemplates.colormap["LS_OBJ_START"]=ANSI_INVERS;
98 lstemplates.colormap["LS_VC_START"]=ANSI_INVERS;
99 // Die *_END Platzhalter sind nicht explizit aufgefuehrt, solange sie
100 // nur ANSI_NORMAL normal waeren - in dem Fall benutzt terminal_colour()
101 // spaeter den 0-Key in colormap
102 break;
MG Mud User88f12472016-06-24 23:31:02 +0200103 case "ansi":
Bugfix9518dd92025-04-09 00:13:36 +0200104 lstemplates.templates[DIR] = "%^LS_DIR_START%^%s/%^LS_DIR_END%^";
105 lstemplates.templates[DIR, 1] = 1;
106 lstemplates.templates[OBJ] = "%^LS_OBJ_START%^%s" + obj + "%^LS_OBJ_END%^";
107 lstemplates.templates[OBJ, 1] = sizeof(obj);
108 lstemplates.templates[VC] = "%^LS_VC_START%^%s" + vc + "%^LS_VC_END%^";
109 lstemplates.templates[VC, 1] = sizeof(vc);
Zesstraa2bb5bf2021-04-20 17:45:22 +0200110 lstemplates.colormap["LS_DIR_START"]=ANSI_BLUE+ANSI_BOLD;
111 lstemplates.colormap["LS_OBJ_START"]=ANSI_RED;
112 lstemplates.colormap["LS_VC_START"]=ANSI_PURPLE;
113 // Die *_END Platzhalter sind nicht explizit aufgefuehrt, solange sie
114 // nur ANSI_NORMAL normal waeren - in dem Fall benutzt terminal_colour()
115 // spaeter den 0-Key in colormap
MG Mud User88f12472016-06-24 23:31:02 +0200116 break;
Zesstraa2bb5bf2021-04-20 17:45:22 +0200117 // In diesen Faellen keine Farbcodeplatzhalter, nur zeichenbasierte
118 // Markierungen anhaengen.
MG Mud User88f12472016-06-24 23:31:02 +0200119 case "dumb":
MG Mud User88f12472016-06-24 23:31:02 +0200120 default:
Zesstraa2bb5bf2021-04-20 17:45:22 +0200121 m_add(lstemplates.templates, DIR, "%s/", 1);
122 m_add(lstemplates.templates, OBJ, "%s*", 1);
Bugfix49837f32025-04-09 00:02:55 +0200123 m_add(lstemplates.templates, VC, "%s+", 1);
Zesstraa2bb5bf2021-04-20 17:45:22 +0200124 break;
MG Mud User88f12472016-06-24 23:31:02 +0200125 }
Zesstraa2bb5bf2021-04-20 17:45:22 +0200126 // Die Defaults von oben koennen mit magierindividuellen Einstellungen
127 // ueberschrieben werden. Offensichtlich sind die nur wirksam fuer
128 // vt100/ansi und man koennte auch ANSI-Sequenzen einstellen, die ein vt100
129 // nicht versteht...
130 if(stringp(vars["LS_DIR"]))
131 lstemplates.colormap["LS_DIR_START"]=implode(map(
132 explode(vars["LS_DIR"],"+"), #'_get_color),"");
133 if(stringp(vars["LS_OBJ"]))
134 lstemplates.colormap["LS_OBJ_START"]=implode(map(
135 explode(vars["LS_OBJ"],"+"), #'_get_color),"");
136 if(stringp(vars["LS_VC"]))
137 lstemplates.colormap["LS_VC_START"]=implode(map(
138 explode(vars["LS_VC"],"+"), #'_get_color),"");
MG Mud User88f12472016-06-24 23:31:02 +0200139}
140
141
Bugfix97ce1d92025-04-08 18:51:23 +0200142private string _ls_output_short(<int|string>* filedata, int maxlen,
143 int counter, int maxcount)
MG Mud User88f12472016-06-24 23:31:02 +0200144{
Bugfix97ce1d92025-04-08 18:51:23 +0200145 string filename = filedata[BASENAME];
146 maxlen -= sizeof(filename);
MG Mud User88f12472016-06-24 23:31:02 +0200147 switch(filedata[FILESIZE])
148 {
Bugfix97ce1d92025-04-08 18:51:23 +0200149 case FSIZE_DIR:
150 filename = sprintf(lstemplates.templates[DIR], filename);
151 maxlen -= lstemplates.templates[DIR, 1];
152 break;
153 case FSIZE_NOFILE:
154 filename = sprintf(lstemplates.templates[VC], filename);
155 maxlen -= lstemplates.templates[VC, 1];
156 break;
157 default:
158 if(find_object(filedata[FULLNAME]))
159 {
160 maxlen -= lstemplates.templates[OBJ, 1];
161 filename = sprintf(lstemplates.templates[OBJ], filename);
162 break;
163 }
MG Mud User88f12472016-06-24 23:31:02 +0200164 }
Bugfix97ce1d92025-04-08 18:51:23 +0200165 if(!maxcount)
166 return terminal_colour(filename + "\n", lstemplates.colormap);
167 return terminal_colour(
168 sprintf(
169 "%-*s%s",
170 (maxlen + sizeof(filename)),
171 filename,
172 ((counter++) == maxcount ? (counter = 0, "\n") : " ")),
173 lstemplates.colormap);
MG Mud User88f12472016-06-24 23:31:02 +0200174}
175
Bugfix97ce1d92025-04-08 18:51:23 +0200176private int _ls_maxlen(<int|string>* filedata,int flags, int maxlen)
MG Mud User88f12472016-06-24 23:31:02 +0200177{
Bugfix97ce1d92025-04-08 18:51:23 +0200178 string base = filedata[BASENAME];
179 if(!(flags & LS_A) && base[0] == '.') return 0;
180 maxlen = max(maxlen, sizeof(base));
MG Mud User88f12472016-06-24 23:31:02 +0200181 return 1;
182}
183
184private string _ls_output_long(mixed filedata, int flags,closure valid_read,
185 closure valid_write,closure creator_file)
186{
Zesstraefad7be2018-11-06 23:18:30 +0100187 string base=filedata[BASENAME];
188 if (!(sizeof(base)) || ( (!(flags&LS_A)) && (base[0]=='.')) )
MG Mud User88f12472016-06-24 23:31:02 +0200189 return 0;
Zesstraefad7be2018-11-06 23:18:30 +0100190 int size=filedata[FILESIZE];
191 string path=filedata[PATHNAME];
Zesstraf22b6a82018-11-07 22:39:55 +0100192 string full=filedata[FULLNAME];
Zesstraefad7be2018-11-06 23:18:30 +0100193 int dir=(size==FSIZE_DIR);
194 object ob=find_object(full);
195 int ftime=filedata[FILEDATE];
196 string date;
Bugfix59cb0e52019-09-06 15:17:29 +0200197 if ((time()-ftime)>31536000) // ein Jahr
Zesstraa2bb5bf2021-04-20 17:45:22 +0200198 date=strftime("%b %e %Y", ftime);
Zesstraefad7be2018-11-06 23:18:30 +0100199 else
200 date=strftime("%b %e %H:%M", ftime);
201
202 string creator="";
203 string group="";
MG Mud User88f12472016-06-24 23:31:02 +0200204 if (flags&LS_U)
205 {
Vanion50652322020-03-10 21:13:25 +0100206 creator=({string})call_other(master(),"creator_file", full);
MG Mud User88f12472016-06-24 23:31:02 +0200207 switch(creator)
208 {
209 case ROOTID: creator="root"; break;
Zesstraefad7be2018-11-06 23:18:30 +0100210 case BACKBONEID: creator="std"; break;
211 case MAILID: creator="mail"; break;
212 case NEWSID: creator="news"; break;
213 case NOBODY: creator="nobody"; break;
214 case POLIZEIID: creator="polizei"; break;
215 case DOCID: creator="doc"; break;
216 case GUILDID: creator="gilde"; break;
217 case ITEMID: creator="items"; break;
MG Mud User88f12472016-06-24 23:31:02 +0200218 default: if(!creator) creator="none"; break;
219 }
220 }
221 if (flags&LS_G)
222 {
Zesstraf22b6a82018-11-07 22:39:55 +0100223 string *tmp=explode(path, "/") - ({""});
MG Mud User88f12472016-06-24 23:31:02 +0200224 if (sizeof(tmp))
225 {
226 switch(tmp[0])
227 {
228 case WIZARDDIR: group="magier"; break;
Zesstraefad7be2018-11-06 23:18:30 +0100229 case NEWSDIR: group="news"; break;
230 case MAILDIR: group="mail"; break;
231 case FTPDIR: group="public"; break;
232 case PROJECTDIR: group="project"; break;
MG Mud User88f12472016-06-24 23:31:02 +0200233 case DOMAINDIR: if (sizeof(tmp)>1) { group=tmp[1]; break; }
234 default: group="mud"; break;
235 }
236 }
237 else group="mud";
238 }
Zesstraa2bb5bf2021-04-20 17:45:22 +0200239 if (dir) base=sprintf(lstemplates.templates[DIR],base);
MG Mud User88f12472016-06-24 23:31:02 +0200240 else
241 {
242 if (ob)
243 {
Zesstraefad7be2018-11-06 23:18:30 +0100244 if (size==FSIZE_NOFILE)
Zesstraa2bb5bf2021-04-20 17:45:22 +0200245 base=sprintf(lstemplates.templates[VC],base);
MG Mud User88f12472016-06-24 23:31:02 +0200246 else
Zesstraa2bb5bf2021-04-20 17:45:22 +0200247 base=sprintf(lstemplates.templates[OBJ],base);
MG Mud User88f12472016-06-24 23:31:02 +0200248 }
249 }
Zesstraa2bb5bf2021-04-20 17:45:22 +0200250 return terminal_colour(
251 sprintf(("%c%c%c%c %3d" + ((flags&LS_U) ? " %-24.24s" : "%-0.1s")
Zesstraefad7be2018-11-06 23:18:30 +0100252 +((flags&LS_G) ? " %-8.8s" : "%0.1s") + " %8s %s %s\n"),
253 (dir ? 'd' : '-'),
MG Mud User88f12472016-06-24 23:31:02 +0200254 (!funcall(valid_read,full,getuid(),
Zesstraefad7be2018-11-06 23:18:30 +0100255 "read_file",this_object()) ? '-' : 'r'),
MG Mud User88f12472016-06-24 23:31:02 +0200256 (!funcall(valid_write,full,getuid(),
Zesstraefad7be2018-11-06 23:18:30 +0100257 "write_file",this_object()) ? '-' : 'w'),
258 (ob ? 'x' : '-'),
259 (dir ? (sizeof((get_dir(full+"/*")||({}))-({".",".."}))) : 0),
260 creator, group,
261 (dir ? "-" : size==FSIZE_NOFILE ? "<vc>" : to_string(size)),
Zesstraa2bb5bf2021-04-20 17:45:22 +0200262 date, base),
263 lstemplates.colormap);
MG Mud User88f12472016-06-24 23:31:02 +0200264}
265
Bugfix97ce1d92025-04-08 18:51:23 +0200266private string* _ls_list(< <int|string>* >* targets, int flags, closure v_read,
267 closure v_write, closure creator)
268{
269 string* entries;
270 if (flags&LS_L)
271 {
272 entries = map(targets, #'_ls_output_long, flags, v_read,
273 v_write, creator) - ({0});
274 }
275 else
276 {
277 int counter, maxlen;
278 targets = filter(targets, #'_ls_maxlen, flags, &maxlen);
279 if (maxlen > 76) maxlen = 76;
280 // maxcount muss immer um 1 niedriger sein als die tatsaechliche Anzahl an
281 // moeglichen Spalten, weil gegen maxcount++ und nicht gegen ++maxcount
282 // geprueft wird.
Bugfix2cd4f712025-04-08 21:28:12 +0200283 int maxcount;
284 if(!(flags & LS_1))
285 {
286 maxcount = (78 / (maxlen + 2)) - 1;
287 }
Bugfix97ce1d92025-04-08 18:51:23 +0200288 entries = map(targets, #'_ls_output_short, maxlen, &counter, maxcount);
289 }
290 return entries;
291}
292
MG Mud User88f12472016-06-24 23:31:02 +0200293
294static int _ls(string cmdline)
295{
Bugfix97ce1d92025-04-08 18:51:23 +0200296 int flags;
MG Mud User88f12472016-06-24 23:31:02 +0200297
Bugfix97ce1d92025-04-08 18:51:23 +0200298 string* args = parseargs(_unparsed_args() || "", &flags, LS_OPTS, 1);
299 if(flags == -1)
MG Mud User88f12472016-06-24 23:31:02 +0200300 return USAGE("ls [-" LS_OPTS "] [<Verzeichnis>] [<Verzeichnis2> ..]");
301 SetColorstrings();
Bugfix97ce1d92025-04-08 18:51:23 +0200302 if(!sizeof(args))
303 args = ({ QueryProp(P_CURRENTDIR) });
304 < <int|string>* >* targets = file_list(args,MODE_LSA,0,"/");
305 if(!sizeof(targets))
306 {
307 notify_fail("ls: Keine passenden Verzeichnisse gefunden.\n");
308 return 0;
309 }
Arathorn06b52922018-11-26 20:47:10 +0100310
Bugfix97ce1d92025-04-08 18:51:23 +0200311 // Files sortieren die erste
312 int cmp;
313 if(flags & LS_T)
314 cmp = FILEDATE;
315 else if(flags & LS_S)
316 cmp = FILESIZE;
317 else
318 cmp = BASENAME;
319
320 closure sort_fun;
321 // Dateinamen aufsteigend sortieren, Groesse und Aenderungsdatum absteigend.
322 if(cmp == BASENAME && !(flags & LS_R) || cmp != BASENAME && (flags & LS_R))
323 {
324 sort_fun = function int (<int|string>* a, <int|string>* b) {
Arathorn06b52922018-11-26 20:47:10 +0100325 return (a[cmp] > b[cmp]);
326 };
Bugfix97ce1d92025-04-08 18:51:23 +0200327 }
Arathorn06b52922018-11-26 20:47:10 +0100328 else
Bugfix97ce1d92025-04-08 18:51:23 +0200329 {
330 sort_fun = function int (<int|string>* a, <int|string>* b) {
Arathorn06b52922018-11-26 20:47:10 +0100331 return (a[cmp] < b[cmp]);
332 };
Bugfix97ce1d92025-04-08 18:51:23 +0200333 }
334 targets = sort_array(targets, sort_fun);
335
336 // Ausgabeformat bestimmen
337 closure v_read,v_write,creator;
MG Mud User88f12472016-06-24 23:31:02 +0200338 if (flags&LS_L)
339 {
340 v_read=VALID_READ_CL;
341 v_write=VALID_WRITE_CL;
342 creator=CREATOR_CL;
343 }
Bugfix97ce1d92025-04-08 18:51:23 +0200344 string output = "";
345 // Falls mehr als ein Ziel angegeben wurde, werden zuerst die Ziele
346 // aufgelistet. Anschließend wird in der Schleife der Inhalt von allen Zielen,
347 // die Verzeichnise sind aufgelistet.
348 // Wurde nur ein Ziel angegeben und dies ist eine Datei, wird diese angezeigt.
349 // In der Schleife wird nichts hinzugefügt, weil hier nur Verzeichnisse
350 // aufgelistet werden.
351 if(sizeof(targets) > 1 || targets[0][FILESIZE] >= 0)
MG Mud User88f12472016-06-24 23:31:02 +0200352 {
Bugfix97ce1d92025-04-08 18:51:23 +0200353 string* entries = _ls_list(targets, flags, v_read, v_write, creator);
354 output = sprintf(
355 "\n%d Dateien/Unterverzeichnisse:\n%s\n",
356 sizeof(entries),
357 implode(entries, ""));
358 }
359 foreach(<int|string>* target : targets)
360 {
361 if(target[FILESIZE] != FSIZE_DIR)
362 continue;
363
364 < <int|string>* >* sub_targets = file_list(({target[FULLNAME] + "/*"}),
365 MODE_LSB, 0, "/");
366 sub_targets = sort_array(sub_targets, sort_fun);
367 string* entries = _ls_list(sub_targets, flags, v_read, v_write, creator);
368 if(sizeof(entries))
369 {
370 output += sprintf(
371 "\n%s: Verzeichnis, %d Dateien/Unterverzeichnisse:\n%s\n",
372 target[FULLNAME],
373 sizeof(entries),
374 implode(entries, ""));
375 }
MG Mud User88f12472016-06-24 23:31:02 +0200376 else
377 {
Bugfix97ce1d92025-04-08 18:51:23 +0200378 output += sprintf("\n%s: Leeres Verzeichnis.\n", target[FULLNAME]);
MG Mud User88f12472016-06-24 23:31:02 +0200379 }
380 }
381 More(output);
382 return 1;
383}
384
385// ########
386//############################### MORE ###################################
387// ########
388//
389// _more_file: Mehrere Files hintereinander ausgeben
390//
391
392private void _more_file(string *arg)
393{
394 if (!sizeof(arg)) return;
395 printf("more: Naechste Datei: %s\n",arg[0]);
Zesstra3de3a032018-11-08 19:17:03 +0100396 More(arg[0],1,#'_more_file,
397 (sizeof(arg)>1 ? ({ arg[1..]}) : ({})) );
MG Mud User88f12472016-06-24 23:31:02 +0200398 return;
399}
400
401
402private mixed _size_filter(mixed *arg)
403{
404 if (arg[FILESIZE]>0) return arg[FULLNAME];
405 if (arg[FILESIZE]==0)
406 {
407 printf("%s: %s: Leere Datei.\n",query_verb()||"more",arg[FULLNAME]);
408 return 0;
409 }
Zesstraefad7be2018-11-06 23:18:30 +0100410 if (arg[FILESIZE]==FSIZE_DIR)
MG Mud User88f12472016-06-24 23:31:02 +0200411 printf("%s: %s ist ein Verzeichnis.\n",query_verb()||"more",arg[FULLNAME]);
412 else
413 printf("%s: %s: Datei existiert nicht.\n", query_verb()||"more",
414 arg[FULLNAME]);
415 return 0;
416}
417
418
419//
420// _more: Dateien anzeigen
421// cmdline: Kommandozeile
422//
423
424static int _more(string cmdline)
425{
426 mixed *args;
427 int flags;
428 cmdline=_unparsed_args();
429 args=parseargs(cmdline,&flags,"",1);
430 if (flags==-1||!sizeof(args)) return USAGE("more <datei> [<datei2>..]");
431 args=file_list(args,MODE_MORE,0,"/");
432 if (!sizeof(args))
433 return printf("more: %s: Keine passende Datei gefunden.\n",cmdline),1;
434 args=map(args,#'_size_filter)-({0});
435 if (sizeof(args)) _more_file(args);
436 return 1;
437}
438
439// ###################
440//########################## HEAD, TAIL, CAT #############################
441// ###################
442
443static int _cat(string cmdline)
444{
445 mixed *args;
446 int flags;
447 cmdline=_unparsed_args();
448 args=parseargs(cmdline,&flags,"",1);
449 if(flags==-1||!sizeof(args))
450 return USAGE(query_verb()+" <dateiname> [<datei2>..]");
451 args=file_list(args,MODE_CAT,0,"/");
452 if (!sizeof(args))
453 return printf("%s: %s: Keine passende Datei gefunden.\n",query_verb(),
454 cmdline),1;
455 args=map(args,#'_size_filter)-({0});
456 if (!sizeof(args)) return 1;
457 while(sizeof(args))
458 {
459 switch(query_verb())
460 {
461 case "cat":
462 if (!cat(args[0]))
463 printf("cat: %s konnte nicht angezeigt werden.\n",args[0]);
464 break;
465 case "head":
466 if (!cat(args[0],0,10))
467 printf("head: %s konnte nicht angezeigt werden.\n",args[0]);
468 break;
469 case "tail": tail(args[0]); break;
470 }
Zesstra3de3a032018-11-08 19:17:03 +0100471 if (sizeof(args) > 1)
472 args=args[1..];
473 else
474 break;
MG Mud User88f12472016-06-24 23:31:02 +0200475 }
476 return 1;
477}
478
479// #######
480//############################### MAN ###################################
481// #######
482
Arathorn99c40582020-08-28 19:46:23 +0200483/* Sortiert ein Array paarweise, wobei von jedem Wertepaar der erste Wert
484 fuer die Sortierung herangezogen wird. */
485private string* SortInPairs(string* arr) {
486 if (!sizeof(arr))
487 return arr;
488
489 string** helper = ({});
490
491 /* In Sub-Arrays zerlegen, die jeweils ein Wertepaart enthalten:
492 ({s0, s1, s2, s3}) => ({({s0,s1}), ({s2,s3})}) */
493 int listsize = sizeof(arr);
494 for(int i ; i<listsize ; i+=2) {
495 helper += ({ arr[i..i+1] });
496 }
497
498 // Nach dem ersten Element jedes Sub-Arrays sortieren.
499 helper = sort_array(helper, function int (string* h1, string* h2) {
500 return lower_case(h1[0]) > lower_case(h2[0]);
501 });
502
503 // Eingabe-Array loeschen und aus den sortierten Wertepaaren neu aufbauen.
504 arr = ({});
505 foreach(string* h : helper) {
506 arr += h;
507 }
508
509 return arr;
510}
511
MG Mud User88f12472016-06-24 23:31:02 +0200512static int _man(string cmdline)
513{
MG Mud User88f12472016-06-24 23:31:02 +0200514 int i, flags;
Arathorndb201cc2020-08-28 17:02:38 +0200515 string *input;
MG Mud User88f12472016-06-24 23:31:02 +0200516
Arathorn1facd5f2020-08-28 16:41:17 +0200517 string* args = parseargs(_unparsed_args(), &flags, MAN_OPTS, 0);
518
MG Mud User88f12472016-06-24 23:31:02 +0200519 if (flags==-1 ||
520 (sizeof(args)!=1 &&
521 (sizeof(args)<2 || sizeof(args[1])>1 || !(i=to_int(args[1])))))
522 return USAGE("man [-" MAN_OPTS "] <hilfeseite>");
MG Mud User88f12472016-06-24 23:31:02 +0200523
Arathorn72c7e542020-08-28 16:50:28 +0200524 input = explode(args[0], "/");
525
Arathorn815f2742020-08-28 17:29:04 +0200526 /* Wenn das Ergebnis einer vorherigen Suche noch vorliegt und die aktuelle
527 Eingabe als einziges eine einstellige Zahl enthaelt, und diese dann in
528 dem alten Suchergebnis enthalten ist, wird die Eingabe durch das alte
529 Ergebnis ersetzt. <i> wird in dem Fall geloescht.
530 Wenn kein altes Ergebnis gefunden wurde, enthaelt <i> die eingegebene
531 Nummer. */
Arathorn72c7e542020-08-28 16:50:28 +0200532 if (oldman_result && sizeof(input)==1 && sizeof(args)==1 &&
533 sizeof(input[0])==1 && (i=to_int(input[0])) &&
Arathornd0a1e662020-08-28 17:01:24 +0200534 member(oldman_result,i))
535 {
Arathorn72c7e542020-08-28 16:50:28 +0200536 input = ({oldman_result[i,0], oldman_result[i,1]});
Arathornd0a1e662020-08-28 17:01:24 +0200537 i = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200538 }
Arathorn815f2742020-08-28 17:29:04 +0200539 /* Ansonsten wenn weder -m, noch -r gesetzt sind und es eine Manpage gibt,
540 die genau der Eingabe entspricht, wird diese verwendet. */
Arathorn72c7e542020-08-28 16:50:28 +0200541 else if (!(flags&(MAN_M|MAN_R)) && sizeof(input)>1)
MG Mud User88f12472016-06-24 23:31:02 +0200542 {
Arathornd0a1e662020-08-28 17:01:24 +0200543 if (file_size(MAND_DOCDIR+args[0]) >= 0)
Arathorn72c7e542020-08-28 16:50:28 +0200544 input = ({input[<1], args[0]});
MG Mud User88f12472016-06-24 23:31:02 +0200545 else
Arathorn72c7e542020-08-28 16:50:28 +0200546 input = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200547 }
548 else
549 {
Arathorn815f2742020-08-28 17:29:04 +0200550 /* Soll eine Regexp-Suche durchgefuehrt werden? Dann erstmal den Ausdruck
551 auf Gueltigkeit pruefen. */
MG Mud User88f12472016-06-24 23:31:02 +0200552 if (flags&MAN_R)
553 {
Arathornd0a1e662020-08-28 17:01:24 +0200554 flags &= (~MAN_M);
555 if (catch(regexp(({""}), args[0])) || !regexp(({""}), args[0]))
556 {
557 printf("man: Ungueltiger Ausdruck in Maske.\n");
558 return 1;
559 }
MG Mud User88f12472016-06-24 23:31:02 +0200560 }
Arathorn815f2742020-08-28 17:29:04 +0200561 /* Die Ausgabe von locate() liefert ein String-Array, das abwechselnd den
562 Dateinamen der gefundenen Manpage und den vollen Pfad dieser Manpage
563 unterhalb von /doc enthaelt. Beispielsweise ({"Defend","lfun/Defend"})
564 */
Arathorn72c7e542020-08-28 16:50:28 +0200565 input = ({string *})MAND->locate(args[0], flags&(MAN_M|MAN_R));
Arathorn84dd5f22020-08-28 17:24:44 +0200566 // Sortierung case-insensitive, ggf. vorhandene Pfade dabei ignorieren
567 // Wird fuer die spaetere Ausgabe der Liste benoetigt.
Arathorn99c40582020-08-28 19:46:23 +0200568 input = SortInPairs(input);
MG Mud User88f12472016-06-24 23:31:02 +0200569 }
570
Arathorn815f2742020-08-28 17:29:04 +0200571 /* Alte Such-Treffer werden entsorgt. */
Arathornd0a1e662020-08-28 17:01:24 +0200572 oldman_result = 0;
573
Arathorn815f2742020-08-28 17:29:04 +0200574 /* <i> kann maximal eine einstellige Zahl sein, 1-9, wenn eine solche als
575 einziges Argument eingegeben wurde und kein passendes Ergebnis in einer
576 vorigen Suchanfrage gefunden wurde.
577
578 <input> kann nur genau dann mehr als 2 Elemente haben, wenn das Ergebnis
579 des Aufrufs MAND->locate() drinsteht. Und nur dann muss ueberhaupt ein
580 hoeheres Wertepaar rausgeschnitten werden, denn wenn <input> bis zu 2
581 Elemente hat, kann damit direkt weitergearbeitet werden.
582
583 Beispiel: bei einer Eingabe von "4" wird hier aus <input> das 7. und
584 8. Element entnommen (Indizes [6..7]). */
Arathorn131bb742020-08-28 17:05:02 +0200585 if(i && sizeof(input)>2 && sizeof(input) >= i*2)
586 input = input[(i*2-2)..(i*2-1)];
Arathornd0a1e662020-08-28 17:01:24 +0200587
Arathorn72c7e542020-08-28 16:50:28 +0200588 switch (sizeof(input))
MG Mud User88f12472016-06-24 23:31:02 +0200589 {
Arathorn815f2742020-08-28 17:29:04 +0200590 /* <input> leer, nichts brauchbares gefunden. */
MG Mud User88f12472016-06-24 23:31:02 +0200591 case 0:
Arathornd0a1e662020-08-28 17:01:24 +0200592 printf("Keine Hilfeseite gefunden fuer '%s'.\n", args[0]);
MG Mud User88f12472016-06-24 23:31:02 +0200593 break;
Arathornd0a1e662020-08-28 17:01:24 +0200594
Arathorn815f2742020-08-28 17:29:04 +0200595 /* Genau 2 Elemente enthalten? Dann kann das direkt ausgegeben werden. */
MG Mud User88f12472016-06-24 23:31:02 +0200596 case 2:
597 /*
598 if (flags&MAN_I)
599 {
600 // BRANCH TO MANUALD
601 return 1;
602 }
603 */
Arathorn72c7e542020-08-28 16:50:28 +0200604 printf("Folgende Hilfeseite wurde gefunden: %s\n", input[1]);
605 More(MAND_DOCDIR+input[1], 1);
MG Mud User88f12472016-06-24 23:31:02 +0200606 return 1;
Arathorn815f2742020-08-28 17:29:04 +0200607
608 /* Alles andere: */
MG Mud User88f12472016-06-24 23:31:02 +0200609 default:
Arathorn131bb742020-08-28 17:05:02 +0200610 i = sizeof(input)/2;
Arathorndb201cc2020-08-28 17:02:38 +0200611 string* output = allocate(i);
Arathorn815f2742020-08-28 17:29:04 +0200612
613 // Inhalt: ([ int nummer : string manpage; string manpage_pfad ])
Arathornd0a1e662020-08-28 17:01:24 +0200614 oldman_result = m_allocate(i, 2);
Arathorn815f2742020-08-28 17:29:04 +0200615
Arathornd0a1e662020-08-28 17:01:24 +0200616 while (i)
MG Mud User88f12472016-06-24 23:31:02 +0200617 {
Arathorn131bb742020-08-28 17:05:02 +0200618 output[i-1] = input[i*2-2];
Arathorn1719b252020-08-28 19:44:32 +0200619 m_add(oldman_result, i, input[i*2-2], input[i*2-1]);
MG Mud User88f12472016-06-24 23:31:02 +0200620 i--;
621 }
Arathorndb5d16d2020-05-11 01:23:18 +0200622
Arathorndb5d16d2020-05-11 01:23:18 +0200623 // Numerierung ergaenzen
Arathorndb201cc2020-08-28 17:02:38 +0200624 foreach(int j : sizeof(output))
Arathornd0a1e662020-08-28 17:01:24 +0200625 {
Arathorndb201cc2020-08-28 17:02:38 +0200626 output[j] = sprintf("%d: %s", j+1, output[j]);
Arathorndb5d16d2020-05-11 01:23:18 +0200627 }
628
629 int tty_cols = QueryProp(P_TTY_COLS)-2;
630 string list = "Es wurden die folgenden, potentiell passenden Seiten "
631 "gefunden:\n";
632
633 // Wer keine Grafik sehen will, bekommt eine andere Anzeige.
Arathornd0a1e662020-08-28 17:01:24 +0200634 if (QueryProp(P_NO_ASCII_ART))
635 {
Arathorndb5d16d2020-05-11 01:23:18 +0200636 // @ als geschuetztes Leerzeichen verwenden, um einen Umbruch
637 // nach den Nummern zu verhindern.
Arathorndb201cc2020-08-28 17:02:38 +0200638 output = map(output, #'regreplace, ": ", ":@", 1);
639 list += break_string(implode(output, " "), tty_cols);
Arathorndb5d16d2020-05-11 01:23:18 +0200640 list = regreplace(list, ":@", ": ", 1);
641 }
Arathornd0a1e662020-08-28 17:01:24 +0200642 else
643 {
Arathorndb5d16d2020-05-11 01:23:18 +0200644 // Anzahl Spalten ausrechnen: Terminalbreite / Laenge des laengsten
Arathorndb201cc2020-08-28 17:02:38 +0200645 // Elements in <output>. Kann der Spaltenmodus von sprintf() an sich
Arathorndb5d16d2020-05-11 01:23:18 +0200646 // selbst, das liefert aber nicht immer so guenstige Ergebnisse.
Arathorndb201cc2020-08-28 17:02:38 +0200647 int maxwidth = max(map(output, #'sizeof));
Arathorndb5d16d2020-05-11 01:23:18 +0200648 int tablecols = tty_cols/maxwidth;
649 list += "-"*tty_cols+"\n"+
Arathorndb201cc2020-08-28 17:02:38 +0200650 sprintf("%#-*.*s\n", tty_cols, tablecols, implode(output,"\n"))+
Arathorndb5d16d2020-05-11 01:23:18 +0200651 "-"*tty_cols+"\n";
652 }
653 printf(list);
MG Mud User88f12472016-06-24 23:31:02 +0200654 break;
655 }
656 return 1;
657}
658
659// ########
660//############################### RMAN ##################################
661// ########
662
663static int _rman(string str)
664{
665 int flags;
666 string *args;
667
668 str=_unparsed_args();
669 args=parseargs(str,&flags,"",0);
670 if (flags==-1||sizeof(args)!=2)
671 return USAGE("rman <hilfeseite> <mudname>");
672 write("man: " +
Vanion50652322020-03-10 21:13:25 +0100673 ({string})call_other(UDP_CMD_DIR+"man","send_request",args[1],args[0]));
MG Mud User88f12472016-06-24 23:31:02 +0200674 return 1;
675}
676
677
678// #############
679//############################# SHOWPROPS ###############################
680// #############
681
682//
683// _showprops: Zeigt Properties zu einem Objekt an
684//
685
686static int _showprops(string str)
687{
688 int i;
689 string *list, ausgabe;
690
691 if (str) {
692 if (str[0]!='/') str="/"+str;
693 if (str[0..4]!="/std/")
694 {
695 printf("showprops: Es koennen nur Properties von Objekten "
696 "aus /std/ angezeigt werden.\n");
697 return 1;
698 }
699 if (str[<1]=='.') str+="c";
700 if (str[<2..<1]!=".c") str+=".c";
701 if (file_size(str)<0)
702 {
703 printf("showprops: %s: Es gibt kein Objekt diesen Namens.\n",str[0..<3]);
704 return 1;
705 }
Zesstraefad7be2018-11-06 23:18:30 +0100706 if (catch(load_object(str)))
MG Mud User88f12472016-06-24 23:31:02 +0200707 {
708 printf("showprops: %s: Datei konnte nicht geladen werden.\n",str);
709 return 1;
710 }
711 }
712 if (!str || !find_object(str)) {
713 notify_fail("Welche Properties moechtest Du sehen?"
714 " Bsp.: <showprops /std/npc>\n");
715 return 0;
716 }
717 list=inherit_list(find_object(str));
MG Mud User88f12472016-06-24 23:31:02 +0200718 list=map(list,(: return $1[5..<2]+"h"; :));
719 list+=map(list,(: return explode($1,"/")[<1]; :));
720 list=map(m_indices(mkmapping(list)),(: return "/sys/"+$1; :));
721 list=filter(list,(: return file_size($1)>0; :));
MG Mud User88f12472016-06-24 23:31:02 +0200722 list=sort_array(list, #'<);
723 ausgabe="";
724 for (i=sizeof(list);i--;)
725 {
MG Mud User88f12472016-06-24 23:31:02 +0200726 str=implode(filter(explode(read_file(list[i]),"\n"),
727 (: return $1[0..9]=="#define P_";:)),"\n");
MG Mud User88f12472016-06-24 23:31:02 +0200728 if (str!="") ausgabe+=sprintf("%s\n%s\n\n", list[i], str);
729 }
730 if (ausgabe!="")
731 More(ausgabe);
732 else
733 printf("Keine Property-Definitionen zu diesem Objekt gefunden!\n");
734 return 1;
735}
736
737// ########
738//############################### GREP ###################################
739// ########
740
MG Mud User88f12472016-06-24 23:31:02 +0200741//
742// grep_file: Datei greppen
743// rexpr: Regular Expression
744// flags: Flags
745//
746
747private int grep_file(mixed filedata, string rexpr, int flags)
748{
Zesstrac70bf582019-11-26 00:43:17 +0100749 string fullname=filedata[FULLNAME];
MG Mud User88f12472016-06-24 23:31:02 +0200750 if ((flags&GREP_F)&&fullname=="/players/"+getuid()+"/grep.out")
Zesstraefad7be2018-11-06 23:18:30 +0100751 {
752 write_file("/players/"+getuid()+"/grep.out",
753 "Uebergehe grep.out ...\n");
754 return RET_FAIL;
MG Mud User88f12472016-06-24 23:31:02 +0200755 }
756 switch(filedata[FILESIZE])
757 {
Zesstraefad7be2018-11-06 23:18:30 +0100758 case FSIZE_DIR: return RET_FAIL;
759 case FSIZE_NOFILE: return ERROR(DOESNT_EXIST,fullname,RET_FAIL);
MG Mud User88f12472016-06-24 23:31:02 +0200760 case 0: return RET_FAIL;
761 default: break;
762 }
Zesstraefad7be2018-11-06 23:18:30 +0100763 if (!MAY_READ(fullname))
764 return ERROR(NO_READ,fullname,RET_FAIL);
Zesstrac70bf582019-11-26 00:43:17 +0100765
766 // Bei case-insensitiver Suche das Pattern in Kleinschreibung wandeln
MG Mud User88f12472016-06-24 23:31:02 +0200767 if (flags&GREP_I)
768 rexpr=lower_case(rexpr);
Zesstrac70bf582019-11-26 00:43:17 +0100769
770 // Portionsweise das komplette File einlesen.
771 int maxlen = query_limits()[LIMIT_BYTE];
772 int ptr;
773 bytes read_buffer;
774 bytes data = b"";
775 while (sizeof(read_buffer = read_bytes(fullname, ptr, maxlen)))
MG Mud User88f12472016-06-24 23:31:02 +0200776 {
Zesstrac70bf582019-11-26 00:43:17 +0100777 data += read_buffer;
778 ptr += sizeof(read_buffer);
779 // die Schleifenbedingung erkennt zwar auch, wenn das File vollstaendig
780 // ist, aber wir koennen den Speicherzugriff auch einsparen und schauen,
781 // ob wir schon alles haben.
782 if (ptr >= filedata[FILESIZE])
783 break;
MG Mud User88f12472016-06-24 23:31:02 +0200784 }
Zesstrac70bf582019-11-26 00:43:17 +0100785 // In string konvertieren, wir gehen davon aus, das File ist UTF8-kodiert.
786 string text = to_text(data, "UTF-8");
787 string *lines = explode(text, "\n");
788 int count; // Anzahl Zeilen mit Treffern
789 <string|string*> result = ({}); // zutreffende Zeilen
790 int linecount = 1;
791 foreach(string line: lines)
MG Mud User88f12472016-06-24 23:31:02 +0200792 {
Zesstrac70bf582019-11-26 00:43:17 +0100793 string orig_line = line;
794 // Suche case-insensitive?
795 if (flags&GREP_I)
796 line = lower_case(line);
797 int match = regmatch(line, rexpr) != 0;
798 if (flags&GREP_V) match = !match; // Match ggf. invertieren
MG Mud User88f12472016-06-24 23:31:02 +0200799 if (match)
800 {
Zesstrac70bf582019-11-26 00:43:17 +0100801 // Ausgeben oder nicht?
802 if (!(flags&GREP_C))
MG Mud User88f12472016-06-24 23:31:02 +0200803 {
Zesstrac70bf582019-11-26 00:43:17 +0100804 // Mit Zeilennummer?
805 if (flags&GREP_N)
806 result+=({ sprintf("%4d %s", linecount, orig_line)});
MG Mud User88f12472016-06-24 23:31:02 +0200807 else
Zesstrac70bf582019-11-26 00:43:17 +0100808 result+=({orig_line});
MG Mud User88f12472016-06-24 23:31:02 +0200809 }
Zesstrac70bf582019-11-26 00:43:17 +0100810 ++count;
MG Mud User88f12472016-06-24 23:31:02 +0200811 }
Zesstrac70bf582019-11-26 00:43:17 +0100812 ++linecount;
MG Mud User88f12472016-06-24 23:31:02 +0200813 }
Zesstrac70bf582019-11-26 00:43:17 +0100814
MG Mud User88f12472016-06-24 23:31:02 +0200815 if (count)
816 {
Zesstrac70bf582019-11-26 00:43:17 +0100817 // Bei -h werden die Dateinamen unterdrueckt.
818 if (flags&GREP_H)
819 fullname="";
MG Mud User88f12472016-06-24 23:31:02 +0200820 else
Zesstrac70bf582019-11-26 00:43:17 +0100821 fullname=sprintf("%s ",fullname);
822
823 if (flags&GREP_C)
824 result=sprintf("%s%d passende Zeile%s.\n",fullname, count,
825 (count==1?"":"n"));
826 else
827 result = ( (sizeof(fullname) ? fullname + "\n" : "")
828 + implode(result,"\n") + "\n");
Zesstrac70bf582019-11-26 00:43:17 +0100829
Zesstra77c0bf22020-01-07 22:27:27 +0100830 // Ergebnis ausgeben in File oder an Magier
831 if (flags&GREP_F)
832 return write_file("/players/"+getuid()+"/grep.out",result);
833 write(result);
834 }
MG Mud User88f12472016-06-24 23:31:02 +0200835 return RET_OK;
836}
837
838static int _grep(string cmdline)
839{
840 string rexpr,mask;
841 mixed *args;
842 int flags;
843 cmdline=_unparsed_args();
844 args=parseargs(cmdline,&flags,GREP_OPTS,0);
845 if ((flags==-1)||!sizeof(args))
846 return USAGE("grep [-" GREP_OPTS
847 "] <regexp> <datei/verz> [<datei2> ... ] [<maske>]");
848 rexpr=args[0];
849 if (catch(regexp(({""}),rexpr))||!regexp(({""}),rexpr))
850 return printf("grep: Ungueltiger Suchausdruck: %s\n",rexpr) ,1;
Zesstracc3f2502018-11-14 23:46:47 +0100851 args=(sizeof(args)>1 ? args[1..] : ({}) );
MG Mud User88f12472016-06-24 23:31:02 +0200852 if (flags&GREP_M)
853 {
854 mask=args[<1];
Zesstracc3f2502018-11-14 23:46:47 +0100855 if (sizeof(args) > 2)
856 args = (sizeof(args) > 1 ? args[0..<2] : ({}) );
MG Mud User88f12472016-06-24 23:31:02 +0200857 }
858 if (!sizeof(args))
859 return USAGE("grep [-" GREP_OPTS
860 "] <regexp> <datei/verz> [<datei2> ... ] [<maske>]");
861 args=map(args,#'to_filename)-({0});
MG Mud User88f12472016-06-24 23:31:02 +0200862 args=file_list(args,MODE_GREP,(flags&GREP_R?1:0),"/",mask);
863 if (!sizeof(args))
864 return printf("Keine passenden Dateien gefunden.\n"),1;
865 if (flags&GREP_I) rexpr=lower_case(rexpr);
866 if (flags&GREP_F)
867 {
Zesstraefad7be2018-11-06 23:18:30 +0100868 if (file_size("/players/"+getuid()+"/grep.out")==FSIZE_DIR
869 || !MAY_WRITE("/players/"+getuid()+"/grep.out"))
MG Mud User88f12472016-06-24 23:31:02 +0200870 return printf("grep: Datei /players/%s/grep.out kann nicht "
Zesstraefad7be2018-11-06 23:18:30 +0100871 "geschrieben werden.\n",getuid()),1;
MG Mud User88f12472016-06-24 23:31:02 +0200872 else
873 write_file("/players/"+getuid()+"/grep.out",
874 "Ausgabe von \"grep " + _unparsed_args() + "\":\n");
875 }
876 asynchron(args,#'grep_file,rexpr,flags,0);
877 return 1;
878}