Return to mandocdb.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
1.220.2.10! schwarze 1: /* $Id: mandocdb.c,v 1.220.2.9 2017/01/27 14:18:42 schwarze Exp $ */ 1.1 kristaps 2: /* 1.48 schwarze 3: * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 1.220.2.5 schwarze 4: * Copyright (c) 2011-2017 Ingo Schwarze <schwarze@openbsd.org> 1.220.2.3 schwarze 5: * Copyright (c) 2016 Ed Maste <emaste@freebsd.org> 1.1 kristaps 6: * 7: * Permission to use, copy, modify, and distribute this software for any 8: * purpose with or without fee is hereby granted, provided that the above 9: * copyright notice and this permission notice appear in all copies. 10: * 1.187 schwarze 11: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 1.1 kristaps 12: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1.187 schwarze 13: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 1.1 kristaps 14: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18: */ 19: #include "config.h" 20: 1.162 schwarze 21: #include <sys/types.h> 1.50 kristaps 22: #include <sys/stat.h> 1.115 schwarze 23: #include <sys/wait.h> 1.1 kristaps 24: 25: #include <assert.h> 1.43 kristaps 26: #include <ctype.h> 1.208 schwarze 27: #if HAVE_ERR 1.198 schwarze 28: #include <err.h> 1.208 schwarze 29: #endif 1.63 schwarze 30: #include <errno.h> 1.1 kristaps 31: #include <fcntl.h> 1.158 schwarze 32: #if HAVE_FTS 1.50 kristaps 33: #include <fts.h> 1.157 schwarze 34: #else 35: #include "compat_fts.h" 36: #endif 1.58 schwarze 37: #include <limits.h> 1.218 kristaps 38: #if HAVE_SANDBOX_INIT 39: #include <sandbox.h> 40: #endif 1.50 kristaps 41: #include <stddef.h> 1.59 schwarze 42: #include <stdio.h> 1.1 kristaps 43: #include <stdint.h> 44: #include <stdlib.h> 45: #include <string.h> 1.17 schwarze 46: #include <unistd.h> 1.1 kristaps 47: 1.50 kristaps 48: #include <sqlite3.h> 1.1 kristaps 49: 1.188 schwarze 50: #include "mandoc_aux.h" 1.202 schwarze 51: #include "mandoc_ohash.h" 1.188 schwarze 52: #include "mandoc.h" 53: #include "roff.h" 1.50 kristaps 54: #include "mdoc.h" 1.1 kristaps 55: #include "man.h" 1.187 schwarze 56: #include "manconf.h" 1.55 kristaps 57: #include "mansearch.h" 1.1 kristaps 58: 1.111 schwarze 59: extern int mansearch_keymax; 60: extern const char *const mansearch_keynames[]; 61: 1.52 kristaps 62: #define SQL_EXEC(_v) \ 63: if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \ 1.134 schwarze 64: say("", "%s: %s", (_v), sqlite3_errmsg(db)) 1.52 kristaps 65: #define SQL_BIND_TEXT(_s, _i, _v) \ 66: if (SQLITE_OK != sqlite3_bind_text \ 67: ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \ 1.134 schwarze 68: say(mlink->file, "%s", sqlite3_errmsg(db)) 1.52 kristaps 69: #define SQL_BIND_INT(_s, _i, _v) \ 70: if (SQLITE_OK != sqlite3_bind_int \ 71: ((_s), (_i)++, (_v))) \ 1.134 schwarze 72: say(mlink->file, "%s", sqlite3_errmsg(db)) 1.52 kristaps 73: #define SQL_BIND_INT64(_s, _i, _v) \ 74: if (SQLITE_OK != sqlite3_bind_int64 \ 75: ((_s), (_i)++, (_v))) \ 1.134 schwarze 76: say(mlink->file, "%s", sqlite3_errmsg(db)) 1.52 kristaps 77: #define SQL_STEP(_s) \ 78: if (SQLITE_DONE != sqlite3_step((_s))) \ 1.134 schwarze 79: say(mlink->file, "%s", sqlite3_errmsg(db)) 1.52 kristaps 80: 1.50 kristaps 81: enum op { 82: OP_DEFAULT = 0, /* new dbs from dir list or default config */ 83: OP_CONFFILE, /* new databases from custom config file */ 84: OP_UPDATE, /* delete/add entries in existing database */ 85: OP_DELETE, /* delete entries from existing database */ 86: OP_TEST /* change no databases, report potential problems */ 1.38 schwarze 87: }; 88: 1.50 kristaps 89: struct str { 1.78 schwarze 90: const struct mpage *mpage; /* if set, the owning parse */ 1.50 kristaps 91: uint64_t mask; /* bitmask in sequence */ 1.176 schwarze 92: char key[]; /* rendered text */ 1.38 schwarze 93: }; 94: 1.79 schwarze 95: struct inodev { 96: ino_t st_ino; 97: dev_t st_dev; 1.50 kristaps 98: }; 1.5 kristaps 99: 1.78 schwarze 100: struct mpage { 1.79 schwarze 101: struct inodev inodev; /* used for hashing routine */ 1.137 schwarze 102: int64_t pageid; /* pageid in mpages SQL table */ 1.82 schwarze 103: char *sec; /* section from file content */ 104: char *arch; /* architecture from file content */ 105: char *title; /* title from file content */ 106: char *desc; /* description from file content */ 1.220.2.3 schwarze 107: struct mpage *next; /* singly linked list */ 1.82 schwarze 108: struct mlink *mlinks; /* singly linked list */ 1.161 schwarze 109: int form; /* format from file content */ 1.174 schwarze 110: int name_head_done; 1.82 schwarze 111: }; 112: 113: struct mlink { 1.58 schwarze 114: char file[PATH_MAX]; /* filename rel. to manpath */ 1.82 schwarze 115: char *dsec; /* section from directory */ 116: char *arch; /* architecture from directory */ 117: char *name; /* name from file name (not empty) */ 118: char *fsec; /* section from file name suffix */ 1.85 schwarze 119: struct mlink *next; /* singly linked list */ 1.118 schwarze 120: struct mpage *mpage; /* parent */ 1.161 schwarze 121: int dform; /* format from directory */ 122: int fform; /* format from file name suffix */ 1.124 schwarze 123: int gzip; /* filename has a .gz suffix */ 1.3 kristaps 124: }; 125: 1.50 kristaps 126: enum stmt { 1.81 schwarze 127: STMT_DELETE_PAGE = 0, /* delete mpage */ 128: STMT_INSERT_PAGE, /* insert mpage */ 129: STMT_INSERT_LINK, /* insert mlink */ 1.133 schwarze 130: STMT_INSERT_NAME, /* insert name */ 1.175 schwarze 131: STMT_SELECT_NAME, /* retrieve existing name flags */ 1.81 schwarze 132: STMT_INSERT_KEY, /* insert parsed key */ 1.50 kristaps 133: STMT__MAX 1.1 kristaps 134: }; 135: 1.190 schwarze 136: typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *, 1.189 schwarze 137: const struct roff_node *); 1.1 kristaps 138: 1.50 kristaps 139: struct mdoc_handler { 140: mdoc_fp fp; /* optional handler */ 141: uint64_t mask; /* set unless handler returns 0 */ 1.220.2.5 schwarze 142: int taboo; /* node flags that must not be set */ 1.1 kristaps 143: }; 1.219 schwarze 144: 145: 146: int mandocdb(int, char *[]); 1.1 kristaps 147: 1.59 schwarze 148: static void dbclose(int); 1.176 schwarze 149: static void dbadd(struct mpage *); 1.118 schwarze 150: static void dbadd_mlink(const struct mlink *mlink); 1.169 schwarze 151: static void dbadd_mlink_name(const struct mlink *mlink); 1.59 schwarze 152: static int dbopen(int); 153: static void dbprune(void); 154: static void filescan(const char *); 1.220.2.3 schwarze 155: #if HAVE_FTS_COMPARE_CONST 156: static int fts_compare(const FTSENT *const *, const FTSENT *const *); 157: #else 158: static int fts_compare(const FTSENT **, const FTSENT **); 159: #endif 1.84 schwarze 160: static void mlink_add(struct mlink *, const struct stat *); 1.131 schwarze 161: static void mlink_check(struct mpage *, struct mlink *); 1.82 schwarze 162: static void mlink_free(struct mlink *); 1.89 schwarze 163: static void mlinks_undupe(struct mpage *); 1.78 schwarze 164: static void mpages_free(void); 1.176 schwarze 165: static void mpages_merge(struct mparse *); 1.130 schwarze 166: static void names_check(void); 1.124 schwarze 167: static void parse_cat(struct mpage *, int); 1.190 schwarze 168: static void parse_man(struct mpage *, const struct roff_meta *, 1.189 schwarze 169: const struct roff_node *); 1.190 schwarze 170: static void parse_mdoc(struct mpage *, const struct roff_meta *, 1.189 schwarze 171: const struct roff_node *); 1.190 schwarze 172: static int parse_mdoc_head(struct mpage *, const struct roff_meta *, 1.189 schwarze 173: const struct roff_node *); 1.190 schwarze 174: static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *, 1.189 schwarze 175: const struct roff_node *); 176: static void parse_mdoc_fname(struct mpage *, const struct roff_node *); 1.190 schwarze 177: static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *, 1.189 schwarze 178: const struct roff_node *); 1.190 schwarze 179: static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *, 1.189 schwarze 180: const struct roff_node *); 1.190 schwarze 181: static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *, 1.189 schwarze 182: const struct roff_node *); 1.190 schwarze 183: static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *, 1.189 schwarze 184: const struct roff_node *); 1.190 schwarze 185: static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *, 1.189 schwarze 186: const struct roff_node *); 1.211 schwarze 187: static int parse_mdoc_Va(struct mpage *, const struct roff_meta *, 188: const struct roff_node *); 1.190 schwarze 189: static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *, 1.189 schwarze 190: const struct roff_node *); 1.112 schwarze 191: static void putkey(const struct mpage *, char *, uint64_t); 1.176 schwarze 192: static void putkeys(const struct mpage *, char *, size_t, uint64_t); 1.78 schwarze 193: static void putmdockey(const struct mpage *, 1.220.2.5 schwarze 194: const struct roff_node *, uint64_t, int); 1.176 schwarze 195: static int render_string(char **, size_t *); 1.220 schwarze 196: static void say(const char *, const char *, ...) 197: __attribute__((__format__ (printf, 2, 3))); 1.165 schwarze 198: static int set_basedir(const char *, int); 1.59 schwarze 199: static int treescan(void); 1.50 kristaps 200: static size_t utf8(unsigned int, char [7]); 201: 1.115 schwarze 202: static char tempfilename[32]; 1.102 schwarze 203: static int nodb; /* no database changes */ 1.116 schwarze 204: static int mparse_options; /* abort the parse early */ 1.141 schwarze 205: static int use_all; /* use all found files */ 206: static int debug; /* print what we're doing */ 207: static int warnings; /* warn about crap */ 1.95 schwarze 208: static int write_utf8; /* write UTF-8 output; else ASCII */ 1.59 schwarze 209: static int exitcode; /* to be returned by main */ 1.141 schwarze 210: static enum op op; /* operational mode */ 1.59 schwarze 211: static char basedir[PATH_MAX]; /* current base directory */ 1.220.2.3 schwarze 212: static struct mpage *mpage_head; /* list of distinct manual pages */ 1.78 schwarze 213: static struct ohash mpages; /* table of distinct manual pages */ 1.83 schwarze 214: static struct ohash mlinks; /* table of directory entries */ 1.133 schwarze 215: static struct ohash names; /* table of all names */ 1.50 kristaps 216: static struct ohash strings; /* table of all strings */ 217: static sqlite3 *db = NULL; /* current database */ 218: static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */ 1.133 schwarze 219: static uint64_t name_mask; 1.25 schwarze 220: 221: static const struct mdoc_handler mdocs[MDOC_MAX] = { 1.220.2.5 schwarze 222: { NULL, 0, 0 }, /* Ap */ 223: { NULL, 0, NODE_NOPRT }, /* Dd */ 224: { NULL, 0, NODE_NOPRT }, /* Dt */ 225: { NULL, 0, NODE_NOPRT }, /* Os */ 226: { parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */ 227: { parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */ 228: { NULL, 0, 0 }, /* Pp */ 229: { NULL, 0, 0 }, /* D1 */ 230: { NULL, 0, 0 }, /* Dl */ 231: { NULL, 0, 0 }, /* Bd */ 232: { NULL, 0, 0 }, /* Ed */ 233: { NULL, 0, 0 }, /* Bl */ 234: { NULL, 0, 0 }, /* El */ 235: { NULL, 0, 0 }, /* It */ 236: { NULL, 0, 0 }, /* Ad */ 237: { NULL, TYPE_An, 0 }, /* An */ 238: { NULL, TYPE_Ar, 0 }, /* Ar */ 239: { NULL, TYPE_Cd, 0 }, /* Cd */ 240: { NULL, TYPE_Cm, 0 }, /* Cm */ 241: { NULL, TYPE_Dv, 0 }, /* Dv */ 242: { NULL, TYPE_Er, 0 }, /* Er */ 243: { NULL, TYPE_Ev, 0 }, /* Ev */ 244: { NULL, 0, 0 }, /* Ex */ 245: { NULL, TYPE_Fa, 0 }, /* Fa */ 246: { parse_mdoc_Fd, 0, 0 }, /* Fd */ 247: { NULL, TYPE_Fl, 0 }, /* Fl */ 248: { parse_mdoc_Fn, 0, 0 }, /* Fn */ 249: { NULL, TYPE_Ft, 0 }, /* Ft */ 250: { NULL, TYPE_Ic, 0 }, /* Ic */ 251: { NULL, TYPE_In, 0 }, /* In */ 252: { NULL, TYPE_Li, 0 }, /* Li */ 253: { parse_mdoc_Nd, 0, 0 }, /* Nd */ 254: { parse_mdoc_Nm, 0, 0 }, /* Nm */ 255: { NULL, 0, 0 }, /* Op */ 256: { NULL, 0, 0 }, /* Ot */ 257: { NULL, TYPE_Pa, NODE_NOSRC }, /* Pa */ 258: { NULL, 0, 0 }, /* Rv */ 259: { NULL, TYPE_St, 0 }, /* St */ 260: { parse_mdoc_Va, TYPE_Va, 0 }, /* Va */ 261: { parse_mdoc_Va, TYPE_Vt, 0 }, /* Vt */ 262: { parse_mdoc_Xr, 0, 0 }, /* Xr */ 263: { NULL, 0, 0 }, /* %A */ 264: { NULL, 0, 0 }, /* %B */ 265: { NULL, 0, 0 }, /* %D */ 266: { NULL, 0, 0 }, /* %I */ 267: { NULL, 0, 0 }, /* %J */ 268: { NULL, 0, 0 }, /* %N */ 269: { NULL, 0, 0 }, /* %O */ 270: { NULL, 0, 0 }, /* %P */ 271: { NULL, 0, 0 }, /* %R */ 272: { NULL, 0, 0 }, /* %T */ 273: { NULL, 0, 0 }, /* %V */ 274: { NULL, 0, 0 }, /* Ac */ 275: { NULL, 0, 0 }, /* Ao */ 276: { NULL, 0, 0 }, /* Aq */ 1.220.2.7 schwarze 277: { NULL, TYPE_At, 0 }, /* At */ 1.220.2.5 schwarze 278: { NULL, 0, 0 }, /* Bc */ 279: { NULL, 0, 0 }, /* Bf */ 280: { NULL, 0, 0 }, /* Bo */ 281: { NULL, 0, 0 }, /* Bq */ 282: { NULL, TYPE_Bsx, NODE_NOSRC }, /* Bsx */ 1.220.2.6 schwarze 283: { NULL, TYPE_Bx, NODE_NOSRC }, /* Bx */ 1.220.2.5 schwarze 284: { NULL, 0, 0 }, /* Db */ 285: { NULL, 0, 0 }, /* Dc */ 286: { NULL, 0, 0 }, /* Do */ 287: { NULL, 0, 0 }, /* Dq */ 288: { NULL, 0, 0 }, /* Ec */ 289: { NULL, 0, 0 }, /* Ef */ 290: { NULL, TYPE_Em, 0 }, /* Em */ 291: { NULL, 0, 0 }, /* Eo */ 292: { NULL, TYPE_Fx, NODE_NOSRC }, /* Fx */ 293: { NULL, TYPE_Ms, 0 }, /* Ms */ 294: { NULL, 0, 0 }, /* No */ 295: { NULL, 0, 0 }, /* Ns */ 296: { NULL, TYPE_Nx, NODE_NOSRC }, /* Nx */ 297: { NULL, TYPE_Ox, NODE_NOSRC }, /* Ox */ 298: { NULL, 0, 0 }, /* Pc */ 299: { NULL, 0, 0 }, /* Pf */ 300: { NULL, 0, 0 }, /* Po */ 301: { NULL, 0, 0 }, /* Pq */ 302: { NULL, 0, 0 }, /* Qc */ 303: { NULL, 0, 0 }, /* Ql */ 304: { NULL, 0, 0 }, /* Qo */ 305: { NULL, 0, 0 }, /* Qq */ 306: { NULL, 0, 0 }, /* Re */ 307: { NULL, 0, 0 }, /* Rs */ 308: { NULL, 0, 0 }, /* Sc */ 309: { NULL, 0, 0 }, /* So */ 310: { NULL, 0, 0 }, /* Sq */ 311: { NULL, 0, 0 }, /* Sm */ 312: { NULL, 0, 0 }, /* Sx */ 313: { NULL, TYPE_Sy, 0 }, /* Sy */ 314: { NULL, TYPE_Tn, 0 }, /* Tn */ 315: { NULL, 0, NODE_NOSRC }, /* Ux */ 316: { NULL, 0, 0 }, /* Xc */ 317: { NULL, 0, 0 }, /* Xo */ 318: { parse_mdoc_Fo, 0, 0 }, /* Fo */ 319: { NULL, 0, 0 }, /* Fc */ 320: { NULL, 0, 0 }, /* Oo */ 321: { NULL, 0, 0 }, /* Oc */ 322: { NULL, 0, 0 }, /* Bk */ 323: { NULL, 0, 0 }, /* Ek */ 324: { NULL, 0, 0 }, /* Bt */ 325: { NULL, 0, 0 }, /* Hf */ 326: { NULL, 0, 0 }, /* Fr */ 327: { NULL, 0, 0 }, /* Ud */ 1.220.2.7 schwarze 328: { NULL, TYPE_Lb, NODE_NOSRC }, /* Lb */ 1.220.2.5 schwarze 329: { NULL, 0, 0 }, /* Lp */ 330: { NULL, TYPE_Lk, 0 }, /* Lk */ 331: { NULL, TYPE_Mt, NODE_NOSRC }, /* Mt */ 332: { NULL, 0, 0 }, /* Brq */ 333: { NULL, 0, 0 }, /* Bro */ 334: { NULL, 0, 0 }, /* Brc */ 335: { NULL, 0, 0 }, /* %C */ 336: { NULL, 0, 0 }, /* Es */ 337: { NULL, 0, 0 }, /* En */ 338: { NULL, TYPE_Dx, NODE_NOSRC }, /* Dx */ 339: { NULL, 0, 0 }, /* %Q */ 340: { NULL, 0, 0 }, /* br */ 341: { NULL, 0, 0 }, /* sp */ 342: { NULL, 0, 0 }, /* %U */ 343: { NULL, 0, 0 }, /* Ta */ 344: { NULL, 0, 0 }, /* ll */ 1.1 kristaps 345: }; 346: 1.141 schwarze 347: 1.1 kristaps 348: int 1.179 schwarze 349: mandocdb(int argc, char *argv[]) 1.1 kristaps 350: { 1.187 schwarze 351: struct manconf conf; 352: struct mparse *mp; 1.206 schwarze 353: const char *path_arg, *progname; 1.187 schwarze 354: size_t j, sz; 1.59 schwarze 355: int ch, i; 1.50 kristaps 356: 1.207 schwarze 357: #if HAVE_PLEDGE 358: if (pledge("stdio rpath wpath cpath fattr flock proc exec", NULL) == -1) { 1.214 schwarze 359: warn("pledge"); 1.218 kristaps 360: return (int)MANDOCLEVEL_SYSERR; 361: } 362: #endif 363: 364: #if HAVE_SANDBOX_INIT 365: if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) { 366: warnx("sandbox_init"); 1.207 schwarze 367: return (int)MANDOCLEVEL_SYSERR; 368: } 369: #endif 370: 1.187 schwarze 371: memset(&conf, 0, sizeof(conf)); 1.50 kristaps 372: memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); 373: 374: /* 1.141 schwarze 375: * We accept a few different invocations. 1.50 kristaps 376: * The CHECKOP macro makes sure that invocation styles don't 377: * clobber each other. 378: */ 379: #define CHECKOP(_op, _ch) do \ 380: if (OP_DEFAULT != (_op)) { \ 1.198 schwarze 381: warnx("-%c: Conflicting option", (_ch)); \ 1.50 kristaps 382: goto usage; \ 383: } while (/*CONSTCOND*/0) 1.10 kristaps 384: 1.59 schwarze 385: path_arg = NULL; 1.38 schwarze 386: op = OP_DEFAULT; 1.1 kristaps 387: 1.126 schwarze 388: while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v"))) 1.1 kristaps 389: switch (ch) { 1.141 schwarze 390: case 'a': 1.12 schwarze 391: use_all = 1; 392: break; 1.141 schwarze 393: case 'C': 1.50 kristaps 394: CHECKOP(op, ch); 1.59 schwarze 395: path_arg = optarg; 1.38 schwarze 396: op = OP_CONFFILE; 1.34 schwarze 397: break; 1.141 schwarze 398: case 'D': 1.125 schwarze 399: debug++; 400: break; 1.141 schwarze 401: case 'd': 1.50 kristaps 402: CHECKOP(op, ch); 1.59 schwarze 403: path_arg = optarg; 1.5 kristaps 404: op = OP_UPDATE; 405: break; 1.141 schwarze 406: case 'n': 1.50 kristaps 407: nodb = 1; 408: break; 1.141 schwarze 409: case 'p': 1.126 schwarze 410: warnings = 1; 411: break; 1.141 schwarze 412: case 'Q': 1.116 schwarze 413: mparse_options |= MPARSE_QUICK; 1.102 schwarze 414: break; 1.141 schwarze 415: case 'T': 1.95 schwarze 416: if (strcmp(optarg, "utf8")) { 1.198 schwarze 417: warnx("-T%s: Unsupported output format", 418: optarg); 1.95 schwarze 419: goto usage; 420: } 421: write_utf8 = 1; 422: break; 1.141 schwarze 423: case 't': 1.50 kristaps 424: CHECKOP(op, ch); 1.38 schwarze 425: dup2(STDOUT_FILENO, STDERR_FILENO); 426: op = OP_TEST; 1.50 kristaps 427: nodb = warnings = 1; 1.38 schwarze 428: break; 1.141 schwarze 429: case 'u': 1.50 kristaps 430: CHECKOP(op, ch); 1.59 schwarze 431: path_arg = optarg; 1.5 kristaps 432: op = OP_DELETE; 433: break; 1.141 schwarze 434: case 'v': 1.126 schwarze 435: /* Compatibility with espie@'s makewhatis. */ 1.38 schwarze 436: break; 1.1 kristaps 437: default: 1.38 schwarze 438: goto usage; 1.1 kristaps 439: } 440: 441: argc -= optind; 442: argv += optind; 443: 1.207 schwarze 444: #if HAVE_PLEDGE 1.210 schwarze 445: if (nodb) { 446: if (pledge("stdio rpath", NULL) == -1) { 1.214 schwarze 447: warn("pledge"); 1.210 schwarze 448: return (int)MANDOCLEVEL_SYSERR; 449: } 1.207 schwarze 450: } 451: #endif 452: 1.38 schwarze 453: if (OP_CONFFILE == op && argc > 0) { 1.198 schwarze 454: warnx("-C: Too many arguments"); 1.38 schwarze 455: goto usage; 456: } 457: 1.59 schwarze 458: exitcode = (int)MANDOCLEVEL_OK; 1.203 schwarze 459: mchars_alloc(); 460: mp = mparse_alloc(mparse_options, MANDOCLEVEL_BADARG, NULL, NULL); 1.202 schwarze 461: mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev)); 462: mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file)); 1.50 kristaps 463: 464: if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) { 1.59 schwarze 465: 1.50 kristaps 466: /* 1.150 schwarze 467: * Most of these deal with a specific directory. 1.140 schwarze 468: * Jump into that directory first. 1.50 kristaps 469: */ 1.165 schwarze 470: if (OP_TEST != op && 0 == set_basedir(path_arg, 1)) 1.50 kristaps 471: goto out; 1.140 schwarze 472: 1.138 schwarze 473: if (dbopen(1)) { 1.140 schwarze 474: /* 475: * The existing database is usable. Process 476: * all files specified on the command-line. 477: */ 1.207 schwarze 478: #if HAVE_PLEDGE 1.210 schwarze 479: if (!nodb) { 480: if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1) { 1.214 schwarze 481: warn("pledge"); 1.210 schwarze 482: exitcode = (int)MANDOCLEVEL_SYSERR; 483: goto out; 484: } 1.207 schwarze 485: } 486: #endif 1.140 schwarze 487: use_all = 1; 1.138 schwarze 488: for (i = 0; i < argc; i++) 489: filescan(argv[i]); 490: if (OP_TEST != op) 491: dbprune(); 492: } else { 493: /* 494: * Database missing or corrupt. 495: * Recreate from scratch. 496: */ 1.140 schwarze 497: exitcode = (int)MANDOCLEVEL_OK; 1.138 schwarze 498: op = OP_DEFAULT; 499: if (0 == treescan()) 500: goto out; 501: if (0 == dbopen(0)) 502: goto out; 503: } 1.50 kristaps 504: if (OP_DELETE != op) 1.176 schwarze 505: mpages_merge(mp); 1.138 schwarze 506: dbclose(OP_DEFAULT == op ? 0 : 1); 1.50 kristaps 507: } else { 508: /* 509: * If we have arguments, use them as our manpaths. 510: * If we don't, grok from manpath(1) or however else 1.187 schwarze 511: * manconf_parse() wants to do it. 1.50 kristaps 512: */ 513: if (argc > 0) { 1.187 schwarze 514: conf.manpath.paths = mandoc_reallocarray(NULL, 1.144 schwarze 515: argc, sizeof(char *)); 1.187 schwarze 516: conf.manpath.sz = (size_t)argc; 1.50 kristaps 517: for (i = 0; i < argc; i++) 1.187 schwarze 518: conf.manpath.paths[i] = mandoc_strdup(argv[i]); 1.50 kristaps 519: } else 1.187 schwarze 520: manconf_parse(&conf, path_arg, NULL, NULL); 1.127 schwarze 521: 1.187 schwarze 522: if (conf.manpath.sz == 0) { 1.127 schwarze 523: exitcode = (int)MANDOCLEVEL_BADARG; 524: say("", "Empty manpath"); 525: } 1.50 kristaps 526: 527: /* 1.69 schwarze 528: * First scan the tree rooted at a base directory, then 529: * build a new database and finally move it into place. 1.50 kristaps 530: * Ignore zero-length directories and strip trailing 531: * slashes. 532: */ 1.187 schwarze 533: for (j = 0; j < conf.manpath.sz; j++) { 534: sz = strlen(conf.manpath.paths[j]); 535: if (sz && conf.manpath.paths[j][sz - 1] == '/') 536: conf.manpath.paths[j][--sz] = '\0'; 1.50 kristaps 537: if (0 == sz) 538: continue; 1.66 schwarze 539: 540: if (j) { 1.202 schwarze 541: mandoc_ohash_init(&mpages, 6, 542: offsetof(struct mpage, inodev)); 543: mandoc_ohash_init(&mlinks, 6, 544: offsetof(struct mlink, file)); 1.66 schwarze 545: } 546: 1.187 schwarze 547: if ( ! set_basedir(conf.manpath.paths[j], argc > 0)) 1.165 schwarze 548: continue; 1.59 schwarze 549: if (0 == treescan()) 1.165 schwarze 550: continue; 1.59 schwarze 551: if (0 == dbopen(0)) 1.165 schwarze 552: continue; 1.52 kristaps 553: 1.176 schwarze 554: mpages_merge(mp); 1.147 schwarze 555: if (warnings && !nodb && 1.130 schwarze 556: ! (MPARSE_QUICK & mparse_options)) 557: names_check(); 1.59 schwarze 558: dbclose(0); 1.66 schwarze 559: 1.187 schwarze 560: if (j + 1 < conf.manpath.sz) { 1.80 schwarze 561: mpages_free(); 1.78 schwarze 562: ohash_delete(&mpages); 1.83 schwarze 563: ohash_delete(&mlinks); 1.66 schwarze 564: } 1.50 kristaps 565: } 566: } 567: out: 1.187 schwarze 568: manconf_free(&conf); 1.168 schwarze 569: mparse_free(mp); 1.203 schwarze 570: mchars_free(); 1.80 schwarze 571: mpages_free(); 1.78 schwarze 572: ohash_delete(&mpages); 1.83 schwarze 573: ohash_delete(&mlinks); 1.197 schwarze 574: return exitcode; 1.50 kristaps 575: usage: 1.206 schwarze 576: progname = getprogname(); 1.126 schwarze 577: fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n" 578: " %s [-aDnpQ] [-Tutf8] dir ...\n" 579: " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n" 580: " %s [-Dnp] -u dir [file ...]\n" 1.102 schwarze 581: " %s [-Q] -t file ...\n", 1.206 schwarze 582: progname, progname, progname, progname, progname); 1.50 kristaps 583: 1.197 schwarze 584: return (int)MANDOCLEVEL_BADARG; 1.50 kristaps 585: } 586: 587: /* 1.220.2.3 schwarze 588: * To get a singly linked list in alpha order while inserting entries 589: * at the beginning, process directory entries in reverse alpha order. 590: */ 591: static int 592: #if HAVE_FTS_COMPARE_CONST 593: fts_compare(const FTSENT *const *a, const FTSENT *const *b) 594: #else 595: fts_compare(const FTSENT **a, const FTSENT **b) 596: #endif 597: { 598: return -strcmp((*a)->fts_name, (*b)->fts_name); 599: } 600: 601: /* 1.59 schwarze 602: * Scan a directory tree rooted at "basedir" for manpages. 1.50 kristaps 603: * We use fts(), scanning directory parts along the way for clues to our 604: * section and architecture. 605: * 606: * If use_all has been specified, grok all files. 607: * If not, sanitise paths to the following: 608: * 1.141 schwarze 609: * [./]man*[/<arch>]/<name>.<section> 1.50 kristaps 610: * or 611: * [./]cat<section>[/<arch>]/<name>.0 612: * 1.216 schwarze 613: * TODO: accommodate for multi-language directories. 1.50 kristaps 614: */ 615: static int 1.59 schwarze 616: treescan(void) 1.50 kristaps 617: { 1.139 schwarze 618: char buf[PATH_MAX]; 1.50 kristaps 619: FTS *f; 620: FTSENT *ff; 1.84 schwarze 621: struct mlink *mlink; 1.124 schwarze 622: int dform, gzip; 1.94 schwarze 623: char *dsec, *arch, *fsec, *cp; 624: const char *path; 1.50 kristaps 625: const char *argv[2]; 626: 627: argv[0] = "."; 628: argv[1] = (char *)NULL; 629: 1.220.2.3 schwarze 630: f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR, 631: fts_compare); 1.200 schwarze 632: if (f == NULL) { 1.59 schwarze 633: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 634: say("", "&fts_open"); 1.197 schwarze 635: return 0; 1.50 kristaps 636: } 637: 638: dsec = arch = NULL; 639: dform = FORM_NONE; 640: 1.200 schwarze 641: while ((ff = fts_read(f)) != NULL) { 1.50 kristaps 642: path = ff->fts_path + 2; 1.139 schwarze 643: switch (ff->fts_info) { 644: 645: /* 646: * Symbolic links require various sanity checks, 647: * then get handled just like regular files. 648: */ 1.141 schwarze 649: case FTS_SL: 1.200 schwarze 650: if (realpath(path, buf) == NULL) { 1.139 schwarze 651: if (warnings) 652: say(path, "&realpath"); 653: continue; 654: } 1.184 schwarze 655: if (strstr(buf, basedir) != buf 656: #ifdef HOMEBREWDIR 657: && strstr(buf, HOMEBREWDIR) != buf 658: #endif 659: ) { 1.139 schwarze 660: if (warnings) say("", 661: "%s: outside base directory", buf); 662: continue; 663: } 664: /* Use logical inode to avoid mpages dupe. */ 1.200 schwarze 665: if (stat(path, ff->fts_statp) == -1) { 1.139 schwarze 666: if (warnings) 667: say(path, "&stat"); 668: continue; 669: } 670: /* FALLTHROUGH */ 671: 1.50 kristaps 672: /* 1.83 schwarze 673: * If we're a regular file, add an mlink by using the 1.50 kristaps 674: * stored directory data and handling the filename. 675: */ 1.141 schwarze 676: case FTS_F: 1.200 schwarze 677: if ( ! strcmp(path, MANDOC_DB)) 1.60 schwarze 678: continue; 1.50 kristaps 679: if ( ! use_all && ff->fts_level < 2) { 1.56 schwarze 680: if (warnings) 1.59 schwarze 681: say(path, "Extraneous file"); 1.50 kristaps 682: continue; 1.124 schwarze 683: } 684: gzip = 0; 685: fsec = NULL; 1.200 schwarze 686: while (fsec == NULL) { 1.124 schwarze 687: fsec = strrchr(ff->fts_name, '.'); 1.200 schwarze 688: if (fsec == NULL || strcmp(fsec+1, "gz")) 1.124 schwarze 689: break; 690: gzip = 1; 691: *fsec = '\0'; 692: fsec = NULL; 693: } 1.200 schwarze 694: if (fsec == NULL) { 1.60 schwarze 695: if ( ! use_all) { 1.56 schwarze 696: if (warnings) 1.60 schwarze 697: say(path, 698: "No filename suffix"); 1.50 kristaps 699: continue; 700: } 1.200 schwarze 701: } else if ( ! strcmp(++fsec, "html")) { 1.60 schwarze 702: if (warnings) 703: say(path, "Skip html"); 704: continue; 1.200 schwarze 705: } else if ( ! strcmp(fsec, "ps")) { 1.60 schwarze 706: if (warnings) 707: say(path, "Skip ps"); 708: continue; 1.200 schwarze 709: } else if ( ! strcmp(fsec, "pdf")) { 1.60 schwarze 710: if (warnings) 711: say(path, "Skip pdf"); 712: continue; 713: } else if ( ! use_all && 1.200 schwarze 714: ((dform == FORM_SRC && 1.185 schwarze 715: strncmp(fsec, dsec, strlen(dsec))) || 1.200 schwarze 716: (dform == FORM_CAT && strcmp(fsec, "0")))) { 1.60 schwarze 717: if (warnings) 718: say(path, "Wrong filename suffix"); 719: continue; 1.66 schwarze 720: } else 1.84 schwarze 721: fsec[-1] = '\0'; 1.94 schwarze 722: 1.84 schwarze 723: mlink = mandoc_calloc(1, sizeof(struct mlink)); 1.143 schwarze 724: if (strlcpy(mlink->file, path, 725: sizeof(mlink->file)) >= 726: sizeof(mlink->file)) { 727: say(path, "Filename too long"); 728: free(mlink); 729: continue; 730: } 1.84 schwarze 731: mlink->dform = dform; 1.94 schwarze 732: mlink->dsec = dsec; 733: mlink->arch = arch; 734: mlink->name = ff->fts_name; 735: mlink->fsec = fsec; 1.124 schwarze 736: mlink->gzip = gzip; 1.84 schwarze 737: mlink_add(mlink, ff->fts_statp); 1.50 kristaps 738: continue; 1.139 schwarze 739: 1.141 schwarze 740: case FTS_D: 741: case FTS_DP: 1.139 schwarze 742: break; 743: 744: default: 1.60 schwarze 745: if (warnings) 746: say(path, "Not a regular file"); 1.50 kristaps 747: continue; 1.60 schwarze 748: } 1.1 kristaps 749: 1.50 kristaps 750: switch (ff->fts_level) { 1.141 schwarze 751: case 0: 1.50 kristaps 752: /* Ignore the root directory. */ 753: break; 1.141 schwarze 754: case 1: 1.50 kristaps 755: /* 756: * This might contain manX/ or catX/. 757: * Try to infer this from the name. 758: * If we're not in use_all, enforce it. 759: */ 760: cp = ff->fts_name; 1.200 schwarze 761: if (ff->fts_info == FTS_DP) { 762: dform = FORM_NONE; 763: dsec = NULL; 1.50 kristaps 764: break; 1.200 schwarze 765: } 1.1 kristaps 766: 1.200 schwarze 767: if ( ! strncmp(cp, "man", 3)) { 1.50 kristaps 768: dform = FORM_SRC; 1.66 schwarze 769: dsec = cp + 3; 1.200 schwarze 770: } else if ( ! strncmp(cp, "cat", 3)) { 1.50 kristaps 771: dform = FORM_CAT; 1.66 schwarze 772: dsec = cp + 3; 1.94 schwarze 773: } else { 774: dform = FORM_NONE; 775: dsec = NULL; 1.50 kristaps 776: } 1.1 kristaps 777: 1.200 schwarze 778: if (dsec != NULL || use_all) 1.50 kristaps 779: break; 1.1 kristaps 780: 1.56 schwarze 781: if (warnings) 1.59 schwarze 782: say(path, "Unknown directory part"); 1.50 kristaps 783: fts_set(f, ff, FTS_SKIP); 784: break; 1.141 schwarze 785: case 2: 1.50 kristaps 786: /* 787: * Possibly our architecture. 788: * If we're descending, keep tabs on it. 789: */ 1.200 schwarze 790: if (ff->fts_info != FTS_DP && dsec != NULL) 1.66 schwarze 791: arch = ff->fts_name; 1.94 schwarze 792: else 793: arch = NULL; 1.50 kristaps 794: break; 795: default: 1.200 schwarze 796: if (ff->fts_info == FTS_DP || use_all) 1.50 kristaps 797: break; 1.56 schwarze 798: if (warnings) 1.59 schwarze 799: say(path, "Extraneous directory part"); 1.50 kristaps 800: fts_set(f, ff, FTS_SKIP); 801: break; 1.5 kristaps 802: } 1.50 kristaps 803: } 804: 805: fts_close(f); 1.197 schwarze 806: return 1; 1.50 kristaps 807: } 1.5 kristaps 808: 1.50 kristaps 809: /* 1.89 schwarze 810: * Add a file to the mlinks table. 1.50 kristaps 811: * Do not verify that it's a "valid" looking manpage (we'll do that 812: * later). 813: * 814: * Try to infer the manual section, architecture, and page name from the 815: * path, assuming it looks like 816: * 1.141 schwarze 817: * [./]man*[/<arch>]/<name>.<section> 1.50 kristaps 818: * or 819: * [./]cat<section>[/<arch>]/<name>.0 820: * 821: * See treescan() for the fts(3) version of this. 822: */ 823: static void 1.59 schwarze 824: filescan(const char *file) 1.50 kristaps 825: { 1.59 schwarze 826: char buf[PATH_MAX]; 1.84 schwarze 827: struct stat st; 828: struct mlink *mlink; 1.59 schwarze 829: char *p, *start; 1.5 kristaps 830: 1.50 kristaps 831: assert(use_all); 1.5 kristaps 832: 1.50 kristaps 833: if (0 == strncmp(file, "./", 2)) 834: file += 2; 1.5 kristaps 835: 1.139 schwarze 836: /* 837: * We have to do lstat(2) before realpath(3) loses 838: * the information whether this is a symbolic link. 839: * We need to know that because for symbolic links, 840: * we want to use the orginal file name, while for 841: * regular files, we want to use the real path. 842: */ 843: if (-1 == lstat(file, &st)) { 844: exitcode = (int)MANDOCLEVEL_BADARG; 845: say(file, "&lstat"); 846: return; 847: } else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) { 848: exitcode = (int)MANDOCLEVEL_BADARG; 849: say(file, "Not a regular file"); 850: return; 851: } 852: 853: /* 854: * We have to resolve the file name to the real path 855: * in any case for the base directory check. 856: */ 1.59 schwarze 857: if (NULL == realpath(file, buf)) { 858: exitcode = (int)MANDOCLEVEL_BADARG; 1.123 schwarze 859: say(file, "&realpath"); 1.59 schwarze 860: return; 1.106 schwarze 861: } 862: 1.150 schwarze 863: if (OP_TEST == op) 1.106 schwarze 864: start = buf; 1.150 schwarze 865: else if (strstr(buf, basedir) == buf) 866: start = buf + strlen(basedir); 1.184 schwarze 867: #ifdef HOMEBREWDIR 868: else if (strstr(buf, HOMEBREWDIR) == buf) 869: start = buf; 870: #endif 1.106 schwarze 871: else { 1.59 schwarze 872: exitcode = (int)MANDOCLEVEL_BADARG; 873: say("", "%s: outside base directory", buf); 874: return; 1.106 schwarze 875: } 876: 1.139 schwarze 877: /* 878: * Now we are sure the file is inside our tree. 879: * If it is a symbolic link, ignore the real path 880: * and use the original name. 881: * This implies passing stuff like "cat1/../man1/foo.1" 882: * on the command line won't work. So don't do that. 883: * Note the stat(2) can still fail if the link target 884: * doesn't exist. 885: */ 886: if (S_IFLNK & st.st_mode) { 887: if (-1 == stat(buf, &st)) { 888: exitcode = (int)MANDOCLEVEL_BADARG; 889: say(file, "&stat"); 890: return; 891: } 1.143 schwarze 892: if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) { 893: say(file, "Filename too long"); 894: return; 895: } 1.150 schwarze 896: start = buf; 897: if (OP_TEST != op && strstr(buf, basedir) == buf) 898: start += strlen(basedir); 1.50 kristaps 899: } 1.106 schwarze 900: 1.84 schwarze 901: mlink = mandoc_calloc(1, sizeof(struct mlink)); 1.161 schwarze 902: mlink->dform = FORM_NONE; 1.143 schwarze 903: if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= 904: sizeof(mlink->file)) { 905: say(start, "Filename too long"); 1.180 schwarze 906: free(mlink); 1.143 schwarze 907: return; 1.220.2.8 schwarze 908: } 909: 910: /* 911: * In test mode or when the original name is absolute 912: * but outside our tree, guess the base directory. 913: */ 914: 915: if (op == OP_TEST || (start == buf && *start == '/')) { 916: if (strncmp(buf, "man/", 4) == 0) 917: start = buf + 4; 918: else if ((start = strstr(buf, "/man/")) != NULL) 919: start += 5; 920: else 921: start = buf; 1.143 schwarze 922: } 1.17 schwarze 923: 1.50 kristaps 924: /* 925: * First try to guess our directory structure. 926: * If we find a separator, try to look for man* or cat*. 927: * If we find one of these and what's underneath is a directory, 928: * assume it's an architecture. 929: */ 930: if (NULL != (p = strchr(start, '/'))) { 931: *p++ = '\0'; 932: if (0 == strncmp(start, "man", 3)) { 1.84 schwarze 933: mlink->dform = FORM_SRC; 1.94 schwarze 934: mlink->dsec = start + 3; 1.50 kristaps 935: } else if (0 == strncmp(start, "cat", 3)) { 1.84 schwarze 936: mlink->dform = FORM_CAT; 1.94 schwarze 937: mlink->dsec = start + 3; 1.17 schwarze 938: } 1.5 kristaps 939: 1.50 kristaps 940: start = p; 1.84 schwarze 941: if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) { 1.50 kristaps 942: *p++ = '\0'; 1.94 schwarze 943: mlink->arch = start; 1.50 kristaps 944: start = p; 1.84 schwarze 945: } 1.50 kristaps 946: } 947: 948: /* 949: * Now check the file suffix. 950: * Suffix of `.0' indicates a catpage, `.1-9' is a manpage. 951: */ 952: p = strrchr(start, '\0'); 953: while (p-- > start && '/' != *p && '.' != *p) 954: /* Loop. */ ; 955: 956: if ('.' == *p) { 957: *p++ = '\0'; 1.94 schwarze 958: mlink->fsec = p; 1.5 kristaps 959: } 960: 1.10 kristaps 961: /* 1.50 kristaps 962: * Now try to parse the name. 963: * Use the filename portion of the path. 1.10 kristaps 964: */ 1.84 schwarze 965: mlink->name = start; 1.50 kristaps 966: if (NULL != (p = strrchr(start, '/'))) { 1.84 schwarze 967: mlink->name = p + 1; 1.50 kristaps 968: *p = '\0'; 1.84 schwarze 969: } 970: mlink_add(mlink, &st); 1.50 kristaps 971: } 1.5 kristaps 972: 1.50 kristaps 973: static void 1.84 schwarze 974: mlink_add(struct mlink *mlink, const struct stat *st) 1.50 kristaps 975: { 1.83 schwarze 976: struct inodev inodev; 1.78 schwarze 977: struct mpage *mpage; 1.83 schwarze 978: unsigned int slot; 1.50 kristaps 979: 1.84 schwarze 980: assert(NULL != mlink->file); 1.50 kristaps 981: 1.94 schwarze 982: mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); 983: mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); 984: mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); 985: mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); 1.84 schwarze 986: 987: if ('0' == *mlink->fsec) { 988: free(mlink->fsec); 989: mlink->fsec = mandoc_strdup(mlink->dsec); 990: mlink->fform = FORM_CAT; 991: } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec) 992: mlink->fform = FORM_SRC; 1.74 schwarze 993: else 1.84 schwarze 994: mlink->fform = FORM_NONE; 1.82 schwarze 995: 1.83 schwarze 996: slot = ohash_qlookup(&mlinks, mlink->file); 997: assert(NULL == ohash_find(&mlinks, slot)); 998: ohash_insert(&mlinks, slot, mlink); 999: 1.186 schwarze 1000: memset(&inodev, 0, sizeof(inodev)); /* Clear padding. */ 1.83 schwarze 1001: inodev.st_ino = st->st_ino; 1002: inodev.st_dev = st->st_dev; 1003: slot = ohash_lookup_memory(&mpages, (char *)&inodev, 1004: sizeof(struct inodev), inodev.st_ino); 1005: mpage = ohash_find(&mpages, slot); 1006: if (NULL == mpage) { 1007: mpage = mandoc_calloc(1, sizeof(struct mpage)); 1008: mpage->inodev.st_ino = inodev.st_ino; 1009: mpage->inodev.st_dev = inodev.st_dev; 1.220.2.3 schwarze 1010: mpage->next = mpage_head; 1011: mpage_head = mpage; 1.83 schwarze 1012: ohash_insert(&mpages, slot, mpage); 1013: } else 1.85 schwarze 1014: mlink->next = mpage->mlinks; 1.82 schwarze 1015: mpage->mlinks = mlink; 1.118 schwarze 1016: mlink->mpage = mpage; 1.82 schwarze 1017: } 1018: 1019: static void 1020: mlink_free(struct mlink *mlink) 1021: { 1022: 1023: free(mlink->dsec); 1024: free(mlink->arch); 1025: free(mlink->name); 1026: free(mlink->fsec); 1027: free(mlink); 1.50 kristaps 1028: } 1.3 kristaps 1029: 1.50 kristaps 1030: static void 1.78 schwarze 1031: mpages_free(void) 1.50 kristaps 1032: { 1.78 schwarze 1033: struct mpage *mpage; 1.82 schwarze 1034: struct mlink *mlink; 1.3 kristaps 1035: 1.220.2.3 schwarze 1036: while ((mpage = mpage_head) != NULL) { 1037: while ((mlink = mpage->mlinks) != NULL) { 1.85 schwarze 1038: mpage->mlinks = mlink->next; 1.82 schwarze 1039: mlink_free(mlink); 1040: } 1.220.2.3 schwarze 1041: mpage_head = mpage->next; 1.78 schwarze 1042: free(mpage->sec); 1043: free(mpage->arch); 1.82 schwarze 1044: free(mpage->title); 1045: free(mpage->desc); 1.78 schwarze 1046: free(mpage); 1.50 kristaps 1047: } 1048: } 1.38 schwarze 1049: 1.50 kristaps 1050: /* 1.89 schwarze 1051: * For each mlink to the mpage, check whether the path looks like 1052: * it is formatted, and if it does, check whether a source manual 1053: * exists by the same name, ignoring the suffix. 1054: * If both conditions hold, drop the mlink. 1055: */ 1056: static void 1057: mlinks_undupe(struct mpage *mpage) 1058: { 1059: char buf[PATH_MAX]; 1060: struct mlink **prev; 1061: struct mlink *mlink; 1062: char *bufp; 1063: 1064: mpage->form = FORM_CAT; 1.90 schwarze 1065: prev = &mpage->mlinks; 1066: while (NULL != (mlink = *prev)) { 1.89 schwarze 1067: if (FORM_CAT != mlink->dform) { 1068: mpage->form = FORM_NONE; 1.90 schwarze 1069: goto nextlink; 1.89 schwarze 1070: } 1.143 schwarze 1071: (void)strlcpy(buf, mlink->file, sizeof(buf)); 1.89 schwarze 1072: bufp = strstr(buf, "cat"); 1073: assert(NULL != bufp); 1074: memcpy(bufp, "man", 3); 1075: if (NULL != (bufp = strrchr(buf, '.'))) 1076: *++bufp = '\0'; 1.143 schwarze 1077: (void)strlcat(buf, mlink->dsec, sizeof(buf)); 1.89 schwarze 1078: if (NULL == ohash_find(&mlinks, 1.141 schwarze 1079: ohash_qlookup(&mlinks, buf))) 1.90 schwarze 1080: goto nextlink; 1.89 schwarze 1081: if (warnings) 1082: say(mlink->file, "Man source exists: %s", buf); 1083: if (use_all) 1.90 schwarze 1084: goto nextlink; 1.89 schwarze 1085: *prev = mlink->next; 1086: mlink_free(mlink); 1.90 schwarze 1087: continue; 1088: nextlink: 1089: prev = &(*prev)->next; 1.89 schwarze 1090: } 1091: } 1092: 1.131 schwarze 1093: static void 1.93 schwarze 1094: mlink_check(struct mpage *mpage, struct mlink *mlink) 1095: { 1.131 schwarze 1096: struct str *str; 1097: unsigned int slot; 1.93 schwarze 1098: 1099: /* 1100: * Check whether the manual section given in a file 1101: * agrees with the directory where the file is located. 1102: * Some manuals have suffixes like (3p) on their 1103: * section number either inside the file or in the 1104: * directory name, some are linked into more than one 1105: * section, like encrypt(1) = makekey(8). 1106: */ 1107: 1108: if (FORM_SRC == mpage->form && 1.131 schwarze 1109: strcasecmp(mpage->sec, mlink->dsec)) 1.93 schwarze 1110: say(mlink->file, "Section \"%s\" manual in %s directory", 1111: mpage->sec, mlink->dsec); 1112: 1113: /* 1114: * Manual page directories exist for each kernel 1115: * architecture as returned by machine(1). 1116: * However, many manuals only depend on the 1117: * application architecture as returned by arch(1). 1118: * For example, some (2/ARM) manuals are shared 1119: * across the "armish" and "zaurus" kernel 1120: * architectures. 1121: * A few manuals are even shared across completely 1122: * different architectures, for example fdformat(1) 1123: * on amd64, i386, sparc, and sparc64. 1124: */ 1125: 1.131 schwarze 1126: if (strcasecmp(mpage->arch, mlink->arch)) 1.93 schwarze 1127: say(mlink->file, "Architecture \"%s\" manual in " 1128: "\"%s\" directory", mpage->arch, mlink->arch); 1129: 1.131 schwarze 1130: /* 1131: * XXX 1.133 schwarze 1132: * parse_cat() doesn't set NAME_TITLE yet. 1.131 schwarze 1133: */ 1134: 1135: if (FORM_CAT == mpage->form) 1136: return; 1137: 1138: /* 1139: * Check whether this mlink 1140: * appears as a name in the NAME section. 1141: */ 1.93 schwarze 1142: 1.133 schwarze 1143: slot = ohash_qlookup(&names, mlink->name); 1144: str = ohash_find(&names, slot); 1.131 schwarze 1145: assert(NULL != str); 1.133 schwarze 1146: if ( ! (NAME_TITLE & str->mask)) 1.131 schwarze 1147: say(mlink->file, "Name missing in NAME section"); 1.93 schwarze 1148: } 1149: 1.89 schwarze 1150: /* 1.80 schwarze 1151: * Run through the files in the global vector "mpages" 1152: * and add them to the database specified in "basedir". 1.50 kristaps 1153: * 1154: * This handles the parsing scheme itself, using the cues of directory 1155: * and filename to determine whether the file is parsable or not. 1156: */ 1.59 schwarze 1157: static void 1.176 schwarze 1158: mpages_merge(struct mparse *mp) 1.50 kristaps 1159: { 1.113 schwarze 1160: char any[] = "any"; 1.118 schwarze 1161: struct mpage *mpage, *mpage_dest; 1162: struct mlink *mlink, *mlink_dest; 1.191 schwarze 1163: struct roff_man *man; 1.118 schwarze 1164: char *sodest; 1.112 schwarze 1165: char *cp; 1.162 schwarze 1166: int fd; 1.69 schwarze 1167: 1.171 schwarze 1168: if ( ! nodb) 1.107 schwarze 1169: SQL_EXEC("BEGIN TRANSACTION"); 1170: 1.220.2.3 schwarze 1171: for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) { 1.89 schwarze 1172: mlinks_undupe(mpage); 1.220.2.3 schwarze 1173: if ((mlink = mpage->mlinks) == NULL) 1.89 schwarze 1174: continue; 1.50 kristaps 1175: 1.133 schwarze 1176: name_mask = NAME_MASK; 1.202 schwarze 1177: mandoc_ohash_init(&names, 4, offsetof(struct str, key)); 1178: mandoc_ohash_init(&strings, 6, offsetof(struct str, key)); 1.50 kristaps 1179: mparse_reset(mp); 1180: man = NULL; 1.124 schwarze 1181: sodest = NULL; 1182: 1.213 schwarze 1183: if ((fd = mparse_open(mp, mlink->file)) == -1) { 1.182 schwarze 1184: say(mlink->file, "&open"); 1.162 schwarze 1185: goto nextpage; 1.124 schwarze 1186: } 1.14 schwarze 1187: 1188: /* 1.183 schwarze 1189: * Interpret the file as mdoc(7) or man(7) source 1190: * code, unless it is known to be formatted. 1.14 schwarze 1191: */ 1.182 schwarze 1192: if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) { 1.183 schwarze 1193: mparse_readfd(mp, fd, mlink->file); 1.212 schwarze 1194: close(fd); 1.220.2.9 schwarze 1195: fd = -1; 1.192 schwarze 1196: mparse_result(mp, &man, &sodest); 1.76 schwarze 1197: } 1.14 schwarze 1198: 1.171 schwarze 1199: if (sodest != NULL) { 1.118 schwarze 1200: mlink_dest = ohash_find(&mlinks, 1201: ohash_qlookup(&mlinks, sodest)); 1.171 schwarze 1202: if (mlink_dest == NULL) { 1203: mandoc_asprintf(&cp, "%s.gz", sodest); 1204: mlink_dest = ohash_find(&mlinks, 1205: ohash_qlookup(&mlinks, cp)); 1206: free(cp); 1207: } 1208: if (mlink_dest != NULL) { 1.118 schwarze 1209: 1210: /* The .so target exists. */ 1211: 1212: mpage_dest = mlink_dest->mpage; 1213: while (1) { 1214: mlink->mpage = mpage_dest; 1215: 1216: /* 1217: * If the target was already 1218: * processed, add the links 1219: * to the database now. 1220: * Otherwise, this will 1221: * happen when we come 1222: * to the target. 1223: */ 1224: 1.137 schwarze 1225: if (mpage_dest->pageid) 1.169 schwarze 1226: dbadd_mlink_name(mlink); 1.118 schwarze 1227: 1.171 schwarze 1228: if (mlink->next == NULL) 1.118 schwarze 1229: break; 1230: mlink = mlink->next; 1231: } 1232: 1233: /* Move all links to the target. */ 1234: 1235: mlink->next = mlink_dest->next; 1236: mlink_dest->next = mpage->mlinks; 1237: mpage->mlinks = NULL; 1238: } 1.124 schwarze 1239: goto nextpage; 1.192 schwarze 1240: } else if (man != NULL && man->macroset == MACROSET_MDOC) { 1.204 schwarze 1241: mdoc_validate(man); 1.82 schwarze 1242: mpage->form = FORM_SRC; 1.193 schwarze 1243: mpage->sec = man->meta.msec; 1.155 schwarze 1244: mpage->sec = mandoc_strdup( 1.171 schwarze 1245: mpage->sec == NULL ? "" : mpage->sec); 1.193 schwarze 1246: mpage->arch = man->meta.arch; 1.84 schwarze 1247: mpage->arch = mandoc_strdup( 1.171 schwarze 1248: mpage->arch == NULL ? "" : mpage->arch); 1.193 schwarze 1249: mpage->title = mandoc_strdup(man->meta.title); 1.192 schwarze 1250: } else if (man != NULL && man->macroset == MACROSET_MAN) { 1.205 schwarze 1251: man_validate(man); 1.220.2.9 schwarze 1252: if (*man->meta.msec != '\0' || 1253: *man->meta.msec != '\0') { 1254: mpage->form = FORM_SRC; 1255: mpage->sec = mandoc_strdup(man->meta.msec); 1256: mpage->arch = mandoc_strdup(mlink->arch); 1257: mpage->title = mandoc_strdup(man->meta.title); 1258: } else 1259: man = NULL; 1260: } 1261: 1262: assert(mpage->desc == NULL); 1263: if (man == NULL) { 1.82 schwarze 1264: mpage->form = FORM_CAT; 1.182 schwarze 1265: mpage->sec = mandoc_strdup(mlink->dsec); 1266: mpage->arch = mandoc_strdup(mlink->arch); 1267: mpage->title = mandoc_strdup(mlink->name); 1.220.2.9 schwarze 1268: parse_cat(mpage, fd); 1269: } else if (man->macroset == MACROSET_MDOC) 1270: parse_mdoc(mpage, &man->meta, man->first); 1271: else 1272: parse_man(mpage, &man->meta, man->first); 1273: 1.97 schwarze 1274: putkey(mpage, mpage->sec, TYPE_sec); 1.164 schwarze 1275: if (*mpage->arch != '\0') 1276: putkey(mpage, mpage->arch, TYPE_arch); 1.50 kristaps 1277: 1.182 schwarze 1278: for ( ; mlink != NULL; mlink = mlink->next) { 1.97 schwarze 1279: if ('\0' != *mlink->dsec) 1280: putkey(mpage, mlink->dsec, TYPE_sec); 1281: if ('\0' != *mlink->fsec) 1282: putkey(mpage, mlink->fsec, TYPE_sec); 1.98 schwarze 1283: putkey(mpage, '\0' == *mlink->arch ? 1.113 schwarze 1284: any : mlink->arch, TYPE_arch); 1.133 schwarze 1285: putkey(mpage, mlink->name, NAME_FILE); 1.97 schwarze 1286: } 1.12 schwarze 1287: 1.174 schwarze 1288: if (mpage->desc == NULL) 1.135 schwarze 1289: mpage->desc = mandoc_strdup(mpage->mlinks->name); 1.131 schwarze 1290: 1291: if (warnings && !use_all) 1292: for (mlink = mpage->mlinks; mlink; 1293: mlink = mlink->next) 1294: mlink_check(mpage, mlink); 1.44 kristaps 1295: 1.176 schwarze 1296: dbadd(mpage); 1.124 schwarze 1297: 1298: nextpage: 1.66 schwarze 1299: ohash_delete(&strings); 1.133 schwarze 1300: ohash_delete(&names); 1.50 kristaps 1301: } 1.107 schwarze 1302: 1303: if (0 == nodb) 1304: SQL_EXEC("END TRANSACTION"); 1.130 schwarze 1305: } 1306: 1307: static void 1308: names_check(void) 1309: { 1310: sqlite3_stmt *stmt; 1311: const char *name, *sec, *arch, *key; 1312: 1313: sqlite3_prepare_v2(db, 1314: "SELECT name, sec, arch, key FROM (" 1.133 schwarze 1315: "SELECT name AS key, pageid FROM names " 1.130 schwarze 1316: "WHERE bits & ? AND NOT EXISTS (" 1317: "SELECT pageid FROM mlinks " 1.133 schwarze 1318: "WHERE mlinks.pageid == names.pageid " 1319: "AND mlinks.name == names.name" 1.130 schwarze 1320: ")" 1321: ") JOIN (" 1.142 schwarze 1322: "SELECT sec, arch, name, pageid FROM mlinks " 1323: "GROUP BY pageid" 1.130 schwarze 1324: ") USING (pageid);", 1325: -1, &stmt, NULL); 1326: 1.201 schwarze 1327: if (sqlite3_bind_int64(stmt, 1, NAME_TITLE) != SQLITE_OK) 1.134 schwarze 1328: say("", "%s", sqlite3_errmsg(db)); 1.130 schwarze 1329: 1.201 schwarze 1330: while (sqlite3_step(stmt) == SQLITE_ROW) { 1.154 schwarze 1331: name = (const char *)sqlite3_column_text(stmt, 0); 1332: sec = (const char *)sqlite3_column_text(stmt, 1); 1333: arch = (const char *)sqlite3_column_text(stmt, 2); 1334: key = (const char *)sqlite3_column_text(stmt, 3); 1.130 schwarze 1335: say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec, 1336: '\0' == *arch ? "" : "/", 1337: '\0' == *arch ? "" : arch, key); 1338: } 1339: sqlite3_finalize(stmt); 1.50 kristaps 1340: } 1.12 schwarze 1341: 1.50 kristaps 1342: static void 1.124 schwarze 1343: parse_cat(struct mpage *mpage, int fd) 1.50 kristaps 1344: { 1345: FILE *stream; 1.220.2.10! schwarze 1346: struct mlink *mlink; ! 1347: char *line, *p, *title, *sec; 1.209 schwarze 1348: size_t linesz, plen, titlesz; 1349: ssize_t len; 1350: int offs; 1.12 schwarze 1351: 1.220.2.10! schwarze 1352: mlink = mpage->mlinks; ! 1353: stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r"); ! 1354: if (stream == NULL) { ! 1355: if (fd != -1) 1.159 schwarze 1356: close(fd); 1.56 schwarze 1357: if (warnings) 1.220.2.10! schwarze 1358: say(mlink->file, "&fopen"); 1.50 kristaps 1359: return; 1360: } 1.1 kristaps 1361: 1.209 schwarze 1362: line = NULL; 1363: linesz = 0; 1364: 1.220.2.10! schwarze 1365: /* Parse the section number from the header line. */ 1.1 kristaps 1366: 1.220.2.10! schwarze 1367: while (getline(&line, &linesz, stream) != -1) { 1.209 schwarze 1368: if (*line == '\n') 1.220.2.10! schwarze 1369: continue; ! 1370: if ((sec = strchr(line, '(')) == NULL) ! 1371: break; ! 1372: if ((p = strchr(++sec, ')')) == NULL) ! 1373: break; ! 1374: free(mpage->sec); ! 1375: mpage->sec = mandoc_strndup(sec, p - sec); ! 1376: if (warnings && *mlink->dsec != '\0' && ! 1377: strcasecmp(mpage->sec, mlink->dsec)) ! 1378: say(mlink->file, ! 1379: "Section \"%s\" manual in %s directory", ! 1380: mpage->sec, mlink->dsec); ! 1381: break; ! 1382: } ! 1383: ! 1384: /* Skip to first blank line. */ ! 1385: ! 1386: while (line == NULL || *line != '\n') ! 1387: if (getline(&line, &linesz, stream) == -1) 1.50 kristaps 1388: break; 1.1 kristaps 1389: 1.50 kristaps 1390: /* 1391: * Assume the first line that is not indented 1392: * is the first section header. Skip to it. 1393: */ 1.1 kristaps 1394: 1.209 schwarze 1395: while (getline(&line, &linesz, stream) != -1) 1396: if (*line != '\n' && *line != ' ') 1.50 kristaps 1397: break; 1.141 schwarze 1398: 1.50 kristaps 1399: /* 1400: * Read up until the next section into a buffer. 1401: * Strip the leading and trailing newline from each read line, 1402: * appending a trailing space. 1403: * Ignore empty (whitespace-only) lines. 1404: */ 1.1 kristaps 1405: 1.50 kristaps 1406: titlesz = 0; 1407: title = NULL; 1.38 schwarze 1408: 1.209 schwarze 1409: while ((len = getline(&line, &linesz, stream)) != -1) { 1410: if (*line != ' ') 1.50 kristaps 1411: break; 1.209 schwarze 1412: offs = 0; 1413: while (isspace((unsigned char)line[offs])) 1414: offs++; 1415: if (line[offs] == '\0') 1.38 schwarze 1416: continue; 1.209 schwarze 1417: title = mandoc_realloc(title, titlesz + len - offs); 1418: memcpy(title + titlesz, line + offs, len - offs); 1419: titlesz += len - offs; 1.50 kristaps 1420: title[titlesz - 1] = ' '; 1421: } 1.209 schwarze 1422: free(line); 1.38 schwarze 1423: 1.50 kristaps 1424: /* 1425: * If no page content can be found, or the input line 1426: * is already the next section header, or there is no 1427: * trailing newline, reuse the page title as the page 1428: * description. 1429: */ 1.44 kristaps 1430: 1.50 kristaps 1431: if (NULL == title || '\0' == *title) { 1.56 schwarze 1432: if (warnings) 1.220.2.10! schwarze 1433: say(mlink->file, "Cannot find NAME section"); 1.50 kristaps 1434: fclose(stream); 1435: free(title); 1436: return; 1437: } 1.1 kristaps 1438: 1.209 schwarze 1439: title[titlesz - 1] = '\0'; 1.33 schwarze 1440: 1.50 kristaps 1441: /* 1442: * Skip to the first dash. 1443: * Use the remaining line as the description (no more than 70 1444: * bytes). 1445: */ 1.33 schwarze 1446: 1.50 kristaps 1447: if (NULL != (p = strstr(title, "- "))) { 1448: for (p += 2; ' ' == *p || '\b' == *p; p++) 1449: /* Skip to next word. */ ; 1450: } else { 1.56 schwarze 1451: if (warnings) 1.220.2.10! schwarze 1452: say(mlink->file, "No dash in title line"); 1.50 kristaps 1453: p = title; 1454: } 1.38 schwarze 1455: 1.50 kristaps 1456: plen = strlen(p); 1.1 kristaps 1457: 1.50 kristaps 1458: /* Strip backspace-encoding from line. */ 1.1 kristaps 1459: 1.50 kristaps 1460: while (NULL != (line = memchr(p, '\b', plen))) { 1461: len = line - p; 1462: if (0 == len) { 1463: memmove(line, line + 1, plen--); 1464: continue; 1.141 schwarze 1465: } 1.50 kristaps 1466: memmove(line - 1, line + 1, plen - len); 1467: plen -= 2; 1468: } 1.1 kristaps 1469: 1.78 schwarze 1470: mpage->desc = mandoc_strdup(p); 1.50 kristaps 1471: fclose(stream); 1472: free(title); 1473: } 1.1 kristaps 1474: 1.50 kristaps 1475: /* 1476: * Put a type/word pair into the word database for this particular file. 1477: */ 1478: static void 1.112 schwarze 1479: putkey(const struct mpage *mpage, char *value, uint64_t type) 1.50 kristaps 1480: { 1.112 schwarze 1481: char *cp; 1.18 kristaps 1482: 1.50 kristaps 1483: assert(NULL != value); 1.112 schwarze 1484: if (TYPE_arch == type) 1485: for (cp = value; *cp; cp++) 1486: if (isupper((unsigned char)*cp)) 1487: *cp = _tolower((unsigned char)*cp); 1.78 schwarze 1488: putkeys(mpage, value, strlen(value), type); 1.3 kristaps 1489: } 1490: 1491: /* 1.50 kristaps 1492: * Grok all nodes at or below a certain mdoc node into putkey(). 1.3 kristaps 1493: */ 1494: static void 1.78 schwarze 1495: putmdockey(const struct mpage *mpage, 1.220.2.5 schwarze 1496: const struct roff_node *n, uint64_t m, int taboo) 1.3 kristaps 1497: { 1.18 kristaps 1498: 1.50 kristaps 1499: for ( ; NULL != n; n = n->next) { 1.220.2.5 schwarze 1500: if (n->flags & taboo) 1501: continue; 1.50 kristaps 1502: if (NULL != n->child) 1.220.2.5 schwarze 1503: putmdockey(mpage, n->child, m, taboo); 1.188 schwarze 1504: if (n->type == ROFFT_TEXT) 1.78 schwarze 1505: putkey(mpage, n->string, m); 1.50 kristaps 1506: } 1507: } 1.18 kristaps 1508: 1.61 schwarze 1509: static void 1.190 schwarze 1510: parse_man(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1511: const struct roff_node *n) 1.50 kristaps 1512: { 1.189 schwarze 1513: const struct roff_node *head, *body; 1.121 schwarze 1514: char *start, *title; 1.50 kristaps 1515: char byte; 1.121 schwarze 1516: size_t sz; 1.18 kristaps 1517: 1.215 schwarze 1518: if (n == NULL) 1.61 schwarze 1519: return; 1.18 kristaps 1520: 1.50 kristaps 1521: /* 1522: * We're only searching for one thing: the first text child in 1523: * the BODY of a NAME section. Since we don't keep track of 1524: * sections in -man, run some hoops to find out whether we're in 1525: * the correct section or not. 1526: */ 1.18 kristaps 1527: 1.188 schwarze 1528: if (n->type == ROFFT_BODY && n->tok == MAN_SH) { 1.50 kristaps 1529: body = n; 1.215 schwarze 1530: if ((head = body->parent->head) != NULL && 1531: (head = head->child) != NULL && 1532: head->next == NULL && 1.188 schwarze 1533: head->type == ROFFT_TEXT && 1.215 schwarze 1534: strcmp(head->string, "NAME") == 0 && 1535: body->child != NULL) { 1.3 kristaps 1536: 1.50 kristaps 1537: /* 1538: * Suck the entire NAME section into memory. 1539: * Yes, we might run away. 1540: * But too many manuals have big, spread-out 1541: * NAME sections over many lines. 1542: */ 1.3 kristaps 1543: 1.121 schwarze 1544: title = NULL; 1.194 schwarze 1545: deroff(&title, body); 1.50 kristaps 1546: if (NULL == title) 1.61 schwarze 1547: return; 1.18 kristaps 1548: 1.141 schwarze 1549: /* 1.50 kristaps 1550: * Go through a special heuristic dance here. 1551: * Conventionally, one or more manual names are 1552: * comma-specified prior to a whitespace, then a 1553: * dash, then a description. Try to puzzle out 1554: * the name parts here. 1555: */ 1.18 kristaps 1556: 1.121 schwarze 1557: start = title; 1.50 kristaps 1558: for ( ;; ) { 1559: sz = strcspn(start, " ,"); 1560: if ('\0' == start[sz]) 1561: break; 1.1 kristaps 1562: 1.50 kristaps 1563: byte = start[sz]; 1564: start[sz] = '\0'; 1.110 schwarze 1565: 1566: /* 1567: * Assume a stray trailing comma in the 1568: * name list if a name begins with a dash. 1569: */ 1570: 1571: if ('-' == start[0] || 1572: ('\\' == start[0] && '-' == start[1])) 1573: break; 1.1 kristaps 1574: 1.133 schwarze 1575: putkey(mpage, start, NAME_TITLE); 1.174 schwarze 1576: if ( ! (mpage->name_head_done || 1577: strcasecmp(start, meta->title))) { 1578: putkey(mpage, start, NAME_HEAD); 1579: mpage->name_head_done = 1; 1580: } 1.1 kristaps 1581: 1.50 kristaps 1582: if (' ' == byte) { 1583: start += sz + 1; 1584: break; 1585: } 1.1 kristaps 1586: 1.50 kristaps 1587: assert(',' == byte); 1588: start += sz + 1; 1589: while (' ' == *start) 1590: start++; 1591: } 1.1 kristaps 1592: 1.121 schwarze 1593: if (start == title) { 1.133 schwarze 1594: putkey(mpage, start, NAME_TITLE); 1.174 schwarze 1595: if ( ! (mpage->name_head_done || 1596: strcasecmp(start, meta->title))) { 1597: putkey(mpage, start, NAME_HEAD); 1598: mpage->name_head_done = 1; 1599: } 1.50 kristaps 1600: free(title); 1.61 schwarze 1601: return; 1.50 kristaps 1602: } 1.1 kristaps 1603: 1.50 kristaps 1604: while (isspace((unsigned char)*start)) 1605: start++; 1.1 kristaps 1606: 1.50 kristaps 1607: if (0 == strncmp(start, "-", 1)) 1608: start += 1; 1609: else if (0 == strncmp(start, "\\-\\-", 4)) 1610: start += 4; 1611: else if (0 == strncmp(start, "\\-", 2)) 1612: start += 2; 1613: else if (0 == strncmp(start, "\\(en", 4)) 1614: start += 4; 1615: else if (0 == strncmp(start, "\\(em", 4)) 1616: start += 4; 1.1 kristaps 1617: 1.50 kristaps 1618: while (' ' == *start) 1619: start++; 1.1 kristaps 1620: 1.78 schwarze 1621: mpage->desc = mandoc_strdup(start); 1.50 kristaps 1622: free(title); 1.61 schwarze 1623: return; 1.50 kristaps 1624: } 1625: } 1.1 kristaps 1626: 1.77 schwarze 1627: for (n = n->child; n; n = n->next) { 1.78 schwarze 1628: if (NULL != mpage->desc) 1.77 schwarze 1629: break; 1.174 schwarze 1630: parse_man(mpage, meta, n); 1.77 schwarze 1631: } 1.1 kristaps 1632: } 1633: 1634: static void 1.190 schwarze 1635: parse_mdoc(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1636: const struct roff_node *n) 1.1 kristaps 1637: { 1638: 1.50 kristaps 1639: assert(NULL != n); 1640: for (n = n->child; NULL != n; n = n->next) { 1.220.2.5 schwarze 1641: if (n->flags & mdocs[n->tok].taboo) 1642: continue; 1.50 kristaps 1643: switch (n->type) { 1.188 schwarze 1644: case ROFFT_ELEM: 1645: case ROFFT_BLOCK: 1646: case ROFFT_HEAD: 1647: case ROFFT_BODY: 1648: case ROFFT_TAIL: 1.50 kristaps 1649: if (NULL != mdocs[n->tok].fp) 1.172 schwarze 1650: if (0 == (*mdocs[n->tok].fp)(mpage, meta, n)) 1.50 kristaps 1651: break; 1.68 schwarze 1652: if (mdocs[n->tok].mask) 1.78 schwarze 1653: putmdockey(mpage, n->child, 1.220.2.5 schwarze 1654: mdocs[n->tok].mask, mdocs[n->tok].taboo); 1.50 kristaps 1655: break; 1656: default: 1.188 schwarze 1657: assert(n->type != ROFFT_ROOT); 1.50 kristaps 1658: continue; 1659: } 1660: if (NULL != n->child) 1.172 schwarze 1661: parse_mdoc(mpage, meta, n); 1.1 kristaps 1662: } 1663: } 1664: 1.25 schwarze 1665: static int 1.190 schwarze 1666: parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1667: const struct roff_node *n) 1.1 kristaps 1668: { 1.176 schwarze 1669: char *start, *end; 1.1 kristaps 1670: size_t sz; 1.25 schwarze 1671: 1.50 kristaps 1672: if (SEC_SYNOPSIS != n->sec || 1.141 schwarze 1673: NULL == (n = n->child) || 1.188 schwarze 1674: n->type != ROFFT_TEXT) 1.197 schwarze 1675: return 0; 1.1 kristaps 1676: 1677: /* 1678: * Only consider those `Fd' macro fields that begin with an 1679: * "inclusion" token (versus, e.g., #define). 1680: */ 1.50 kristaps 1681: 1.1 kristaps 1682: if (strcmp("#include", n->string)) 1.197 schwarze 1683: return 0; 1.1 kristaps 1684: 1.188 schwarze 1685: if ((n = n->next) == NULL || n->type != ROFFT_TEXT) 1.197 schwarze 1686: return 0; 1.1 kristaps 1687: 1688: /* 1689: * Strip away the enclosing angle brackets and make sure we're 1690: * not zero-length. 1691: */ 1692: 1693: start = n->string; 1694: if ('<' == *start || '"' == *start) 1695: start++; 1696: 1697: if (0 == (sz = strlen(start))) 1.197 schwarze 1698: return 0; 1.1 kristaps 1699: 1700: end = &start[(int)sz - 1]; 1701: if ('>' == *end || '"' == *end) 1702: end--; 1703: 1.50 kristaps 1704: if (end > start) 1.78 schwarze 1705: putkeys(mpage, start, end - start + 1, TYPE_In); 1.197 schwarze 1706: return 0; 1.1 kristaps 1707: } 1708: 1.178 schwarze 1709: static void 1.189 schwarze 1710: parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) 1.1 kristaps 1711: { 1.112 schwarze 1712: char *cp; 1.178 schwarze 1713: size_t sz; 1.1 kristaps 1714: 1.188 schwarze 1715: if (n->type != ROFFT_TEXT) 1.178 schwarze 1716: return; 1.25 schwarze 1717: 1.178 schwarze 1718: /* Skip function pointer punctuation. */ 1.1 kristaps 1719: 1.178 schwarze 1720: cp = n->string; 1721: while (*cp == '(' || *cp == '*') 1.1 kristaps 1722: cp++; 1.178 schwarze 1723: sz = strcspn(cp, "()"); 1.1 kristaps 1724: 1.178 schwarze 1725: putkeys(mpage, cp, sz, TYPE_Fn); 1.173 schwarze 1726: if (n->sec == SEC_SYNOPSIS) 1.178 schwarze 1727: putkeys(mpage, cp, sz, NAME_SYN); 1728: } 1.25 schwarze 1729: 1.178 schwarze 1730: static int 1.190 schwarze 1731: parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1732: const struct roff_node *n) 1.178 schwarze 1733: { 1734: 1735: if (n->child == NULL) 1.197 schwarze 1736: return 0; 1.178 schwarze 1737: 1738: parse_mdoc_fname(mpage, n->child); 1.25 schwarze 1739: 1.178 schwarze 1740: for (n = n->child->next; n != NULL; n = n->next) 1.188 schwarze 1741: if (n->type == ROFFT_TEXT) 1.78 schwarze 1742: putkey(mpage, n->string, TYPE_Fa); 1.25 schwarze 1743: 1.197 schwarze 1744: return 0; 1.173 schwarze 1745: } 1746: 1747: static int 1.190 schwarze 1748: parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1749: const struct roff_node *n) 1.173 schwarze 1750: { 1.177 schwarze 1751: 1.188 schwarze 1752: if (n->type != ROFFT_HEAD) 1.197 schwarze 1753: return 1; 1.173 schwarze 1754: 1.178 schwarze 1755: if (n->child != NULL) 1756: parse_mdoc_fname(mpage, n->child); 1757: 1.197 schwarze 1758: return 0; 1.1 kristaps 1759: } 1760: 1.25 schwarze 1761: static int 1.211 schwarze 1762: parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta, 1763: const struct roff_node *n) 1764: { 1765: char *cp; 1766: 1767: if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY) 1768: return 0; 1769: 1.215 schwarze 1770: if (n->child != NULL && 1771: n->child->next == NULL && 1772: n->child->type == ROFFT_TEXT) 1.211 schwarze 1773: return 1; 1774: 1775: cp = NULL; 1776: deroff(&cp, n); 1777: if (cp != NULL) { 1778: putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va || 1779: n->type == ROFFT_BODY ? TYPE_Va : 0)); 1780: free(cp); 1781: } 1782: 1783: return 0; 1784: } 1785: 1786: static int 1.190 schwarze 1787: parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1788: const struct roff_node *n) 1.1 kristaps 1789: { 1.67 schwarze 1790: char *cp; 1.1 kristaps 1791: 1792: if (NULL == (n = n->child)) 1.197 schwarze 1793: return 0; 1.1 kristaps 1794: 1.67 schwarze 1795: if (NULL == n->next) { 1.78 schwarze 1796: putkey(mpage, n->string, TYPE_Xr); 1.197 schwarze 1797: return 0; 1.67 schwarze 1798: } 1799: 1.120 schwarze 1800: mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); 1.78 schwarze 1801: putkey(mpage, cp, TYPE_Xr); 1.67 schwarze 1802: free(cp); 1.197 schwarze 1803: return 0; 1.1 kristaps 1804: } 1805: 1.25 schwarze 1806: static int 1.190 schwarze 1807: parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1808: const struct roff_node *n) 1.1 kristaps 1809: { 1810: 1.188 schwarze 1811: if (n->type == ROFFT_BODY) 1.194 schwarze 1812: deroff(&mpage->desc, n); 1.197 schwarze 1813: return 0; 1.1 kristaps 1814: } 1815: 1.25 schwarze 1816: static int 1.190 schwarze 1817: parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1818: const struct roff_node *n) 1.1 kristaps 1819: { 1820: 1.129 schwarze 1821: if (SEC_NAME == n->sec) 1.220.2.5 schwarze 1822: putmdockey(mpage, n->child, NAME_TITLE, 0); 1.188 schwarze 1823: else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { 1.172 schwarze 1824: if (n->child == NULL) 1825: putkey(mpage, meta->name, NAME_SYN); 1826: else 1.220.2.5 schwarze 1827: putmdockey(mpage, n->child, NAME_SYN, 0); 1.174 schwarze 1828: } 1829: if ( ! (mpage->name_head_done || 1830: n->child == NULL || n->child->string == NULL || 1831: strcasecmp(n->child->string, meta->title))) { 1.220.2.1 schwarze 1832: putkey(mpage, n->child->string, NAME_HEAD); 1.174 schwarze 1833: mpage->name_head_done = 1; 1.172 schwarze 1834: } 1.197 schwarze 1835: return 0; 1.1 kristaps 1836: } 1837: 1.25 schwarze 1838: static int 1.190 schwarze 1839: parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1840: const struct roff_node *n) 1.1 kristaps 1841: { 1842: 1.197 schwarze 1843: return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; 1.1 kristaps 1844: } 1845: 1.50 kristaps 1846: static int 1.190 schwarze 1847: parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1848: const struct roff_node *n) 1.1 kristaps 1849: { 1850: 1.197 schwarze 1851: return n->type == ROFFT_HEAD; 1.1 kristaps 1852: } 1853: 1.50 kristaps 1854: /* 1.66 schwarze 1855: * Add a string to the hash table for the current manual. 1856: * Each string has a bitmask telling which macros it belongs to. 1857: * When we finish the manual, we'll dump the table. 1.50 kristaps 1858: */ 1859: static void 1.176 schwarze 1860: putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) 1.50 kristaps 1861: { 1.133 schwarze 1862: struct ohash *htab; 1.50 kristaps 1863: struct str *s; 1.111 schwarze 1864: const char *end; 1.65 schwarze 1865: unsigned int slot; 1.176 schwarze 1866: int i, mustfree; 1.25 schwarze 1867: 1.50 kristaps 1868: if (0 == sz) 1869: return; 1.111 schwarze 1870: 1.176 schwarze 1871: mustfree = render_string(&cp, &sz); 1872: 1.133 schwarze 1873: if (TYPE_Nm & v) { 1874: htab = &names; 1875: v &= name_mask; 1.169 schwarze 1876: if (v & NAME_FIRST) 1877: name_mask &= ~NAME_FIRST; 1.133 schwarze 1878: if (debug > 1) 1879: say(mpage->mlinks->file, 1.220.2.4 schwarze 1880: "Adding name %*s, bits=0x%llx", (int)sz, cp, 1881: (unsigned long long)v); 1.133 schwarze 1882: } else { 1883: htab = &strings; 1884: if (debug > 1) 1885: for (i = 0; i < mansearch_keymax; i++) 1.163 schwarze 1886: if ((uint64_t)1 << i & v) 1.133 schwarze 1887: say(mpage->mlinks->file, 1888: "Adding key %s=%*s", 1.220 schwarze 1889: mansearch_keynames[i], (int)sz, cp); 1.111 schwarze 1890: } 1.25 schwarze 1891: 1.65 schwarze 1892: end = cp + sz; 1.133 schwarze 1893: slot = ohash_qlookupi(htab, cp, &end); 1894: s = ohash_find(htab, slot); 1.25 schwarze 1895: 1.78 schwarze 1896: if (NULL != s && mpage == s->mpage) { 1.50 kristaps 1897: s->mask |= v; 1898: return; 1899: } else if (NULL == s) { 1.144 schwarze 1900: s = mandoc_calloc(1, sizeof(struct str) + sz + 1); 1.50 kristaps 1901: memcpy(s->key, cp, sz); 1.133 schwarze 1902: ohash_insert(htab, slot, s); 1.1 kristaps 1903: } 1.78 schwarze 1904: s->mpage = mpage; 1.50 kristaps 1905: s->mask = v; 1.176 schwarze 1906: 1907: if (mustfree) 1908: free(cp); 1.1 kristaps 1909: } 1910: 1.50 kristaps 1911: /* 1912: * Take a Unicode codepoint and produce its UTF-8 encoding. 1913: * This isn't the best way to do this, but it works. 1914: * The magic numbers are from the UTF-8 packaging. 1915: * They're not as scary as they seem: read the UTF-8 spec for details. 1916: */ 1917: static size_t 1918: utf8(unsigned int cp, char out[7]) 1.1 kristaps 1919: { 1.50 kristaps 1920: size_t rc; 1.1 kristaps 1921: 1.50 kristaps 1922: rc = 0; 1923: if (cp <= 0x0000007F) { 1924: rc = 1; 1925: out[0] = (char)cp; 1926: } else if (cp <= 0x000007FF) { 1927: rc = 2; 1928: out[0] = (cp >> 6 & 31) | 192; 1929: out[1] = (cp & 63) | 128; 1930: } else if (cp <= 0x0000FFFF) { 1931: rc = 3; 1932: out[0] = (cp >> 12 & 15) | 224; 1933: out[1] = (cp >> 6 & 63) | 128; 1934: out[2] = (cp & 63) | 128; 1935: } else if (cp <= 0x001FFFFF) { 1936: rc = 4; 1937: out[0] = (cp >> 18 & 7) | 240; 1938: out[1] = (cp >> 12 & 63) | 128; 1939: out[2] = (cp >> 6 & 63) | 128; 1940: out[3] = (cp & 63) | 128; 1941: } else if (cp <= 0x03FFFFFF) { 1942: rc = 5; 1943: out[0] = (cp >> 24 & 3) | 248; 1944: out[1] = (cp >> 18 & 63) | 128; 1945: out[2] = (cp >> 12 & 63) | 128; 1946: out[3] = (cp >> 6 & 63) | 128; 1947: out[4] = (cp & 63) | 128; 1948: } else if (cp <= 0x7FFFFFFF) { 1949: rc = 6; 1950: out[0] = (cp >> 30 & 1) | 252; 1951: out[1] = (cp >> 24 & 63) | 128; 1952: out[2] = (cp >> 18 & 63) | 128; 1953: out[3] = (cp >> 12 & 63) | 128; 1954: out[4] = (cp >> 6 & 63) | 128; 1955: out[5] = (cp & 63) | 128; 1956: } else 1.197 schwarze 1957: return 0; 1.1 kristaps 1958: 1.50 kristaps 1959: out[rc] = '\0'; 1.197 schwarze 1960: return rc; 1.50 kristaps 1961: } 1.1 kristaps 1962: 1.50 kristaps 1963: /* 1.176 schwarze 1964: * If the string contains escape sequences, 1965: * replace it with an allocated rendering and return 1, 1966: * such that the caller can free it after use. 1967: * Otherwise, do nothing and return 0. 1.50 kristaps 1968: */ 1.176 schwarze 1969: static int 1970: render_string(char **public, size_t *psz) 1.50 kristaps 1971: { 1.176 schwarze 1972: const char *src, *scp, *addcp, *seq; 1973: char *dst; 1974: size_t ssz, dsz, addsz; 1.114 schwarze 1975: char utfbuf[7], res[6]; 1.176 schwarze 1976: int seqlen, unicode; 1.50 kristaps 1977: 1978: res[0] = '\\'; 1979: res[1] = '\t'; 1980: res[2] = ASCII_NBRSP; 1981: res[3] = ASCII_HYPH; 1.114 schwarze 1982: res[4] = ASCII_BREAK; 1983: res[5] = '\0'; 1.1 kristaps 1984: 1.176 schwarze 1985: src = scp = *public; 1986: ssz = *psz; 1987: dst = NULL; 1988: dsz = 0; 1989: 1990: while (scp < src + *psz) { 1991: 1992: /* Leave normal characters unchanged. */ 1993: 1994: if (strchr(res, *scp) == NULL) { 1995: if (dst != NULL) 1996: dst[dsz++] = *scp; 1997: scp++; 1998: continue; 1999: } 1.46 kristaps 2000: 1.50 kristaps 2001: /* 1.176 schwarze 2002: * Found something that requires replacing, 2003: * make sure we have a destination buffer. 1.50 kristaps 2004: */ 1.176 schwarze 2005: 2006: if (dst == NULL) { 2007: dst = mandoc_malloc(ssz + 1); 2008: dsz = scp - src; 2009: memcpy(dst, src, dsz); 1.50 kristaps 2010: } 1.46 kristaps 2011: 1.176 schwarze 2012: /* Handle single-char special characters. */ 2013: 2014: switch (*scp) { 2015: case '\\': 2016: break; 1.141 schwarze 2017: case '\t': 2018: case ASCII_NBRSP: 1.176 schwarze 2019: dst[dsz++] = ' '; 2020: scp++; 2021: continue; 2022: case ASCII_HYPH: 2023: dst[dsz++] = '-'; 1.114 schwarze 2024: /* FALLTHROUGH */ 1.141 schwarze 2025: case ASCII_BREAK: 1.176 schwarze 2026: scp++; 1.50 kristaps 2027: continue; 1.114 schwarze 2028: default: 1.176 schwarze 2029: abort(); 1.114 schwarze 2030: } 1.46 kristaps 2031: 1.50 kristaps 2032: /* 1.176 schwarze 2033: * Found an escape sequence. 2034: * Read past the slash, then parse it. 2035: * Ignore everything except characters. 1.50 kristaps 2036: */ 1.95 schwarze 2037: 1.176 schwarze 2038: scp++; 2039: if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL) 1.50 kristaps 2040: continue; 1.1 kristaps 2041: 1.50 kristaps 2042: /* 1.95 schwarze 2043: * Render the special character 2044: * as either UTF-8 or ASCII. 1.50 kristaps 2045: */ 1.95 schwarze 2046: 2047: if (write_utf8) { 1.203 schwarze 2048: unicode = mchars_spec2cp(seq, seqlen); 1.176 schwarze 2049: if (unicode <= 0) 1.95 schwarze 2050: continue; 1.176 schwarze 2051: addsz = utf8(unicode, utfbuf); 2052: if (addsz == 0) 1.95 schwarze 2053: continue; 1.176 schwarze 2054: addcp = utfbuf; 1.95 schwarze 2055: } else { 1.203 schwarze 2056: addcp = mchars_spec2str(seq, seqlen, &addsz); 1.176 schwarze 2057: if (addcp == NULL) 1.95 schwarze 2058: continue; 1.176 schwarze 2059: if (*addcp == ASCII_NBRSP) { 2060: addcp = " "; 2061: addsz = 1; 1.95 schwarze 2062: } 2063: } 1.1 kristaps 2064: 1.50 kristaps 2065: /* Copy the rendered glyph into the stream. */ 1.1 kristaps 2066: 1.176 schwarze 2067: ssz += addsz; 2068: dst = mandoc_realloc(dst, ssz + 1); 2069: memcpy(dst + dsz, addcp, addsz); 2070: dsz += addsz; 2071: } 2072: if (dst != NULL) { 2073: *public = dst; 2074: *psz = dsz; 2075: } 2076: 2077: /* Trim trailing whitespace and NUL-terminate. */ 2078: 2079: while (*psz > 0 && (*public)[*psz - 1] == ' ') 2080: --*psz; 2081: if (dst != NULL) { 2082: (*public)[*psz] = '\0'; 1.197 schwarze 2083: return 1; 1.176 schwarze 2084: } else 1.197 schwarze 2085: return 0; 1.1 kristaps 2086: } 2087: 1.118 schwarze 2088: static void 2089: dbadd_mlink(const struct mlink *mlink) 2090: { 2091: size_t i; 2092: 2093: i = 1; 2094: SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec); 2095: SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch); 2096: SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name); 1.137 schwarze 2097: SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid); 1.118 schwarze 2098: SQL_STEP(stmts[STMT_INSERT_LINK]); 2099: sqlite3_reset(stmts[STMT_INSERT_LINK]); 1.169 schwarze 2100: } 2101: 2102: static void 2103: dbadd_mlink_name(const struct mlink *mlink) 2104: { 1.175 schwarze 2105: uint64_t bits; 1.169 schwarze 2106: size_t i; 2107: 2108: dbadd_mlink(mlink); 1.160 schwarze 2109: 2110: i = 1; 1.175 schwarze 2111: SQL_BIND_INT64(stmts[STMT_SELECT_NAME], i, mlink->mpage->pageid); 2112: bits = NAME_FILE & NAME_MASK; 2113: if (sqlite3_step(stmts[STMT_SELECT_NAME]) == SQLITE_ROW) { 2114: bits |= sqlite3_column_int64(stmts[STMT_SELECT_NAME], 0); 2115: sqlite3_reset(stmts[STMT_SELECT_NAME]); 2116: } 2117: 2118: i = 1; 2119: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, bits); 1.160 schwarze 2120: SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, mlink->name); 2121: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mlink->mpage->pageid); 2122: SQL_STEP(stmts[STMT_INSERT_NAME]); 2123: sqlite3_reset(stmts[STMT_INSERT_NAME]); 1.118 schwarze 2124: } 2125: 1.14 schwarze 2126: /* 1.50 kristaps 2127: * Flush the current page's terms (and their bits) into the database. 2128: * Wrap the entire set of additions in a transaction to make sqlite be a 2129: * little faster. 1.96 schwarze 2130: * Also, handle escape sequences at the last possible moment. 1.14 schwarze 2131: */ 2132: static void 1.176 schwarze 2133: dbadd(struct mpage *mpage) 1.14 schwarze 2134: { 1.87 schwarze 2135: struct mlink *mlink; 1.50 kristaps 2136: struct str *key; 1.176 schwarze 2137: char *cp; 1.52 kristaps 2138: size_t i; 1.66 schwarze 2139: unsigned int slot; 1.176 schwarze 2140: int mustfree; 1.14 schwarze 2141: 1.128 schwarze 2142: mlink = mpage->mlinks; 1.43 kristaps 2143: 1.128 schwarze 2144: if (nodb) { 1.147 schwarze 2145: for (key = ohash_first(&names, &slot); NULL != key; 1.176 schwarze 2146: key = ohash_next(&names, &slot)) 1.147 schwarze 2147: free(key); 2148: for (key = ohash_first(&strings, &slot); NULL != key; 1.176 schwarze 2149: key = ohash_next(&strings, &slot)) 1.147 schwarze 2150: free(key); 1.145 schwarze 2151: if (0 == debug) 2152: return; 1.128 schwarze 2153: while (NULL != mlink) { 2154: fputs(mlink->name, stdout); 2155: if (NULL == mlink->next || 2156: strcmp(mlink->dsec, mlink->next->dsec) || 2157: strcmp(mlink->fsec, mlink->next->fsec) || 2158: strcmp(mlink->arch, mlink->next->arch)) { 2159: putchar('('); 2160: if ('\0' == *mlink->dsec) 2161: fputs(mlink->fsec, stdout); 2162: else 2163: fputs(mlink->dsec, stdout); 2164: if ('\0' != *mlink->arch) 2165: printf("/%s", mlink->arch); 2166: putchar(')'); 2167: } 2168: mlink = mlink->next; 2169: if (NULL != mlink) 2170: fputs(", ", stdout); 2171: } 1.132 schwarze 2172: printf(" - %s\n", mpage->desc); 1.14 schwarze 2173: return; 1.128 schwarze 2174: } 2175: 2176: if (debug) 2177: say(mlink->file, "Adding to database"); 1.28 kristaps 2178: 1.176 schwarze 2179: cp = mpage->desc; 2180: i = strlen(cp); 2181: mustfree = render_string(&cp, &i); 1.52 kristaps 2182: i = 1; 1.176 schwarze 2183: SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp); 1.161 schwarze 2184: SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form); 1.81 schwarze 2185: SQL_STEP(stmts[STMT_INSERT_PAGE]); 1.137 schwarze 2186: mpage->pageid = sqlite3_last_insert_rowid(db); 1.81 schwarze 2187: sqlite3_reset(stmts[STMT_INSERT_PAGE]); 1.176 schwarze 2188: if (mustfree) 2189: free(cp); 1.81 schwarze 2190: 1.128 schwarze 2191: while (NULL != mlink) { 1.118 schwarze 2192: dbadd_mlink(mlink); 1.128 schwarze 2193: mlink = mlink->next; 2194: } 1.134 schwarze 2195: mlink = mpage->mlinks; 1.50 kristaps 2196: 1.133 schwarze 2197: for (key = ohash_first(&names, &slot); NULL != key; 2198: key = ohash_next(&names, &slot)) { 2199: assert(key->mpage == mpage); 2200: i = 1; 2201: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask); 1.176 schwarze 2202: SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key); 1.137 schwarze 2203: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid); 1.133 schwarze 2204: SQL_STEP(stmts[STMT_INSERT_NAME]); 2205: sqlite3_reset(stmts[STMT_INSERT_NAME]); 2206: free(key); 2207: } 1.66 schwarze 2208: for (key = ohash_first(&strings, &slot); NULL != key; 2209: key = ohash_next(&strings, &slot)) { 1.78 schwarze 2210: assert(key->mpage == mpage); 1.52 kristaps 2211: i = 1; 2212: SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask); 1.176 schwarze 2213: SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key); 1.137 schwarze 2214: SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid); 1.52 kristaps 2215: SQL_STEP(stmts[STMT_INSERT_KEY]); 1.50 kristaps 2216: sqlite3_reset(stmts[STMT_INSERT_KEY]); 1.66 schwarze 2217: free(key); 1.38 schwarze 2218: } 1.14 schwarze 2219: } 2220: 1.5 kristaps 2221: static void 1.59 schwarze 2222: dbprune(void) 1.5 kristaps 2223: { 1.78 schwarze 2224: struct mpage *mpage; 1.82 schwarze 2225: struct mlink *mlink; 1.52 kristaps 2226: size_t i; 1.80 schwarze 2227: unsigned int slot; 1.5 kristaps 2228: 1.106 schwarze 2229: if (0 == nodb) 2230: SQL_EXEC("BEGIN TRANSACTION"); 1.12 schwarze 2231: 1.106 schwarze 2232: for (mpage = ohash_first(&mpages, &slot); NULL != mpage; 2233: mpage = ohash_next(&mpages, &slot)) { 1.82 schwarze 2234: mlink = mpage->mlinks; 1.125 schwarze 2235: if (debug) 1.106 schwarze 2236: say(mlink->file, "Deleting from database"); 2237: if (nodb) 2238: continue; 2239: for ( ; NULL != mlink; mlink = mlink->next) { 2240: i = 1; 2241: SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], 2242: i, mlink->dsec); 2243: SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], 2244: i, mlink->arch); 2245: SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], 2246: i, mlink->name); 2247: SQL_STEP(stmts[STMT_DELETE_PAGE]); 2248: sqlite3_reset(stmts[STMT_DELETE_PAGE]); 2249: } 1.5 kristaps 2250: } 1.106 schwarze 2251: 2252: if (0 == nodb) 2253: SQL_EXEC("END TRANSACTION"); 1.5 kristaps 2254: } 2255: 1.4 kristaps 2256: /* 1.50 kristaps 2257: * Close an existing database and its prepared statements. 2258: * If "real" is not set, rename the temporary file into the real one. 1.4 kristaps 2259: */ 1.35 kristaps 2260: static void 1.59 schwarze 2261: dbclose(int real) 1.4 kristaps 2262: { 1.50 kristaps 2263: size_t i; 1.115 schwarze 2264: int status; 2265: pid_t child; 1.4 kristaps 2266: 1.50 kristaps 2267: if (nodb) 1.38 schwarze 2268: return; 1.50 kristaps 2269: 2270: for (i = 0; i < STMT__MAX; i++) { 2271: sqlite3_finalize(stmts[i]); 2272: stmts[i] = NULL; 1.4 kristaps 2273: } 2274: 1.50 kristaps 2275: sqlite3_close(db); 2276: db = NULL; 1.12 schwarze 2277: 1.50 kristaps 2278: if (real) 2279: return; 1.12 schwarze 2280: 1.115 schwarze 2281: if ('\0' == *tempfilename) { 2282: if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) { 2283: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2284: say(MANDOC_DB, "&rename"); 1.115 schwarze 2285: } 2286: return; 2287: } 2288: 2289: switch (child = fork()) { 1.141 schwarze 2290: case -1: 1.115 schwarze 2291: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2292: say("", "&fork cmp"); 1.115 schwarze 2293: return; 1.141 schwarze 2294: case 0: 1.115 schwarze 2295: execlp("cmp", "cmp", "-s", 1.196 schwarze 2296: tempfilename, MANDOC_DB, (char *)NULL); 1.123 schwarze 2297: say("", "&exec cmp"); 1.115 schwarze 2298: exit(0); 2299: default: 2300: break; 2301: } 2302: if (-1 == waitpid(child, &status, 0)) { 2303: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2304: say("", "&wait cmp"); 1.115 schwarze 2305: } else if (WIFSIGNALED(status)) { 2306: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2307: say("", "cmp died from signal %d", WTERMSIG(status)); 1.115 schwarze 2308: } else if (WEXITSTATUS(status)) { 1.59 schwarze 2309: exitcode = (int)MANDOCLEVEL_SYSERR; 1.115 schwarze 2310: say(MANDOC_DB, 2311: "Data changed, but cannot replace database"); 2312: } 2313: 2314: *strrchr(tempfilename, '/') = '\0'; 2315: switch (child = fork()) { 1.141 schwarze 2316: case -1: 1.115 schwarze 2317: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2318: say("", "&fork rm"); 1.115 schwarze 2319: return; 1.141 schwarze 2320: case 0: 1.196 schwarze 2321: execlp("rm", "rm", "-rf", tempfilename, (char *)NULL); 1.123 schwarze 2322: say("", "&exec rm"); 1.115 schwarze 2323: exit((int)MANDOCLEVEL_SYSERR); 2324: default: 2325: break; 2326: } 2327: if (-1 == waitpid(child, &status, 0)) { 2328: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2329: say("", "&wait rm"); 1.115 schwarze 2330: } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) { 2331: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2332: say("", "%s: Cannot remove temporary directory", 2333: tempfilename); 1.59 schwarze 2334: } 1.50 kristaps 2335: } 1.14 schwarze 2336: 1.50 kristaps 2337: /* 2338: * This is straightforward stuff. 2339: * Open a database connection to a "temporary" database, then open a set 2340: * of prepared statements we'll use over and over again. 2341: * If "real" is set, we use the existing database; if not, we truncate a 2342: * temporary one. 2343: * Must be matched by dbclose(). 2344: */ 2345: static int 1.59 schwarze 2346: dbopen(int real) 1.50 kristaps 2347: { 1.115 schwarze 2348: const char *sql; 1.50 kristaps 2349: int rc, ofl; 1.12 schwarze 2350: 1.141 schwarze 2351: if (nodb) 1.197 schwarze 2352: return 1; 1.12 schwarze 2353: 1.115 schwarze 2354: *tempfilename = '\0'; 1.63 schwarze 2355: ofl = SQLITE_OPEN_READWRITE; 1.115 schwarze 2356: 2357: if (real) { 2358: rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL); 2359: if (SQLITE_OK != rc) { 1.63 schwarze 2360: exitcode = (int)MANDOCLEVEL_SYSERR; 1.149 schwarze 2361: if (SQLITE_CANTOPEN != rc) 2362: say(MANDOC_DB, "%s", sqlite3_errstr(rc)); 1.197 schwarze 2363: return 0; 1.63 schwarze 2364: } 1.115 schwarze 2365: goto prepare_statements; 2366: } 2367: 2368: ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE; 1.45 kristaps 2369: 1.115 schwarze 2370: remove(MANDOC_DB "~"); 2371: rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL); 1.141 schwarze 2372: if (SQLITE_OK == rc) 1.115 schwarze 2373: goto create_tables; 1.116 schwarze 2374: if (MPARSE_QUICK & mparse_options) { 1.59 schwarze 2375: exitcode = (int)MANDOCLEVEL_SYSERR; 1.146 schwarze 2376: say(MANDOC_DB "~", "%s", sqlite3_errstr(rc)); 1.197 schwarze 2377: return 0; 1.50 kristaps 2378: } 1.12 schwarze 2379: 1.143 schwarze 2380: (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX", 2381: sizeof(tempfilename)); 1.115 schwarze 2382: if (NULL == mkdtemp(tempfilename)) { 2383: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2384: say("", "&%s", tempfilename); 1.197 schwarze 2385: return 0; 1.115 schwarze 2386: } 1.143 schwarze 2387: (void)strlcat(tempfilename, "/" MANDOC_DB, 2388: sizeof(tempfilename)); 1.115 schwarze 2389: rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL); 2390: if (SQLITE_OK != rc) { 1.59 schwarze 2391: exitcode = (int)MANDOCLEVEL_SYSERR; 1.146 schwarze 2392: say("", "%s: %s", tempfilename, sqlite3_errstr(rc)); 1.197 schwarze 2393: return 0; 1.50 kristaps 2394: } 1.12 schwarze 2395: 1.115 schwarze 2396: create_tables: 1.81 schwarze 2397: sql = "CREATE TABLE \"mpages\" (\n" 1.132 schwarze 2398: " \"desc\" TEXT NOT NULL,\n" 1.50 kristaps 2399: " \"form\" INTEGER NOT NULL,\n" 1.137 schwarze 2400: " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" 1.50 kristaps 2401: ");\n" 2402: "\n" 1.81 schwarze 2403: "CREATE TABLE \"mlinks\" (\n" 2404: " \"sec\" TEXT NOT NULL,\n" 2405: " \"arch\" TEXT NOT NULL,\n" 2406: " \"name\" TEXT NOT NULL,\n" 1.137 schwarze 2407: " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " 1.109 schwarze 2408: "ON DELETE CASCADE\n" 1.81 schwarze 2409: ");\n" 1.136 schwarze 2410: "CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n" 1.81 schwarze 2411: "\n" 1.133 schwarze 2412: "CREATE TABLE \"names\" (\n" 2413: " \"bits\" INTEGER NOT NULL,\n" 2414: " \"name\" TEXT NOT NULL,\n" 1.137 schwarze 2415: " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " 1.175 schwarze 2416: "ON DELETE CASCADE,\n" 2417: " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\n" 1.133 schwarze 2418: ");\n" 2419: "\n" 1.50 kristaps 2420: "CREATE TABLE \"keys\" (\n" 2421: " \"bits\" INTEGER NOT NULL,\n" 2422: " \"key\" TEXT NOT NULL,\n" 1.137 schwarze 2423: " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " 1.109 schwarze 2424: "ON DELETE CASCADE\n" 1.136 schwarze 2425: ");\n" 2426: "CREATE INDEX keys_pageid_idx ON keys (pageid);\n"; 1.14 schwarze 2427: 1.50 kristaps 2428: if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) { 1.59 schwarze 2429: exitcode = (int)MANDOCLEVEL_SYSERR; 1.115 schwarze 2430: say(MANDOC_DB, "%s", sqlite3_errmsg(db)); 1.146 schwarze 2431: sqlite3_close(db); 1.197 schwarze 2432: return 0; 1.50 kristaps 2433: } 1.4 kristaps 2434: 1.57 schwarze 2435: prepare_statements: 1.146 schwarze 2436: if (SQLITE_OK != sqlite3_exec(db, 2437: "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) { 2438: exitcode = (int)MANDOCLEVEL_SYSERR; 2439: say(MANDOC_DB, "PRAGMA foreign_keys: %s", 2440: sqlite3_errmsg(db)); 2441: sqlite3_close(db); 1.197 schwarze 2442: return 0; 1.146 schwarze 2443: } 2444: 1.137 schwarze 2445: sql = "DELETE FROM mpages WHERE pageid IN " 1.106 schwarze 2446: "(SELECT pageid FROM mlinks WHERE " 2447: "sec=? AND arch=? AND name=?)"; 1.81 schwarze 2448: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL); 2449: sql = "INSERT INTO mpages " 1.132 schwarze 2450: "(desc,form) VALUES (?,?)"; 1.81 schwarze 2451: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL); 2452: sql = "INSERT INTO mlinks " 1.104 schwarze 2453: "(sec,arch,name,pageid) VALUES (?,?,?,?)"; 1.81 schwarze 2454: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL); 1.175 schwarze 2455: sql = "SELECT bits FROM names where pageid = ?"; 2456: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_SELECT_NAME], NULL); 1.133 schwarze 2457: sql = "INSERT INTO names " 2458: "(bits,name,pageid) VALUES (?,?,?)"; 2459: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL); 1.50 kristaps 2460: sql = "INSERT INTO keys " 1.81 schwarze 2461: "(bits,key,pageid) VALUES (?,?,?)"; 1.50 kristaps 2462: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL); 1.70 schwarze 2463: 2464: #ifndef __APPLE__ 2465: /* 2466: * When opening a new database, we can turn off 2467: * synchronous mode for much better performance. 2468: */ 2469: 1.146 schwarze 2470: if (real && SQLITE_OK != sqlite3_exec(db, 2471: "PRAGMA synchronous = OFF", NULL, NULL, NULL)) { 2472: exitcode = (int)MANDOCLEVEL_SYSERR; 2473: say(MANDOC_DB, "PRAGMA synchronous: %s", 1.162 schwarze 2474: sqlite3_errmsg(db)); 1.146 schwarze 2475: sqlite3_close(db); 1.197 schwarze 2476: return 0; 1.146 schwarze 2477: } 1.70 schwarze 2478: #endif 2479: 1.197 schwarze 2480: return 1; 1.4 kristaps 2481: } 2482: 1.50 kristaps 2483: static int 1.165 schwarze 2484: set_basedir(const char *targetdir, int report_baddir) 1.4 kristaps 2485: { 1.59 schwarze 2486: static char startdir[PATH_MAX]; 1.151 schwarze 2487: static int getcwd_status; /* 1 = ok, 2 = failure */ 2488: static int chdir_status; /* 1 = changed directory */ 1.150 schwarze 2489: char *cp; 1.4 kristaps 2490: 1.59 schwarze 2491: /* 1.151 schwarze 2492: * Remember the original working directory, if possible. 2493: * This will be needed if the second or a later directory 2494: * on the command line is given as a relative path. 2495: * Do not error out if the current directory is not 2496: * searchable: Maybe it won't be needed after all. 2497: */ 2498: if (0 == getcwd_status) { 2499: if (NULL == getcwd(startdir, sizeof(startdir))) { 2500: getcwd_status = 2; 2501: (void)strlcpy(startdir, strerror(errno), 2502: sizeof(startdir)); 2503: } else 2504: getcwd_status = 1; 2505: } 2506: 2507: /* 2508: * We are leaving the old base directory. 2509: * Do not use it any longer, not even for messages. 2510: */ 2511: *basedir = '\0'; 2512: 2513: /* 2514: * If and only if the directory was changed earlier and 2515: * the next directory to process is given as a relative path, 2516: * first go back, or bail out if that is impossible. 1.59 schwarze 2517: */ 1.151 schwarze 2518: if (chdir_status && '/' != *targetdir) { 2519: if (2 == getcwd_status) { 1.59 schwarze 2520: exitcode = (int)MANDOCLEVEL_SYSERR; 1.151 schwarze 2521: say("", "getcwd: %s", startdir); 1.197 schwarze 2522: return 0; 1.59 schwarze 2523: } 1.151 schwarze 2524: if (-1 == chdir(startdir)) { 1.59 schwarze 2525: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2526: say("", "&chdir %s", startdir); 1.197 schwarze 2527: return 0; 1.59 schwarze 2528: } 2529: } 1.151 schwarze 2530: 2531: /* 2532: * Always resolve basedir to the canonicalized absolute 2533: * pathname and append a trailing slash, such that 2534: * we can reliably check whether files are inside. 2535: */ 1.59 schwarze 2536: if (NULL == realpath(targetdir, basedir)) { 1.165 schwarze 2537: if (report_baddir || errno != ENOENT) { 2538: exitcode = (int)MANDOCLEVEL_BADARG; 2539: say("", "&%s: realpath", targetdir); 2540: } 1.197 schwarze 2541: return 0; 1.59 schwarze 2542: } else if (-1 == chdir(basedir)) { 1.165 schwarze 2543: if (report_baddir || errno != ENOENT) { 2544: exitcode = (int)MANDOCLEVEL_BADARG; 2545: say("", "&chdir"); 2546: } 1.197 schwarze 2547: return 0; 1.150 schwarze 2548: } 1.151 schwarze 2549: chdir_status = 1; 1.150 schwarze 2550: cp = strchr(basedir, '\0'); 2551: if ('/' != cp[-1]) { 2552: if (cp - basedir >= PATH_MAX - 1) { 2553: exitcode = (int)MANDOCLEVEL_SYSERR; 2554: say("", "Filename too long"); 1.197 schwarze 2555: return 0; 1.150 schwarze 2556: } 2557: *cp++ = '/'; 2558: *cp = '\0'; 1.4 kristaps 2559: } 1.197 schwarze 2560: return 1; 1.56 schwarze 2561: } 2562: 2563: static void 1.59 schwarze 2564: say(const char *file, const char *format, ...) 1.56 schwarze 2565: { 2566: va_list ap; 1.123 schwarze 2567: int use_errno; 1.56 schwarze 2568: 1.59 schwarze 2569: if ('\0' != *basedir) 2570: fprintf(stderr, "%s", basedir); 2571: if ('\0' != *basedir && '\0' != *file) 1.151 schwarze 2572: fputc('/', stderr); 1.56 schwarze 2573: if ('\0' != *file) 1.59 schwarze 2574: fprintf(stderr, "%s", file); 2575: 1.123 schwarze 2576: use_errno = 1; 2577: if (NULL != format) { 2578: switch (*format) { 1.141 schwarze 2579: case '&': 1.123 schwarze 2580: format++; 2581: break; 1.141 schwarze 2582: case '\0': 1.123 schwarze 2583: format = NULL; 2584: break; 2585: default: 2586: use_errno = 0; 2587: break; 2588: } 2589: } 2590: if (NULL != format) { 2591: if ('\0' != *basedir || '\0' != *file) 2592: fputs(": ", stderr); 2593: va_start(ap, format); 2594: vfprintf(stderr, format, ap); 2595: va_end(ap); 2596: } 2597: if (use_errno) { 2598: if ('\0' != *basedir || '\0' != *file || NULL != format) 2599: fputs(": ", stderr); 1.59 schwarze 2600: perror(NULL); 1.123 schwarze 2601: } else 2602: fputc('\n', stderr); 1.1 kristaps 2603: }