DTrace: Howto get struct fileinfo_t from file descriptor

In Solaris, there is a fds function to get file information from a file descriptor. The resulting structure is defined as follows:

Code:
typedef struct fileinfo {
        string fi_name;         /* name (basename of fi_pathname) */
        string fi_dirname;      /* directory (dirname of fi_pathname) */
        string fi_pathname;     /* full pathname */
        offset_t fi_offset;     /* offset within file */
        string fi_fs;           /* filesystem */
        string fi_mount;        /* mount point of filesystem */
        int fi_oflags;          /* open(2) flags for file descriptor */
} fileinfo_t;

The task to get the file name from a file descriptor is non trivial in FreeBSD. Some of the hard work has already done by Sergey Slobodyanyuk in http://docs.freebsd.org/cgi/getmsg....2012/freebsd-hackers/20120610.freebsd-hackers.

A note of caution, the following constructs should be defined in syscall:freebsd::entry probes and not in syscall:freebsd::return probes. Otherwise, you may see a lot of errors of the type: INVALID ADDRESSES. (Some common syscalls like read(2) takes a file descriptor as one of its argument.)

First we get a file pointer to the file represented by the file descriptor args[0]. Then from the file pointer we get a vnode pointer.

Code:
this->fp = curthread->td_proc->p_fd->fd_ofiles[args[0]];
this->vp = this->fp != 0 ? this->fp->f_vnode : 0;

The following two constructs are used so that we would not reference non existence objects. Note that they can also be deferred to the corresponding return probe. But remember to replace this->vp with self->vp in this case. The two long constructs are to make sure that the corresponding entries exists, or you will have all those pesky INVALID ADDRESSES errors.

Code:
this->ncp = this->vp != NULL ? (&(this->vp->v_cache_dst) != NULL ? 
        this->vp->v_cache_dst.tqh_first : 0) : 0;
this->fi_name = this->ncp ? (this->ncp->nc_name != 0 ? 
        stringof(this->ncp->nc_name) : "<unknown>") : "<unknown>"; 
/* Here we may possibly get the filename */

This take care of the first item in fileinfo_t. The following two items in that structure will be deferred to the last part of this howto. With both the file and vnode pointers, getting the remaining 4 items are easy.

Code:
this->fi_offset = this->fp != 0 ? this->fp->f_offset : 0;       /* file offset */
this->ftype = this->fp != 0 ? this->fp->f_type : 0;             /* file type */
this->mount = this->vp != NULL ? this->vp->v_mount : 0;         /* ptr to vfs we are in */
this->fi_fs = this->mount ? stringof(this->mount->mnt_stat.f_fstypename) : "<unknown>";
/* type of filesystem */
this->fi_mount = this->mount ? stringof(this->mount->mnt_stat.f_mntonname) : "<unknown>";
/* where we mount on */
this->fi_oflags = curthread->td_proc->p_fd->fd_ofileflags[arg0]; 
/* open flags for file descriptor */

As fi_pathname is just fi_dirname + fi_name, we only need to get fi_dirname. Unfortunately, this is not so easy and with many pitfalls. We get the parent directory with the following constructs:

Code:
/this->fi_name != "unknown"/
this->dvp = this->ncp->nc_dvp ? (&(this->ncp->nc_dvp->v_cache_dst) != NULL 
        ? (this->ncp->nc_dvp->v_cache_dst.tqh_first : 0) : 0;
this->d_name = this->dvp ? (this->dvp->nc_name != 0 ? 
        stringof(this->dvp->nc_name) ? "\0") : "\0";

Afterwards, iterating with the following constructs should give us the full directory name.

Code:
/this->dvp/
this->dvp = this->ncp->nc_dvp ? (&(this->ncp->nc_dvp->v_cache_dst) != NULL 
        ? (this->ncp->nc_dvp->v_cache_dst.tqh_first : 0) : 0;
this->d_name = this->dvp ? (this->dvp->nc_name != 0 ? 
        stringof(this->dvp->nc_name) ? "\0") : "\0";

While this may seem easy, remember that DTrace has no loop structure. So you have to repeat the above codes a few times, effectively limits the depth of directory. Moreover, I have discovered to my own chagrin that the strjoin has a number of limitation. The following is the code that I used to get the full pathname:

Code:
#pragma D option quiet
#pragma D option switchrate=10hz
#pragma D option dynvarsize=16m
#pragma D option bufsize=8m

syscall:freebsd:pread:entry
{
        this->fp = curthread->td_proc->p_fd->fd_ofiles[arg0];
        this->vp = this->fp != 0 ? this->fp->f_vnode : 0;
        this->ts = vtimestamp;
        @c = count();
}

syscall:freebsd:pread:entry
/this->vp/
{
        this->ncp = &(this->vp->v_cache_dst) != NULL ? 
                this->vp->v_cache_dst.tqh_first : 0;
        this->fi_name = this->ncp ? (this->ncp->nc_name != 0 ? 
                stringof(this->ncp->nc_name) : "<unknown>") : "<unknown>";
        this->mount = this->vp->v_mount; /* ptr to vfs we are in */
        this->fi_fs = this->mount != 0 ? stringof(this->mount->mnt_stat.f_fstypename) 
                : "<unknown>"; /* filesystem */
        this->fi_mount = this->mount != 0 ? stringof(this->mount->mnt_stat.f_mntonname) 
                : "<unknown>";
}

syscall:freebsd:pread:entry
/* A short cut */
/this->vp == 0 || this->fi_fs == "devfs" || this->fi_fs == 0 || 
this->fi_fs == "<unknown>" || this->fi_name == "<unknown>"/
{
        this->ncp = 0;
}

syscall:freebsd:pread:entry
/this->ncp/
{
        this->dvp = this->ncp->nc_dvp != NULL ? 
               (&(this->ncp->nc_dvp->v_cache_dst) != NULL ? 
               this->ncp->nc_dvp->v_cache_dst.tqh_first : 0) : 0;
        self->name[1] = this->dvp != 0 ? (this->dvp->nc_name != 0 ? 
               stringof(this->dvp->nc_name) : "<unknown>") : "<unknown>";
}

syscall:freebsd:pread:entry
/self->name[1] == "<unknown>" || this->fi_fs == "devfs" || 
this->fi_fs == 0 || this->fi_fs == "<unknown>" || self->name[1] == "/" 
|| self->name[1] == 0/
{
        this->dvp = 0;
}

syscall:freebsd:pread:entry
/this->dvp/
{
        this->dvp = this->dvp->nc_dvp != NULL ? (&(this->dvp->nc_dvp->v_cache_dst) != NULL 
                ? this->dvp->nc_dvp->v_cache_dst.tqh_first : 0) : 0;
        self->name[2] = this->dvp != 0 ? (this->dvp->nc_name != 0 ? 
                stringof(this->dvp->nc_name) : "\0") : "\0";
}

syscall:freebsd:pread:entry
/this->dvp/
{
        this->dvp = this->dvp->nc_dvp != NULL ? (&(this->dvp->nc_dvp->v_cache_dst) != NULL 
                ? this->dvp->nc_dvp->v_cache_dst.tqh_first : 0) : 0;
        self->name[3] = this->dvp != 0 ? (this->dvp->nc_name != 0 ? 
                stringof(this->dvp->nc_name) : "\0") : "\0";
}

syscall:freebsd:pread:entry
/this->fi_mount/
{
        printf("%s/", this->fi_mount);
}

syscall:freebsd:pread:entry
/self->name[3]/
{
        printf("%s/", self->name[3]);
}

syscall:freebsd:pread:entry
/self->name[2]/
{
        printf("%s/", self->name[2]);
}

syscall:freebsd:pread:entry
/self->name[1]/
{
        printf("%s/%s\n", self->name[1], this->fi_name);
}

syscall:freebsd:pread:entry
{
        self->name[1] = 0;
        self->name[2] = 0;
        self->name[3] = 0;
}

tick-10s
{
        exit(0);
}

Here I only go up to 3 parent directories. Feel free to point out any mistakes that I had made. I am sure there are quite a few.

References: sys/sys/file.h, sys/sys/filedesc.h, sys/sys/vnode.h, sys/sys/mount.h, sys/sys/proc.h.
 
Back
Top