1 /*
2 * catman.c: create and/or update cat files
3 *
4 * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
5 * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008, 2009, 2010, 2011
6 * Colin Watson.
7 *
8 * This file is part of man-db.
9 *
10 * man-db is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * man-db is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with man-db; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 *
24 * Thu Dec 8 00:03:12 GMT 1994 Wilf. (G.Wilford@ee.surrey.ac.uk)
25 */
26
27 /* MAX_ARGS must be >= 7, 5 for options, 1 for page and 1 for NULL */
28 #define MAX_ARGS 1024
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif /* HAVE_CONFIG_H */
33
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <assert.h>
37 #include <sys/types.h>
38 #include <errno.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <limits.h>
43
44 #ifndef NAME_MAX
45 # if defined(_POSIX_VERSION) && defined(_POSIX_NAME_MAX)
46 # define NAME_MAX _POSIX_NAME_MAX
47 # else /* !_POSIX_VERSION */
48 # ifdef MAXNAMLEN
49 # define NAME_MAX MAXNAMLEN
50 # else /* !MAXNAMLEN */
51 # define NAME_MAX 255 /* default to max */
52 # endif /* MAXNAMLEN */
53 # endif /* _POSIX_VERSION */
54 #endif /* !NAME_MAX */
55
56 #ifndef ARG_MAX
57 # if defined(_POSIX_VERSION) && defined(_POSIX_ARG_MAX)
58 # define ARG_MAX _POSIX_ARG_MAX
59 # else /* !_POSIX_VERSION */
60 # define ARG_MAX 4096 /* default to min */
61 # endif /* _POSIX_VERSION */
62 #endif /* !ARG_MAX */
63
64 #include "argp.h"
65 #include "error.h"
66 #include "gl_list.h"
67 #include "progname.h"
68 #include "xalloc.h"
69
70 #include "gettext.h"
71 #include <locale.h>
72 #define _(String) gettext (String)
73 #define N_(String) gettext_noop (String)
74
75 #include "manconfig.h"
76
77 #include "appendstr.h"
78 #include "cleanup.h"
79 #include "debug.h"
80 #include "fatal.h"
81 #include "filenames.h"
82 #include "glcontainers.h"
83 #include "pipeline.h"
84 #include "util.h"
85
86 #include "mydbm.h"
87 #include "db_storage.h"
88
89 #include "manp.h"
90
91 /* globals */
92 int quiet = 1;
93 MYDBM_FILE dbf_close_post_fork;
94 char *manp;
95 extern char *user_config_file;
96
97 static const char **sections;
98
99 const char *argp_program_version = "catman " PACKAGE_VERSION;
100 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
101 error_t argp_err_exit_status = FAIL;
102
103 static const char args_doc[] = N_("[SECTION...]");
104
105 static struct argp_option options[] = {
106 OPT ("debug", 'd', 0, N_("emit debugging messages")),
107 OPT ("manpath", 'M', N_("PATH"),
108 N_("set search path for manual pages to PATH")),
109 OPT ("config-file", 'C', N_("FILE"),
110 N_("use this user configuration file")),
111 OPT_HELP_COMPAT,
112 { 0 }
113 };
114
115 static error_t parse_opt (int key, char *arg, struct argp_state *state)
116 {
117 char *mansect;
118
119 switch (key) {
120 case 'd':
121 debug_level = true;
122 return 0;
123 case 'M':
124 manp = arg;
125 return 0;
126 case 'C':
127 user_config_file = arg;
128 return 0;
129 case 'h':
130 argp_state_help (state, state->out_stream,
131 ARGP_HELP_STD_HELP);
132 break;
133 case ARGP_KEY_ARGS:
134 sections = xmalloc ((state->argc - state->next + 1) *
135 sizeof *sections);
136 memcpy (sections, state->argv + state->next,
137 (state->argc - state->next) *
138 sizeof *sections);
139 sections[state->argc - state->next] = NULL;
140 return 0;
141 case ARGP_KEY_NO_ARGS:
142 mansect = getenv ("MANSECT");
143 if (mansect && *mansect) {
144 /* MANSECT contains sections */
145 const char *sec;
146 int i = 0;
147
148 mansect = xstrdup (mansect);
149 sections = NULL;
150 for (sec = strtok (mansect, ":"); sec;
151 sec = strtok (NULL, ":")) {
152 sections = xnrealloc
153 (sections, i + 2,
154 sizeof *sections);
155 sections[i++] = sec;
156 }
157 if (sections)
158 sections[i] = NULL;
159 free (mansect);
160 } else {
161 /* use default sections */
162 static const char *std_sections[] =
163 STD_SECTIONS;
164 sections = std_sections;
165 }
166 return 0;
167 }
168 return ARGP_ERR_UNKNOWN;
169 }
170
171 static struct argp argp = { options, parse_opt, args_doc };
172
173 static char *locale;
174
175 static gl_list_t manpathlist;
176
177 static void post_fork (void)
178 {
179 pop_all_cleanups ();
180 MYDBM_FREE (dbf_close_post_fork);
181 }
182
183 /* Execute man with the appropriate catman args. Always frees cmd. */
184 static void catman (pipecmd *cmd)
185 {
186 pipeline *p;
187 int status;
188
189 if (debug_level) {
190 /* just show the command, but don't execute it */
191 fputs ("man command = ", stderr);
192 pipecmd_dump (cmd, stderr);
193 putc ('\n', stderr);
194 pipecmd_free (cmd);
195 return;
196 }
197
198 p = pipeline_new_commands (cmd, (void *) 0);
199 status = pipeline_run (p);
200 if (status)
201 error (CHILD_FAIL, 0,
202 _("man command failed with exit status %d"), status);
203 }
204
205 /* Add key to this command, stripping off tab-and-following if necessary.
206 * Return length of argument.
207 */
208 static size_t add_arg (pipecmd *cmd, datum key)
209 {
210 char *tab;
211 size_t len;
212
213 tab = strrchr (MYDBM_DPTR (key), '\t');
214 if (tab == MYDBM_DPTR (key))
215 tab = NULL;
216
217 if (tab)
218 *tab = '\0';
219 pipecmd_arg (cmd, MYDBM_DPTR (key));
220 len = strlen (MYDBM_DPTR (key));
221 debug ("key: '%s' (%zu), len: %zu\n",
222 MYDBM_DPTR (key), (size_t) MYDBM_DSIZE (key), len);
223 if (tab)
224 *tab = '\t';
225
226 return len;
227 }
228
229 /* find all pages that are in the supplied manpath and section and that are
230 ultimate source files. */
231 static int parse_for_sec (MYDBM_FILE dbf,
232 const char *manpath, const char *section)
233 {
234 pipecmd *basecmd, *cmd;
235 datum key;
236 size_t arg_size, initial_bit;
237 bool message = true;
238 int first_arg;
239
240 basecmd = pipecmd_new (MAN);
241 pipecmd_clearenv (basecmd);
242
243 /* As we supply a NULL environment to save precious execve() space,
244 we must also supply a locale if necessary */
245 if (locale) {
246 pipecmd_args (basecmd, "-L", locale, (void *) 0);
247 initial_bit = sizeof "-L" + strlen (locale) + 1;
248 } else
249 initial_bit = 0;
250
251 pipecmd_args (basecmd, "-caM", manpath, (void *) 0); /* manpath */
252 pipecmd_args (basecmd, "-S", section, (void *) 0); /* section */
253
254 initial_bit += sizeof MAN + sizeof "-caM" +
255 strlen (manpath) + strlen (section) + 2;
256
257 cmd = pipecmd_dup (basecmd);
258 first_arg = pipecmd_get_nargs (cmd);
259
260 arg_size = initial_bit;
261 key = MYDBM_FIRSTKEY (dbf);
262
263 while (MYDBM_DPTR (key) != NULL) {
264 datum nextkey;
265
266 /* ignore db identifier keys */
267 #pragma GCC diagnostic push
268 #if GNUC_PREREQ(10,0)
269 # pragma GCC diagnostic ignored "-Wanalyzer-use-after-free"
270 #endif
271 if (*MYDBM_DPTR (key) != '$') {
272 #pragma GCC diagnostic pop
273 datum content;
274
275 content = MYDBM_FETCH (dbf, key);
276
277 if (!MYDBM_DPTR (content))
278 fatal (0,
279 _( "NULL content for key: %s"),
280 MYDBM_DPTR (key));
281
282 /* ignore overflow entries */
283 #pragma GCC diagnostic push
284 #if GNUC_PREREQ(10,0)
285 # pragma GCC diagnostic ignored "-Wanalyzer-use-after-free"
286 #endif
287 if (*MYDBM_DPTR (content) != '\t') {
288 #pragma GCC diagnostic pop
289 struct mandata *entry;
290
291 entry = split_content (dbf,
292 MYDBM_DPTR (content));
293
294 /* Accept if the entry is an ultimate manual
295 page and the section matches the one we're
296 currently dealing with */
297 if (entry->id == ULT_MAN &&
298 strcmp (entry->sec, section) == 0) {
299 if (message) {
300 printf (_("\nUpdating cat files for section %s of man hierarchy %s\n"),
301 section, manpath);
302 message = false;
303 }
304
305 arg_size += add_arg (cmd, key) + 1;
306
307 debug ("arg space free: %zu bytes\n",
308 ARG_MAX - arg_size);
309
310 /* Check to see if we have enough room
311 to add another max sized filename
312 and that we haven't run out of array
313 space too */
314 if (arg_size >= ARG_MAX - NAME_MAX ||
315 pipecmd_get_nargs (cmd) ==
316 MAX_ARGS) {
317 catman (cmd);
318
319 cmd = pipecmd_dup (basecmd);
320 arg_size = initial_bit;
321 }
322 }
323
324 free_mandata_struct (entry);
325 }
326
327 /* we don't need the content ever again */
328 assert (MYDBM_DPTR (content)); /* just to be sure */
329 MYDBM_FREE_DPTR (content);
330 }
331
332 nextkey = MYDBM_NEXTKEY (dbf, key);
333 MYDBM_FREE_DPTR (key);
334 key = nextkey;
335 }
336
337 if (pipecmd_get_nargs (cmd) > first_arg)
338 catman (cmd);
339 else
340 pipecmd_free (cmd);
341
342 pipecmd_free (basecmd);
343
344 return 0;
345 }
346
347 static bool check_access (const char *directory)
348 {
349 if (!CAN_ACCESS (directory, W_OK)) {
350 error (0, errno, _("cannot write within %s"), directory);
351 return true;
352 }
353
354 return false;
355 }
356
357 int main (int argc, char *argv[])
358 {
359 char *sys_manp;
360 char *mp;
361 const char **sp;
362
363 set_program_name (argv[0]);
364
365 init_debug ();
366 pipeline_install_post_fork (post_fork);
367
368 init_locale ();
369 locale = setlocale (LC_MESSAGES, NULL);
370 if (locale)
371 locale = xstrdup (locale);
372 else
373 locale = xstrdup ("C");
374
375 if (argp_parse (&argp, argc, argv, 0, 0, 0))
376 exit (FAIL);
377
378 for (sp = sections; sp && *sp; sp++)
379 debug ("sections: %s\n", *sp);
380
381 /* Deal with the MANPATH */
382
383 /* This is required for get_catpath(), regardless */
384 sys_manp = get_manpath (NULL);
385
386 /* pick up the system manpath or use the supplied one */
387 if (!manp) {
388 manp = get_mandb_manpath ();
389 if (!manp)
390 manp = sys_manp;
391 }
392
393 /* get the manpath as a list of pointers */
394 manpathlist = create_pathlist (manp);
395
396 GL_LIST_FOREACH (manpathlist, mp) {
397 char *catpath, *database;
398 MYDBM_FILE dbf;
399 size_t len;
400
401 catpath = get_catpath (mp, SYSTEM_CAT | USER_CAT);
402
403 if (catpath) {
404 if (is_directory (catpath) != 1) {
405 free (catpath);
406 continue;
407 }
408 database = mkdbname (catpath);
409 } else {
410 if (is_directory (mp) != 1)
411 continue;
412 database = mkdbname (mp);
413 catpath = xstrdup (mp);
414 }
415 dbf = MYDBM_NEW (database);
416 if (!MYDBM_RDOPEN (dbf) || dbver_rd (dbf)) {
417 error (0, errno, _("cannot read database %s"),
418 database);
419 goto next;
420 }
421 dbf_close_post_fork = dbf;
422
423 len = strlen (catpath);
424
425 for (sp = sections; sp && *sp; sp++) {
426 *(catpath + len) = '\0';
427 catpath = appendstr (catpath, "/cat", *sp, (void *) 0);
428 if (is_directory (catpath) != 1)
429 continue;
430 if (check_access (catpath))
431 continue;
432 if (parse_for_sec (dbf, mp, *sp)) {
433 error (0, 0, _("unable to update %s"), mp);
434 break;
435 }
436 }
437
438 next:
439 dbf_close_post_fork = NULL;
440 MYDBM_FREE (dbf);
441 free (database);
442 free (catpath);
443 }
444
445 free_pathlist (manpathlist);
446 free (locale);
447 exit (OK);
448 }