// dentry tapset
// Copyright (c) 2009-2010, 2012 Red Hat Inc.
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.
 

function __dentry_IS_ROOT:long(dentry:long)
{
        return (@cast(dentry, "dentry")->d_parent == dentry)
}


function __dentry_prepend:string(dentry:long,name:string)
{
        dname = d_name(dentry)

        /*
         * In case we are following down a mount point trigger, we can get
         * multiple instances of a root mount.
         */
        c = substr(name, strlen(name)-1, strlen(name)-1)
        if (dname == "/" && c == "/")
                return name;

        if (name == "") {
                return dname;
        } else {
                return sprintf("%s/%s", dname, name);
        }
}



/**
 *   sfunction d_name - get the dirent name
 *
 *   Returns the dirent name (path basename).
 *   @dentry: Pointer to dentry.
 */
function d_name:string(dentry:long)
{
        len = @cast(dentry, "dentry")->d_name->len;
        return kernel_string_n(@cast(dentry, "dentry")->d_name->name, len);
}


function __inode_first_dentry:long(inode:long)
{
        /* i_dentry is an hlist_head on 3.6+, or a list_head before that.  */
        d_alias = @choose_defined(
                        @cast(inode, "struct inode")->i_dentry->first,
                        @cast(inode, "struct inode")->i_dentry->next)
        dentry = d_alias - (& @cast(0, "struct dentry")->d_alias)
        return dentry
}


function __inode_vfsmount:long(inode:long)
{
        /* s_mounts was added in kernel 3.6, commit b3d9b7a3c.  */
        if (@defined(&@cast(0, "struct super_block")->s_mounts)) {
                mnt_ns = @cast(task_current(), "struct task_struct")->nsproxy->mnt_ns
                sb = @cast(inode, "struct inode")->i_sb

                /* Look for the mount which matches the current namespace */
                head = &@cast(sb, "struct super_block")->s_mounts
                for (pos = @cast(head, "struct list_head")->next; pos != head;
                                pos = @cast(pos, "struct list_head")->next) {
                        mount = pos - (& @cast(0, "struct mount")->mnt_instance)
                        if (@cast(mount, "struct mount")->mnt_ns == mnt_ns)
                                return & @cast(mount, "struct mount")->mnt
                }
        }
        return 0
}


/**
 *   sfunction inode_name - get the inode name
 * 
 *   Returns the first path basename associated with the given inode.
 *   @inode: Pointer to inode.
 */
function inode_name:string(inode:long)
{
        return reverse_path_walk(__inode_first_dentry(inode))
}


/**
 *   sfunction inode_path - get the path to an inode
 * 
 *   Returns the full path associated with the given inode.
 *   @inode: Pointer to inode.
 */
function inode_path:string(inode:long)
{
        dentry = __inode_first_dentry(inode)
        vfsmount = __inode_vfsmount(inode)
        if (vfsmount != 0)
                return task_dentry_path(task_current(), dentry, vfsmount)

        /* This is not really a full path...  */
        return reverse_path_walk(dentry)
}


/**
 *   sfunction reverse_path_walk - get the full dirent path
 *
 *   Returns the path name (partial path to mount point).
 *   @dentry: Pointer to dentry.
 */
function reverse_path_walk:string(dentry:long)
{
        while(1) {
                name = __dentry_prepend(dentry, name);
                dentry = @cast(dentry, "dentry")->d_parent;
                if (__dentry_IS_ROOT(dentry))
                        return name;
        }
}


/**
 *   sfunction real_mount - get the 'struct mount' pointer
 *
 *   Returns the 'struct mount' pointer value for a 'struct vfsmount'
 *   pointer.
 *   @vfsmnt: Pointer to 'struct vfsmount'
 */
function real_mount:long(vfsmnt:long)
{
	if (@defined(@cast(0, "mount")->mnt_parent)) {
		/*
		 * The following is the script language equivalent of:
		 *
		 *    return container_of(vfsmnt, struct mount, mnt);
		 *
		 * We can't do the above because 'struct mount' is
		 * defined in a private header (in fs/mount.h).  But,
		 * we can do the script language equivalent (because
		 * we've got dwarf info).
		 *
		 * More spelled out in C, the above would look like:
		 *
		 *    return (vfsmnt - offsetof(struct mount, mnt));
		 * 
		 * Notice we're casting 0 here on purpose to find the
		 * offset of the 'mnt' member of 'struct mount.
		 */
		offset = &@cast(0, "mount")->mnt
		if (vfsmnt < 0 || vfsmnt > offset)
			return (vfsmnt - offset)
	}
	return 0
}

/**
 *   sfunction task_dentry_path - get the full dentry path
 *
 *   Returns the full dirent name (full path to the root), like
 *   the kernel d_path function.
 *   @task: task_struct pointer.
 *   @dentry: direntry pointer.
 *   @vfsmnt: vfsmnt pointer.
 */
function task_dentry_path:string(task:long,dentry:long,vfsmnt:long)
{
        root = & @cast(task, "task_struct")->fs->root

        while (1) {
		# If we've found the right dentry/vfsmnt, we're done.
		#
		# (Why check for 'struct path' and 'struct vfs_path'?
		# Later RHEL5 kernels renamed 'struct path' to 'struct
		# vfs_path'.)
		if (@defined(@cast(0, "path")->dentry)
		    ? (dentry == @cast(root, "path")->dentry
		       && vfsmnt == @cast(root, "path")->mnt)
		    : (dentry == @cast(root, "vfs_path")->dentry
		       && vfsmnt == @cast(root, "vfs_path")->mnt))
                        break;

                if (dentry == @cast(vfsmnt, "vfsmount")->mnt_root ||
                    __dentry_IS_ROOT(dentry)) {
			if (! @defined(@cast(0, "vfsmount")->mnt_parent)) {
				mnt = real_mount(vfsmnt)
				if (mnt == 0)
					return "<unknown>"

				/* Global root? */
				if (@cast(mnt, "mount")->mnt_parent == vfsmnt)
					return sprintf("/%s", name);

				dentry = @cast(mnt, "mount")->mnt_mountpoint
				vfsmnt = &@cast(mnt, "mount")->mnt_parent->mnt
			}
			else {
				/* Global root? */
				if (@cast(vfsmnt, "vfsmount")->mnt_parent
				    == vfsmnt)
					return sprintf("/%s", name);

				dentry = @cast(vfsmnt, "vfsmount")->mnt_mountpoint
				vfsmnt = @cast(vfsmnt, "vfsmount")->mnt_parent
			}
                        continue;
                }
                name = __dentry_prepend(dentry, name);
                dentry = @cast(dentry, "dentry")->d_parent;
        }

        return sprintf("/%s", name);
}



/**
 *   sfunction d_path - get the full nameidata path
 *
 *   Returns the full dirent name (full path to the root), like
 *   the kernel d_path function.
 *   @nd: Pointer to nameidata.
 */
function d_path:string(nd:long)
{
	dentry = @choose_defined(@cast(nd,"nameidata")->path->dentry,
			         @cast(nd,"nameidata")->dentry)
	vfsmnt = @choose_defined(@cast(nd,"nameidata")->path->mnt,
			         @cast(nd,"nameidata")->mnt)

	return sprintf("%s/", task_dentry_path(task_current(), dentry, vfsmnt))
}

