| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 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 |  * @info: associated profiling data structure if not a directory | 
 | 37 |  * @ghost: when an object file containing profiling data is unloaded we keep a | 
 | 38 |  *         copy of the profiling data here to allow collecting coverage data | 
 | 39 |  *         for cleanup code. Such a node is called a "ghost". | 
 | 40 |  * @dentry: main debugfs entry, either a directory or data file | 
 | 41 |  * @links: associated symbolic links | 
 | 42 |  * @name: data file basename | 
 | 43 |  * | 
 | 44 |  * struct gcov_node represents an entity within the gcov/ subdirectory | 
 | 45 |  * of debugfs. There are directory and data file nodes. The latter represent | 
 | 46 |  * the actual synthesized data file plus any associated symbolic links which | 
 | 47 |  * are needed by the gcov tool to work correctly. | 
 | 48 |  */ | 
 | 49 | struct gcov_node { | 
 | 50 | 	struct list_head list; | 
 | 51 | 	struct list_head children; | 
 | 52 | 	struct list_head all; | 
 | 53 | 	struct gcov_node *parent; | 
 | 54 | 	struct gcov_info *info; | 
 | 55 | 	struct gcov_info *ghost; | 
 | 56 | 	struct dentry *dentry; | 
 | 57 | 	struct dentry **links; | 
 | 58 | 	char name[0]; | 
 | 59 | }; | 
 | 60 |  | 
 | 61 | static const char objtree[] = OBJTREE; | 
 | 62 | static const char srctree[] = SRCTREE; | 
 | 63 | static struct gcov_node root_node; | 
 | 64 | static struct dentry *reset_dentry; | 
 | 65 | static LIST_HEAD(all_head); | 
 | 66 | static DEFINE_MUTEX(node_lock); | 
 | 67 |  | 
 | 68 | /* If non-zero, keep copies of profiling data for unloaded modules. */ | 
 | 69 | static int gcov_persist = 1; | 
 | 70 |  | 
 | 71 | static int __init gcov_persist_setup(char *str) | 
 | 72 | { | 
 | 73 | 	unsigned long val; | 
 | 74 |  | 
 | 75 | 	if (strict_strtoul(str, 0, &val)) { | 
 | 76 | 		pr_warning("invalid gcov_persist parameter '%s'\n", str); | 
 | 77 | 		return 0; | 
 | 78 | 	} | 
 | 79 | 	gcov_persist = val; | 
 | 80 | 	pr_info("setting gcov_persist to %d\n", gcov_persist); | 
 | 81 |  | 
 | 82 | 	return 1; | 
 | 83 | } | 
 | 84 | __setup("gcov_persist=", gcov_persist_setup); | 
 | 85 |  | 
 | 86 | /* | 
 | 87 |  * seq_file.start() implementation for gcov data files. Note that the | 
 | 88 |  * gcov_iterator interface is designed to be more restrictive than seq_file | 
 | 89 |  * (no start from arbitrary position, etc.), to simplify the iterator | 
 | 90 |  * implementation. | 
 | 91 |  */ | 
 | 92 | static void *gcov_seq_start(struct seq_file *seq, loff_t *pos) | 
 | 93 | { | 
 | 94 | 	loff_t i; | 
 | 95 |  | 
 | 96 | 	gcov_iter_start(seq->private); | 
 | 97 | 	for (i = 0; i < *pos; i++) { | 
 | 98 | 		if (gcov_iter_next(seq->private)) | 
 | 99 | 			return NULL; | 
 | 100 | 	} | 
 | 101 | 	return seq->private; | 
 | 102 | } | 
 | 103 |  | 
 | 104 | /* seq_file.next() implementation for gcov data files. */ | 
 | 105 | static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos) | 
 | 106 | { | 
 | 107 | 	struct gcov_iterator *iter = data; | 
 | 108 |  | 
 | 109 | 	if (gcov_iter_next(iter)) | 
 | 110 | 		return NULL; | 
 | 111 | 	(*pos)++; | 
 | 112 |  | 
 | 113 | 	return iter; | 
 | 114 | } | 
 | 115 |  | 
 | 116 | /* seq_file.show() implementation for gcov data files. */ | 
 | 117 | static int gcov_seq_show(struct seq_file *seq, void *data) | 
 | 118 | { | 
 | 119 | 	struct gcov_iterator *iter = data; | 
 | 120 |  | 
 | 121 | 	if (gcov_iter_write(iter, seq)) | 
 | 122 | 		return -EINVAL; | 
 | 123 | 	return 0; | 
 | 124 | } | 
 | 125 |  | 
 | 126 | static void gcov_seq_stop(struct seq_file *seq, void *data) | 
 | 127 | { | 
 | 128 | 	/* Unused. */ | 
 | 129 | } | 
 | 130 |  | 
 | 131 | static const struct seq_operations gcov_seq_ops = { | 
 | 132 | 	.start	= gcov_seq_start, | 
 | 133 | 	.next	= gcov_seq_next, | 
 | 134 | 	.show	= gcov_seq_show, | 
 | 135 | 	.stop	= gcov_seq_stop, | 
 | 136 | }; | 
 | 137 |  | 
 | 138 | /* | 
 | 139 |  * Return the profiling data set for a given node. This can either be the | 
 | 140 |  * original profiling data structure or a duplicate (also called "ghost") | 
 | 141 |  * in case the associated object file has been unloaded. | 
 | 142 |  */ | 
 | 143 | static struct gcov_info *get_node_info(struct gcov_node *node) | 
 | 144 | { | 
 | 145 | 	if (node->info) | 
 | 146 | 		return node->info; | 
 | 147 |  | 
 | 148 | 	return node->ghost; | 
 | 149 | } | 
 | 150 |  | 
 | 151 | /* | 
 | 152 |  * open() implementation for gcov data files. Create a copy of the profiling | 
 | 153 |  * data set and initialize the iterator and seq_file interface. | 
 | 154 |  */ | 
 | 155 | static int gcov_seq_open(struct inode *inode, struct file *file) | 
 | 156 | { | 
 | 157 | 	struct gcov_node *node = inode->i_private; | 
 | 158 | 	struct gcov_iterator *iter; | 
 | 159 | 	struct seq_file *seq; | 
 | 160 | 	struct gcov_info *info; | 
 | 161 | 	int rc = -ENOMEM; | 
 | 162 |  | 
 | 163 | 	mutex_lock(&node_lock); | 
 | 164 | 	/* | 
 | 165 | 	 * Read from a profiling data copy to minimize reference tracking | 
 | 166 | 	 * complexity and concurrent access. | 
 | 167 | 	 */ | 
 | 168 | 	info = gcov_info_dup(get_node_info(node)); | 
 | 169 | 	if (!info) | 
 | 170 | 		goto out_unlock; | 
 | 171 | 	iter = gcov_iter_new(info); | 
 | 172 | 	if (!iter) | 
 | 173 | 		goto err_free_info; | 
 | 174 | 	rc = seq_open(file, &gcov_seq_ops); | 
 | 175 | 	if (rc) | 
 | 176 | 		goto err_free_iter_info; | 
 | 177 | 	seq = file->private_data; | 
 | 178 | 	seq->private = iter; | 
 | 179 | out_unlock: | 
 | 180 | 	mutex_unlock(&node_lock); | 
 | 181 | 	return rc; | 
 | 182 |  | 
 | 183 | err_free_iter_info: | 
 | 184 | 	gcov_iter_free(iter); | 
 | 185 | err_free_info: | 
 | 186 | 	gcov_info_free(info); | 
 | 187 | 	goto out_unlock; | 
 | 188 | } | 
 | 189 |  | 
 | 190 | /* | 
 | 191 |  * release() implementation for gcov data files. Release resources allocated | 
 | 192 |  * by open(). | 
 | 193 |  */ | 
 | 194 | static int gcov_seq_release(struct inode *inode, struct file *file) | 
 | 195 | { | 
 | 196 | 	struct gcov_iterator *iter; | 
 | 197 | 	struct gcov_info *info; | 
 | 198 | 	struct seq_file *seq; | 
 | 199 |  | 
 | 200 | 	seq = file->private_data; | 
 | 201 | 	iter = seq->private; | 
 | 202 | 	info = gcov_iter_get_info(iter); | 
 | 203 | 	gcov_iter_free(iter); | 
 | 204 | 	gcov_info_free(info); | 
 | 205 | 	seq_release(inode, file); | 
 | 206 |  | 
 | 207 | 	return 0; | 
 | 208 | } | 
 | 209 |  | 
 | 210 | /* | 
 | 211 |  * Find a node by the associated data file name. Needs to be called with | 
 | 212 |  * node_lock held. | 
 | 213 |  */ | 
 | 214 | static struct gcov_node *get_node_by_name(const char *name) | 
 | 215 | { | 
 | 216 | 	struct gcov_node *node; | 
 | 217 | 	struct gcov_info *info; | 
 | 218 |  | 
 | 219 | 	list_for_each_entry(node, &all_head, all) { | 
 | 220 | 		info = get_node_info(node); | 
 | 221 | 		if (info && (strcmp(info->filename, name) == 0)) | 
 | 222 | 			return node; | 
 | 223 | 	} | 
 | 224 |  | 
 | 225 | 	return NULL; | 
 | 226 | } | 
 | 227 |  | 
 | 228 | static void remove_node(struct gcov_node *node); | 
 | 229 |  | 
 | 230 | /* | 
 | 231 |  * write() implementation for gcov data files. Reset profiling data for the | 
 | 232 |  * associated file. If the object file has been unloaded (i.e. this is | 
 | 233 |  * a "ghost" node), remove the debug fs node as well. | 
 | 234 |  */ | 
 | 235 | static ssize_t gcov_seq_write(struct file *file, const char __user *addr, | 
 | 236 | 			      size_t len, loff_t *pos) | 
 | 237 | { | 
 | 238 | 	struct seq_file *seq; | 
 | 239 | 	struct gcov_info *info; | 
 | 240 | 	struct gcov_node *node; | 
 | 241 |  | 
 | 242 | 	seq = file->private_data; | 
 | 243 | 	info = gcov_iter_get_info(seq->private); | 
 | 244 | 	mutex_lock(&node_lock); | 
 | 245 | 	node = get_node_by_name(info->filename); | 
 | 246 | 	if (node) { | 
 | 247 | 		/* Reset counts or remove node for unloaded modules. */ | 
 | 248 | 		if (node->ghost) | 
 | 249 | 			remove_node(node); | 
 | 250 | 		else | 
 | 251 | 			gcov_info_reset(node->info); | 
 | 252 | 	} | 
 | 253 | 	/* Reset counts for open file. */ | 
 | 254 | 	gcov_info_reset(info); | 
 | 255 | 	mutex_unlock(&node_lock); | 
 | 256 |  | 
 | 257 | 	return len; | 
 | 258 | } | 
 | 259 |  | 
 | 260 | /* | 
 | 261 |  * Given a string <path> representing a file path of format: | 
 | 262 |  *   path/to/file.gcda | 
 | 263 |  * construct and return a new string: | 
 | 264 |  *   <dir/>path/to/file.<ext> | 
 | 265 |  */ | 
 | 266 | static char *link_target(const char *dir, const char *path, const char *ext) | 
 | 267 | { | 
 | 268 | 	char *target; | 
 | 269 | 	char *old_ext; | 
 | 270 | 	char *copy; | 
 | 271 |  | 
 | 272 | 	copy = kstrdup(path, GFP_KERNEL); | 
 | 273 | 	if (!copy) | 
 | 274 | 		return NULL; | 
 | 275 | 	old_ext = strrchr(copy, '.'); | 
 | 276 | 	if (old_ext) | 
 | 277 | 		*old_ext = '\0'; | 
 | 278 | 	if (dir) | 
 | 279 | 		target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext); | 
 | 280 | 	else | 
 | 281 | 		target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext); | 
 | 282 | 	kfree(copy); | 
 | 283 |  | 
 | 284 | 	return target; | 
 | 285 | } | 
 | 286 |  | 
 | 287 | /* | 
 | 288 |  * Construct a string representing the symbolic link target for the given | 
 | 289 |  * gcov data file name and link type. Depending on the link type and the | 
 | 290 |  * location of the data file, the link target can either point to a | 
 | 291 |  * subdirectory of srctree, objtree or in an external location. | 
 | 292 |  */ | 
 | 293 | static char *get_link_target(const char *filename, const struct gcov_link *ext) | 
 | 294 | { | 
 | 295 | 	const char *rel; | 
 | 296 | 	char *result; | 
 | 297 |  | 
 | 298 | 	if (strncmp(filename, objtree, strlen(objtree)) == 0) { | 
 | 299 | 		rel = filename + strlen(objtree) + 1; | 
 | 300 | 		if (ext->dir == SRC_TREE) | 
 | 301 | 			result = link_target(srctree, rel, ext->ext); | 
 | 302 | 		else | 
 | 303 | 			result = link_target(objtree, rel, ext->ext); | 
 | 304 | 	} else { | 
 | 305 | 		/* External compilation. */ | 
 | 306 | 		result = link_target(NULL, filename, ext->ext); | 
 | 307 | 	} | 
 | 308 |  | 
 | 309 | 	return result; | 
 | 310 | } | 
 | 311 |  | 
 | 312 | #define SKEW_PREFIX	".tmp_" | 
 | 313 |  | 
 | 314 | /* | 
 | 315 |  * For a filename .tmp_filename.ext return filename.ext. Needed to compensate | 
 | 316 |  * for filename skewing caused by the mod-versioning mechanism. | 
 | 317 |  */ | 
 | 318 | static const char *deskew(const char *basename) | 
 | 319 | { | 
 | 320 | 	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0) | 
 | 321 | 		return basename + sizeof(SKEW_PREFIX) - 1; | 
 | 322 | 	return basename; | 
 | 323 | } | 
 | 324 |  | 
 | 325 | /* | 
 | 326 |  * Create links to additional files (usually .c and .gcno files) which the | 
 | 327 |  * gcov tool expects to find in the same directory as the gcov data file. | 
 | 328 |  */ | 
 | 329 | static void add_links(struct gcov_node *node, struct dentry *parent) | 
 | 330 | { | 
 | 331 | 	char *basename; | 
 | 332 | 	char *target; | 
 | 333 | 	int num; | 
 | 334 | 	int i; | 
 | 335 |  | 
 | 336 | 	for (num = 0; gcov_link[num].ext; num++) | 
 | 337 | 		/* Nothing. */; | 
 | 338 | 	node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL); | 
 | 339 | 	if (!node->links) | 
 | 340 | 		return; | 
 | 341 | 	for (i = 0; i < num; i++) { | 
 | 342 | 		target = get_link_target(get_node_info(node)->filename, | 
 | 343 | 					 &gcov_link[i]); | 
 | 344 | 		if (!target) | 
 | 345 | 			goto out_err; | 
 | 346 | 		basename = strrchr(target, '/'); | 
 | 347 | 		if (!basename) | 
 | 348 | 			goto out_err; | 
 | 349 | 		basename++; | 
 | 350 | 		node->links[i] = debugfs_create_symlink(deskew(basename), | 
 | 351 | 							parent,	target); | 
 | 352 | 		if (!node->links[i]) | 
 | 353 | 			goto out_err; | 
 | 354 | 		kfree(target); | 
 | 355 | 	} | 
 | 356 |  | 
 | 357 | 	return; | 
 | 358 | out_err: | 
 | 359 | 	kfree(target); | 
 | 360 | 	while (i-- > 0) | 
 | 361 | 		debugfs_remove(node->links[i]); | 
 | 362 | 	kfree(node->links); | 
 | 363 | 	node->links = NULL; | 
 | 364 | } | 
 | 365 |  | 
 | 366 | static const struct file_operations gcov_data_fops = { | 
 | 367 | 	.open		= gcov_seq_open, | 
 | 368 | 	.release	= gcov_seq_release, | 
 | 369 | 	.read		= seq_read, | 
 | 370 | 	.llseek		= seq_lseek, | 
 | 371 | 	.write		= gcov_seq_write, | 
 | 372 | }; | 
 | 373 |  | 
 | 374 | /* Basic initialization of a new node. */ | 
 | 375 | static void init_node(struct gcov_node *node, struct gcov_info *info, | 
 | 376 | 		      const char *name, struct gcov_node *parent) | 
 | 377 | { | 
 | 378 | 	INIT_LIST_HEAD(&node->list); | 
 | 379 | 	INIT_LIST_HEAD(&node->children); | 
 | 380 | 	INIT_LIST_HEAD(&node->all); | 
 | 381 | 	node->info = info; | 
 | 382 | 	node->parent = parent; | 
 | 383 | 	if (name) | 
 | 384 | 		strcpy(node->name, name); | 
 | 385 | } | 
 | 386 |  | 
 | 387 | /* | 
 | 388 |  * Create a new node and associated debugfs entry. Needs to be called with | 
 | 389 |  * node_lock held. | 
 | 390 |  */ | 
 | 391 | static struct gcov_node *new_node(struct gcov_node *parent, | 
 | 392 | 				  struct gcov_info *info, const char *name) | 
 | 393 | { | 
 | 394 | 	struct gcov_node *node; | 
 | 395 |  | 
 | 396 | 	node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL); | 
 | 397 | 	if (!node) { | 
 | 398 | 		pr_warning("out of memory\n"); | 
 | 399 | 		return NULL; | 
 | 400 | 	} | 
 | 401 | 	init_node(node, info, name, parent); | 
 | 402 | 	/* Differentiate between gcov data file nodes and directory nodes. */ | 
 | 403 | 	if (info) { | 
 | 404 | 		node->dentry = debugfs_create_file(deskew(node->name), 0600, | 
 | 405 | 					parent->dentry, node, &gcov_data_fops); | 
 | 406 | 	} else | 
 | 407 | 		node->dentry = debugfs_create_dir(node->name, parent->dentry); | 
 | 408 | 	if (!node->dentry) { | 
 | 409 | 		pr_warning("could not create file\n"); | 
 | 410 | 		kfree(node); | 
 | 411 | 		return NULL; | 
 | 412 | 	} | 
 | 413 | 	if (info) | 
 | 414 | 		add_links(node, parent->dentry); | 
 | 415 | 	list_add(&node->list, &parent->children); | 
 | 416 | 	list_add(&node->all, &all_head); | 
 | 417 |  | 
 | 418 | 	return node; | 
 | 419 | } | 
 | 420 |  | 
 | 421 | /* Remove symbolic links associated with node. */ | 
 | 422 | static void remove_links(struct gcov_node *node) | 
 | 423 | { | 
 | 424 | 	int i; | 
 | 425 |  | 
 | 426 | 	if (!node->links) | 
 | 427 | 		return; | 
 | 428 | 	for (i = 0; gcov_link[i].ext; i++) | 
 | 429 | 		debugfs_remove(node->links[i]); | 
 | 430 | 	kfree(node->links); | 
 | 431 | 	node->links = NULL; | 
 | 432 | } | 
 | 433 |  | 
 | 434 | /* | 
 | 435 |  * Remove node from all lists and debugfs and release associated resources. | 
 | 436 |  * Needs to be called with node_lock held. | 
 | 437 |  */ | 
 | 438 | static void release_node(struct gcov_node *node) | 
 | 439 | { | 
 | 440 | 	list_del(&node->list); | 
 | 441 | 	list_del(&node->all); | 
 | 442 | 	debugfs_remove(node->dentry); | 
 | 443 | 	remove_links(node); | 
 | 444 | 	if (node->ghost) | 
 | 445 | 		gcov_info_free(node->ghost); | 
 | 446 | 	kfree(node); | 
 | 447 | } | 
 | 448 |  | 
 | 449 | /* Release node and empty parents. Needs to be called with node_lock held. */ | 
 | 450 | static void remove_node(struct gcov_node *node) | 
 | 451 | { | 
 | 452 | 	struct gcov_node *parent; | 
 | 453 |  | 
 | 454 | 	while ((node != &root_node) && list_empty(&node->children)) { | 
 | 455 | 		parent = node->parent; | 
 | 456 | 		release_node(node); | 
 | 457 | 		node = parent; | 
 | 458 | 	} | 
 | 459 | } | 
 | 460 |  | 
 | 461 | /* | 
 | 462 |  * Find child node with given basename. Needs to be called with node_lock | 
 | 463 |  * held. | 
 | 464 |  */ | 
 | 465 | static struct gcov_node *get_child_by_name(struct gcov_node *parent, | 
 | 466 | 					   const char *name) | 
 | 467 | { | 
 | 468 | 	struct gcov_node *node; | 
 | 469 |  | 
 | 470 | 	list_for_each_entry(node, &parent->children, list) { | 
 | 471 | 		if (strcmp(node->name, name) == 0) | 
 | 472 | 			return node; | 
 | 473 | 	} | 
 | 474 |  | 
 | 475 | 	return NULL; | 
 | 476 | } | 
 | 477 |  | 
 | 478 | /* | 
 | 479 |  * write() implementation for reset file. Reset all profiling data to zero | 
 | 480 |  * and remove ghost nodes. | 
 | 481 |  */ | 
 | 482 | static ssize_t reset_write(struct file *file, const char __user *addr, | 
 | 483 | 			   size_t len, loff_t *pos) | 
 | 484 | { | 
 | 485 | 	struct gcov_node *node; | 
 | 486 |  | 
 | 487 | 	mutex_lock(&node_lock); | 
 | 488 | restart: | 
 | 489 | 	list_for_each_entry(node, &all_head, all) { | 
 | 490 | 		if (node->info) | 
 | 491 | 			gcov_info_reset(node->info); | 
 | 492 | 		else if (list_empty(&node->children)) { | 
 | 493 | 			remove_node(node); | 
 | 494 | 			/* Several nodes may have gone - restart loop. */ | 
 | 495 | 			goto restart; | 
 | 496 | 		} | 
 | 497 | 	} | 
 | 498 | 	mutex_unlock(&node_lock); | 
 | 499 |  | 
 | 500 | 	return len; | 
 | 501 | } | 
 | 502 |  | 
 | 503 | /* read() implementation for reset file. Unused. */ | 
 | 504 | static ssize_t reset_read(struct file *file, char __user *addr, size_t len, | 
 | 505 | 			  loff_t *pos) | 
 | 506 | { | 
 | 507 | 	/* Allow read operation so that a recursive copy won't fail. */ | 
 | 508 | 	return 0; | 
 | 509 | } | 
 | 510 |  | 
 | 511 | static const struct file_operations gcov_reset_fops = { | 
 | 512 | 	.write	= reset_write, | 
 | 513 | 	.read	= reset_read, | 
 | 514 | }; | 
 | 515 |  | 
 | 516 | /* | 
 | 517 |  * Create a node for a given profiling data set and add it to all lists and | 
 | 518 |  * debugfs. Needs to be called with node_lock held. | 
 | 519 |  */ | 
 | 520 | static void add_node(struct gcov_info *info) | 
 | 521 | { | 
 | 522 | 	char *filename; | 
 | 523 | 	char *curr; | 
 | 524 | 	char *next; | 
 | 525 | 	struct gcov_node *parent; | 
 | 526 | 	struct gcov_node *node; | 
 | 527 |  | 
 | 528 | 	filename = kstrdup(info->filename, GFP_KERNEL); | 
 | 529 | 	if (!filename) | 
 | 530 | 		return; | 
 | 531 | 	parent = &root_node; | 
 | 532 | 	/* Create directory nodes along the path. */ | 
 | 533 | 	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) { | 
 | 534 | 		if (curr == next) | 
 | 535 | 			continue; | 
 | 536 | 		*next = 0; | 
 | 537 | 		if (strcmp(curr, ".") == 0) | 
 | 538 | 			continue; | 
 | 539 | 		if (strcmp(curr, "..") == 0) { | 
 | 540 | 			if (!parent->parent) | 
 | 541 | 				goto err_remove; | 
 | 542 | 			parent = parent->parent; | 
 | 543 | 			continue; | 
 | 544 | 		} | 
 | 545 | 		node = get_child_by_name(parent, curr); | 
 | 546 | 		if (!node) { | 
 | 547 | 			node = new_node(parent, NULL, curr); | 
 | 548 | 			if (!node) | 
 | 549 | 				goto err_remove; | 
 | 550 | 		} | 
 | 551 | 		parent = node; | 
 | 552 | 	} | 
 | 553 | 	/* Create file node. */ | 
 | 554 | 	node = new_node(parent, info, curr); | 
 | 555 | 	if (!node) | 
 | 556 | 		goto err_remove; | 
 | 557 | out: | 
 | 558 | 	kfree(filename); | 
 | 559 | 	return; | 
 | 560 |  | 
 | 561 | err_remove: | 
 | 562 | 	remove_node(parent); | 
 | 563 | 	goto out; | 
 | 564 | } | 
 | 565 |  | 
 | 566 | /* | 
 | 567 |  * The profiling data set associated with this node is being unloaded. Store a | 
 | 568 |  * copy of the profiling data and turn this node into a "ghost". | 
 | 569 |  */ | 
 | 570 | static int ghost_node(struct gcov_node *node) | 
 | 571 | { | 
 | 572 | 	node->ghost = gcov_info_dup(node->info); | 
 | 573 | 	if (!node->ghost) { | 
 | 574 | 		pr_warning("could not save data for '%s' (out of memory)\n", | 
 | 575 | 			   node->info->filename); | 
 | 576 | 		return -ENOMEM; | 
 | 577 | 	} | 
 | 578 | 	node->info = NULL; | 
 | 579 |  | 
 | 580 | 	return 0; | 
 | 581 | } | 
 | 582 |  | 
 | 583 | /* | 
 | 584 |  * Profiling data for this node has been loaded again. Add profiling data | 
 | 585 |  * from previous instantiation and turn this node into a regular node. | 
 | 586 |  */ | 
 | 587 | static void revive_node(struct gcov_node *node, struct gcov_info *info) | 
 | 588 | { | 
 | 589 | 	if (gcov_info_is_compatible(node->ghost, info)) | 
 | 590 | 		gcov_info_add(info, node->ghost); | 
 | 591 | 	else { | 
 | 592 | 		pr_warning("discarding saved data for '%s' (version changed)\n", | 
 | 593 | 			   info->filename); | 
 | 594 | 	} | 
 | 595 | 	gcov_info_free(node->ghost); | 
 | 596 | 	node->ghost = NULL; | 
 | 597 | 	node->info = info; | 
 | 598 | } | 
 | 599 |  | 
 | 600 | /* | 
 | 601 |  * Callback to create/remove profiling files when code compiled with | 
 | 602 |  * -fprofile-arcs is loaded/unloaded. | 
 | 603 |  */ | 
 | 604 | void gcov_event(enum gcov_action action, struct gcov_info *info) | 
 | 605 | { | 
 | 606 | 	struct gcov_node *node; | 
 | 607 |  | 
 | 608 | 	mutex_lock(&node_lock); | 
 | 609 | 	node = get_node_by_name(info->filename); | 
 | 610 | 	switch (action) { | 
 | 611 | 	case GCOV_ADD: | 
 | 612 | 		/* Add new node or revive ghost. */ | 
 | 613 | 		if (!node) { | 
 | 614 | 			add_node(info); | 
 | 615 | 			break; | 
 | 616 | 		} | 
 | 617 | 		if (gcov_persist) | 
 | 618 | 			revive_node(node, info); | 
 | 619 | 		else { | 
 | 620 | 			pr_warning("could not add '%s' (already exists)\n", | 
 | 621 | 				   info->filename); | 
 | 622 | 		} | 
 | 623 | 		break; | 
 | 624 | 	case GCOV_REMOVE: | 
 | 625 | 		/* Remove node or turn into ghost. */ | 
 | 626 | 		if (!node) { | 
 | 627 | 			pr_warning("could not remove '%s' (not found)\n", | 
 | 628 | 				   info->filename); | 
 | 629 | 			break; | 
 | 630 | 		} | 
 | 631 | 		if (gcov_persist) { | 
 | 632 | 			if (!ghost_node(node)) | 
 | 633 | 				break; | 
 | 634 | 		} | 
 | 635 | 		remove_node(node); | 
 | 636 | 		break; | 
 | 637 | 	} | 
 | 638 | 	mutex_unlock(&node_lock); | 
 | 639 | } | 
 | 640 |  | 
 | 641 | /* Create debugfs entries. */ | 
 | 642 | static __init int gcov_fs_init(void) | 
 | 643 | { | 
 | 644 | 	int rc = -EIO; | 
 | 645 |  | 
 | 646 | 	init_node(&root_node, NULL, NULL, NULL); | 
 | 647 | 	/* | 
 | 648 | 	 * /sys/kernel/debug/gcov will be parent for the reset control file | 
 | 649 | 	 * and all profiling files. | 
 | 650 | 	 */ | 
 | 651 | 	root_node.dentry = debugfs_create_dir("gcov", NULL); | 
 | 652 | 	if (!root_node.dentry) | 
 | 653 | 		goto err_remove; | 
 | 654 | 	/* | 
 | 655 | 	 * Create reset file which resets all profiling counts when written | 
 | 656 | 	 * to. | 
 | 657 | 	 */ | 
 | 658 | 	reset_dentry = debugfs_create_file("reset", 0600, root_node.dentry, | 
 | 659 | 					   NULL, &gcov_reset_fops); | 
 | 660 | 	if (!reset_dentry) | 
 | 661 | 		goto err_remove; | 
 | 662 | 	/* Replay previous events to get our fs hierarchy up-to-date. */ | 
 | 663 | 	gcov_enable_events(); | 
 | 664 | 	return 0; | 
 | 665 |  | 
 | 666 | err_remove: | 
 | 667 | 	pr_err("init failed\n"); | 
 | 668 | 	if (root_node.dentry) | 
 | 669 | 		debugfs_remove(root_node.dentry); | 
 | 670 |  | 
 | 671 | 	return rc; | 
 | 672 | } | 
 | 673 | device_initcall(gcov_fs_init); |