5

I'm writing some Perl which takes TV shows recorded on Windows Media Center and moves/renames/deletes them depending on certain criteria.

Since the Perl runs fairly frequently, I'd like to cleanly determine whether or not the file is in use (in other words, the show is in the process of being recorded) so I can avoid doing anything with it.

My current method looks at the status of a file (using "stat") and compares it again after 5 seconds, like so:

sub file_in_use
{
  my $file = shift;

  my @before = stat($file);
  sleep 5;
  my @after = stat($file);

  return 0 if ($before ~~ $after);
  return 1;
 }

It seems to work, but I'm concious that there is probably a better and cleaner way to do this.

Can you please advise?

Richard
  • 1,471
  • 7
  • 23
  • 47
  • This is a fine approch to it and i don't belive you will find many options around ... another way of doing it would be by reading the application pid and move the files once the pid has gone off... – Prix Jul 10 '10 at 17:45
  • 1
    From Prix (I think it's that important to point out): *If the file is being used full time and not released you cannot perform a move [or delete] on the file as it will produce an error...*. (A copy may work depending upon the various file mode settings.) Even with the stat approach, you must *still* handle the FS scenario and use the `file_in_use` as a 'hint' only (although the potential race conditions may never materialize here). –  Jul 10 '10 at 18:44
  • @pst True, which is why i think move is a fine way to go ... since if the file is still locked it will either return 0 or tell you an error depending on how you code it and you can just keep trying until it is released or so... – Prix Jul 10 '10 at 19:06
  • Good points. I've just found a fatal flaw in my original logic, turns out that the results of "stat" don't change during a recording. So the only way I can check if a file is locked is rename it and check the return code. – Richard Jul 10 '10 at 19:43
  • actually that is because the file is being written and locked but instead of being written on the fly it only creates and open the file and after the record is done, close the file with the data and updates the file. – Prix Jul 10 '10 at 22:33

3 Answers3

8

If the recording process locks the file, you could attempt to open it in read-write mode and see if it fails with ERROR_SHARING_VIOLATION as GetLastError (accessed via Perl's $^E special variable).

For example:

#! /usr/bin/perl

use warnings;
use strict;

sub usage { "Usage: $0 file ..\n" }

die usage unless @ARGV;

foreach my $path (@ARGV) {
  print "$path: ";

  if (open my $fh, "+<", $path) {
    print "available\n";
    close $fh;
  }
  else {
    print $^E == 0x20 ? "in use by another process\n" : "$!\n";
  }
}

Sample output with Dir100526Lt.pdf open by the Adobe reader:

C:\Users\Greg\Downloads>check-lock.pl Dir100526Lt.pdf setup.exe
Dir100526Lt.pdf: in use by another process
setup.exe: available

Be aware that any time you first test a condition and then later act based on the result of that test, you're creating a race condition. It seems that the worst this could bite you in your application is in the following unlucky sequence:

  1. test a video for availability as above
  2. answer: available!
  3. in the meantime, a recorder starts up and locks the video
  4. back in your program, you try to move the video, but it fails with a sharing violation
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
  • Perfect! Thank you very much. Good point about the race condition - as such I'm hoping that, once the recording has finished, there should be no reason for it to start it back up again. – Richard Jul 10 '10 at 20:34
  • cookie for you i like that special variable... just a quick question, if he knows the file he needs to check and do check it is availability at the time and lock it right away it would be like miliseconds until another application can get a hold of it so it would be like very very very hard to happen no ? – Prix Jul 10 '10 at 22:30
  • 1
    @Prix A millisecond is a long time to a computer. Unlikely means that it *will* happen sometimes, and these sorts of errors tend to be difficult to debug. Granted, in this case, it's probably not worth the fuss, but it's good for us to train ourselves to spot synchronization cracks. Rather than checking for availability and then locking, acquire the lock straight away. If someone else has it, the system will give it to you as soon as it's available. Otherwise, you know you have it, and it won't appear available to anyone else. – Greg Bacon Jul 10 '10 at 22:58
  • Is `0x20` documented somewhere? How did you arrive at this number? – René Nyffenegger Jul 15 '14 at 11:56
  • @RenéNyffenegger See `ERROR_SHARING_VIOLATION` on [System Error Codes on MSDN](http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx#ERROR_SHARING_VIOLATION). – Greg Bacon Jul 15 '14 at 14:45
1

The only improvement I would suggest is to stat all of your files at once, so you only need to sleep for 5 seconds one time instead of sleeping 5 seconds for every file:

my (%before, %after);
foreach my $file (@files_that_might_be_in_use) {
    $before{$file} = [ stat $file ];
}
sleep 5;
foreach my $file (@files_that_might_be_in_use) {
    $after{$file} = [ stat $file ];

    if ( $before{$file} ~~ $after{$file} ) {
        # file is not in use ... 
    } else {
        # file is in use ... 
    }
}
mob
  • 117,087
  • 18
  • 149
  • 283
0

A windows API exists that will list the number of processes that keep a file open. It is the Restart Manager (Rststmgr.dll). Based on artical How do I find out which process has a file open?, and with help of Win32::API you can transform the program in a Perl program that tells you the number of processes that have a file open and even list the process ids.

Each test should be a sequence of RmStartSession, RmRegisterResources, RmGetList, RmEndSession. Use encode("UTF-16LE",$filename) to make a CWSTR type string.

eremmel
  • 322
  • 1
  • 11