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