1

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?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
  • The Linux script is broken or at least clumsy too, you want simply `if ! fuser "$FILE" >/dev/null` and probably the variable should be lower case, too. – tripleee Feb 19 '19 at 06:04
  • Looks like the MacOS `fuser` doesn't set its exit code to reflect the result though. – tripleee Feb 19 '19 at 06:10
  • I also tried it with a fresh LiveCD from FreeBSD 12. Broken on that as well. – Stuart Howard Feb 19 '19 at 19:26
  • This is a misunderstanding. Keep tail waiting for the content of the test-file. Open a second terminal and run fuser. Also keep the program waiting for the input. Open third terminal and run fuser. I've edited my answer and provided you with the details. – Vladimir Botka Feb 20 '19 at 06:12
  • This is exactly what I have done. I have just re-tested and each time fuser produces no output apart from: `/scratch/test-file:`. There are no process numbers output even though the file is definitely in use (confirmed by `fstat`). fuser always returns a 0 exit code. I'd be interested to see which version of fuser is working properly for you. Mine does not produce a version number (i.e. `fuser -v`, `fuser -V`, `fuser --version` all do not work) but the FreeBSD version is 12.0-RELEASE-p2. – Stuart Howard Feb 20 '19 at 21:39
  • What you have described a '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. Are you running both sessions as the same / root user ? – Gavin Jackson Feb 28 '19 at 16:54

4 Answers4

2

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.

Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
1

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

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"
nbari
  • 25,603
  • 10
  • 76
  • 131
  • Thank you for this. I have Samba running on the server, which means that unfortunately `fstat` _always_ lists smbd as a process using every file. – Stuart Howard Feb 19 '19 at 18:39
0

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.

Gavin Jackson
  • 1,907
  • 1
  • 22
  • 28