Filehandles, files, directories

lstat#

Return the 13-element status list for a path without following a symbolic link.

lstat does exactly what stat does — same 13-element result, same special _ filehandle cache, same failure semantics — with one difference: if the argument names a symbolic link, lstat returns information about the link itself (its own mode, owner, size, mtime), not the target the link points to. For anything that is not a symbolic link, lstat and stat are indistinguishable. On systems without symbolic links lstat silently degrades to stat.

Synopsis#

lstat FILEHANDLE
lstat EXPR
lstat DIRHANDLE
lstat

What you get back#

In list context, the same 13-element list as stat:

my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
    $atime,$mtime,$ctime,$blksize,$blocks)
       = lstat($path);

Field meanings are identical to stat — device, inode, mode (type + permissions), link count, owner uid/gid, rdev, size, atime, mtime, ctime, preferred I/O block size, and allocated blocks. The type bits in $mode are what distinguish a symbolic link: on the link itself they report S_IFLNK, whereas stat on the same name would report the type of the target.

In scalar context lstat returns a true value on success and the empty string on failure. On failure the list form returns the empty list and $! is set to the reason (ENOENT, EACCES, etc.).

If EXPR is omitted, lstat operates on $_.

The _ filehandle#

Every successful lstat (and every successful stat or filetest) populates the interpreter’s internal “last stat buffer”. Passing the bareword _ as the argument reuses that buffer instead of making another system call:

if (-l $path && (my @s = lstat(_))) {
    # $path was a symlink; @s describes the link, not the target
}

Because -l itself does an lstat under the hood, the lstat(_) above costs nothing extra. This is the canonical way to “ask several questions about one file without re-stat’ing it”. Note that a filetest on a bareword filename (-l $path) leaves the buffer describing the link; a filetest on a filehandle follows the open descriptor and thus leaves a stat-shaped buffer. When you want symlink data, reach for lstat explicitly.

Global state it touches#

  • $_ — the argument when EXPR is omitted.

  • $! — set on failure to the errno from the underlying lstat(2) call.

  • The special _ filehandle — overwritten on every successful call.

Examples#

Distinguish a symlink from its target. stat on a symlink silently follows it; lstat does not:

symlink "/etc/passwd", "link.tmp";

my $via_stat  = (stat  "link.tmp")[7];  # size of /etc/passwd
my $via_lstat = (lstat "link.tmp")[7];  # size of the link inode
                                        # (length of "/etc/passwd")

Walk a directory and separate links from regular files without a second syscall per entry, using the _ buffer:

use Fcntl ':mode';

for my $name (readdir $dh) {
    next if $name eq '.' || $name eq '..';
    lstat "$dir/$name" or next;
    if (S_ISLNK((lstat(_))[2])) {
        print "link: $name\n";
    } elsif (-f _) {
        print "file: $name\n";
    } elsif (-d _) {
        print "dir:  $name\n";
    }
}

Detect a dangling symlink — lstat succeeds on the link, stat fails because the target is gone:

if (lstat $path and not stat $path) {
    warn "$path is a dangling symlink: $!\n";
}

Follow the link manually when you want both pieces of information:

my @link   = lstat $path       or die "lstat $path: $!";
my $target = readlink $path    // die "readlink $path: $!";
my @target = stat $path        or die "stat $path: $!";

No-argument form operating on $_, idiomatic in File::Find preprocess blocks and grep filters:

my @symlinks = grep { lstat; -l _ } @paths;

Edge cases#

  • Not a symlink: lstat behaves exactly like stat — there is no penalty to preferring lstat when you are about to branch on -l.

  • Systems without symlinks: lstat silently falls back to stat. Code written against lstat stays portable; it just loses the distinguishing behaviour.

  • Filehandle / dirhandle argument: a passed FILEHANDLE or DIRHANDLE refers to an already-opened descriptor, which cannot itself be a symlink (the kernel resolved the link at open time). lstat FILEHANDLE therefore returns the same result as stat on that handle. Use the EXPR form on the path when you need the link metadata.

  • lstat(_) after a failed lstat: the _ buffer is left in whatever state the last successful stat put it in. A failed lstat does not clear it. Always check the return value before reading _.

  • Relative paths: resolved against the process’s current working directory, same as stat.

  • Permission to lstat: requires search (x) permission on every directory component of the path, but — unlike stat — does not require any permission on the link target. lstat therefore succeeds on links into directories you cannot read.

  • Bareword vs expression: lstat $fh where $fh holds a filehandle stats the handle; lstat "$fh" stringifies $fh and stats the resulting (usually meaningless) pathname. Quote intentionally.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • stat — the link-following counterpart; identical return shape and _ semantics

  • readlink — read the target string of a symbolic link without stat’ing it

  • symlink — create a symbolic link

  • -l — filetest operator for “is a symbolic link”; internally performs an lstat and leaves the result in _

  • $_ — default argument when EXPR is omitted

  • $! — reason for failure when lstat returns the empty list