9

I want to reproduce the output of ls --full-time from a Perl script to avoid the overhead of calling ls several thousand times. I was hoping to use the stat function and grab all the information from there. However, the timestamp in the ls output uses the high-resolution clock so it includes the number of nanoseconds as well (according to the GNU docs, this is because --full-time is equivalent to --format=long --time-style=full-iso, and the full-iso time style includes the nanoseconds).

I came across the Time::HiRes module, which overrides the standard stat function with one that returns atime/mtime/ctime as floating point numbers, but there's no override for lstat. This is a problem, because calling stat on a symlink returns info for the linked file, not for the link itself.

So my question is this - where can I find a version of lstat that returns atime/mtime/ctime in the same way as Time::HiRes::stat? Failing that, is there another way to get the modtime for a symlink in high resolution (other than calling ls).

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
kbro
  • 4,754
  • 7
  • 29
  • 40
  • 2
    Do you need a portable solution or is there just one target operating system? – mob Mar 18 '10 at 15:46

4 Answers4

3

If you are willing to use Inline::C, this will work with recent linux

#!/usr/bin/perl

use strict;
use warnings;

use Inline C => <<'EOC';

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

long mtime_nsec(char* fname)
{
    struct stat st;
    if (-1 == lstat(fname, &st))
        return -1;
    return (long)st.st_mtim.tv_nsec;
}
EOC

print mtime_nsec($ARGV[0]);
niry
  • 3,238
  • 22
  • 34
2

For the record, lstat has been added to Time-HiRes version 1.9726 in August 2013.

See https://rt.cpan.org/Public/Bug/Display.html?id=83356 for details.

However, it's still 1.9725 that's included in the latest stable version of perl as of 2014-01-31 (5.18.2). It was bumped to 1.9726 in the development version in 5.19.3 though.

Note that (as of perl 5.19.8), regardless of whether Time::HiRes's lstat is used or not, perl's -M/-A/-C still don't do sub-second granularity (files with time in the same second will be shown as having the same age), so you still can't do things like sort {-M $a <=> -M $b} @files to sort files by modification time.

Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31
  • As of `perl` 5.22 (cygwin) and 5.16 (RHEL 7.2), I'm seeing differences in the 6th decimal place in the sub-second part returned by `Time::HiRes::stat` and the value returned by the `C` library `stat` function. This causes differences in the timestamps reported by `perl` and `find -printf %TS`, for example. – jrw32982 Oct 03 '19 at 20:21
  • This might be related to `perl` representing the timestamp as a single floating-point number vs. the `C` library representing it as two integers. – jrw32982 Oct 03 '19 at 20:30
2

Your best bet would be to ask for lstat to be added to Time::HiRes. In fact, you could probably do it yourself. I'd bet that all you need to do is copy the function that starts

void
stat(...)

in HiRes.xs, change stat(...) to lstat(...) & OP_STAT to OP_LSTAT, add lstat to @EXPORT_OK in HiRes.pm, and recompile. Then submit a patch so others can benefit.

cjm
  • 61,471
  • 9
  • 126
  • 175
  • Changing OP_STAT to OP_LSTAT would not work as PL_ppaddr[OP_LSTAT] is the same as PL_ppaddr[OP_STAT] since Perl_pp_lstat is internally implemented by Perl_pp_stat... But +1 for the fact that your answer gave me an idea of how to approach the problem. – Bharath K Jan 11 '12 at 12:20
1

The following changes work. This essentially contains changes to both the HiRes.pm module as well as the xs file.

In HiRes.pm

sub lstat { 
     my @lstatvalues = CORE::lstat(shift);   
     my @nanosecvalues =  Time::HiRes::lstatimplementation( $lstatvalues[8], $lstatvalues[9], $lstatvalues[10]);   
     ( $lstatvalues[8], $lstatvalues[9], $lstatvalues[10] ) = ( $nanosecvalues[0], $nanosecvalues[1], $nanosecvalues[2]);   
     return @lstatvalues;
}

Also added lstat to @EXPORT_OK list.

In HiRes.xs

void 
lstatimplementation(...)
PPCODE:
  UV atime = SvUV( ST( 0 ) );
  UV mtime = SvUV( ST( 1 ) );
  UV ctime = SvUV( ST( 2 ) );
  UV atime_nsec;
  UV mtime_nsec;
  UV ctime_nsec;
  hrstatns(atime, mtime, ctime,
       &atime_nsec, &mtime_nsec, &ctime_nsec);
  if (atime_nsec)
    XPUSHs( sv_2mortal(newSVnv(atime + 1e-9 * (NV) atime_nsec)));
  if (mtime_nsec)
    XPUSHs( sv_2mortal(newSVnv(mtime + 1e-9 * (NV) mtime_nsec)));
  if (ctime_nsec)
    XPUSHs( sv_2mortal(newSVnv(ctime + 1e-9 * (NV) ctime_nsec)));
Bharath K
  • 2,109
  • 1
  • 14
  • 18
  • Have you sent in a patch to [RT](https://rt.cpan.org/Public/Dist/Display.html?Name=Time-HiRes)? – Brad Gilbert Jan 11 '12 at 14:33
  • @BradGilbert - Not yet. I also found another way to complete this change entirely in the xs file. still deciding which would be the better option – Bharath K Jan 11 '12 at 16:37