cd9d4a008bb8d1af4f5a125220722037bb886e95
[empserver] / src / lib / commands / info.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2020, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  info.c: display an info page
28  *
29  *  Known contributors to this file:
30  *     Dave Pare, 1986
31  *     Mike Wise, 1997 - added apropos and case insensitivity
32  *     Doug Hay, 1998
33  *     Steve McClure, 1998-2000
34  *     Ron Koenderink, 2004
35  */
36
37 #include <config.h>
38
39 #include <ctype.h>
40 #include <errno.h>
41 #include <sys/stat.h>
42 #include <stdio.h>
43 #if !defined(_WIN32)
44 #include <strings.h>
45 #include <dirent.h>
46 #endif
47 #include "commands.h"
48 #include "optlist.h"
49
50 static char *
51 lowerit(char *buf, int n, char *orig)
52 {                               /* converts a string to lower case */
53     /* lower case output buffer */
54     /* size of output buffer */
55     /* input strig */
56     int i;
57     for (i = 0; i < n - 1 && orig[i]; i++)
58         buf[i] = tolower(orig[i]);
59     buf[i] = 0;
60     return buf;
61 }
62
63 #if !defined(_WIN32)
64
65 int
66 info(void)
67 {
68     char buf[255];
69     FILE *fp;
70     char *name;
71     char *tmp_name;
72     struct stat statb;
73     struct dirent *dp;
74     char filename[1024];
75     char last[256];
76     DIR *info_dp;
77     int nmatch = 0;
78     int width = 0;
79     char sep;
80
81     name = player->argp[1];
82     if (name) {
83         /*
84          * don't let sneaky people go outside the info directory
85          */
86         if (NULL != (tmp_name = strrchr(name, '/')))
87             name = tmp_name + 1;
88     }
89     if (!name || !*name)
90         name = "TOP";
91
92     snprintf(filename, sizeof(filename), "%s/%s", infodir, name);
93     fp = fopen(filename, "r");
94     if (fp == NULL) {
95         /* may be a "partial" request. */
96         info_dp = opendir(infodir);
97         if (!info_dp) {
98             pr("Can't open info dir\n");
99             logerror("Can't open info dir \"%s\"\n", infodir);
100             return RET_FAIL;
101         }
102
103         while ((dp = readdir(info_dp))) {
104             if (strncasecmp(name, dp->d_name, strlen(name)) != 0)
105                 continue;
106             nmatch++;
107             if (nmatch == 1) {
108                 snprintf(last, sizeof(last), "%s", dp->d_name);
109             } else {
110                 if (nmatch == 2) {
111                     pr("`%s' is ambiguous.  The following topics match:\n%s",
112                         name, last);
113                     width = strlen(last);
114                 }
115                 width += 2 + strlen(dp->d_name);
116                 sep = ' ';
117                 if (width > 75) {
118                     sep = '\n';
119                     width = strlen(dp->d_name);
120                 }
121                 pr(",%c%s", sep, dp->d_name);
122             }
123         }
124         closedir(info_dp);
125         if (nmatch == 0) {
126             pr("Sorry, there is no info on %s\n", name);
127             return RET_FAIL;
128         } else if (nmatch > 1) {
129             pr(".\n");
130             return RET_FAIL;
131         }
132         snprintf(filename, sizeof(filename), "%s/%s", infodir, last);
133         fp = fopen(filename, "r");
134         if (fp == NULL) {
135             pr("Error reading info file for %s\n", name);
136             logerror("Cannot open for \"%s\" info file (%s)",
137                      filename, strerror(errno));
138             return RET_FAIL;
139         }
140     }
141     if (fstat(fileno(fp), &statb) < 0) {
142         pr("Error reading info file for %s\n", name);
143         logerror("Cannot fstat for \"%s\" info file (%s)",
144                  filename, strerror(errno));
145         fclose(fp);
146         return RET_FAIL;
147     }
148     if ((statb.st_mode & S_IFREG) == 0) {
149         pr("Error reading info file for %s\n", name);
150         logerror("The info file \"%s\" is not regular file\n", filename);
151         fclose(fp);
152         return RET_FAIL;
153     }
154
155     while (fgets(buf, sizeof(buf), fp))
156         pr("%s", buf);
157     (void)fclose(fp);
158     return RET_OK;
159 }
160
161 int
162 apro(void)
163 {
164     FILE *fp;
165     char *name, *lbp;
166     char *fbuf;
167     char *lbuf;
168     struct dirent *dp;
169     char filename[1024];
170     DIR *info_dp;
171     long nf, nhf, nl, nlhl, nhl, nll;
172     int alreadyhit;
173     int lhitlim;
174     struct stat statb;
175
176     if (!player->argp[1] || !*player->argp[1]) {
177         pr("Apropos what?\n");
178         return RET_FAIL;
179     }
180
181     lhitlim = 100;
182     if (player->argp[2]) {
183         lhitlim = atoi(player->argp[2]);
184         if (lhitlim <= 0)
185             lhitlim = 100;
186     }
187
188     info_dp = opendir(infodir);
189     if (info_dp == NULL) {
190         pr("Can't open info dir\n");
191         logerror("Can't open info dir \"%s\"", infodir);
192         return RET_FAIL;
193     }
194
195     fbuf = malloc(256);
196     lbuf = malloc(256);
197     lbp = malloc(256);
198
199     /*
200      *  lower case search string into lbp
201      */
202     name = player->argp[1];
203     lowerit(lbp, 256, name);
204
205     /*
206      *  search
207      */
208     nf = nhf = nl = nhl = 0;
209     while ((dp = readdir(info_dp))) {
210         if (dp->d_name[0] == '.')
211             continue;
212         snprintf(filename, sizeof(filename), "%s/%s", infodir, dp->d_name);
213         fp = fopen(filename, "r");
214         alreadyhit = 0;
215         nll = nlhl = 0;
216         if (fp != NULL) {
217             if (fstat(fileno(fp), &statb) < 0) {
218                 logerror("Cannot stat for \"%s\" info file (%s)",
219                          filename, strerror(errno));
220                 fclose(fp);
221                 continue;
222             }
223             if ((statb.st_mode & S_IFREG) == 0) {
224                 logerror("The info file \"%s\" is not regular file\n",
225                          filename);
226                 fclose(fp);
227                 continue;
228             }
229             while (fgets(fbuf, 256, fp)) {
230                 lowerit(lbuf, 256, fbuf);
231                 if (strstr(lbuf, lbp)) {
232                     if (!alreadyhit) {
233                         pr("*** %s ***\n", dp->d_name);
234                         alreadyhit = 1;
235                         nhf++;
236                     }
237                     fbuf[74] = '\n';
238                     fbuf[75] = 0;
239                     pr("   %s", fbuf);
240                     nlhl++;
241                     /*
242                      * break if too many lines
243                      */
244                     if ((nhl + nlhl) > lhitlim)
245                         break;
246                 }
247                 nll++;
248             }
249             fclose(fp);
250         }
251         nhl += nlhl;
252         nl += nll;
253         nf++;
254         if (nhl > lhitlim)
255             break;
256     }
257     closedir(info_dp);
258
259     free(fbuf);
260     free(lbuf);
261     free(lbp);
262
263     if ((nhl) > lhitlim) {
264         pr("Limit of %d lines exceeded\n", lhitlim);
265     }
266     pr("Found %s in %ld of %ld files and in %ld of %ld lines\n",
267        name, nhf, nf, nhl, nl);
268     return RET_OK;
269 }
270
271 #else  /* _WIN32 */
272
273 int
274 info(void)
275 {
276     char buf[255];
277     FILE *fp;
278     char *name;
279     char *tmp_name;
280     char filename[1024];
281     char last[256];
282     int nmatch = 0;
283     int width = 0;
284     char sep;
285
286     name = player->argp[1];
287     if (name) {
288         /*
289          * don't let sneaky people go outside the info directory
290          */
291         if (NULL != (tmp_name = strrchr(name, '/')))
292             name = tmp_name + 1;
293         if (NULL != (tmp_name = strrchr(name, '\\')))
294             name = tmp_name + 1;
295         if (NULL != (tmp_name = strrchr(name, ':')))
296             name = tmp_name + 1;
297     }
298     if (!name || !*name)
299         name = "TOP";
300
301     snprintf(filename, sizeof(filename) - 1, "%s\\%s", infodir, name);
302     fp = fopen(filename, "rb");
303     if (fp == NULL) {
304         /* may be a "partial" request. */
305         HANDLE hDir;
306         WIN32_FIND_DATA fData;
307         strcat(filename, "*");
308         hDir = FindFirstFile(filename, &fData);
309         if (hDir == INVALID_HANDLE_VALUE) {
310             switch (GetLastError()) {
311             case ERROR_FILE_NOT_FOUND:
312                 pr("Sorry, there is no info on %s\n", name);
313                 break;
314             case ERROR_PATH_NOT_FOUND:
315                 pr("Can't open info dir\n");
316                 logerror("Can't open info dir \"%s\"", infodir);
317                 break;
318             default:
319                 pr("Error reading info dir\n");
320                 logerror("Error (%lu) reading info dir(%s)\\file(%s)",
321                          GetLastError(), infodir, filename);
322             }
323             return RET_FAIL;
324         }
325         do {
326             if ((fData.dwFileAttributes != (DWORD)-1) &&
327                 ((fData.dwFileAttributes == FILE_ATTRIBUTE_NORMAL) ||
328                  (fData.dwFileAttributes == FILE_ATTRIBUTE_ARCHIVE) ||
329                  (fData.dwFileAttributes == FILE_ATTRIBUTE_READONLY)) &&
330                 (strncasecmp(name, fData.cFileName, strlen(name)) == 0)) {
331                 nmatch++;
332                 if (nmatch == 1) {
333                     snprintf(last, sizeof(last), "%s", fData.cFileName);
334                 } else {
335                     if (nmatch == 2) {
336                         pr("`%s' is ambiguous.  The following topics match:\n%s",
337                             name, last);
338                         width = strlen(last);
339                     }
340                     width += 2 + strlen(fData.cFileName);
341                     sep = ' ';
342                     if (width > 75) {
343                         sep = '\n';
344                         width = strlen(fData.cFileName);
345                     }
346                     pr(",%c%s", sep, fData.cFileName);
347                 }
348             }
349         } while (FindNextFile(hDir, &fData));
350         FindClose(hDir);
351         if (nmatch == 0) {
352             pr("Sorry, there is no info on %s\n", name);
353             return RET_FAIL;
354         } else if (nmatch > 1) {
355             pr(".\n");
356             return RET_FAIL;
357         }
358         snprintf(filename, sizeof(filename), "%s/%s", infodir, last);
359         fp = fopen(filename, "rb");
360         if (fp == NULL) {
361             pr("Error reading info file for %s\n", name);
362             logerror("Cannot open for \"%s\" info file (%s)",
363                      filename, strerror(errno));
364             return RET_FAIL;
365         }
366     } else {
367         DWORD fAttrib = GetFileAttributes(filename);
368         if ((fAttrib == (DWORD)-1) || /* INVALID_FILE_ATTRIBUTES */
369             ((fAttrib != FILE_ATTRIBUTE_NORMAL) &&
370              (fAttrib != FILE_ATTRIBUTE_ARCHIVE) &&
371              (fAttrib != FILE_ATTRIBUTE_READONLY))) {
372             pr("Error reading info file for %s\n", name);
373             logerror("The info file \"%s\" is not regular file\n",
374                      filename);
375             fclose(fp);
376             return RET_FAIL;
377         }
378     }
379
380     while (fgets(buf, sizeof(buf), fp) != 0)
381         pr("%s", buf);
382     (void)fclose(fp);
383     return RET_OK;
384 }
385
386 int
387 apro(void)
388 {
389     HANDLE hDir;
390     WIN32_FIND_DATA fData;
391     FILE *fp;
392     char *name, *lbp;
393     char *fbuf;
394     char *lbuf;
395     char filename[1024];
396     long nf, nhf, nl, nlhl, nhl, nll;
397     int alreadyhit;
398     int lhitlim;
399
400     if (player->argp[1] == 0 || !*player->argp[1]) {
401         pr("Apropos what?\n");
402         return RET_FAIL;
403     }
404
405     lhitlim = 100;
406     if (player->argp[2]) {
407         lhitlim = atoi(player->argp[2]);
408         if (lhitlim <= 0)
409             lhitlim = 100;
410     }
411
412     snprintf(filename, sizeof(filename), "%s\\*", infodir);
413     hDir = FindFirstFile(filename, &fData);
414     if (hDir == INVALID_HANDLE_VALUE) {
415         if (GetLastError() == ERROR_PATH_NOT_FOUND) {
416             pr("Can't open info dir\n");
417             logerror("Can't open info dir \"%s\"", infodir);
418         } else {
419             pr("Error reading info dir\n");
420             logerror("Error (%lu) reading info dir(%s)\\file(%s)",
421                      GetLastError(), infodir, filename);
422         }
423         return RET_FAIL;
424     }
425
426     fbuf = malloc(256);
427     lbuf = malloc(256);
428     lbp = malloc(256);
429
430     /*
431      *  lower case search string into lbp
432      */
433     name = player->argp[1];
434     lowerit(lbp, 256, name);
435
436     /*
437      *  search
438      */
439     nf = nhf = nl = nhl = 0;
440     do {
441         if ((fData.dwFileAttributes != (DWORD)-1) &&
442             ((fData.dwFileAttributes == FILE_ATTRIBUTE_NORMAL) ||
443              (fData.dwFileAttributes == FILE_ATTRIBUTE_ARCHIVE) ||
444              (fData.dwFileAttributes == FILE_ATTRIBUTE_READONLY))) {
445             snprintf(filename, sizeof(filename), "%s\\%s", infodir,
446                      fData.cFileName);
447             fp = fopen(filename, "rb");
448             alreadyhit = 0;
449             nll = nlhl = 0;
450             if (fp != NULL) {
451                 while (fgets(fbuf, 256, fp)) {
452                     lowerit(lbuf, 256, fbuf);
453                     if (strstr(lbuf, lbp)) {
454                         if (!alreadyhit) {
455                             pr("*** %s ***\n", fData.cFileName);
456                             alreadyhit = 1;
457                             nhf++;
458                         }
459                         fbuf[74] = '\n';
460                         fbuf[75] = 0;
461                         pr("   %s", fbuf);
462                         nlhl++;
463                         /*
464                          * break if too many lines
465                          */
466                         if ((nhl + nlhl) > lhitlim)
467                             break;
468                     }
469                     nll++;
470                 }
471                 fclose(fp);
472             }
473             nhl += nlhl;
474             nl += nll;
475             nf++;
476             if (nhl > lhitlim)
477                 break;
478         }
479     } while (FindNextFile(hDir, &fData));
480     FindClose(hDir);
481
482     free(fbuf);
483     free(lbuf);
484     free(lbp);
485
486     if ((nhl) > lhitlim) {
487         pr("Limit of %d lines exceeded\n", lhitlim);
488     }
489     pr("Found %s in %ld of %ld files and in %ld of %ld lines\n",
490        name, nhf, nf, nhl, nl);
491     return RET_OK;
492 }
493
494 #endif  /* _WIN32 */