Return to mandocdb.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
1.220.2.12! schwarze 1: /* $Id: mandocdb.c,v 1.220.2.11 2017/01/27 14:32:54 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 *, ...) 1.220.2.12! schwarze 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] = "."; 1.220.2.12! schwarze 628: argv[1] = NULL; 1.50 kristaps 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); 1.220.2.11 schwarze 1273: if (mpage->desc == NULL) { 1274: mpage->desc = mandoc_strdup(mlink->name); 1275: if (warnings) 1276: say(mlink->file, "No one-line description, " 1277: "using filename \"%s\"", mlink->name); 1278: } 1.220.2.9 schwarze 1279: 1.97 schwarze 1280: putkey(mpage, mpage->sec, TYPE_sec); 1.164 schwarze 1281: if (*mpage->arch != '\0') 1282: putkey(mpage, mpage->arch, TYPE_arch); 1.50 kristaps 1283: 1.182 schwarze 1284: for ( ; mlink != NULL; mlink = mlink->next) { 1.97 schwarze 1285: if ('\0' != *mlink->dsec) 1286: putkey(mpage, mlink->dsec, TYPE_sec); 1287: if ('\0' != *mlink->fsec) 1288: putkey(mpage, mlink->fsec, TYPE_sec); 1.98 schwarze 1289: putkey(mpage, '\0' == *mlink->arch ? 1.113 schwarze 1290: any : mlink->arch, TYPE_arch); 1.133 schwarze 1291: putkey(mpage, mlink->name, NAME_FILE); 1.97 schwarze 1292: } 1.12 schwarze 1293: 1.131 schwarze 1294: if (warnings && !use_all) 1295: for (mlink = mpage->mlinks; mlink; 1296: mlink = mlink->next) 1297: mlink_check(mpage, mlink); 1.44 kristaps 1298: 1.176 schwarze 1299: dbadd(mpage); 1.124 schwarze 1300: 1301: nextpage: 1.66 schwarze 1302: ohash_delete(&strings); 1.133 schwarze 1303: ohash_delete(&names); 1.50 kristaps 1304: } 1.107 schwarze 1305: 1306: if (0 == nodb) 1307: SQL_EXEC("END TRANSACTION"); 1.130 schwarze 1308: } 1309: 1310: static void 1311: names_check(void) 1312: { 1313: sqlite3_stmt *stmt; 1314: const char *name, *sec, *arch, *key; 1315: 1316: sqlite3_prepare_v2(db, 1317: "SELECT name, sec, arch, key FROM (" 1.133 schwarze 1318: "SELECT name AS key, pageid FROM names " 1.130 schwarze 1319: "WHERE bits & ? AND NOT EXISTS (" 1320: "SELECT pageid FROM mlinks " 1.133 schwarze 1321: "WHERE mlinks.pageid == names.pageid " 1322: "AND mlinks.name == names.name" 1.130 schwarze 1323: ")" 1324: ") JOIN (" 1.142 schwarze 1325: "SELECT sec, arch, name, pageid FROM mlinks " 1326: "GROUP BY pageid" 1.130 schwarze 1327: ") USING (pageid);", 1328: -1, &stmt, NULL); 1329: 1.201 schwarze 1330: if (sqlite3_bind_int64(stmt, 1, NAME_TITLE) != SQLITE_OK) 1.134 schwarze 1331: say("", "%s", sqlite3_errmsg(db)); 1.130 schwarze 1332: 1.201 schwarze 1333: while (sqlite3_step(stmt) == SQLITE_ROW) { 1.154 schwarze 1334: name = (const char *)sqlite3_column_text(stmt, 0); 1335: sec = (const char *)sqlite3_column_text(stmt, 1); 1336: arch = (const char *)sqlite3_column_text(stmt, 2); 1337: key = (const char *)sqlite3_column_text(stmt, 3); 1.130 schwarze 1338: say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec, 1339: '\0' == *arch ? "" : "/", 1340: '\0' == *arch ? "" : arch, key); 1341: } 1342: sqlite3_finalize(stmt); 1.50 kristaps 1343: } 1.12 schwarze 1344: 1.50 kristaps 1345: static void 1.124 schwarze 1346: parse_cat(struct mpage *mpage, int fd) 1.50 kristaps 1347: { 1348: FILE *stream; 1.220.2.10 schwarze 1349: struct mlink *mlink; 1350: char *line, *p, *title, *sec; 1.209 schwarze 1351: size_t linesz, plen, titlesz; 1352: ssize_t len; 1353: int offs; 1.12 schwarze 1354: 1.220.2.10 schwarze 1355: mlink = mpage->mlinks; 1356: stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r"); 1357: if (stream == NULL) { 1358: if (fd != -1) 1.159 schwarze 1359: close(fd); 1.56 schwarze 1360: if (warnings) 1.220.2.10 schwarze 1361: say(mlink->file, "&fopen"); 1.50 kristaps 1362: return; 1363: } 1.1 kristaps 1364: 1.209 schwarze 1365: line = NULL; 1366: linesz = 0; 1367: 1.220.2.10 schwarze 1368: /* Parse the section number from the header line. */ 1.1 kristaps 1369: 1.220.2.10 schwarze 1370: while (getline(&line, &linesz, stream) != -1) { 1.209 schwarze 1371: if (*line == '\n') 1.220.2.10 schwarze 1372: continue; 1373: if ((sec = strchr(line, '(')) == NULL) 1374: break; 1375: if ((p = strchr(++sec, ')')) == NULL) 1376: break; 1377: free(mpage->sec); 1378: mpage->sec = mandoc_strndup(sec, p - sec); 1379: if (warnings && *mlink->dsec != '\0' && 1380: strcasecmp(mpage->sec, mlink->dsec)) 1381: say(mlink->file, 1382: "Section \"%s\" manual in %s directory", 1383: mpage->sec, mlink->dsec); 1384: break; 1385: } 1386: 1387: /* Skip to first blank line. */ 1388: 1389: while (line == NULL || *line != '\n') 1390: if (getline(&line, &linesz, stream) == -1) 1.50 kristaps 1391: break; 1.1 kristaps 1392: 1.50 kristaps 1393: /* 1394: * Assume the first line that is not indented 1395: * is the first section header. Skip to it. 1396: */ 1.1 kristaps 1397: 1.209 schwarze 1398: while (getline(&line, &linesz, stream) != -1) 1399: if (*line != '\n' && *line != ' ') 1.50 kristaps 1400: break; 1.141 schwarze 1401: 1.50 kristaps 1402: /* 1403: * Read up until the next section into a buffer. 1404: * Strip the leading and trailing newline from each read line, 1405: * appending a trailing space. 1406: * Ignore empty (whitespace-only) lines. 1407: */ 1.1 kristaps 1408: 1.50 kristaps 1409: titlesz = 0; 1410: title = NULL; 1.38 schwarze 1411: 1.209 schwarze 1412: while ((len = getline(&line, &linesz, stream)) != -1) { 1413: if (*line != ' ') 1.50 kristaps 1414: break; 1.209 schwarze 1415: offs = 0; 1416: while (isspace((unsigned char)line[offs])) 1417: offs++; 1418: if (line[offs] == '\0') 1.38 schwarze 1419: continue; 1.209 schwarze 1420: title = mandoc_realloc(title, titlesz + len - offs); 1421: memcpy(title + titlesz, line + offs, len - offs); 1422: titlesz += len - offs; 1.50 kristaps 1423: title[titlesz - 1] = ' '; 1424: } 1.209 schwarze 1425: free(line); 1.38 schwarze 1426: 1.50 kristaps 1427: /* 1428: * If no page content can be found, or the input line 1429: * is already the next section header, or there is no 1430: * trailing newline, reuse the page title as the page 1431: * description. 1432: */ 1.44 kristaps 1433: 1.50 kristaps 1434: if (NULL == title || '\0' == *title) { 1.56 schwarze 1435: if (warnings) 1.220.2.10 schwarze 1436: say(mlink->file, "Cannot find NAME section"); 1.50 kristaps 1437: fclose(stream); 1438: free(title); 1439: return; 1440: } 1.1 kristaps 1441: 1.209 schwarze 1442: title[titlesz - 1] = '\0'; 1.33 schwarze 1443: 1.50 kristaps 1444: /* 1445: * Skip to the first dash. 1446: * Use the remaining line as the description (no more than 70 1447: * bytes). 1448: */ 1.33 schwarze 1449: 1.50 kristaps 1450: if (NULL != (p = strstr(title, "- "))) { 1451: for (p += 2; ' ' == *p || '\b' == *p; p++) 1452: /* Skip to next word. */ ; 1453: } else { 1.56 schwarze 1454: if (warnings) 1.220.2.11 schwarze 1455: say(mlink->file, "No dash in title line, " 1456: "reusing \"%s\" as one-line description", title); 1.50 kristaps 1457: p = title; 1458: } 1.38 schwarze 1459: 1.50 kristaps 1460: plen = strlen(p); 1.1 kristaps 1461: 1.50 kristaps 1462: /* Strip backspace-encoding from line. */ 1.1 kristaps 1463: 1.50 kristaps 1464: while (NULL != (line = memchr(p, '\b', plen))) { 1465: len = line - p; 1466: if (0 == len) { 1467: memmove(line, line + 1, plen--); 1468: continue; 1.141 schwarze 1469: } 1.50 kristaps 1470: memmove(line - 1, line + 1, plen - len); 1471: plen -= 2; 1472: } 1.1 kristaps 1473: 1.78 schwarze 1474: mpage->desc = mandoc_strdup(p); 1.50 kristaps 1475: fclose(stream); 1476: free(title); 1477: } 1.1 kristaps 1478: 1.50 kristaps 1479: /* 1480: * Put a type/word pair into the word database for this particular file. 1481: */ 1482: static void 1.112 schwarze 1483: putkey(const struct mpage *mpage, char *value, uint64_t type) 1.50 kristaps 1484: { 1.112 schwarze 1485: char *cp; 1.18 kristaps 1486: 1.50 kristaps 1487: assert(NULL != value); 1.112 schwarze 1488: if (TYPE_arch == type) 1489: for (cp = value; *cp; cp++) 1490: if (isupper((unsigned char)*cp)) 1491: *cp = _tolower((unsigned char)*cp); 1.78 schwarze 1492: putkeys(mpage, value, strlen(value), type); 1.3 kristaps 1493: } 1494: 1495: /* 1.50 kristaps 1496: * Grok all nodes at or below a certain mdoc node into putkey(). 1.3 kristaps 1497: */ 1498: static void 1.78 schwarze 1499: putmdockey(const struct mpage *mpage, 1.220.2.5 schwarze 1500: const struct roff_node *n, uint64_t m, int taboo) 1.3 kristaps 1501: { 1.18 kristaps 1502: 1.50 kristaps 1503: for ( ; NULL != n; n = n->next) { 1.220.2.5 schwarze 1504: if (n->flags & taboo) 1505: continue; 1.50 kristaps 1506: if (NULL != n->child) 1.220.2.5 schwarze 1507: putmdockey(mpage, n->child, m, taboo); 1.188 schwarze 1508: if (n->type == ROFFT_TEXT) 1.78 schwarze 1509: putkey(mpage, n->string, m); 1.50 kristaps 1510: } 1511: } 1.18 kristaps 1512: 1.61 schwarze 1513: static void 1.190 schwarze 1514: parse_man(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1515: const struct roff_node *n) 1.50 kristaps 1516: { 1.189 schwarze 1517: const struct roff_node *head, *body; 1.121 schwarze 1518: char *start, *title; 1.50 kristaps 1519: char byte; 1.121 schwarze 1520: size_t sz; 1.18 kristaps 1521: 1.215 schwarze 1522: if (n == NULL) 1.61 schwarze 1523: return; 1.18 kristaps 1524: 1.50 kristaps 1525: /* 1526: * We're only searching for one thing: the first text child in 1527: * the BODY of a NAME section. Since we don't keep track of 1528: * sections in -man, run some hoops to find out whether we're in 1529: * the correct section or not. 1530: */ 1.18 kristaps 1531: 1.188 schwarze 1532: if (n->type == ROFFT_BODY && n->tok == MAN_SH) { 1.50 kristaps 1533: body = n; 1.215 schwarze 1534: if ((head = body->parent->head) != NULL && 1535: (head = head->child) != NULL && 1536: head->next == NULL && 1.188 schwarze 1537: head->type == ROFFT_TEXT && 1.215 schwarze 1538: strcmp(head->string, "NAME") == 0 && 1539: body->child != NULL) { 1.3 kristaps 1540: 1.50 kristaps 1541: /* 1542: * Suck the entire NAME section into memory. 1543: * Yes, we might run away. 1544: * But too many manuals have big, spread-out 1545: * NAME sections over many lines. 1546: */ 1.3 kristaps 1547: 1.121 schwarze 1548: title = NULL; 1.194 schwarze 1549: deroff(&title, body); 1.50 kristaps 1550: if (NULL == title) 1.61 schwarze 1551: return; 1.18 kristaps 1552: 1.141 schwarze 1553: /* 1.50 kristaps 1554: * Go through a special heuristic dance here. 1555: * Conventionally, one or more manual names are 1556: * comma-specified prior to a whitespace, then a 1557: * dash, then a description. Try to puzzle out 1558: * the name parts here. 1559: */ 1.18 kristaps 1560: 1.121 schwarze 1561: start = title; 1.50 kristaps 1562: for ( ;; ) { 1563: sz = strcspn(start, " ,"); 1564: if ('\0' == start[sz]) 1565: break; 1.1 kristaps 1566: 1.50 kristaps 1567: byte = start[sz]; 1568: start[sz] = '\0'; 1.110 schwarze 1569: 1570: /* 1571: * Assume a stray trailing comma in the 1572: * name list if a name begins with a dash. 1573: */ 1574: 1575: if ('-' == start[0] || 1576: ('\\' == start[0] && '-' == start[1])) 1577: break; 1.1 kristaps 1578: 1.133 schwarze 1579: putkey(mpage, start, NAME_TITLE); 1.174 schwarze 1580: if ( ! (mpage->name_head_done || 1581: strcasecmp(start, meta->title))) { 1582: putkey(mpage, start, NAME_HEAD); 1583: mpage->name_head_done = 1; 1584: } 1.1 kristaps 1585: 1.50 kristaps 1586: if (' ' == byte) { 1587: start += sz + 1; 1588: break; 1589: } 1.1 kristaps 1590: 1.50 kristaps 1591: assert(',' == byte); 1592: start += sz + 1; 1593: while (' ' == *start) 1594: start++; 1595: } 1.1 kristaps 1596: 1.121 schwarze 1597: if (start == title) { 1.133 schwarze 1598: putkey(mpage, start, NAME_TITLE); 1.174 schwarze 1599: if ( ! (mpage->name_head_done || 1600: strcasecmp(start, meta->title))) { 1601: putkey(mpage, start, NAME_HEAD); 1602: mpage->name_head_done = 1; 1603: } 1.50 kristaps 1604: free(title); 1.61 schwarze 1605: return; 1.50 kristaps 1606: } 1.1 kristaps 1607: 1.50 kristaps 1608: while (isspace((unsigned char)*start)) 1609: start++; 1.1 kristaps 1610: 1.50 kristaps 1611: if (0 == strncmp(start, "-", 1)) 1612: start += 1; 1613: else if (0 == strncmp(start, "\\-\\-", 4)) 1614: start += 4; 1615: else if (0 == strncmp(start, "\\-", 2)) 1616: start += 2; 1617: else if (0 == strncmp(start, "\\(en", 4)) 1618: start += 4; 1619: else if (0 == strncmp(start, "\\(em", 4)) 1620: start += 4; 1.1 kristaps 1621: 1.50 kristaps 1622: while (' ' == *start) 1623: start++; 1.1 kristaps 1624: 1.78 schwarze 1625: mpage->desc = mandoc_strdup(start); 1.50 kristaps 1626: free(title); 1.61 schwarze 1627: return; 1.50 kristaps 1628: } 1629: } 1.1 kristaps 1630: 1.77 schwarze 1631: for (n = n->child; n; n = n->next) { 1.78 schwarze 1632: if (NULL != mpage->desc) 1.77 schwarze 1633: break; 1.174 schwarze 1634: parse_man(mpage, meta, n); 1.77 schwarze 1635: } 1.1 kristaps 1636: } 1637: 1638: static void 1.190 schwarze 1639: parse_mdoc(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1640: const struct roff_node *n) 1.1 kristaps 1641: { 1642: 1.50 kristaps 1643: assert(NULL != n); 1644: for (n = n->child; NULL != n; n = n->next) { 1.220.2.5 schwarze 1645: if (n->flags & mdocs[n->tok].taboo) 1646: continue; 1.50 kristaps 1647: switch (n->type) { 1.188 schwarze 1648: case ROFFT_ELEM: 1649: case ROFFT_BLOCK: 1650: case ROFFT_HEAD: 1651: case ROFFT_BODY: 1652: case ROFFT_TAIL: 1.50 kristaps 1653: if (NULL != mdocs[n->tok].fp) 1.172 schwarze 1654: if (0 == (*mdocs[n->tok].fp)(mpage, meta, n)) 1.50 kristaps 1655: break; 1.68 schwarze 1656: if (mdocs[n->tok].mask) 1.78 schwarze 1657: putmdockey(mpage, n->child, 1.220.2.5 schwarze 1658: mdocs[n->tok].mask, mdocs[n->tok].taboo); 1.50 kristaps 1659: break; 1660: default: 1.188 schwarze 1661: assert(n->type != ROFFT_ROOT); 1.50 kristaps 1662: continue; 1663: } 1664: if (NULL != n->child) 1.172 schwarze 1665: parse_mdoc(mpage, meta, n); 1.1 kristaps 1666: } 1667: } 1668: 1.25 schwarze 1669: static int 1.190 schwarze 1670: parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1671: const struct roff_node *n) 1.1 kristaps 1672: { 1.176 schwarze 1673: char *start, *end; 1.1 kristaps 1674: size_t sz; 1.25 schwarze 1675: 1.50 kristaps 1676: if (SEC_SYNOPSIS != n->sec || 1.141 schwarze 1677: NULL == (n = n->child) || 1.188 schwarze 1678: n->type != ROFFT_TEXT) 1.197 schwarze 1679: return 0; 1.1 kristaps 1680: 1681: /* 1682: * Only consider those `Fd' macro fields that begin with an 1683: * "inclusion" token (versus, e.g., #define). 1684: */ 1.50 kristaps 1685: 1.1 kristaps 1686: if (strcmp("#include", n->string)) 1.197 schwarze 1687: return 0; 1.1 kristaps 1688: 1.188 schwarze 1689: if ((n = n->next) == NULL || n->type != ROFFT_TEXT) 1.197 schwarze 1690: return 0; 1.1 kristaps 1691: 1692: /* 1693: * Strip away the enclosing angle brackets and make sure we're 1694: * not zero-length. 1695: */ 1696: 1697: start = n->string; 1698: if ('<' == *start || '"' == *start) 1699: start++; 1700: 1701: if (0 == (sz = strlen(start))) 1.197 schwarze 1702: return 0; 1.1 kristaps 1703: 1704: end = &start[(int)sz - 1]; 1705: if ('>' == *end || '"' == *end) 1706: end--; 1707: 1.50 kristaps 1708: if (end > start) 1.78 schwarze 1709: putkeys(mpage, start, end - start + 1, TYPE_In); 1.197 schwarze 1710: return 0; 1.1 kristaps 1711: } 1712: 1.178 schwarze 1713: static void 1.189 schwarze 1714: parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) 1.1 kristaps 1715: { 1.112 schwarze 1716: char *cp; 1.178 schwarze 1717: size_t sz; 1.1 kristaps 1718: 1.188 schwarze 1719: if (n->type != ROFFT_TEXT) 1.178 schwarze 1720: return; 1.25 schwarze 1721: 1.178 schwarze 1722: /* Skip function pointer punctuation. */ 1.1 kristaps 1723: 1.178 schwarze 1724: cp = n->string; 1725: while (*cp == '(' || *cp == '*') 1.1 kristaps 1726: cp++; 1.178 schwarze 1727: sz = strcspn(cp, "()"); 1.1 kristaps 1728: 1.178 schwarze 1729: putkeys(mpage, cp, sz, TYPE_Fn); 1.173 schwarze 1730: if (n->sec == SEC_SYNOPSIS) 1.178 schwarze 1731: putkeys(mpage, cp, sz, NAME_SYN); 1732: } 1.25 schwarze 1733: 1.178 schwarze 1734: static int 1.190 schwarze 1735: parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1736: const struct roff_node *n) 1.178 schwarze 1737: { 1738: 1739: if (n->child == NULL) 1.197 schwarze 1740: return 0; 1.178 schwarze 1741: 1742: parse_mdoc_fname(mpage, n->child); 1.25 schwarze 1743: 1.178 schwarze 1744: for (n = n->child->next; n != NULL; n = n->next) 1.188 schwarze 1745: if (n->type == ROFFT_TEXT) 1.78 schwarze 1746: putkey(mpage, n->string, TYPE_Fa); 1.25 schwarze 1747: 1.197 schwarze 1748: return 0; 1.173 schwarze 1749: } 1750: 1751: static int 1.190 schwarze 1752: parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1753: const struct roff_node *n) 1.173 schwarze 1754: { 1.177 schwarze 1755: 1.188 schwarze 1756: if (n->type != ROFFT_HEAD) 1.197 schwarze 1757: return 1; 1.173 schwarze 1758: 1.178 schwarze 1759: if (n->child != NULL) 1760: parse_mdoc_fname(mpage, n->child); 1761: 1.197 schwarze 1762: return 0; 1.1 kristaps 1763: } 1764: 1.25 schwarze 1765: static int 1.211 schwarze 1766: parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta, 1767: const struct roff_node *n) 1768: { 1769: char *cp; 1770: 1771: if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY) 1772: return 0; 1773: 1.215 schwarze 1774: if (n->child != NULL && 1775: n->child->next == NULL && 1776: n->child->type == ROFFT_TEXT) 1.211 schwarze 1777: return 1; 1778: 1779: cp = NULL; 1780: deroff(&cp, n); 1781: if (cp != NULL) { 1782: putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va || 1783: n->type == ROFFT_BODY ? TYPE_Va : 0)); 1784: free(cp); 1785: } 1786: 1787: return 0; 1788: } 1789: 1790: static int 1.190 schwarze 1791: parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1792: const struct roff_node *n) 1.1 kristaps 1793: { 1.67 schwarze 1794: char *cp; 1.1 kristaps 1795: 1796: if (NULL == (n = n->child)) 1.197 schwarze 1797: return 0; 1.1 kristaps 1798: 1.67 schwarze 1799: if (NULL == n->next) { 1.78 schwarze 1800: putkey(mpage, n->string, TYPE_Xr); 1.197 schwarze 1801: return 0; 1.67 schwarze 1802: } 1803: 1.120 schwarze 1804: mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); 1.78 schwarze 1805: putkey(mpage, cp, TYPE_Xr); 1.67 schwarze 1806: free(cp); 1.197 schwarze 1807: return 0; 1.1 kristaps 1808: } 1809: 1.25 schwarze 1810: static int 1.190 schwarze 1811: parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1812: const struct roff_node *n) 1.1 kristaps 1813: { 1814: 1.188 schwarze 1815: if (n->type == ROFFT_BODY) 1.194 schwarze 1816: deroff(&mpage->desc, n); 1.197 schwarze 1817: return 0; 1.1 kristaps 1818: } 1819: 1.25 schwarze 1820: static int 1.190 schwarze 1821: parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1822: const struct roff_node *n) 1.1 kristaps 1823: { 1824: 1.129 schwarze 1825: if (SEC_NAME == n->sec) 1.220.2.5 schwarze 1826: putmdockey(mpage, n->child, NAME_TITLE, 0); 1.188 schwarze 1827: else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { 1.172 schwarze 1828: if (n->child == NULL) 1829: putkey(mpage, meta->name, NAME_SYN); 1830: else 1.220.2.5 schwarze 1831: putmdockey(mpage, n->child, NAME_SYN, 0); 1.174 schwarze 1832: } 1833: if ( ! (mpage->name_head_done || 1834: n->child == NULL || n->child->string == NULL || 1835: strcasecmp(n->child->string, meta->title))) { 1.220.2.1 schwarze 1836: putkey(mpage, n->child->string, NAME_HEAD); 1.174 schwarze 1837: mpage->name_head_done = 1; 1.172 schwarze 1838: } 1.197 schwarze 1839: return 0; 1.1 kristaps 1840: } 1841: 1.25 schwarze 1842: static int 1.190 schwarze 1843: parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1844: const struct roff_node *n) 1.1 kristaps 1845: { 1846: 1.197 schwarze 1847: return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; 1.1 kristaps 1848: } 1849: 1.50 kristaps 1850: static int 1.190 schwarze 1851: parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, 1.189 schwarze 1852: const struct roff_node *n) 1.1 kristaps 1853: { 1854: 1.197 schwarze 1855: return n->type == ROFFT_HEAD; 1.1 kristaps 1856: } 1857: 1.50 kristaps 1858: /* 1.66 schwarze 1859: * Add a string to the hash table for the current manual. 1860: * Each string has a bitmask telling which macros it belongs to. 1861: * When we finish the manual, we'll dump the table. 1.50 kristaps 1862: */ 1863: static void 1.176 schwarze 1864: putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) 1.50 kristaps 1865: { 1.133 schwarze 1866: struct ohash *htab; 1.50 kristaps 1867: struct str *s; 1.111 schwarze 1868: const char *end; 1.65 schwarze 1869: unsigned int slot; 1.176 schwarze 1870: int i, mustfree; 1.25 schwarze 1871: 1.50 kristaps 1872: if (0 == sz) 1873: return; 1.111 schwarze 1874: 1.176 schwarze 1875: mustfree = render_string(&cp, &sz); 1876: 1.133 schwarze 1877: if (TYPE_Nm & v) { 1878: htab = &names; 1879: v &= name_mask; 1.169 schwarze 1880: if (v & NAME_FIRST) 1881: name_mask &= ~NAME_FIRST; 1.133 schwarze 1882: if (debug > 1) 1883: say(mpage->mlinks->file, 1.220.2.4 schwarze 1884: "Adding name %*s, bits=0x%llx", (int)sz, cp, 1885: (unsigned long long)v); 1.133 schwarze 1886: } else { 1887: htab = &strings; 1888: if (debug > 1) 1889: for (i = 0; i < mansearch_keymax; i++) 1.163 schwarze 1890: if ((uint64_t)1 << i & v) 1.133 schwarze 1891: say(mpage->mlinks->file, 1892: "Adding key %s=%*s", 1.220 schwarze 1893: mansearch_keynames[i], (int)sz, cp); 1.111 schwarze 1894: } 1.25 schwarze 1895: 1.65 schwarze 1896: end = cp + sz; 1.133 schwarze 1897: slot = ohash_qlookupi(htab, cp, &end); 1898: s = ohash_find(htab, slot); 1.25 schwarze 1899: 1.78 schwarze 1900: if (NULL != s && mpage == s->mpage) { 1.50 kristaps 1901: s->mask |= v; 1902: return; 1903: } else if (NULL == s) { 1.144 schwarze 1904: s = mandoc_calloc(1, sizeof(struct str) + sz + 1); 1.50 kristaps 1905: memcpy(s->key, cp, sz); 1.133 schwarze 1906: ohash_insert(htab, slot, s); 1.1 kristaps 1907: } 1.78 schwarze 1908: s->mpage = mpage; 1.50 kristaps 1909: s->mask = v; 1.176 schwarze 1910: 1911: if (mustfree) 1912: free(cp); 1.1 kristaps 1913: } 1914: 1.50 kristaps 1915: /* 1916: * Take a Unicode codepoint and produce its UTF-8 encoding. 1917: * This isn't the best way to do this, but it works. 1918: * The magic numbers are from the UTF-8 packaging. 1919: * They're not as scary as they seem: read the UTF-8 spec for details. 1920: */ 1921: static size_t 1922: utf8(unsigned int cp, char out[7]) 1.1 kristaps 1923: { 1.50 kristaps 1924: size_t rc; 1.1 kristaps 1925: 1.50 kristaps 1926: rc = 0; 1927: if (cp <= 0x0000007F) { 1928: rc = 1; 1929: out[0] = (char)cp; 1930: } else if (cp <= 0x000007FF) { 1931: rc = 2; 1932: out[0] = (cp >> 6 & 31) | 192; 1933: out[1] = (cp & 63) | 128; 1934: } else if (cp <= 0x0000FFFF) { 1935: rc = 3; 1936: out[0] = (cp >> 12 & 15) | 224; 1937: out[1] = (cp >> 6 & 63) | 128; 1938: out[2] = (cp & 63) | 128; 1939: } else if (cp <= 0x001FFFFF) { 1940: rc = 4; 1941: out[0] = (cp >> 18 & 7) | 240; 1942: out[1] = (cp >> 12 & 63) | 128; 1943: out[2] = (cp >> 6 & 63) | 128; 1944: out[3] = (cp & 63) | 128; 1945: } else if (cp <= 0x03FFFFFF) { 1946: rc = 5; 1947: out[0] = (cp >> 24 & 3) | 248; 1948: out[1] = (cp >> 18 & 63) | 128; 1949: out[2] = (cp >> 12 & 63) | 128; 1950: out[3] = (cp >> 6 & 63) | 128; 1951: out[4] = (cp & 63) | 128; 1952: } else if (cp <= 0x7FFFFFFF) { 1953: rc = 6; 1954: out[0] = (cp >> 30 & 1) | 252; 1955: out[1] = (cp >> 24 & 63) | 128; 1956: out[2] = (cp >> 18 & 63) | 128; 1957: out[3] = (cp >> 12 & 63) | 128; 1958: out[4] = (cp >> 6 & 63) | 128; 1959: out[5] = (cp & 63) | 128; 1960: } else 1.197 schwarze 1961: return 0; 1.1 kristaps 1962: 1.50 kristaps 1963: out[rc] = '\0'; 1.197 schwarze 1964: return rc; 1.50 kristaps 1965: } 1.1 kristaps 1966: 1.50 kristaps 1967: /* 1.176 schwarze 1968: * If the string contains escape sequences, 1969: * replace it with an allocated rendering and return 1, 1970: * such that the caller can free it after use. 1971: * Otherwise, do nothing and return 0. 1.50 kristaps 1972: */ 1.176 schwarze 1973: static int 1974: render_string(char **public, size_t *psz) 1.50 kristaps 1975: { 1.176 schwarze 1976: const char *src, *scp, *addcp, *seq; 1977: char *dst; 1978: size_t ssz, dsz, addsz; 1.114 schwarze 1979: char utfbuf[7], res[6]; 1.176 schwarze 1980: int seqlen, unicode; 1.50 kristaps 1981: 1982: res[0] = '\\'; 1983: res[1] = '\t'; 1984: res[2] = ASCII_NBRSP; 1985: res[3] = ASCII_HYPH; 1.114 schwarze 1986: res[4] = ASCII_BREAK; 1987: res[5] = '\0'; 1.1 kristaps 1988: 1.176 schwarze 1989: src = scp = *public; 1990: ssz = *psz; 1991: dst = NULL; 1992: dsz = 0; 1993: 1994: while (scp < src + *psz) { 1995: 1996: /* Leave normal characters unchanged. */ 1997: 1998: if (strchr(res, *scp) == NULL) { 1999: if (dst != NULL) 2000: dst[dsz++] = *scp; 2001: scp++; 2002: continue; 2003: } 1.46 kristaps 2004: 1.50 kristaps 2005: /* 1.176 schwarze 2006: * Found something that requires replacing, 2007: * make sure we have a destination buffer. 1.50 kristaps 2008: */ 1.176 schwarze 2009: 2010: if (dst == NULL) { 2011: dst = mandoc_malloc(ssz + 1); 2012: dsz = scp - src; 2013: memcpy(dst, src, dsz); 1.50 kristaps 2014: } 1.46 kristaps 2015: 1.176 schwarze 2016: /* Handle single-char special characters. */ 2017: 2018: switch (*scp) { 2019: case '\\': 2020: break; 1.141 schwarze 2021: case '\t': 2022: case ASCII_NBRSP: 1.176 schwarze 2023: dst[dsz++] = ' '; 2024: scp++; 2025: continue; 2026: case ASCII_HYPH: 2027: dst[dsz++] = '-'; 1.114 schwarze 2028: /* FALLTHROUGH */ 1.141 schwarze 2029: case ASCII_BREAK: 1.176 schwarze 2030: scp++; 1.50 kristaps 2031: continue; 1.114 schwarze 2032: default: 1.176 schwarze 2033: abort(); 1.114 schwarze 2034: } 1.46 kristaps 2035: 1.50 kristaps 2036: /* 1.176 schwarze 2037: * Found an escape sequence. 2038: * Read past the slash, then parse it. 2039: * Ignore everything except characters. 1.50 kristaps 2040: */ 1.95 schwarze 2041: 1.176 schwarze 2042: scp++; 2043: if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL) 1.50 kristaps 2044: continue; 1.1 kristaps 2045: 1.50 kristaps 2046: /* 1.95 schwarze 2047: * Render the special character 2048: * as either UTF-8 or ASCII. 1.50 kristaps 2049: */ 1.95 schwarze 2050: 2051: if (write_utf8) { 1.203 schwarze 2052: unicode = mchars_spec2cp(seq, seqlen); 1.176 schwarze 2053: if (unicode <= 0) 1.95 schwarze 2054: continue; 1.176 schwarze 2055: addsz = utf8(unicode, utfbuf); 2056: if (addsz == 0) 1.95 schwarze 2057: continue; 1.176 schwarze 2058: addcp = utfbuf; 1.95 schwarze 2059: } else { 1.203 schwarze 2060: addcp = mchars_spec2str(seq, seqlen, &addsz); 1.176 schwarze 2061: if (addcp == NULL) 1.95 schwarze 2062: continue; 1.176 schwarze 2063: if (*addcp == ASCII_NBRSP) { 2064: addcp = " "; 2065: addsz = 1; 1.95 schwarze 2066: } 2067: } 1.1 kristaps 2068: 1.50 kristaps 2069: /* Copy the rendered glyph into the stream. */ 1.1 kristaps 2070: 1.176 schwarze 2071: ssz += addsz; 2072: dst = mandoc_realloc(dst, ssz + 1); 2073: memcpy(dst + dsz, addcp, addsz); 2074: dsz += addsz; 2075: } 2076: if (dst != NULL) { 2077: *public = dst; 2078: *psz = dsz; 2079: } 2080: 2081: /* Trim trailing whitespace and NUL-terminate. */ 2082: 2083: while (*psz > 0 && (*public)[*psz - 1] == ' ') 2084: --*psz; 2085: if (dst != NULL) { 2086: (*public)[*psz] = '\0'; 1.197 schwarze 2087: return 1; 1.176 schwarze 2088: } else 1.197 schwarze 2089: return 0; 1.1 kristaps 2090: } 2091: 1.118 schwarze 2092: static void 2093: dbadd_mlink(const struct mlink *mlink) 2094: { 2095: size_t i; 2096: 2097: i = 1; 2098: SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec); 2099: SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch); 2100: SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name); 1.137 schwarze 2101: SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid); 1.118 schwarze 2102: SQL_STEP(stmts[STMT_INSERT_LINK]); 2103: sqlite3_reset(stmts[STMT_INSERT_LINK]); 1.169 schwarze 2104: } 2105: 2106: static void 2107: dbadd_mlink_name(const struct mlink *mlink) 2108: { 1.175 schwarze 2109: uint64_t bits; 1.169 schwarze 2110: size_t i; 2111: 2112: dbadd_mlink(mlink); 1.160 schwarze 2113: 2114: i = 1; 1.175 schwarze 2115: SQL_BIND_INT64(stmts[STMT_SELECT_NAME], i, mlink->mpage->pageid); 2116: bits = NAME_FILE & NAME_MASK; 2117: if (sqlite3_step(stmts[STMT_SELECT_NAME]) == SQLITE_ROW) { 2118: bits |= sqlite3_column_int64(stmts[STMT_SELECT_NAME], 0); 2119: sqlite3_reset(stmts[STMT_SELECT_NAME]); 2120: } 2121: 2122: i = 1; 2123: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, bits); 1.160 schwarze 2124: SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, mlink->name); 2125: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mlink->mpage->pageid); 2126: SQL_STEP(stmts[STMT_INSERT_NAME]); 2127: sqlite3_reset(stmts[STMT_INSERT_NAME]); 1.118 schwarze 2128: } 2129: 1.14 schwarze 2130: /* 1.50 kristaps 2131: * Flush the current page's terms (and their bits) into the database. 2132: * Wrap the entire set of additions in a transaction to make sqlite be a 2133: * little faster. 1.96 schwarze 2134: * Also, handle escape sequences at the last possible moment. 1.14 schwarze 2135: */ 2136: static void 1.176 schwarze 2137: dbadd(struct mpage *mpage) 1.14 schwarze 2138: { 1.87 schwarze 2139: struct mlink *mlink; 1.50 kristaps 2140: struct str *key; 1.176 schwarze 2141: char *cp; 1.52 kristaps 2142: size_t i; 1.66 schwarze 2143: unsigned int slot; 1.176 schwarze 2144: int mustfree; 1.14 schwarze 2145: 1.128 schwarze 2146: mlink = mpage->mlinks; 1.43 kristaps 2147: 1.128 schwarze 2148: if (nodb) { 1.147 schwarze 2149: for (key = ohash_first(&names, &slot); NULL != key; 1.176 schwarze 2150: key = ohash_next(&names, &slot)) 1.147 schwarze 2151: free(key); 2152: for (key = ohash_first(&strings, &slot); NULL != key; 1.176 schwarze 2153: key = ohash_next(&strings, &slot)) 1.147 schwarze 2154: free(key); 1.145 schwarze 2155: if (0 == debug) 2156: return; 1.128 schwarze 2157: while (NULL != mlink) { 2158: fputs(mlink->name, stdout); 2159: if (NULL == mlink->next || 2160: strcmp(mlink->dsec, mlink->next->dsec) || 2161: strcmp(mlink->fsec, mlink->next->fsec) || 2162: strcmp(mlink->arch, mlink->next->arch)) { 2163: putchar('('); 2164: if ('\0' == *mlink->dsec) 2165: fputs(mlink->fsec, stdout); 2166: else 2167: fputs(mlink->dsec, stdout); 2168: if ('\0' != *mlink->arch) 2169: printf("/%s", mlink->arch); 2170: putchar(')'); 2171: } 2172: mlink = mlink->next; 2173: if (NULL != mlink) 2174: fputs(", ", stdout); 2175: } 1.132 schwarze 2176: printf(" - %s\n", mpage->desc); 1.14 schwarze 2177: return; 1.128 schwarze 2178: } 2179: 2180: if (debug) 2181: say(mlink->file, "Adding to database"); 1.28 kristaps 2182: 1.176 schwarze 2183: cp = mpage->desc; 2184: i = strlen(cp); 2185: mustfree = render_string(&cp, &i); 1.52 kristaps 2186: i = 1; 1.176 schwarze 2187: SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp); 1.161 schwarze 2188: SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form); 1.81 schwarze 2189: SQL_STEP(stmts[STMT_INSERT_PAGE]); 1.137 schwarze 2190: mpage->pageid = sqlite3_last_insert_rowid(db); 1.81 schwarze 2191: sqlite3_reset(stmts[STMT_INSERT_PAGE]); 1.176 schwarze 2192: if (mustfree) 2193: free(cp); 1.81 schwarze 2194: 1.128 schwarze 2195: while (NULL != mlink) { 1.118 schwarze 2196: dbadd_mlink(mlink); 1.128 schwarze 2197: mlink = mlink->next; 2198: } 1.134 schwarze 2199: mlink = mpage->mlinks; 1.50 kristaps 2200: 1.133 schwarze 2201: for (key = ohash_first(&names, &slot); NULL != key; 2202: key = ohash_next(&names, &slot)) { 2203: assert(key->mpage == mpage); 2204: i = 1; 2205: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask); 1.176 schwarze 2206: SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key); 1.137 schwarze 2207: SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid); 1.133 schwarze 2208: SQL_STEP(stmts[STMT_INSERT_NAME]); 2209: sqlite3_reset(stmts[STMT_INSERT_NAME]); 2210: free(key); 2211: } 1.66 schwarze 2212: for (key = ohash_first(&strings, &slot); NULL != key; 2213: key = ohash_next(&strings, &slot)) { 1.78 schwarze 2214: assert(key->mpage == mpage); 1.52 kristaps 2215: i = 1; 2216: SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask); 1.176 schwarze 2217: SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key); 1.137 schwarze 2218: SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid); 1.52 kristaps 2219: SQL_STEP(stmts[STMT_INSERT_KEY]); 1.50 kristaps 2220: sqlite3_reset(stmts[STMT_INSERT_KEY]); 1.66 schwarze 2221: free(key); 1.38 schwarze 2222: } 1.14 schwarze 2223: } 2224: 1.5 kristaps 2225: static void 1.59 schwarze 2226: dbprune(void) 1.5 kristaps 2227: { 1.78 schwarze 2228: struct mpage *mpage; 1.82 schwarze 2229: struct mlink *mlink; 1.52 kristaps 2230: size_t i; 1.80 schwarze 2231: unsigned int slot; 1.5 kristaps 2232: 1.106 schwarze 2233: if (0 == nodb) 2234: SQL_EXEC("BEGIN TRANSACTION"); 1.12 schwarze 2235: 1.106 schwarze 2236: for (mpage = ohash_first(&mpages, &slot); NULL != mpage; 2237: mpage = ohash_next(&mpages, &slot)) { 1.82 schwarze 2238: mlink = mpage->mlinks; 1.125 schwarze 2239: if (debug) 1.106 schwarze 2240: say(mlink->file, "Deleting from database"); 2241: if (nodb) 2242: continue; 2243: for ( ; NULL != mlink; mlink = mlink->next) { 2244: i = 1; 2245: SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], 2246: i, mlink->dsec); 2247: SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], 2248: i, mlink->arch); 2249: SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], 2250: i, mlink->name); 2251: SQL_STEP(stmts[STMT_DELETE_PAGE]); 2252: sqlite3_reset(stmts[STMT_DELETE_PAGE]); 2253: } 1.5 kristaps 2254: } 1.106 schwarze 2255: 2256: if (0 == nodb) 2257: SQL_EXEC("END TRANSACTION"); 1.5 kristaps 2258: } 2259: 1.4 kristaps 2260: /* 1.50 kristaps 2261: * Close an existing database and its prepared statements. 2262: * If "real" is not set, rename the temporary file into the real one. 1.4 kristaps 2263: */ 1.35 kristaps 2264: static void 1.59 schwarze 2265: dbclose(int real) 1.4 kristaps 2266: { 1.50 kristaps 2267: size_t i; 1.115 schwarze 2268: int status; 2269: pid_t child; 1.4 kristaps 2270: 1.50 kristaps 2271: if (nodb) 1.38 schwarze 2272: return; 1.50 kristaps 2273: 2274: for (i = 0; i < STMT__MAX; i++) { 2275: sqlite3_finalize(stmts[i]); 2276: stmts[i] = NULL; 1.4 kristaps 2277: } 2278: 1.50 kristaps 2279: sqlite3_close(db); 2280: db = NULL; 1.12 schwarze 2281: 1.50 kristaps 2282: if (real) 2283: return; 1.12 schwarze 2284: 1.115 schwarze 2285: if ('\0' == *tempfilename) { 2286: if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) { 2287: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2288: say(MANDOC_DB, "&rename"); 1.115 schwarze 2289: } 2290: return; 2291: } 2292: 2293: switch (child = fork()) { 1.141 schwarze 2294: case -1: 1.115 schwarze 2295: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2296: say("", "&fork cmp"); 1.115 schwarze 2297: return; 1.141 schwarze 2298: case 0: 1.115 schwarze 2299: execlp("cmp", "cmp", "-s", 1.196 schwarze 2300: tempfilename, MANDOC_DB, (char *)NULL); 1.123 schwarze 2301: say("", "&exec cmp"); 1.115 schwarze 2302: exit(0); 2303: default: 2304: break; 2305: } 2306: if (-1 == waitpid(child, &status, 0)) { 2307: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2308: say("", "&wait cmp"); 1.115 schwarze 2309: } else if (WIFSIGNALED(status)) { 2310: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2311: say("", "cmp died from signal %d", WTERMSIG(status)); 1.115 schwarze 2312: } else if (WEXITSTATUS(status)) { 1.59 schwarze 2313: exitcode = (int)MANDOCLEVEL_SYSERR; 1.115 schwarze 2314: say(MANDOC_DB, 2315: "Data changed, but cannot replace database"); 2316: } 2317: 2318: *strrchr(tempfilename, '/') = '\0'; 2319: switch (child = fork()) { 1.141 schwarze 2320: case -1: 1.115 schwarze 2321: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2322: say("", "&fork rm"); 1.115 schwarze 2323: return; 1.141 schwarze 2324: case 0: 1.196 schwarze 2325: execlp("rm", "rm", "-rf", tempfilename, (char *)NULL); 1.123 schwarze 2326: say("", "&exec rm"); 1.115 schwarze 2327: exit((int)MANDOCLEVEL_SYSERR); 2328: default: 2329: break; 2330: } 2331: if (-1 == waitpid(child, &status, 0)) { 2332: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2333: say("", "&wait rm"); 1.115 schwarze 2334: } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) { 2335: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2336: say("", "%s: Cannot remove temporary directory", 2337: tempfilename); 1.59 schwarze 2338: } 1.50 kristaps 2339: } 1.14 schwarze 2340: 1.50 kristaps 2341: /* 2342: * This is straightforward stuff. 2343: * Open a database connection to a "temporary" database, then open a set 2344: * of prepared statements we'll use over and over again. 2345: * If "real" is set, we use the existing database; if not, we truncate a 2346: * temporary one. 2347: * Must be matched by dbclose(). 2348: */ 2349: static int 1.59 schwarze 2350: dbopen(int real) 1.50 kristaps 2351: { 1.115 schwarze 2352: const char *sql; 1.50 kristaps 2353: int rc, ofl; 1.12 schwarze 2354: 1.141 schwarze 2355: if (nodb) 1.197 schwarze 2356: return 1; 1.12 schwarze 2357: 1.115 schwarze 2358: *tempfilename = '\0'; 1.63 schwarze 2359: ofl = SQLITE_OPEN_READWRITE; 1.115 schwarze 2360: 2361: if (real) { 2362: rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL); 2363: if (SQLITE_OK != rc) { 1.63 schwarze 2364: exitcode = (int)MANDOCLEVEL_SYSERR; 1.149 schwarze 2365: if (SQLITE_CANTOPEN != rc) 2366: say(MANDOC_DB, "%s", sqlite3_errstr(rc)); 1.197 schwarze 2367: return 0; 1.63 schwarze 2368: } 1.115 schwarze 2369: goto prepare_statements; 2370: } 2371: 2372: ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE; 1.45 kristaps 2373: 1.115 schwarze 2374: remove(MANDOC_DB "~"); 2375: rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL); 1.141 schwarze 2376: if (SQLITE_OK == rc) 1.115 schwarze 2377: goto create_tables; 1.116 schwarze 2378: if (MPARSE_QUICK & mparse_options) { 1.59 schwarze 2379: exitcode = (int)MANDOCLEVEL_SYSERR; 1.146 schwarze 2380: say(MANDOC_DB "~", "%s", sqlite3_errstr(rc)); 1.197 schwarze 2381: return 0; 1.50 kristaps 2382: } 1.12 schwarze 2383: 1.143 schwarze 2384: (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX", 2385: sizeof(tempfilename)); 1.115 schwarze 2386: if (NULL == mkdtemp(tempfilename)) { 2387: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2388: say("", "&%s", tempfilename); 1.197 schwarze 2389: return 0; 1.115 schwarze 2390: } 1.143 schwarze 2391: (void)strlcat(tempfilename, "/" MANDOC_DB, 2392: sizeof(tempfilename)); 1.115 schwarze 2393: rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL); 2394: if (SQLITE_OK != rc) { 1.59 schwarze 2395: exitcode = (int)MANDOCLEVEL_SYSERR; 1.146 schwarze 2396: say("", "%s: %s", tempfilename, sqlite3_errstr(rc)); 1.197 schwarze 2397: return 0; 1.50 kristaps 2398: } 1.12 schwarze 2399: 1.115 schwarze 2400: create_tables: 1.81 schwarze 2401: sql = "CREATE TABLE \"mpages\" (\n" 1.132 schwarze 2402: " \"desc\" TEXT NOT NULL,\n" 1.50 kristaps 2403: " \"form\" INTEGER NOT NULL,\n" 1.137 schwarze 2404: " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" 1.50 kristaps 2405: ");\n" 2406: "\n" 1.81 schwarze 2407: "CREATE TABLE \"mlinks\" (\n" 2408: " \"sec\" TEXT NOT NULL,\n" 2409: " \"arch\" TEXT NOT NULL,\n" 2410: " \"name\" TEXT NOT NULL,\n" 1.137 schwarze 2411: " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " 1.109 schwarze 2412: "ON DELETE CASCADE\n" 1.81 schwarze 2413: ");\n" 1.136 schwarze 2414: "CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n" 1.81 schwarze 2415: "\n" 1.133 schwarze 2416: "CREATE TABLE \"names\" (\n" 2417: " \"bits\" INTEGER NOT NULL,\n" 2418: " \"name\" TEXT NOT NULL,\n" 1.137 schwarze 2419: " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " 1.175 schwarze 2420: "ON DELETE CASCADE,\n" 2421: " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\n" 1.133 schwarze 2422: ");\n" 2423: "\n" 1.50 kristaps 2424: "CREATE TABLE \"keys\" (\n" 2425: " \"bits\" INTEGER NOT NULL,\n" 2426: " \"key\" TEXT NOT NULL,\n" 1.137 schwarze 2427: " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " 1.109 schwarze 2428: "ON DELETE CASCADE\n" 1.136 schwarze 2429: ");\n" 2430: "CREATE INDEX keys_pageid_idx ON keys (pageid);\n"; 1.14 schwarze 2431: 1.50 kristaps 2432: if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) { 1.59 schwarze 2433: exitcode = (int)MANDOCLEVEL_SYSERR; 1.115 schwarze 2434: say(MANDOC_DB, "%s", sqlite3_errmsg(db)); 1.146 schwarze 2435: sqlite3_close(db); 1.197 schwarze 2436: return 0; 1.50 kristaps 2437: } 1.4 kristaps 2438: 1.57 schwarze 2439: prepare_statements: 1.146 schwarze 2440: if (SQLITE_OK != sqlite3_exec(db, 2441: "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) { 2442: exitcode = (int)MANDOCLEVEL_SYSERR; 2443: say(MANDOC_DB, "PRAGMA foreign_keys: %s", 2444: sqlite3_errmsg(db)); 2445: sqlite3_close(db); 1.197 schwarze 2446: return 0; 1.146 schwarze 2447: } 2448: 1.137 schwarze 2449: sql = "DELETE FROM mpages WHERE pageid IN " 1.106 schwarze 2450: "(SELECT pageid FROM mlinks WHERE " 2451: "sec=? AND arch=? AND name=?)"; 1.81 schwarze 2452: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL); 2453: sql = "INSERT INTO mpages " 1.132 schwarze 2454: "(desc,form) VALUES (?,?)"; 1.81 schwarze 2455: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL); 2456: sql = "INSERT INTO mlinks " 1.104 schwarze 2457: "(sec,arch,name,pageid) VALUES (?,?,?,?)"; 1.81 schwarze 2458: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL); 1.175 schwarze 2459: sql = "SELECT bits FROM names where pageid = ?"; 2460: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_SELECT_NAME], NULL); 1.133 schwarze 2461: sql = "INSERT INTO names " 2462: "(bits,name,pageid) VALUES (?,?,?)"; 2463: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL); 1.50 kristaps 2464: sql = "INSERT INTO keys " 1.81 schwarze 2465: "(bits,key,pageid) VALUES (?,?,?)"; 1.50 kristaps 2466: sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL); 1.70 schwarze 2467: 2468: #ifndef __APPLE__ 2469: /* 2470: * When opening a new database, we can turn off 2471: * synchronous mode for much better performance. 2472: */ 2473: 1.146 schwarze 2474: if (real && SQLITE_OK != sqlite3_exec(db, 2475: "PRAGMA synchronous = OFF", NULL, NULL, NULL)) { 2476: exitcode = (int)MANDOCLEVEL_SYSERR; 2477: say(MANDOC_DB, "PRAGMA synchronous: %s", 1.162 schwarze 2478: sqlite3_errmsg(db)); 1.146 schwarze 2479: sqlite3_close(db); 1.197 schwarze 2480: return 0; 1.146 schwarze 2481: } 1.70 schwarze 2482: #endif 2483: 1.197 schwarze 2484: return 1; 1.4 kristaps 2485: } 2486: 1.50 kristaps 2487: static int 1.165 schwarze 2488: set_basedir(const char *targetdir, int report_baddir) 1.4 kristaps 2489: { 1.59 schwarze 2490: static char startdir[PATH_MAX]; 1.151 schwarze 2491: static int getcwd_status; /* 1 = ok, 2 = failure */ 2492: static int chdir_status; /* 1 = changed directory */ 1.150 schwarze 2493: char *cp; 1.4 kristaps 2494: 1.59 schwarze 2495: /* 1.151 schwarze 2496: * Remember the original working directory, if possible. 2497: * This will be needed if the second or a later directory 2498: * on the command line is given as a relative path. 2499: * Do not error out if the current directory is not 2500: * searchable: Maybe it won't be needed after all. 2501: */ 2502: if (0 == getcwd_status) { 2503: if (NULL == getcwd(startdir, sizeof(startdir))) { 2504: getcwd_status = 2; 2505: (void)strlcpy(startdir, strerror(errno), 2506: sizeof(startdir)); 2507: } else 2508: getcwd_status = 1; 2509: } 2510: 2511: /* 2512: * We are leaving the old base directory. 2513: * Do not use it any longer, not even for messages. 2514: */ 2515: *basedir = '\0'; 2516: 2517: /* 2518: * If and only if the directory was changed earlier and 2519: * the next directory to process is given as a relative path, 2520: * first go back, or bail out if that is impossible. 1.59 schwarze 2521: */ 1.151 schwarze 2522: if (chdir_status && '/' != *targetdir) { 2523: if (2 == getcwd_status) { 1.59 schwarze 2524: exitcode = (int)MANDOCLEVEL_SYSERR; 1.151 schwarze 2525: say("", "getcwd: %s", startdir); 1.197 schwarze 2526: return 0; 1.59 schwarze 2527: } 1.151 schwarze 2528: if (-1 == chdir(startdir)) { 1.59 schwarze 2529: exitcode = (int)MANDOCLEVEL_SYSERR; 1.123 schwarze 2530: say("", "&chdir %s", startdir); 1.197 schwarze 2531: return 0; 1.59 schwarze 2532: } 2533: } 1.151 schwarze 2534: 2535: /* 2536: * Always resolve basedir to the canonicalized absolute 2537: * pathname and append a trailing slash, such that 2538: * we can reliably check whether files are inside. 2539: */ 1.59 schwarze 2540: if (NULL == realpath(targetdir, basedir)) { 1.165 schwarze 2541: if (report_baddir || errno != ENOENT) { 2542: exitcode = (int)MANDOCLEVEL_BADARG; 2543: say("", "&%s: realpath", targetdir); 2544: } 1.197 schwarze 2545: return 0; 1.59 schwarze 2546: } else if (-1 == chdir(basedir)) { 1.165 schwarze 2547: if (report_baddir || errno != ENOENT) { 2548: exitcode = (int)MANDOCLEVEL_BADARG; 2549: say("", "&chdir"); 2550: } 1.197 schwarze 2551: return 0; 1.150 schwarze 2552: } 1.151 schwarze 2553: chdir_status = 1; 1.150 schwarze 2554: cp = strchr(basedir, '\0'); 2555: if ('/' != cp[-1]) { 2556: if (cp - basedir >= PATH_MAX - 1) { 2557: exitcode = (int)MANDOCLEVEL_SYSERR; 2558: say("", "Filename too long"); 1.197 schwarze 2559: return 0; 1.150 schwarze 2560: } 2561: *cp++ = '/'; 2562: *cp = '\0'; 1.4 kristaps 2563: } 1.197 schwarze 2564: return 1; 1.56 schwarze 2565: } 2566: 2567: static void 1.59 schwarze 2568: say(const char *file, const char *format, ...) 1.56 schwarze 2569: { 2570: va_list ap; 1.123 schwarze 2571: int use_errno; 1.56 schwarze 2572: 1.59 schwarze 2573: if ('\0' != *basedir) 2574: fprintf(stderr, "%s", basedir); 2575: if ('\0' != *basedir && '\0' != *file) 1.151 schwarze 2576: fputc('/', stderr); 1.56 schwarze 2577: if ('\0' != *file) 1.59 schwarze 2578: fprintf(stderr, "%s", file); 2579: 1.123 schwarze 2580: use_errno = 1; 2581: if (NULL != format) { 2582: switch (*format) { 1.141 schwarze 2583: case '&': 1.123 schwarze 2584: format++; 2585: break; 1.141 schwarze 2586: case '\0': 1.123 schwarze 2587: format = NULL; 2588: break; 2589: default: 2590: use_errno = 0; 2591: break; 2592: } 2593: } 2594: if (NULL != format) { 2595: if ('\0' != *basedir || '\0' != *file) 2596: fputs(": ", stderr); 2597: va_start(ap, format); 2598: vfprintf(stderr, format, ap); 2599: va_end(ap); 2600: } 2601: if (use_errno) { 2602: if ('\0' != *basedir || '\0' != *file || NULL != format) 2603: fputs(": ", stderr); 1.59 schwarze 2604: perror(NULL); 1.123 schwarze 2605: } else 2606: fputc('\n', stderr); 1.1 kristaps 2607: }