1 /*-
2 * Copyright (c) 1999 Chris Costello
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $Id: rtfm.c,v 1.9 2000/01/18 05:06:29 chris Exp $
27 *
28 */
29
30 #include <sys/types.h>
31 #include <sys/param.h>
32
33 #include <ctype.h>
34 #include <dirent.h>
35 #include <err.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 void chomp(char *);
42 void search_manuals(void);
43 void search_texinfo(void);
44 void usage(void);
45 char *add_spaces(char *);
46 char *Rstrcasestr(char *, char *);
47 char *Rstrstr(char *, char *);
48
49 static char *prog, *keyword;
50 extern char *__progname;
51
52 static int cflag, dflag, iflag, mflag, tflag, wflag;
53
54 /* From /usr/src/contrib/texinfo/info/filesys.h */
55 const char *info_paths[] = {
56 "/usr/local/info",
57 "/usr/info",
58 "/usr/local/lib/info",
59 "/usr/lib/info",
60 "/usr/local/gnu/info",
61 "/usr/local/gnu/lib/info",
62 "/usr/gnu/info",
63 "/usr/gnu/lib/info",
64 "/opt/gnu/info",
65 "/usr/share/info",
66 "/usr/share/lib/info",
67 "/usr/local/share/info",
68 "/usr/local/share/lib/info",
69 "/usr/gnu/lib/emacs/info",
70 "/usr/local/gnu/lib/emacs/info",
71 "/usr/local/lib/emacs/info",
72 "/usr/local/emacs/info",
73 ".",
74 NULL
75 };
76
77 int ninfo_paths = sizeof(info_paths) / sizeof(info_paths[0]);
78
79 /*
80 * Match strings depending on iflag
81 */
82 char *
Rstrstr(char * s,char * find)83 Rstrstr(char *s, char *find)
84 {
85 if (iflag)
86 return Rstrcasestr(s, find);
87 return strstr(s, find);
88 }
89
90 char *
Rstrcasestr(char * s,char * find)91 Rstrcasestr(char *s, char *find)
92 {
93 register char c, sc;
94 register size_t len;
95
96 if ((c = *find++) != 0) {
97 len = strlen(find);
98 do {
99 do {
100 if ((sc = *s++) == 0)
101 return (NULL);
102 } while (toupper(sc) != toupper(c));
103 } while (strncasecmp(s, find, len) != 0);
104 s--;
105 }
106 return ((char *)s);
107 }
108
109 /*
110 * Put a space at the beginning and end of a string.
111 */
112 char *
add_spaces(char * s)113 add_spaces(char *s)
114 {
115 char *ret = calloc(sizeof(char), strlen(s) +3);
116
117 sprintf(ret, " %s ", s);
118
119 return ret;
120 }
121
122 /*
123 * This function cuts off any trailing line terminators.
124 */
125 void
chomp(char * s)126 chomp(char *s)
127 {
128 size_t loc = strlen(s) - 1;
129
130 if (s[loc] == '\n')
131 s[loc--] = 0;
132 if (s[loc] == '\r')
133 s[loc--] = 0;
134 }
135
136 void
usage(void)137 usage(void)
138 {
139 fprintf(stderr, "usage: %s [-cdimtw] keyword\n", __progname);
140 exit(1);
141 }
142
143 /*
144 * Searches the manuals.
145 */
146 void
search_manuals(void)147 search_manuals(void)
148 {
149 FILE *p;
150 char *sp, *prog_args;
151 char fbuf[BUFSIZ];
152 char *bad_val = NULL;
153 int printed_header = 0;
154
155 /*
156 * Create what we'll compare to prog's output so we can check if
157 * anything was found.
158 */
159 asprintf(&bad_val, "%s: nothing appropriate", keyword);
160
161 /* Create the command string that we'll pass to popen(3) */
162 prog_args = malloc(strlen(prog) + strlen(keyword) + 1);
163 sprintf(prog_args, "%s %s", prog, keyword);
164
165 if ((p = popen(prog_args, "r")) == NULL) {
166 free(prog_args);
167 err(1, "popen: %s", prog);
168 return;
169 }
170 while (fgets(fbuf, sizeof(fbuf), p)) {
171 register char *paren, *name;
172
173 if (!printed_header) {
174 printed_header++;
175 printf("Manual pages found:\n");
176 }
177 chomp(fbuf);
178
179 if (!strcmp(fbuf, bad_val)) {
180 if (bad_val)
181 free(bad_val);
182 return;
183 }
184
185 name = fbuf;
186 paren = strchr(fbuf, '(');
187
188 if (paren == NULL)
189 return;
190
191 *paren = 0;
192 paren++;
193
194 sp = paren;
195 paren = strchr(sp, ')');
196 if (paren == NULL)
197 return;
198
199 *paren = 0;
200
201 if (cflag)
202 printf(" man %s %s\n", sp, name);
203 else
204 printf(" %s(%s)\n", name, sp);
205
206 if (dflag) {
207 char *dash;
208
209 dash = strchr(paren + 1, '-');
210 if (dash == NULL)
211 continue;
212
213 dash += 2;
214 if (*(dash - 1) == '\0' || *dash == '\0')
215 continue;
216
217 printf(" %s\n", dash);
218 }
219 }
220
221 if (bad_val)
222 free(bad_val);
223
224 free(prog_args);
225 }
226
227 /*
228 * Search GNU Texinfo pages
229 */
230 void
search_texinfo()231 search_texinfo()
232 {
233 DIR *dir;
234 struct dirent *ent;
235 int printed_header = 0, i;
236 char startdir[MAXPATHLEN];
237
238 /* Preserve original directory. */
239 getcwd(startdir, sizeof(startdir));
240
241 for (i = 0; i < ninfo_paths; i++) {
242 if (!printed_header) {
243 printed_header++;
244 printf("\nGNU Texinfo pages found:\n");
245 }
246 if ((dir = opendir(info_paths[i])) == NULL)
247 continue;
248
249 /*
250 * Change to the directory so we can more easily access
251 * files.
252 */
253 chdir(info_paths[i]);
254
255 while ((ent = readdir(dir)) != NULL) {
256 if (ent->d_type == DT_REG) {
257 char *command, c;
258 int matches = 0;
259 FILE *p;
260
261 /*
262 * Anything that does not end in '.info.gz'
263 * is not a Texinfo page.
264 */
265 if (strcasecmp(".info.gz", &ent->d_name[ent->d_namlen - 8])
266 && strcasecmp(".info", &ent->d_name[ent->d_namlen - 5]))
267 continue;
268
269 /*
270 * See if we can read it. We don't want
271 * confusing stderr from zgrep annoying the
272 * user.
273 */
274 if (access(ent->d_name, R_OK) < 0)
275 continue;
276
277 /*
278 * Create the string we'll be passing to
279 * popen(3)
280 */
281 command = calloc(sizeof(char), strlen(ent->d_name) +
282 strlen(keyword) + 8);
283
284 (void)sprintf(command, "zgrep %s %s %s %s",
285 wflag ? "-w" : "", iflag ? "-i" : "",
286 keyword, ent->d_name);
287
288 if ((p = popen(command, "r")) == NULL) {
289 warn("popen");
290 return;
291 }
292 while ((c = fgetc(p)) != EOF)
293 if (c == '\n')
294 matches++;
295
296 pclose(p);
297
298 if (!matches)
299 continue;
300
301 if (cflag) {
302 char *iptr;
303
304 /*
305 * Strip the .info.gz from the end of
306 * the string to create a command the
307 * user can type at his shell prompt.
308 */
309 iptr = strchr(ent->d_name, '.');
310 if (iptr)
311 *iptr = 0;
312 (void)printf(" info %s\n", ent->d_name);
313 } else
314 (void)printf(" %s (%d matches)\n", ent->d_name,
315 matches);
316 }
317 }
318 (void)closedir(dir);
319 }
320
321 /* Switch back to the original directory. */
322 chdir(startdir);
323
324 }
325
326 int
main(int argc,char ** argv)327 main(int argc, char **argv)
328 {
329 int c;
330
331 prog = "apropos";
332
333 while ((c = getopt(argc, argv, "cdimtw")) != -1)
334 switch (c) {
335 case 'c':
336 cflag = 1;
337 break;
338 case 'd':
339 dflag = 1;
340 break;
341 case 'i':
342 iflag = 1;
343 break;
344 case 'm':
345 mflag = 1;
346 break;
347 case 't':
348 tflag = 1;
349 break;
350 case 'w':
351 wflag = 1;
352 prog = "whatis";
353 break;
354 default:
355 usage();
356 /* NOTREACHED */
357 }
358
359 argc -= optind;
360 argv += optind;
361
362 if (argc < 1) {
363 usage();
364 /* NOTREACHED */
365 }
366 keyword = argv[0];
367
368 if (wflag)
369 keyword = add_spaces(keyword);
370
371 /* Search manuals only */
372 if (mflag) {
373 search_manuals();
374 exit(0);
375 }
376 /* Search texinfo only */
377 if (tflag) {
378 search_texinfo();
379 exit(0);
380 }
381 search_manuals();
382 search_texinfo();
383
384 return 0;
385 }
386