search.c revision b55195be
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, "Cant 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#ifdef NO_MANPATH_SUPPORT       /* not quite correct, but the best I can do. */
218        snprintf(cmdbuf, sizeof(cmdbuf), APROPOS_FORMAT, search_string, mantmp);
219#else
220        snprintf(cmdbuf, sizeof(cmdbuf), APROPOS_FORMAT, path, search_string,
221                 mantmp);
222#endif
223
224        if (system(cmdbuf) != 0) {      /* execute search. */
225            snprintf(error_buf, sizeof(error_buf),
226                     "Something went wrong trying to run %s\n", cmdbuf);
227            PopupWarning(man_globals, error_buf);
228        }
229
230        if ((file = fdopen(fd, "r")) == NULL)
231            PrintError("lost temp file? out of temp space?");
232
233/*
234 * Since we keep the FD open we can remove the file safely, this
235 * will keep extra files out of /tmp.
236 */
237
238        remove(mantmp);
239
240        snprintf(string_buf, sizeof(string_buf), "%s: nothing appropriate",
241                 search_string);
242
243        /*
244         * Check first LOOKLINES lines for "nothing appropriate".
245         */
246
247        count = 0;
248        flag = FALSE;
249        while ((fgets(cmp_str, BUFSIZ, file) != NULL) && (count < LOOKLINES)) {
250            size_t len = strlen(cmp_str);
251
252            if (len > 0 && cmp_str[len - 1] == '\n')  /* strip off the '\n' */
253                cmp_str[len - 1] = '\0';
254
255            if (streq(cmp_str, string_buf)) {
256                flag = TRUE;
257                break;
258            }
259            count++;
260        }
261
262        /*
263         * If the file is less than this number of lines then assume that there is
264         * nothing appropriate found. This does not confuse the apropos filter.
265         */
266
267        if (flag) {
268            fclose(file);
269            file = NULL;
270            ChangeLabel(man_globals->label, string_buf);
271            return (NULL);
272        }
273
274        snprintf(man_globals->manpage_title, sizeof(man_globals->manpage_title),
275                 "%s", label);
276        ChangeLabel(man_globals->label, label);
277        fseek(file, 0L, SEEK_SET);      /* reset file to point at top. */
278    }
279    else {                      /* MANUAL SEARCH */
280        file = DoManualSearch(man_globals, search_string);
281        if (file == NULL) {
282            snprintf(string_buf, sizeof(string_buf), "No manual entry for %s.",
283                     search_string);
284            ChangeLabel(man_globals->label, string_buf);
285            if (man_globals->label == NULL)
286                PopupWarning(man_globals, string_buf);
287            return (NULL);
288        }
289    }
290
291    if (resources.clear_search_string) {
292        Arg arglist[1];
293        Widget dialog;
294
295        dialog = XtNameToWidget(man_globals->search_widget, DIALOG);
296        if (dialog == NULL)
297            PopupWarning(man_globals, "Could not clear the search string.");
298
299        XtSetArg(arglist[0], XtNvalue, "");
300        XtSetValues(dialog, arglist, (Cardinal) 1);
301    }
302
303    return (file);
304}
305
306/*	Function Name: DoManualSearch
307 *	Description: performs a manual search.
308 *	Arguments: man_globals - the manual page specific globals.
309 *	Returns: the filename of the man page.
310 */
311
312#define NO_ENTRY -100
313
314static FILE *
315DoManualSearch(ManpageGlobals * man_globals, char *string)
316{
317    int e_num = NO_ENTRY;
318    int i;
319
320/* search current section first. */
321
322    i = man_globals->current_directory;
323    e_num = BEntrySearch(string, manual[i].entries, manual[i].nentries);
324
325/* search other sections. */
326
327    if (e_num == NO_ENTRY) {
328        i = 0;                  /* At the exit of the loop i needs to
329                                   be the one we used. */
330        while (TRUE) {
331            if (i == man_globals->current_directory)
332                if (++i >= sections)
333                    return (NULL);
334            e_num = BEntrySearch(string, manual[i].entries, manual[i].nentries);
335            if (e_num != NO_ENTRY)
336                break;
337            if (++i >= sections)
338                return (NULL);
339        }
340
341/*
342 * Manual page found in some other section, unhighlight the current one.
343 */
344        if (man_globals->manpagewidgets.box != NULL)
345            XawListUnhighlight(man_globals->manpagewidgets.
346                               box[man_globals->current_directory]);
347    }
348    else {
349        /*
350         * Highlight the element we are searching for if it is in the directory
351         * listing currently being shown.
352         */
353        if (man_globals->manpagewidgets.box != NULL)
354            XawListHighlight(man_globals->manpagewidgets.box[i], e_num);
355    }
356    return (FindManualFile(man_globals, i, e_num));
357}
358
359/*	Function Name: BEntrySearch
360 *	Description: binary search through entries.
361 *	Arguments: string - the string to match.
362 *                 first - the first entry in the list.
363 *                 number - the number of entries.
364 *	Returns: a pointer to the entry found.
365 */
366
367static int
368BEntrySearch(char *string, char **first, int number)
369{
370    int check, cmp, len_cmp, global_number;
371    char *head, *tail;
372
373    global_number = 0;
374    while (TRUE) {
375
376        if (number == 0) {
377            return (NO_ENTRY);  /* didn't find it. */
378        }
379
380        check = number / 2;
381
382        head = strrchr(first[global_number + check], '/');
383        if (head == NULL)
384            PrintError("index failure in BEntrySearch");
385        head++;
386
387        tail = strrchr(head, '.');
388        if (tail == NULL)
389            /* not an error, some systems (e.g. sgi) have only a .z suffix */
390            tail = head + strlen(head);
391
392        cmp = strncmp(string, head, (tail - head));
393        len_cmp = strlen(string) - (int) (tail - head);
394
395        if (cmp == 0 && len_cmp == 0) {
396            return (global_number + check);
397        }
398        else if (cmp < 0 || ((cmp == 0) && (len_cmp < 0)))
399            number = check;
400        else {                  /* cmp > 0 || ((cmp == 0) && (len_cmp > 0)) */
401
402            global_number += (check + 1);
403            number -= (check + 1);
404        }
405    }
406}
407