1/*
2
3Copyright (c) 1987, 1988  X Consortium
4
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
12
13The above copyright notice and this permission notice shall be included
14in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
20OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22OTHER DEALINGS IN THE SOFTWARE.
23
24Except as contained in this notice, the name of the X Consortium shall
25not be used in advertising or otherwise to promote the sale, use or
26other dealings in this Software without prior written authorization
27from the X Consortium.
28
29*/
30
31#ifdef HAVE_CONFIG_H
32# include "config.h"
33#endif
34
35#include "globals.h"
36#include "vendor.h"
37#include <X11/Xos.h>            /* sys/types.h and unistd.h included in here */
38#include <sys/stat.h>
39
40/* Map <CR> and control-M to goto beginning of file. */
41
42#define SEARCHARGS 10
43
44static FILE *DoManualSearch(ManpageGlobals *man_globals, char *string);
45static int BEntrySearch(char *string, char **first, int number);
46
47/*	Function Name: MakeSearchWidget
48 *	Description: This Function Creates the Search Widget.
49 *	Arguments: man_globals - the pseudo globals for this manpage.
50 *                 w - the widgets parent
51 *	Returns: the search widget.
52 */
53
54void
55MakeSearchWidget(ManpageGlobals * man_globals, Widget parent)
56{
57    Widget dialog, command, text, cancel;
58    Arg arglist[2];
59    Cardinal num_args = 0;
60
61    XtSetArg(arglist[0], XtNtransientFor, parent);
62    man_globals->search_widget = XtCreatePopupShell(SEARCHNAME,
63                                                    transientShellWidgetClass,
64                                                    parent, arglist, 1);
65
66    if (resources.clear_search_string) {
67        XtSetArg(arglist[0], XtNvalue, "");
68        num_args++;
69    }
70
71    dialog = XtCreateManagedWidget(DIALOG, dialogWidgetClass,
72                                   man_globals->search_widget,
73                                   arglist, num_args);
74
75    if ((text = XtNameToWidget(dialog, "value")) == (Widget) NULL)
76        PopupWarning(NULL, "Could not find text widget in MakeSearchWidget.");
77    else
78        XtSetKeyboardFocus(dialog, text);
79
80    XawDialogAddButton(dialog, MANUALSEARCH, NULL, NULL);
81    XawDialogAddButton(dialog, APROPOSSEARCH, NULL, NULL);
82    XawDialogAddButton(dialog, CANCEL, NULL, NULL);
83
84/*
85 * This is a bit gross, but it get the cancel button underneath the
86 * others, and forms them up to the right size..
87 */
88
89    if (((command = XtNameToWidget(dialog, MANUALSEARCH)) == (Widget) NULL) ||
90        ((cancel = XtNameToWidget(dialog, CANCEL)) == (Widget) NULL))
91        PopupWarning(NULL,
92                     "Could not find manual search widget in MakeSearchWidget.");
93    else {
94        static const char *half_size[] = {
95            MANUALSEARCH, APROPOSSEARCH, NULL
96        };
97        static const char *full_size[] = {
98            "label", "value", CANCEL, NULL
99        };
100
101        num_args = 0;
102        XtSetArg(arglist[num_args], XtNfromVert, command);
103        num_args++;
104        XtSetArg(arglist[num_args], XtNfromHoriz, NULL);
105        num_args++;
106        XtSetValues(cancel, arglist, num_args);
107        FormUpWidgets(dialog, full_size, half_size);
108    }
109
110}
111
112/*      Function Name: SearchString
113 *      Description: Returns the search string.
114 *      Arguments: man_globals - the globals.
115 *      Returns: the search string.
116 */
117
118static char *
119SearchString(ManpageGlobals * man_globals)
120{
121    Widget dialog;
122
123    dialog = XtNameToWidget(man_globals->search_widget, DIALOG);
124    if (dialog != NULL)
125        return (XawDialogGetValueString(dialog));
126
127    PopupWarning(man_globals,
128                 "Could not get the search string, no search will be performed.");
129    return (NULL);
130}
131
132
133/*	Function Name: DoSearch
134 *	Description: This function performs a search for a man page or apropos
135 *                   search upon search string.
136 *	Arguments: man_globals - the pseudo globals for this manpage.
137 *                 type - the type of search.
138 *	Returns: none.
139 */
140
141#define LOOKLINES 6
142
143/*
144 * Manual searches look through the list of manual pages for the right one
145 * with a binary search.
146 *
147 * Apropos searches still exec man -k.
148 *
149 * If nothing is found then I send a warning message to the user, and do
150 * nothing.
151 */
152
153FILE *
154DoSearch(ManpageGlobals * man_globals, int type)
155{
156    char cmdbuf[BUFSIZ], *mantmp, *manpath;
157    char tmp[BUFSIZ], path[BUFSIZ];
158    char string_buf[BUFSIZ], cmp_str[BUFSIZ], error_buf[BUFSIZ];
159    char *search_string = SearchString(man_globals);
160    FILE *file;
161    int fd, omask;
162    int count;
163    Boolean flag;
164
165    if (search_string == NULL)
166        return (NULL);
167
168    /* If the string is empty or starts with a space then do not search */
169
170    if (streq(search_string, "")) {
171        PopupWarning(man_globals, "Search string is empty.");
172        return (NULL);
173    }
174
175    if (strlen(search_string) >= BUFSIZ) {
176        PopupWarning(man_globals, "Search string too long.");
177        return (NULL);
178    }
179    if (search_string[0] == ' ') {
180        PopupWarning(man_globals, "First character cannot be a space.");
181        return (NULL);
182    }
183
184    if (type == APROPOS) {
185        char label[BUFSIZ];
186
187        strcpy(tmp, MANTEMP);   /* get a temp file. */
188	omask = umask(077);
189        fd = mkstemp(tmp);
190	umask(omask);
191        if (fd < 0) {
192            PopupWarning(man_globals, "Can't create temp file");
193            return NULL;
194        }
195        mantmp = tmp;
196
197        manpath = getenv("MANPATH");
198        if (manpath == NULL || streq(manpath, "")) {
199#ifdef MANCONF
200            if (!ReadManConfig(path))
201#endif
202            {
203                strcpy(path, SYSMANPATH);
204#ifdef LOCALMANPATH
205                strcat(path, ":");
206                strcat(path, LOCALMANPATH);
207#endif
208            }
209        }
210        else {
211            strcpy(path, manpath);
212        }
213
214        snprintf(label, sizeof(label),
215                 "Results of apropos search on: %s", search_string);
216
217        snprintf(cmdbuf, sizeof(cmdbuf), APROPOS_FORMAT, path, search_string,
218                 mantmp);
219
220        if (system(cmdbuf) != 0) {      /* execute search. */
221            snprintf(error_buf, sizeof(error_buf),
222                     "Something went wrong trying to run %s\n", cmdbuf);
223            PopupWarning(man_globals, error_buf);
224        }
225
226        if ((file = fdopen(fd, "r")) == NULL)
227            PrintError("lost temp file? out of temp space?");
228
229/*
230 * Since we keep the FD open we can remove the file safely, this
231 * will keep extra files out of /tmp.
232 */
233
234        remove(mantmp);
235
236        snprintf(string_buf, sizeof(string_buf), "%s: nothing appropriate",
237                 search_string);
238
239        /*
240         * Check first LOOKLINES lines for "nothing appropriate".
241         */
242
243        count = 0;
244        flag = FALSE;
245        while ((fgets(cmp_str, BUFSIZ, file) != NULL) && (count < LOOKLINES)) {
246            size_t len = strlen(cmp_str);
247
248            if (len > 0 && cmp_str[len - 1] == '\n')  /* strip off the '\n' */
249                cmp_str[len - 1] = '\0';
250
251            if (streq(cmp_str, string_buf)) {
252                flag = TRUE;
253                break;
254            }
255            count++;
256        }
257
258        /*
259         * If the file is less than this number of lines then assume that there is
260         * nothing appropriate found. This does not confuse the apropos filter.
261         */
262
263        if (flag) {
264            fclose(file);
265            file = NULL;
266            ChangeLabel(man_globals->label, string_buf);
267            return (NULL);
268        }
269
270        snprintf(man_globals->manpage_title, sizeof(man_globals->manpage_title),
271                 "%s", label);
272        ChangeLabel(man_globals->label, label);
273        fseek(file, 0L, SEEK_SET);      /* reset file to point at top. */
274    }
275    else {                      /* MANUAL SEARCH */
276        file = DoManualSearch(man_globals, search_string);
277        if (file == NULL) {
278            snprintf(string_buf, sizeof(string_buf), "No manual entry for %s.",
279                     search_string);
280            ChangeLabel(man_globals->label, string_buf);
281            if (man_globals->label == NULL)
282                PopupWarning(man_globals, string_buf);
283            return (NULL);
284        }
285    }
286
287    if (resources.clear_search_string) {
288        Arg arglist[1];
289        Widget dialog;
290
291        dialog = XtNameToWidget(man_globals->search_widget, DIALOG);
292        if (dialog == NULL)
293            PopupWarning(man_globals, "Could not clear the search string.");
294
295        XtSetArg(arglist[0], XtNvalue, "");
296        XtSetValues(dialog, arglist, (Cardinal) 1);
297    }
298
299    return (file);
300}
301
302/*	Function Name: DoManualSearch
303 *	Description: performs a manual search.
304 *	Arguments: man_globals - the manual page specific globals.
305 *	Returns: the filename of the man page.
306 */
307
308#define NO_ENTRY -100
309
310static FILE *
311DoManualSearch(ManpageGlobals * man_globals, char *string)
312{
313    int e_num = NO_ENTRY;
314    int i;
315
316/* search current section first. */
317
318    i = man_globals->current_directory;
319    e_num = BEntrySearch(string, manual[i].entries, manual[i].nentries);
320
321/* search other sections. */
322
323    if (e_num == NO_ENTRY) {
324        i = 0;                  /* At the exit of the loop i needs to
325                                   be the one we used. */
326        while (TRUE) {
327            if (i == man_globals->current_directory)
328                if (++i >= sections)
329                    return (NULL);
330            e_num = BEntrySearch(string, manual[i].entries, manual[i].nentries);
331            if (e_num != NO_ENTRY)
332                break;
333            if (++i >= sections)
334                return (NULL);
335        }
336
337/*
338 * Manual page found in some other section, unhighlight the current one.
339 */
340        if (man_globals->manpagewidgets.box != NULL)
341            XawListUnhighlight(man_globals->manpagewidgets.
342                               box[man_globals->current_directory]);
343    }
344    else {
345        /*
346         * Highlight the element we are searching for if it is in the directory
347         * listing currently being shown.
348         */
349        if (man_globals->manpagewidgets.box != NULL)
350            XawListHighlight(man_globals->manpagewidgets.box[i], e_num);
351    }
352    return (FindManualFile(man_globals, i, e_num));
353}
354
355/*	Function Name: BEntrySearch
356 *	Description: binary search through entries.
357 *	Arguments: string - the string to match.
358 *                 first - the first entry in the list.
359 *                 number - the number of entries.
360 *	Returns: a pointer to the entry found.
361 */
362
363static int
364BEntrySearch(char *string, char **first, int number)
365{
366    int check, cmp, len_cmp, global_number;
367    char *head, *tail;
368
369    global_number = 0;
370    while (TRUE) {
371
372        if (number == 0) {
373            return (NO_ENTRY);  /* didn't find it. */
374        }
375
376        check = number / 2;
377
378        head = strrchr(first[global_number + check], '/');
379        if (head == NULL)
380            PrintError("index failure in BEntrySearch");
381        head++;
382
383        tail = strrchr(head, '.');
384        if (tail == NULL)
385            /* not an error, some systems (e.g. sgi) have only a .z suffix */
386            tail = head + strlen(head);
387
388        cmp = strncmp(string, head, (tail - head));
389        len_cmp = strlen(string) - (int) (tail - head);
390
391        if (cmp == 0 && len_cmp == 0) {
392            return (global_number + check);
393        }
394        else if (cmp < 0 || ((cmp == 0) && (len_cmp < 0)))
395            number = check;
396        else {                  /* cmp > 0 || ((cmp == 0) && (len_cmp > 0)) */
397
398            global_number += (check + 1);
399            number -= (check + 1);
400        }
401    }
402}
403