#!/usr/bin/perl -w
#
# lsfd - list open file descriptors. Solaris 10, Perl.
#
# This program lists processes and their open file descriptors with the 
# path to each file they have open. This is made easy by a new procfs
# structure in Solaris 10.
#
# NOTE: The path printed isn't 100% accurate! for example, in the case of
# hard links, the path printed could be any of the names for that inode.
#
# 23-Jan-2006, ver 0.85     (check for newer versions)
#
# USAGE:    lsfd [-hl] [-n name] [-p PID]
#
#           -h          # help
#           -l          # long output, includes command name
#           -n name     # match on this command name
#           -p PID      # match on this PID
#    eg,
#           lsfd                # default output: PID, FD, PATH
#           lsfd -p 386         # examine PID 386
#           lsfd -n syslogd     # match processes called syslogd
#
# NOTE: Running this as root will display more output; as a regular
# user we can only see processes that we own. PATHs that appear blank
# may be sockets - lsfd cannot print their data.
#
# SEE ALSO: lsof     # this works for all versions of Solaris, prints sockets.
#           pfiles   # updated in Solaris 10
#
# COPYRIGHT: Copyright (c) 2004 Brendan Gregg.
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#  (http://www.gnu.org/copyleft/gpl.html)
#
# Author: Brendan Gregg  [Sydney, Australia]
#
# 05-Apr-2005   Brendan Gregg   Created this.
# 23-Jan-2006      "      "     Tweaked style.

use strict;
use Getopt::Std;

#
#  Process Arguments
#
getopts('hln:p:') or usage();
usage() if defined $main::opt_h;
my $LONG = defined $main::opt_l ? $main::opt_l : 0;
my $PID  = defined $main::opt_p ? $main::opt_p : 0;
my $NAME = defined $main::opt_n ? $main::opt_n : "";
$main::opt_h = 0;
my $PRFNSZ = 12;                            # chars of COMM to print
my $comm;
my @Out;

#
#  Check for Solaris 10
#
if (! -e "/proc/$$/path") {
    print STDERR "ERROR1: Can't find /proc/PID/path,\n";
    print STDERR "        This needs Solaris 10 or newer.\n";
    exit 1;
}

#
#  Fetch PIDs
#
chdir "/proc";
my @PIDs = $PID ? $PID : <*>;

#
#  Fetch Paths
#
foreach my $pid (@PIDs) {

    ### Try path dir,
    chdir "/proc/$pid/path" or next;

    ### Fetch the numerical FDs,
    my @FD = grep(/^\d+$/, <*>);

    ### Fetch the path data,
    foreach my $fd (@FD) {
        my $path = readlink $fd;
        $path = defined $path ? $path : "";

        if ($LONG or $NAME ne "") {
            # determine command name
            $comm = readlink "a.out";
            $comm =~ s:.*/::;
            $comm = substr $comm, 0, $PRFNSZ;
        }

        # check for name
        next if $NAME ne "" and $NAME ne $comm;

        # don't print yet - this loop needs to be quick,
        if ($LONG) {
            push @Out,
                 sprintf "%5d %-${PRFNSZ}s %4d %s\n", $pid, $comm, $fd, $path;
        }
        else {
            push @Out, sprintf "%5d %4d %s\n", $pid, $fd, $path;
        }
    }
}

#
#  Print Report
#
if ($LONG) {
    printf "%5s %-${PRFNSZ}s %4s %s\n", "PID", "COMM", "FD", "PATH";
}
else {
    printf "%5s %4s %s\n", "PID", "FD", "PATH";
}
print @Out;

# usage - print usage message and exit
#
sub usage {
    print STDERR <<END;
USAGE:    lsfd [-hl] [-n name] [-p PID]

          -h          # help
          -l          # long output, includes command name
          -n name     # match on this command name
          -p PID      # match on this PID
   eg,
          lsfd                # default output: PID, FD, PATH
          lsfd -p 386         # examine PID 386
          lsfd -n syslogd     # match processes called syslogd
END
    exit 2;
}
