linux/scripts/docproc.c
<<
>>
Prefs
   1/*
   2 *      docproc is a simple preprocessor for the template files
   3 *      used as placeholders for the kernel internal documentation.
   4 *      docproc is used for documentation-frontend and
   5 *      dependency-generator.
   6 *      The two usages have in common that they require
   7 *      some knowledge of the .tmpl syntax, therefore they
   8 *      are kept together.
   9 *
  10 *      documentation-frontend
  11 *              Scans the template file and call kernel-doc for
  12 *              all occurrences of ![EIF]file
  13 *              Beforehand each referenced file is scanned for
  14 *              any symbols that are exported via these macros:
  15 *                      EXPORT_SYMBOL(), EXPORT_SYMBOL_GPL(), &
  16 *                      EXPORT_SYMBOL_GPL_FUTURE()
  17 *              This is used to create proper -function and
  18 *              -nofunction arguments in calls to kernel-doc.
  19 *              Usage: docproc doc file.tmpl
  20 *
  21 *      dependency-generator:
  22 *              Scans the template file and list all files
  23 *              referenced in a format recognized by make.
  24 *              Usage:  docproc depend file.tmpl
  25 *              Writes dependency information to stdout
  26 *              in the following format:
  27 *              file.tmpl src.c src2.c
  28 *              The filenames are obtained from the following constructs:
  29 *              !Efilename
  30 *              !Ifilename
  31 *              !Dfilename
  32 *              !Ffilename
  33 *              !Pfilename
  34 *
  35 */
  36
  37#define _GNU_SOURCE
  38#include <stdio.h>
  39#include <stdlib.h>
  40#include <string.h>
  41#include <ctype.h>
  42#include <unistd.h>
  43#include <limits.h>
  44#include <errno.h>
  45#include <getopt.h>
  46#include <sys/types.h>
  47#include <sys/wait.h>
  48#include <time.h>
  49
  50/* exitstatus is used to keep track of any failing calls to kernel-doc,
  51 * but execution continues. */
  52int exitstatus = 0;
  53
  54typedef void DFL(char *);
  55DFL *defaultline;
  56
  57typedef void FILEONLY(char * file);
  58FILEONLY *internalfunctions;
  59FILEONLY *externalfunctions;
  60FILEONLY *symbolsonly;
  61FILEONLY *findall;
  62
  63typedef void FILELINE(char * file, char * line);
  64FILELINE * singlefunctions;
  65FILELINE * entity_system;
  66FILELINE * docsection;
  67
  68#define MAXLINESZ     2048
  69#define MAXFILES      250
  70#define KERNELDOCPATH "scripts/"
  71#define KERNELDOC     "kernel-doc"
  72#define DOCBOOK       "-docbook"
  73#define RST           "-rst"
  74#define LIST          "-list"
  75#define FUNCTION      "-function"
  76#define NOFUNCTION    "-nofunction"
  77#define NODOCSECTIONS "-no-doc-sections"
  78#define SHOWNOTFOUND  "-show-not-found"
  79
  80enum file_format {
  81        FORMAT_AUTO,
  82        FORMAT_DOCBOOK,
  83        FORMAT_RST,
  84};
  85
  86static enum file_format file_format = FORMAT_AUTO;
  87
  88#define KERNELDOC_FORMAT        (file_format == FORMAT_RST ? RST : DOCBOOK)
  89
  90static char *srctree, *kernsrctree;
  91
  92static char **all_list = NULL;
  93static int all_list_len = 0;
  94
  95static void consume_symbol(const char *sym)
  96{
  97        int i;
  98
  99        for (i = 0; i < all_list_len; i++) {
 100                if (!all_list[i])
 101                        continue;
 102                if (strcmp(sym, all_list[i]))
 103                        continue;
 104                all_list[i] = NULL;
 105                break;
 106        }
 107}
 108
 109static void usage (void)
 110{
 111        fprintf(stderr, "Usage: docproc [{--docbook|--rst}] {doc|depend} file\n");
 112        fprintf(stderr, "Input is read from file.tmpl. Output is sent to stdout\n");
 113        fprintf(stderr, "doc: frontend when generating kernel documentation\n");
 114        fprintf(stderr, "depend: generate list of files referenced within file\n");
 115        fprintf(stderr, "Environment variable SRCTREE: absolute path to sources.\n");
 116        fprintf(stderr, "                     KBUILD_SRC: absolute path to kernel source tree.\n");
 117}
 118
 119/*
 120 * Execute kernel-doc with parameters given in svec
 121 */
 122static void exec_kernel_doc(char **svec)
 123{
 124        pid_t pid;
 125        int ret;
 126        char real_filename[PATH_MAX + 1];
 127        /* Make sure output generated so far are flushed */
 128        fflush(stdout);
 129        switch (pid=fork()) {
 130                case -1:
 131                        perror("fork");
 132                        exit(1);
 133                case  0:
 134                        memset(real_filename, 0, sizeof(real_filename));
 135                        strncat(real_filename, kernsrctree, PATH_MAX);
 136                        strncat(real_filename, "/" KERNELDOCPATH KERNELDOC,
 137                                        PATH_MAX - strlen(real_filename));
 138                        execvp(real_filename, svec);
 139                        fprintf(stderr, "exec ");
 140                        perror(real_filename);
 141                        exit(1);
 142                default:
 143                        waitpid(pid, &ret ,0);
 144        }
 145        if (WIFEXITED(ret))
 146                exitstatus |= WEXITSTATUS(ret);
 147        else
 148                exitstatus = 0xff;
 149}
 150
 151/* Types used to create list of all exported symbols in a number of files */
 152struct symbols
 153{
 154        char *name;
 155};
 156
 157struct symfile
 158{
 159        char *filename;
 160        struct symbols *symbollist;
 161        int symbolcnt;
 162};
 163
 164struct symfile symfilelist[MAXFILES];
 165int symfilecnt = 0;
 166
 167static void add_new_symbol(struct symfile *sym, char * symname)
 168{
 169        sym->symbollist =
 170          realloc(sym->symbollist, (sym->symbolcnt + 1) * sizeof(char *));
 171        sym->symbollist[sym->symbolcnt++].name = strdup(symname);
 172}
 173
 174/* Add a filename to the list */
 175static struct symfile * add_new_file(char * filename)
 176{
 177        symfilelist[symfilecnt++].filename = strdup(filename);
 178        return &symfilelist[symfilecnt - 1];
 179}
 180
 181/* Check if file already are present in the list */
 182static struct symfile * filename_exist(char * filename)
 183{
 184        int i;
 185        for (i=0; i < symfilecnt; i++)
 186                if (strcmp(symfilelist[i].filename, filename) == 0)
 187                        return &symfilelist[i];
 188        return NULL;
 189}
 190
 191/*
 192 * List all files referenced within the template file.
 193 * Files are separated by tabs.
 194 */
 195static void adddep(char * file)            { printf("\t%s", file); }
 196static void adddep2(char * file, char * line)     { line = line; adddep(file); }
 197static void noaction(char * line)                  { line = line; }
 198static void noaction2(char * file, char * line)   { file = file; line = line; }
 199
 200/* Echo the line without further action */
 201static void printline(char * line)               { printf("%s", line); }
 202
 203/*
 204 * Find all symbols in filename that are exported with EXPORT_SYMBOL &
 205 * EXPORT_SYMBOL_GPL (& EXPORT_SYMBOL_GPL_FUTURE implicitly).
 206 * All symbols located are stored in symfilelist.
 207 */
 208static void find_export_symbols(char * filename)
 209{
 210        FILE * fp;
 211        struct symfile *sym;
 212        char line[MAXLINESZ];
 213        if (filename_exist(filename) == NULL) {
 214                char real_filename[PATH_MAX + 1];
 215                memset(real_filename, 0, sizeof(real_filename));
 216                strncat(real_filename, srctree, PATH_MAX);
 217                strncat(real_filename, "/", PATH_MAX - strlen(real_filename));
 218                strncat(real_filename, filename,
 219                                PATH_MAX - strlen(real_filename));
 220                sym = add_new_file(filename);
 221                fp = fopen(real_filename, "r");
 222                if (fp == NULL) {
 223                        fprintf(stderr, "docproc: ");
 224                        perror(real_filename);
 225                        exit(1);
 226                }
 227                while (fgets(line, MAXLINESZ, fp)) {
 228                        char *p;
 229                        char *e;
 230                        if (((p = strstr(line, "EXPORT_SYMBOL_GPL")) != NULL) ||
 231                            ((p = strstr(line, "EXPORT_SYMBOL")) != NULL)) {
 232                                /* Skip EXPORT_SYMBOL{_GPL} */
 233                                while (isalnum(*p) || *p == '_')
 234                                        p++;
 235                                /* Remove parentheses & additional whitespace */
 236                                while (isspace(*p))
 237                                        p++;
 238                                if (*p != '(')
 239                                        continue; /* Syntax error? */
 240                                else
 241                                        p++;
 242                                while (isspace(*p))
 243                                        p++;
 244                                e = p;
 245                                while (isalnum(*e) || *e == '_')
 246                                        e++;
 247                                *e = '\0';
 248                                add_new_symbol(sym, p);
 249                        }
 250                }
 251                fclose(fp);
 252        }
 253}
 254
 255/*
 256 * Document all external or internal functions in a file.
 257 * Call kernel-doc with following parameters:
 258 * kernel-doc [-docbook|-rst] -nofunction function_name1 filename
 259 * Function names are obtained from all the src files
 260 * by find_export_symbols.
 261 * intfunc uses -nofunction
 262 * extfunc uses -function
 263 */
 264static void docfunctions(char * filename, char * type)
 265{
 266        int i,j;
 267        int symcnt = 0;
 268        int idx = 0;
 269        char **vec;
 270
 271        for (i=0; i <= symfilecnt; i++)
 272                symcnt += symfilelist[i].symbolcnt;
 273        vec = malloc((2 + 2 * symcnt + 3) * sizeof(char *));
 274        if (vec == NULL) {
 275                perror("docproc: ");
 276                exit(1);
 277        }
 278        vec[idx++] = KERNELDOC;
 279        vec[idx++] = KERNELDOC_FORMAT;
 280        vec[idx++] = NODOCSECTIONS;
 281        for (i=0; i < symfilecnt; i++) {
 282                struct symfile * sym = &symfilelist[i];
 283                for (j=0; j < sym->symbolcnt; j++) {
 284                        vec[idx++]     = type;
 285                        consume_symbol(sym->symbollist[j].name);
 286                        vec[idx++] = sym->symbollist[j].name;
 287                }
 288        }
 289        vec[idx++]     = filename;
 290        vec[idx] = NULL;
 291        if (file_format == FORMAT_RST)
 292                printf(".. %s\n", filename);
 293        else
 294                printf("<!-- %s -->\n", filename);
 295        exec_kernel_doc(vec);
 296        fflush(stdout);
 297        free(vec);
 298}
 299static void intfunc(char * filename) {  docfunctions(filename, NOFUNCTION); }
 300static void extfunc(char * filename) { docfunctions(filename, FUNCTION);   }
 301
 302/*
 303 * Document specific function(s) in a file.
 304 * Call kernel-doc with the following parameters:
 305 * kernel-doc -docbook -function function1 [-function function2]
 306 */
 307static void singfunc(char * filename, char * line)
 308{
 309        char *vec[200]; /* Enough for specific functions */
 310        int i, idx = 0;
 311        int startofsym = 1;
 312        vec[idx++] = KERNELDOC;
 313        vec[idx++] = KERNELDOC_FORMAT;
 314        vec[idx++] = SHOWNOTFOUND;
 315
 316        /* Split line up in individual parameters preceded by FUNCTION */
 317        for (i=0; line[i]; i++) {
 318                if (isspace(line[i])) {
 319                        line[i] = '\0';
 320                        startofsym = 1;
 321                        continue;
 322                }
 323                if (startofsym) {
 324                        startofsym = 0;
 325                        vec[idx++] = FUNCTION;
 326                        vec[idx++] = &line[i];
 327                }
 328        }
 329        for (i = 0; i < idx; i++) {
 330                if (strcmp(vec[i], FUNCTION))
 331                        continue;
 332                consume_symbol(vec[i + 1]);
 333        }
 334        vec[idx++] = filename;
 335        vec[idx] = NULL;
 336        exec_kernel_doc(vec);
 337}
 338
 339/*
 340 * Insert specific documentation section from a file.
 341 * Call kernel-doc with the following parameters:
 342 * kernel-doc -docbook -function "doc section" filename
 343 */
 344static void docsect(char *filename, char *line)
 345{
 346        /* kerneldoc -docbook -show-not-found -function "section" file NULL */
 347        char *vec[7];
 348        char *s;
 349
 350        for (s = line; *s; s++)
 351                if (*s == '\n')
 352                        *s = '\0';
 353
 354        if (asprintf(&s, "DOC: %s", line) < 0) {
 355                perror("asprintf");
 356                exit(1);
 357        }
 358        consume_symbol(s);
 359        free(s);
 360
 361        vec[0] = KERNELDOC;
 362        vec[1] = KERNELDOC_FORMAT;
 363        vec[2] = SHOWNOTFOUND;
 364        vec[3] = FUNCTION;
 365        vec[4] = line;
 366        vec[5] = filename;
 367        vec[6] = NULL;
 368        exec_kernel_doc(vec);
 369}
 370
 371static void find_all_symbols(char *filename)
 372{
 373        char *vec[4]; /* kerneldoc -list file NULL */
 374        pid_t pid;
 375        int ret, i, count, start;
 376        char real_filename[PATH_MAX + 1];
 377        int pipefd[2];
 378        char *data, *str;
 379        size_t data_len = 0;
 380
 381        vec[0] = KERNELDOC;
 382        vec[1] = LIST;
 383        vec[2] = filename;
 384        vec[3] = NULL;
 385
 386        if (pipe(pipefd)) {
 387                perror("pipe");
 388                exit(1);
 389        }
 390
 391        switch (pid=fork()) {
 392                case -1:
 393                        perror("fork");
 394                        exit(1);
 395                case  0:
 396                        close(pipefd[0]);
 397                        dup2(pipefd[1], 1);
 398                        memset(real_filename, 0, sizeof(real_filename));
 399                        strncat(real_filename, kernsrctree, PATH_MAX);
 400                        strncat(real_filename, "/" KERNELDOCPATH KERNELDOC,
 401                                        PATH_MAX - strlen(real_filename));
 402                        execvp(real_filename, vec);
 403                        fprintf(stderr, "exec ");
 404                        perror(real_filename);
 405                        exit(1);
 406                default:
 407                        close(pipefd[1]);
 408                        data = malloc(4096);
 409                        do {
 410                                while ((ret = read(pipefd[0],
 411                                                   data + data_len,
 412                                                   4096)) > 0) {
 413                                        data_len += ret;
 414                                        data = realloc(data, data_len + 4096);
 415                                }
 416                        } while (ret == -EAGAIN);
 417                        if (ret != 0) {
 418                                perror("read");
 419                                exit(1);
 420                        }
 421                        waitpid(pid, &ret ,0);
 422        }
 423        if (WIFEXITED(ret))
 424                exitstatus |= WEXITSTATUS(ret);
 425        else
 426                exitstatus = 0xff;
 427
 428        count = 0;
 429        /* poor man's strtok, but with counting */
 430        for (i = 0; i < data_len; i++) {
 431                if (data[i] == '\n') {
 432                        count++;
 433                        data[i] = '\0';
 434                }
 435        }
 436        start = all_list_len;
 437        all_list_len += count;
 438        all_list = realloc(all_list, sizeof(char *) * all_list_len);
 439        str = data;
 440        for (i = 0; i < data_len && start != all_list_len; i++) {
 441                if (data[i] == '\0') {
 442                        all_list[start] = str;
 443                        str = data + i + 1;
 444                        start++;
 445                }
 446        }
 447}
 448
 449/*
 450 * Terminate s at first space, if any. If there was a space, return pointer to
 451 * the character after that. Otherwise, return pointer to the terminating NUL.
 452 */
 453static char *chomp(char *s)
 454{
 455        while (*s && !isspace(*s))
 456                s++;
 457
 458        if (*s)
 459                *s++ = '\0';
 460
 461        return s;
 462}
 463
 464/* Return pointer to directive content, or NULL if not a directive. */
 465static char *is_directive(char *line)
 466{
 467        if (file_format == FORMAT_DOCBOOK && line[0] == '!')
 468                return line + 1;
 469        else if (file_format == FORMAT_RST && !strncmp(line, ".. !", 4))
 470                return line + 4;
 471
 472        return NULL;
 473}
 474
 475/*
 476 * Parse file, calling action specific functions for:
 477 * 1) Lines containing !E
 478 * 2) Lines containing !I
 479 * 3) Lines containing !D
 480 * 4) Lines containing !F
 481 * 5) Lines containing !P
 482 * 6) Lines containing !C
 483 * 7) Default lines - lines not matching the above
 484 */
 485static void parse_file(FILE *infile)
 486{
 487        char line[MAXLINESZ];
 488        char *p, *s;
 489        while (fgets(line, MAXLINESZ, infile)) {
 490                p = is_directive(line);
 491                if (!p) {
 492                        defaultline(line);
 493                        continue;
 494                }
 495
 496                switch (*p++) {
 497                case 'E':
 498                        chomp(p);
 499                        externalfunctions(p);
 500                        break;
 501                case 'I':
 502                        chomp(p);
 503                        internalfunctions(p);
 504                        break;
 505                case 'D':
 506                        chomp(p);
 507                        symbolsonly(p);
 508                        break;
 509                case 'F':
 510                        /* filename */
 511                        s = chomp(p);
 512                        /* function names */
 513                        while (isspace(*s))
 514                                s++;
 515                        singlefunctions(p, s);
 516                        break;
 517                case 'P':
 518                        /* filename */
 519                        s = chomp(p);
 520                        /* DOC: section name */
 521                        while (isspace(*s))
 522                                s++;
 523                        docsection(p, s);
 524                        break;
 525                case 'C':
 526                        chomp(p);
 527                        if (findall)
 528                                findall(p);
 529                        break;
 530                default:
 531                        defaultline(line);
 532                }
 533        }
 534        fflush(stdout);
 535}
 536
 537/*
 538 * Is this a RestructuredText template?  Answer the question by seeing if its
 539 * name ends in ".rst".
 540 */
 541static int is_rst(const char *file)
 542{
 543        char *dot = strrchr(file, '.');
 544
 545        return dot && !strcmp(dot + 1, "rst");
 546}
 547
 548enum opts {
 549        OPT_DOCBOOK,
 550        OPT_RST,
 551        OPT_HELP,
 552};
 553
 554int main(int argc, char *argv[])
 555{
 556        const char *subcommand, *filename;
 557        FILE * infile;
 558        int i;
 559
 560        srctree = getenv("SRCTREE");
 561        if (!srctree)
 562                srctree = getcwd(NULL, 0);
 563        kernsrctree = getenv("KBUILD_SRC");
 564        if (!kernsrctree || !*kernsrctree)
 565                kernsrctree = srctree;
 566
 567        for (;;) {
 568                int c;
 569                struct option opts[] = {
 570                        { "docbook",    no_argument, NULL, OPT_DOCBOOK },
 571                        { "rst",        no_argument, NULL, OPT_RST },
 572                        { "help",       no_argument, NULL, OPT_HELP },
 573                        {}
 574                };
 575
 576                c = getopt_long_only(argc, argv, "", opts, NULL);
 577                if (c == -1)
 578                        break;
 579
 580                switch (c) {
 581                case OPT_DOCBOOK:
 582                        file_format = FORMAT_DOCBOOK;
 583                        break;
 584                case OPT_RST:
 585                        file_format = FORMAT_RST;
 586                        break;
 587                case OPT_HELP:
 588                        usage();
 589                        return 0;
 590                default:
 591                case '?':
 592                        usage();
 593                        return 1;
 594                }
 595        }
 596
 597        argc -= optind;
 598        argv += optind;
 599
 600        if (argc != 2) {
 601                usage();
 602                exit(1);
 603        }
 604
 605        subcommand = argv[0];
 606        filename = argv[1];
 607
 608        if (file_format == FORMAT_AUTO)
 609                file_format = is_rst(filename) ? FORMAT_RST : FORMAT_DOCBOOK;
 610
 611        /* Open file, exit on error */
 612        infile = fopen(filename, "r");
 613        if (infile == NULL) {
 614                fprintf(stderr, "docproc: ");
 615                perror(filename);
 616                exit(2);
 617        }
 618
 619        if (strcmp("doc", subcommand) == 0) {
 620                if (file_format == FORMAT_RST) {
 621                        time_t t = time(NULL);
 622                        printf(".. generated from %s by docproc %s\n",
 623                               filename, ctime(&t));
 624                }
 625
 626                /* Need to do this in two passes.
 627                 * First pass is used to collect all symbols exported
 628                 * in the various files;
 629                 * Second pass generate the documentation.
 630                 * This is required because some functions are declared
 631                 * and exported in different files :-((
 632                 */
 633                /* Collect symbols */
 634                defaultline       = noaction;
 635                internalfunctions = find_export_symbols;
 636                externalfunctions = find_export_symbols;
 637                symbolsonly       = find_export_symbols;
 638                singlefunctions   = noaction2;
 639                docsection        = noaction2;
 640                findall           = find_all_symbols;
 641                parse_file(infile);
 642
 643                /* Rewind to start from beginning of file again */
 644                fseek(infile, 0, SEEK_SET);
 645                defaultline       = printline;
 646                internalfunctions = intfunc;
 647                externalfunctions = extfunc;
 648                symbolsonly       = printline;
 649                singlefunctions   = singfunc;
 650                docsection        = docsect;
 651                findall           = NULL;
 652
 653                parse_file(infile);
 654
 655                for (i = 0; i < all_list_len; i++) {
 656                        if (!all_list[i])
 657                                continue;
 658                        fprintf(stderr, "Warning: didn't use docs for %s\n",
 659                                all_list[i]);
 660                }
 661        } else if (strcmp("depend", subcommand) == 0) {
 662                /* Create first part of dependency chain
 663                 * file.tmpl */
 664                printf("%s\t", filename);
 665                defaultline       = noaction;
 666                internalfunctions = adddep;
 667                externalfunctions = adddep;
 668                symbolsonly       = adddep;
 669                singlefunctions   = adddep2;
 670                docsection        = adddep2;
 671                findall           = adddep;
 672                parse_file(infile);
 673                printf("\n");
 674        } else {
 675                fprintf(stderr, "Unknown option: %s\n", subcommand);
 676                exit(1);
 677        }
 678        fclose(infile);
 679        fflush(stdout);
 680        return exitstatus;
 681}
 682