How to determine whether a file is open in a bash script on FreeBSD

1.3k views Asked by At

I'm trying to port a Linux bash script to FreeBSD. The script needs to check if a file is open (for writing) before deciding whether or not to take some action.

On Linux, this was easy using the fuser command:

if [[ ! `/usr/bin/fuser "$FILE"` ]]
     then
     ...
fi

However, on FreeBSD the fuser command seems totally broken (borne out by this) and doesn't return any proper exit codes or indeed any useful output to stdout. For example, on a file which is actively being written to:

# fuser file 
file:

Edit:


Vladimir Botka's comment:

"Simple test in FreeBSD 12.0 shows":

# tail -f /scratch/test-file`
# fuser /scratch/test-file
/scratch/test-file: 45042
# echo $?
0
# ps ax | grep 45042
45042  0  I+       0:00.00 tail -f /scratch/test-file
45232  1  R+       0:00.00 grep 45042

On my FreeBSD box (also FreeBSD 12), the same test yields the following:

# tail -f /scratch/test-file
# fuser /scratch/test-file
/scratch/test-file:
# echo $?
0

Vladimir Botka's comment:

Let's try and test the writing to a file with a simple C program which opens the file, waits for an input and writes the input to the file.

Here is my test on the compiled C code:

# ./a.out
Enter num:

# fuser /scratch/test-file
/scratch/test-file:
# echo $?
0

Therefore, fuser does seem to be broken. However, it only seems broken on my system, not on Vladimir Botka's system, which is strange as they're both FreeBSD 12.


It seems I could use lsof or fstat to get this information but not without some complex parsing of the output which is going to make my script more complicated. I wondered if anyone can point me towards a simple 'yes/no' command to determine whether a file is in use like fuser that will work on FreeBSD?

4

There are 4 answers

2
Stuart Howard On BEST ANSWER

I conclude that fuser is broken in FreeBSD. In the end I went with a parse of the fstat output to solve the problem. fstat produces the following output on a file which is being actively written (and there is also another process reading it, hence the two lines):

# fstat file 
USER     CMD          PID   FD MOUNT      INUM MODE         SZ|DV R/W NAME
root     smbd       36299   41 /data    1282756 -rwxr--r--  7407140864 rw  file
root     smbd       36295   30 /data    1282756 -rwxr--r--  7407140864  r  file

The following bash code tests the file using fstat, removes the header line (sed), then uses awk to grab the 9th column, that which is concerned with whether the file is open for reading or writing. A grep then follows to look for the w write flag in any of the lines. If it finds one, it will return true. The whole condition is negated (!) to ensure that the action is only carried out if the file is NOT in use:

 if [[ ! `/usr/bin/fstat "$FILE" | sed 1d | awk '{print $9}' | grep "w"` ]]
 then
    ... do something ...
 fi
0
Vladimir Botka On

Your claim

"on FreeBSD the fuser command seems totally broken"

refers to Port details: fuser POSIX fuser utility for FreeBSD which is deprecated and expired on: 2012-11-26

Let's try this simple test in FreeBSD 12.0. Open a terminal and run

# touch /scratch/test-file
# tail -f /scratch/test-file

Keep tail waiting for the content of the test-file. Open a second terminal and run

# fuser /scratch/test-file
/scratch/test-file: 45042
# echo $?
0
# ps ax | grep 45042
45042  0  I+       0:00.00 tail -f /scratch/test-file
45232  1  R+       0:00.00 grep 45042

Let's try and test the writing to a file with a simple C program which opens the file, waits for an input and writes the input to the file.

#include <stdio.h>
#include <stdlib.h>
int main()
{
   int num;
   FILE *fptr;
   fptr = fopen("/scratch/test-file","w");
   printf("Enter num: ");
   scanf("%d",&num);
   fprintf(fptr,"%d",num);
   fclose(fptr);
   return 0;
}

# ./a.out 
Enter num: 

Keep the program waiting for the input. Open a third terminal and run

# fuser /scratch/test-file
/scratch/test-file: 45448w 45042
# echo $?
0
# ps ax | grep 45448
45448  0  I+       0:00.00 ./a.out
45464  1  S+       0:00.00 grep 45448

If a file is opened for writing fuser appends "w" to PID and reports

w ... The file is open for writing.

Simple bash script

#!/usr/local/bin/bash
my_file=/scratch/test-file
result=`fuser $my_file 2>&1`
pid=`echo $result | cut -d ':' -f 2-`
if [ -z "$pid" ]; then
    echo "$my_file is not opened by any process"
else
    echo "$my_file is opened by process PID(s): $pid"
fi

shows

# ./test.bash
/scratch/test-file is opened by process PID(s): 45448w 45042

respectively

# ./test.bash
/scratch/test-file is not opened by any process

If this does not work for you see How to create a Minimal, Complete, and Verifiable example to provide details.

1
nbari On

If fuser not working in your system and if is ok to do some parsing give a try to fstat, for example, while having a file open:

$ cat > /tmp/test

This is the output of fstat /tmp/test:

# fstat /tmp/text
USER     CMD          PID   FD MOUNT      INUM MODE         SZ|DV R/W NAME
root     cat        39590    1 [restricted]   3812 -rw-r--r--       0  w  /tmp/text

If the file is not in use you will only get the headers:

# fstat /tmp/text
USER     CMD          PID   FD MOUNT      INUM MODE         SZ|DV R/W NAME

So then you could use something like:

[ `fstat /tmp/test | wc -l` -gt 1 ] && echo "in use"

or

test `fstat /tmp/test | wc -l` -gt 1 && echo "in use"
0
Gavin Jackson On

What you have described as 'broken', could also be proper behaviour depending on the process owner privileges.

You would only be able to see your own processes via fuser, unless you had root or equivalent privileges and could see other user's processes. Make sure that you are running both sessions as the same / root user.