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