linux/kernel/gcov/fs.c
<<
>>
Prefs
   1/*
   2 *  This code exports profiling data as debugfs files to userspace.
   3 *
   4 *    Copyright IBM Corp. 2009
   5 *    Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
   6 *
   7 *    Uses gcc-internal data definitions.
   8 *    Based on the gcov-kernel patch by:
   9 *               Hubertus Franke <frankeh@us.ibm.com>
  10 *               Nigel Hinds <nhinds@us.ibm.com>
  11 *               Rajan Ravindran <rajancr@us.ibm.com>
  12 *               Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
  13 *               Paul Larson
  14 *               Yi CDL Yang
  15 */
  16
  17#define pr_fmt(fmt)     "gcov: " fmt
  18
  19#include <linux/init.h>
  20#include <linux/module.h>
  21#include <linux/debugfs.h>
  22#include <linux/fs.h>
  23#include <linux/list.h>
  24#include <linux/string.h>
  25#include <linux/slab.h>
  26#include <linux/mutex.h>
  27#include <linux/seq_file.h>
  28#include "gcov.h"
  29
  30/**
  31 * struct gcov_node - represents a debugfs entry
  32 * @list: list head for child node list
  33 * @children: child nodes
  34 * @all: list head for list of all nodes
  35 * @parent: parent node
  36 * @loaded_info: array of pointers to profiling data sets for loaded object
  37 *   files.
  38 * @num_loaded: number of profiling data sets for loaded object files.
  39 * @unloaded_info: accumulated copy of profiling data sets for unloaded
  40 *   object files. Used only when gcov_persist=1.
  41 * @dentry: main debugfs entry, either a directory or data file
  42 * @links: associated symbolic links
  43 * @name: data file basename
  44 *
  45 * struct gcov_node represents an entity within the gcov/ subdirectory
  46 * of debugfs. There are directory and data file nodes. The latter represent
  47 * the actual synthesized data file plus any associated symbolic links which
  48 * are needed by the gcov tool to work correctly.
  49 */
  50struct gcov_node {
  51        struct list_head list;
  52        struct list_head children;
  53        struct list_head all;
  54        struct gcov_node *parent;
  55        struct gcov_info **loaded_info;
  56        struct gcov_info *unloaded_info;
  57        struct dentry *dentry;
  58        struct dentry **links;
  59        int num_loaded;
  60        char name[0];
  61};
  62
  63static const char objtree[] = OBJTREE;
  64static const char srctree[] = SRCTREE;
  65static struct gcov_node root_node;
  66static struct dentry *reset_dentry;
  67static LIST_HEAD(all_head);
  68static DEFINE_MUTEX(node_lock);
  69
  70/* If non-zero, keep copies of profiling data for unloaded modules. */
  71static int gcov_persist = 1;
  72
  73static int __init gcov_persist_setup(char *str)
  74{
  75        unsigned long val;
  76
  77        if (strict_strtoul(str, 0, &val)) {
  78                pr_warning("invalid gcov_persist parameter '%s'\n", str);
  79                return 0;
  80        }
  81        gcov_persist = val;
  82        pr_info("setting gcov_persist to %d\n", gcov_persist);
  83
  84        return 1;
  85}
  86__setup("gcov_persist=", gcov_persist_setup);
  87
  88/*
  89 * seq_file.start() implementation for gcov data files. Note that the
  90 * gcov_iterator interface is designed to be more restrictive than seq_file
  91 * (no start from arbitrary position, etc.), to simplify the iterator
  92 * implementation.
  93 */
  94static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
  95{
  96        loff_t i;
  97
  98        gcov_iter_start(seq->private);
  99        for (i = 0; i < *pos; i++) {
 100                if (gcov_iter_next(seq->private))
 101                        return NULL;
 102        }
 103        return seq->private;
 104}
 105
 106/* seq_file.next() implementation for gcov data files. */
 107static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
 108{
 109        struct gcov_iterator *iter = data;
 110
 111        if (gcov_iter_next(iter))
 112                return NULL;
 113        (*pos)++;
 114
 115        return iter;
 116}
 117
 118/* seq_file.show() implementation for gcov data files. */
 119static int gcov_seq_show(struct seq_file *seq, void *data)
 120{
 121        struct gcov_iterator *iter = data;
 122
 123        if (gcov_iter_write(iter, seq))
 124                return -EINVAL;
 125        return 0;
 126}
 127
 128static void gcov_seq_stop(struct seq_file *seq, void *data)
 129{
 130        /* Unused. */
 131}
 132
 133static const struct seq_operations gcov_seq_ops = {
 134        .start  = gcov_seq_start,
 135        .next   = gcov_seq_next,
 136        .show   = gcov_seq_show,
 137        .stop   = gcov_seq_stop,
 138};
 139
 140/*
 141 * Return a profiling data set associated with the given node. This is
 142 * either a data set for a loaded object file or a data set copy in case
 143 * all associated object files have been unloaded.
 144 */
 145static struct gcov_info *get_node_info(struct gcov_node *node)
 146{
 147        if (node->num_loaded > 0)
 148                return node->loaded_info[0];
 149
 150        return node->unloaded_info;
 151}
 152
 153/*
 154 * Return a newly allocated profiling data set which contains the sum of
 155 * all profiling data associated with the given node.
 156 */
 157static struct gcov_info *get_accumulated_info(struct gcov_node *node)
 158{
 159        struct gcov_info *info;
 160        int i = 0;
 161
 162        if (node->unloaded_info)
 163                info = gcov_info_dup(node->unloaded_info);
 164        else
 165                info = gcov_info_dup(node->loaded_info[i++]);
 166        if (!info)
 167                return NULL;
 168        for (; i < node->num_loaded; i++)
 169                gcov_info_add(info, node->loaded_info[i]);
 170
 171        return info;
 172}
 173
 174/*
 175 * open() implementation for gcov data files. Create a copy of the profiling
 176 * data set and initialize the iterator and seq_file interface.
 177 */
 178static int gcov_seq_open(struct inode *inode, struct file *file)
 179{
 180        struct gcov_node *node = inode->i_private;
 181        struct gcov_iterator *iter;
 182        struct seq_file *seq;
 183        struct gcov_info *info;
 184        int rc = -ENOMEM;
 185
 186        mutex_lock(&node_lock);
 187        /*
 188         * Read from a profiling data copy to minimize reference tracking
 189         * complexity and concurrent access and to keep accumulating multiple
 190         * profiling data sets associated with one node simple.
 191         */
 192        info = get_accumulated_info(node);
 193        if (!info)
 194                goto out_unlock;
 195        iter = gcov_iter_new(info);
 196        if (!iter)
 197                goto err_free_info;
 198        rc = seq_open(file, &gcov_seq_ops);
 199        if (rc)
 200                goto err_free_iter_info;
 201        seq = file->private_data;
 202        seq->private = iter;
 203out_unlock:
 204        mutex_unlock(&node_lock);
 205        return rc;
 206
 207err_free_iter_info:
 208        gcov_iter_free(iter);
 209err_free_info:
 210        gcov_info_free(info);
 211        goto out_unlock;
 212}
 213
 214/*
 215 * release() implementation for gcov data files. Release resources allocated
 216 * by open().
 217 */
 218static int gcov_seq_release(struct inode *inode, struct file *file)
 219{
 220        struct gcov_iterator *iter;
 221        struct gcov_info *info;
 222        struct seq_file *seq;
 223
 224        seq = file->private_data;
 225        iter = seq->private;
 226        info = gcov_iter_get_info(iter);
 227        gcov_iter_free(iter);
 228        gcov_info_free(info);
 229        seq_release(inode, file);
 230
 231        return 0;
 232}
 233
 234/*
 235 * Find a node by the associated data file name. Needs to be called with
 236 * node_lock held.
 237 */
 238static struct gcov_node *get_node_by_name(const char *name)
 239{
 240        struct gcov_node *node;
 241        struct gcov_info *info;
 242
 243        list_for_each_entry(node, &all_head, all) {
 244                info = get_node_info(node);
 245                if (info && (strcmp(info->filename, name) == 0))
 246                        return node;
 247        }
 248
 249        return NULL;
 250}
 251
 252/*
 253 * Reset all profiling data associated with the specified node.
 254 */
 255static void reset_node(struct gcov_node *node)
 256{
 257        int i;
 258
 259        if (node->unloaded_info)
 260                gcov_info_reset(node->unloaded_info);
 261        for (i = 0; i < node->num_loaded; i++)
 262                gcov_info_reset(node->loaded_info[i]);
 263}
 264
 265static void remove_node(struct gcov_node *node);
 266
 267/*
 268 * write() implementation for gcov data files. Reset profiling data for the
 269 * corresponding file. If all associated object files have been unloaded,
 270 * remove the debug fs node as well.
 271 */
 272static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
 273                              size_t len, loff_t *pos)
 274{
 275        struct seq_file *seq;
 276        struct gcov_info *info;
 277        struct gcov_node *node;
 278
 279        seq = file->private_data;
 280        info = gcov_iter_get_info(seq->private);
 281        mutex_lock(&node_lock);
 282        node = get_node_by_name(info->filename);
 283        if (node) {
 284                /* Reset counts or remove node for unloaded modules. */
 285                if (node->num_loaded == 0)
 286                        remove_node(node);
 287                else
 288                        reset_node(node);
 289        }
 290        /* Reset counts for open file. */
 291        gcov_info_reset(info);
 292        mutex_unlock(&node_lock);
 293
 294        return len;
 295}
 296
 297/*
 298 * Given a string <path> representing a file path of format:
 299 *   path/to/file.gcda
 300 * construct and return a new string:
 301 *   <dir/>path/to/file.<ext>
 302 */
 303static char *link_target(const char *dir, const char *path, const char *ext)
 304{
 305        char *target;
 306        char *old_ext;
 307        char *copy;
 308
 309        copy = kstrdup(path, GFP_KERNEL);
 310        if (!copy)
 311                return NULL;
 312        old_ext = strrchr(copy, '.');
 313        if (old_ext)
 314                *old_ext = '\0';
 315        if (dir)
 316                target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
 317        else
 318                target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
 319        kfree(copy);
 320
 321        return target;
 322}
 323
 324/*
 325 * Construct a string representing the symbolic link target for the given
 326 * gcov data file name and link type. Depending on the link type and the
 327 * location of the data file, the link target can either point to a
 328 * subdirectory of srctree, objtree or in an external location.
 329 */
 330static char *get_link_target(const char *filename, const struct gcov_link *ext)
 331{
 332        const char *rel;
 333        char *result;
 334
 335        if (strncmp(filename, objtree, strlen(objtree)) == 0) {
 336                rel = filename + strlen(objtree) + 1;
 337                if (ext->dir == SRC_TREE)
 338                        result = link_target(srctree, rel, ext->ext);
 339                else
 340                        result = link_target(objtree, rel, ext->ext);
 341        } else {
 342                /* External compilation. */
 343                result = link_target(NULL, filename, ext->ext);
 344        }
 345
 346        return result;
 347}
 348
 349#define SKEW_PREFIX     ".tmp_"
 350
 351/*
 352 * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
 353 * for filename skewing caused by the mod-versioning mechanism.
 354 */
 355static const char *deskew(const char *basename)
 356{
 357        if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
 358                return basename + sizeof(SKEW_PREFIX) - 1;
 359        return basename;
 360}
 361
 362/*
 363 * Create links to additional files (usually .c and .gcno files) which the
 364 * gcov tool expects to find in the same directory as the gcov data file.
 365 */
 366static void add_links(struct gcov_node *node, struct dentry *parent)
 367{
 368        char *basename;
 369        char *target;
 370        int num;
 371        int i;
 372
 373        for (num = 0; gcov_link[num].ext; num++)
 374                /* Nothing. */;
 375        node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
 376        if (!node->links)
 377                return;
 378        for (i = 0; i < num; i++) {
 379                target = get_link_target(get_node_info(node)->filename,
 380                                         &gcov_link[i]);
 381                if (!target)
 382                        goto out_err;
 383                basename = strrchr(target, '/');
 384                if (!basename)
 385                        goto out_err;
 386                basename++;
 387                node->links[i] = debugfs_create_symlink(deskew(basename),
 388                                                        parent, target);
 389                if (!node->links[i])
 390                        goto out_err;
 391                kfree(target);
 392        }
 393
 394        return;
 395out_err:
 396        kfree(target);
 397        while (i-- > 0)
 398                debugfs_remove(node->links[i]);
 399        kfree(node->links);
 400        node->links = NULL;
 401}
 402
 403static const struct file_operations gcov_data_fops = {
 404        .open           = gcov_seq_open,
 405        .release        = gcov_seq_release,
 406        .read           = seq_read,
 407        .llseek         = seq_lseek,
 408        .write          = gcov_seq_write,
 409};
 410
 411/* Basic initialization of a new node. */
 412static void init_node(struct gcov_node *node, struct gcov_info *info,
 413                      const char *name, struct gcov_node *parent)
 414{
 415        INIT_LIST_HEAD(&node->list);
 416        INIT_LIST_HEAD(&node->children);
 417        INIT_LIST_HEAD(&node->all);
 418        if (node->loaded_info) {
 419                node->loaded_info[0] = info;
 420                node->num_loaded = 1;
 421        }
 422        node->parent = parent;
 423        if (name)
 424                strcpy(node->name, name);
 425}
 426
 427/*
 428 * Create a new node and associated debugfs entry. Needs to be called with
 429 * node_lock held.
 430 */
 431static struct gcov_node *new_node(struct gcov_node *parent,
 432                                  struct gcov_info *info, const char *name)
 433{
 434        struct gcov_node *node;
 435
 436        node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
 437        if (!node)
 438                goto err_nomem;
 439        if (info) {
 440                node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
 441                                           GFP_KERNEL);
 442                if (!node->loaded_info)
 443                        goto err_nomem;
 444        }
 445        init_node(node, info, name, parent);
 446        /* Differentiate between gcov data file nodes and directory nodes. */
 447        if (info) {
 448                node->dentry = debugfs_create_file(deskew(node->name), 0600,
 449                                        parent->dentry, node, &gcov_data_fops);
 450        } else
 451                node->dentry = debugfs_create_dir(node->name, parent->dentry);
 452        if (!node->dentry) {
 453                pr_warning("could not create file\n");
 454                kfree(node);
 455                return NULL;
 456        }
 457        if (info)
 458                add_links(node, parent->dentry);
 459        list_add(&node->list, &parent->children);
 460        list_add(&node->all, &all_head);
 461
 462        return node;
 463
 464err_nomem:
 465        kfree(node);
 466        pr_warning("out of memory\n");
 467        return NULL;
 468}
 469
 470/* Remove symbolic links associated with node. */
 471static void remove_links(struct gcov_node *node)
 472{
 473        int i;
 474
 475        if (!node->links)
 476                return;
 477        for (i = 0; gcov_link[i].ext; i++)
 478                debugfs_remove(node->links[i]);
 479        kfree(node->links);
 480        node->links = NULL;
 481}
 482
 483/*
 484 * Remove node from all lists and debugfs and release associated resources.
 485 * Needs to be called with node_lock held.
 486 */
 487static void release_node(struct gcov_node *node)
 488{
 489        list_del(&node->list);
 490        list_del(&node->all);
 491        debugfs_remove(node->dentry);
 492        remove_links(node);
 493        kfree(node->loaded_info);
 494        if (node->unloaded_info)
 495                gcov_info_free(node->unloaded_info);
 496        kfree(node);
 497}
 498
 499/* Release node and empty parents. Needs to be called with node_lock held. */
 500static void remove_node(struct gcov_node *node)
 501{
 502        struct gcov_node *parent;
 503
 504        while ((node != &root_node) && list_empty(&node->children)) {
 505                parent = node->parent;
 506                release_node(node);
 507                node = parent;
 508        }
 509}
 510
 511/*
 512 * Find child node with given basename. Needs to be called with node_lock
 513 * held.
 514 */
 515static struct gcov_node *get_child_by_name(struct gcov_node *parent,
 516                                           const char *name)
 517{
 518        struct gcov_node *node;
 519
 520        list_for_each_entry(node, &parent->children, list) {
 521                if (strcmp(node->name, name) == 0)
 522                        return node;
 523        }
 524
 525        return NULL;
 526}
 527
 528/*
 529 * write() implementation for reset file. Reset all profiling data to zero
 530 * and remove nodes for which all associated object files are unloaded.
 531 */
 532static ssize_t reset_write(struct file *file, const char __user *addr,
 533                           size_t len, loff_t *pos)
 534{
 535        struct gcov_node *node;
 536
 537        mutex_lock(&node_lock);
 538restart:
 539        list_for_each_entry(node, &all_head, all) {
 540                if (node->num_loaded > 0)
 541                        reset_node(node);
 542                else if (list_empty(&node->children)) {
 543                        remove_node(node);
 544                        /* Several nodes may have gone - restart loop. */
 545                        goto restart;
 546                }
 547        }
 548        mutex_unlock(&node_lock);
 549
 550        return len;
 551}
 552
 553/* read() implementation for reset file. Unused. */
 554static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
 555                          loff_t *pos)
 556{
 557        /* Allow read operation so that a recursive copy won't fail. */
 558        return 0;
 559}
 560
 561static const struct file_operations gcov_reset_fops = {
 562        .write  = reset_write,
 563        .read   = reset_read,
 564        .llseek = noop_llseek,
 565};
 566
 567/*
 568 * Create a node for a given profiling data set and add it to all lists and
 569 * debugfs. Needs to be called with node_lock held.
 570 */
 571static void add_node(struct gcov_info *info)
 572{
 573        char *filename;
 574        char *curr;
 575        char *next;
 576        struct gcov_node *parent;
 577        struct gcov_node *node;
 578
 579        filename = kstrdup(info->filename, GFP_KERNEL);
 580        if (!filename)
 581                return;
 582        parent = &root_node;
 583        /* Create directory nodes along the path. */
 584        for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
 585                if (curr == next)
 586                        continue;
 587                *next = 0;
 588                if (strcmp(curr, ".") == 0)
 589                        continue;
 590                if (strcmp(curr, "..") == 0) {
 591                        if (!parent->parent)
 592                                goto err_remove;
 593                        parent = parent->parent;
 594                        continue;
 595                }
 596                node = get_child_by_name(parent, curr);
 597                if (!node) {
 598                        node = new_node(parent, NULL, curr);
 599                        if (!node)
 600                                goto err_remove;
 601                }
 602                parent = node;
 603        }
 604        /* Create file node. */
 605        node = new_node(parent, info, curr);
 606        if (!node)
 607                goto err_remove;
 608out:
 609        kfree(filename);
 610        return;
 611
 612err_remove:
 613        remove_node(parent);
 614        goto out;
 615}
 616
 617/*
 618 * Associate a profiling data set with an existing node. Needs to be called
 619 * with node_lock held.
 620 */
 621static void add_info(struct gcov_node *node, struct gcov_info *info)
 622{
 623        struct gcov_info **loaded_info;
 624        int num = node->num_loaded;
 625
 626        /*
 627         * Prepare new array. This is done first to simplify cleanup in
 628         * case the new data set is incompatible, the node only contains
 629         * unloaded data sets and there's not enough memory for the array.
 630         */
 631        loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
 632        if (!loaded_info) {
 633                pr_warning("could not add '%s' (out of memory)\n",
 634                           info->filename);
 635                return;
 636        }
 637        memcpy(loaded_info, node->loaded_info,
 638               num * sizeof(struct gcov_info *));
 639        loaded_info[num] = info;
 640        /* Check if the new data set is compatible. */
 641        if (num == 0) {
 642                /*
 643                 * A module was unloaded, modified and reloaded. The new
 644                 * data set replaces the copy of the last one.
 645                 */
 646                if (!gcov_info_is_compatible(node->unloaded_info, info)) {
 647                        pr_warning("discarding saved data for %s "
 648                                   "(incompatible version)\n", info->filename);
 649                        gcov_info_free(node->unloaded_info);
 650                        node->unloaded_info = NULL;
 651                }
 652        } else {
 653                /*
 654                 * Two different versions of the same object file are loaded.
 655                 * The initial one takes precedence.
 656                 */
 657                if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
 658                        pr_warning("could not add '%s' (incompatible "
 659                                   "version)\n", info->filename);
 660                        kfree(loaded_info);
 661                        return;
 662                }
 663        }
 664        /* Overwrite previous array. */
 665        kfree(node->loaded_info);
 666        node->loaded_info = loaded_info;
 667        node->num_loaded = num + 1;
 668}
 669
 670/*
 671 * Return the index of a profiling data set associated with a node.
 672 */
 673static int get_info_index(struct gcov_node *node, struct gcov_info *info)
 674{
 675        int i;
 676
 677        for (i = 0; i < node->num_loaded; i++) {
 678                if (node->loaded_info[i] == info)
 679                        return i;
 680        }
 681        return -ENOENT;
 682}
 683
 684/*
 685 * Save the data of a profiling data set which is being unloaded.
 686 */
 687static void save_info(struct gcov_node *node, struct gcov_info *info)
 688{
 689        if (node->unloaded_info)
 690                gcov_info_add(node->unloaded_info, info);
 691        else {
 692                node->unloaded_info = gcov_info_dup(info);
 693                if (!node->unloaded_info) {
 694                        pr_warning("could not save data for '%s' "
 695                                   "(out of memory)\n", info->filename);
 696                }
 697        }
 698}
 699
 700/*
 701 * Disassociate a profiling data set from a node. Needs to be called with
 702 * node_lock held.
 703 */
 704static void remove_info(struct gcov_node *node, struct gcov_info *info)
 705{
 706        int i;
 707
 708        i = get_info_index(node, info);
 709        if (i < 0) {
 710                pr_warning("could not remove '%s' (not found)\n",
 711                           info->filename);
 712                return;
 713        }
 714        if (gcov_persist)
 715                save_info(node, info);
 716        /* Shrink array. */
 717        node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
 718        node->num_loaded--;
 719        if (node->num_loaded > 0)
 720                return;
 721        /* Last loaded data set was removed. */
 722        kfree(node->loaded_info);
 723        node->loaded_info = NULL;
 724        node->num_loaded = 0;
 725        if (!node->unloaded_info)
 726                remove_node(node);
 727}
 728
 729/*
 730 * Callback to create/remove profiling files when code compiled with
 731 * -fprofile-arcs is loaded/unloaded.
 732 */
 733void gcov_event(enum gcov_action action, struct gcov_info *info)
 734{
 735        struct gcov_node *node;
 736
 737        mutex_lock(&node_lock);
 738        node = get_node_by_name(info->filename);
 739        switch (action) {
 740        case GCOV_ADD:
 741                if (node)
 742                        add_info(node, info);
 743                else
 744                        add_node(info);
 745                break;
 746        case GCOV_REMOVE:
 747                if (node)
 748                        remove_info(node, info);
 749                else {
 750                        pr_warning("could not remove '%s' (not found)\n",
 751                                   info->filename);
 752                }
 753                break;
 754        }
 755        mutex_unlock(&node_lock);
 756}
 757
 758/* Create debugfs entries. */
 759static __init int gcov_fs_init(void)
 760{
 761        int rc = -EIO;
 762
 763        init_node(&root_node, NULL, NULL, NULL);
 764        /*
 765         * /sys/kernel/debug/gcov will be parent for the reset control file
 766         * and all profiling files.
 767         */
 768        root_node.dentry = debugfs_create_dir("gcov", NULL);
 769        if (!root_node.dentry)
 770                goto err_remove;
 771        /*
 772         * Create reset file which resets all profiling counts when written
 773         * to.
 774         */
 775        reset_dentry = debugfs_create_file("reset", 0600, root_node.dentry,
 776                                           NULL, &gcov_reset_fops);
 777        if (!reset_dentry)
 778                goto err_remove;
 779        /* Replay previous events to get our fs hierarchy up-to-date. */
 780        gcov_enable_events();
 781        return 0;
 782
 783err_remove:
 784        pr_err("init failed\n");
 785        if (root_node.dentry)
 786                debugfs_remove(root_node.dentry);
 787
 788        return rc;
 789}
 790device_initcall(gcov_fs_init);
 791
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.