11

I am working on a multithreaded system where a file can be shared among different threads based on the file access permissions.

How can I check if file is already opened by another thread?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
user503403
  • 259
  • 1
  • 2
  • 14
  • 1
    You need either a physical lock, or define a semaphore across the thread (resides in a shared memory?) – CppLearner Sep 06 '13 at 01:08
  • 3
    The easiest thing is to keep a shared list of all opened files, and make it available to all threads. – Sergey Kalinichenko Sep 06 '13 at 01:09
  • 2
    Well the easiest way is to keep track of it yourself. – Duck Sep 06 '13 at 01:09
  • I have sharing permissions which I check before granting access to open a file in second thread, but still is there any file IO operations or any other method which I can use to check if file is opened or closed . – user503403 Sep 06 '13 at 01:21
  • 2
    As a side note: If you wanted to check if the file is already open (by *any other process or thread*), you can try getting a file write lease (`fcntl(fileno(stream),F_SETLEASE,F_WRLCK)`). It will fail if the file is already open by anyone else. This only works for normal files owned by the user. If anyone else tries to open the file, the lease owner will get a signal, and has up to `/proc/sys/fs/lease_break_time` seconds to manipulate the file and either release or downgrade the lease, before that other open will proceed (but it will, eventually, even if you rename or unlink the file). – Nominal Animal Sep 06 '13 at 02:08
  • 1
    Whatever problem this is meant to solve, it really doesn't solve it - you can only find out if the file was/wasn't opened - and that can change at any time (even *while* you're gathering your data...). So any answer you get is immediately obsolete and effectively meaningless. Using any answer found here is a [TOCTOU bug](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) waiting to happen. Per that Wiki link: "TOCTOU race conditions are common in Unix between operations on the file system." Not just Unix - Windows file systems too. – Andrew Henle May 22 '21 at 15:11
  • @AndrewHenle I beg to differ, my answer explains the TOCTOU problem (without using that abbreviation) and how to go around it by using a common resource instead of attempting to do it _the wrong way_. – Alexis Wilke May 22 '21 at 15:32
  • 1
    @AlexisWilke Your answer isn't merely *checking* for open files, it's also *controlling* the opening of files. – Andrew Henle May 22 '21 at 16:04

5 Answers5

4

To find out if a named file is already opened on , you can scan the /proc/self/fd directory to see if the file is associated with a file descriptor. The program below sketches out a solution:

DIR *d = opendir("/proc/self/fd");
if (d) {
    struct dirent *entry;
    struct dirent *result;

    entry = malloc(sizeof(struct dirent) + NAME_MAX + 1);
    result = 0;
    while (readdir_r(d, entry, &result) == 0) {
        if (result == 0) break;
        if (isdigit(result->d_name[0])) {
            char path[NAME_MAX+1];
            char buf[NAME_MAX+1];
            snprintf(path, sizeof(path), "/proc/self/fd/%s",
                     result->d_name);
            ssize_t bytes = readlink(path, buf, sizeof(buf));
            buf[bytes] = '\0';
            if (strcmp(file_of_interest, buf) == 0) break;
        }
    }
    free(entry);
    closedir(d);
    if (result) return FILE_IS_FOUND;
}
return FILE_IS_NOT_FOUND;

From your comment, it seems what you want to do is to retrieve an existing FILE * if one has already been created by a previous call to fopen() on the file. There is no mechanism provided by the standard C library to iterate through all currently opened FILE *. If there was such a mechanism, you could derive its file descriptor with fileno(), and then query /proc/self/fd/# with readlink() as shown above.

This means you will need to use a data structure to manage your open FILE *s. Probably a hash table using the file name as the key would be the most useful for you.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • I am using file pointers not the descriptor . So this solution will not help me – user503403 Sep 06 '13 at 01:49
  • 5
    There is `fileno(FILE)` macro that returns descriptor from file pointer. However, I would not scan `/proc/self/fd` simply because it is not atomic - other thread can still close or open file while you are scanning `/proc/self/fd`. – mvp Sep 06 '13 at 03:52
  • See the lsof source code at https://github.com/lsof-org/lsof. – Jonathan Ben-Avraham Feb 08 '21 at 12:05
2

You can use int flock(int fd, int operation); to mark a file as locked and also to check if it is locked.

   Apply or remove an advisory lock on the open file specified by fd.
   The argument operation is one of the following:

       LOCK_SH  Place a shared lock.  More than one process may hold a
                shared lock for a given file at a given time.

       LOCK_EX  Place an exclusive lock.  Only one process may hold an
                exclusive lock for a given file at a given time.

       LOCK_UN  Remove an existing lock held by this process.

flock should work in a threaded app if you open the file separately in each thread: multiple threads able to get flock at the same time

There's more information about flock and it's potential weaknesses here.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
dcaswell
  • 3,137
  • 2
  • 26
  • 25
  • Unfortunately, it will also affect other processes if they are trying to place lock on our file. Asker seems to want thread-only lock. – mvp Sep 06 '13 at 03:56
  • Good statement. I assumed he was doing fork/exec but maybe not. – dcaswell Sep 06 '13 at 03:59
  • This post does not answer the question posed in the OP. – Jonathan Ben-Avraham Feb 08 '21 at 07:36
  • @JonathanBen-Avraham Any actual answer to that question is immediately useless - it only tells you if the file ***was*** open, not if it ***is*** open. Actually trying to use any answer to the question as posed is a [TOCTOU bug](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use). – Andrew Henle May 22 '21 at 15:13
  • @AndrewHenle The point of my comment is that this post only answers a degenerate case where you 1) have access to the application code 2) can build the code 3) have permission from your management to change the code 3) have a license to change the code 4) it is feasible to re-design to the code without having to re-certify the final system. The OP said nothing about TOCTOU situations. If that had been the intention then he wouldn't have asked an impossible question. I up-voted jxh's post, which provides a working solution with only **one** condition, that the application runs on Linux. – Jonathan Ben-Avraham May 23 '21 at 15:18
2

If you tend to do it in shell, you can simply use lsof $filename.

wizawu
  • 1,881
  • 21
  • 33
  • 1
    This is inherently thread unsafe - as soon as it returns the answer can change. – Stewart Sep 06 '13 at 05:10
  • 1
    @Stewart Actually the answer can change while it runs... – Alexis Wilke May 22 '21 at 14:41
  • @AlexisWilke Yep. ***Any*** of the checks posted here to check if a file is opened by another thread or process both can change while the check runs ***and*** is immediately useless afterwards. – Andrew Henle May 22 '21 at 15:19
1

I don't know much in the way of multithreading on Windows, but you have a lot of options if you're on Linux. Here is a FANTASTIC resource. You might also take advantage of any file-locking features offered inherently or explicitly by the OS (ex: fcntl). More on Linux locks here. Creating and manually managing your own mutexes offers you more flexibility than you would otherwise have. user814064's comment about flock() looks like the perfect solution, but it never hurts to have options!

Added a code example:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

FILE *fp;
int counter;
pthread_mutex_t fmutex = PTHREAD_MUTEX_INITIALIZER;

void *foo() {
        // pthread_mutex_trylock() checks if the mutex is
        // locked without blocking
        //int busy = pthread_mutex_trylock(&fmutex);

        // this blocks until the lock is released
        pthread_mutex_lock(&fmutex);
        fprintf(fp, "counter = %d\n", counter);
        printf("counter = %d\n", counter);
        counter++;
        pthread_mutex_unlock(&fmutex);
}

int main() {

        counter = 0;
        fp = fopen("threads.txt", "w");

        pthread_t thread1, thread2;

        if (pthread_create(&thread1, NULL, &foo, NULL))
                printf("Error creating thread 1");
        if (pthread_create(&thread2, NULL, &foo, NULL))
                printf("Error creating thread 2");

        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);

        fclose(fp);
        return 0;
}
xikkub
  • 1,641
  • 1
  • 16
  • 28
0

If you need to determine whether another thread opened a file instead of knowing that a file was already opened, you're probably doing it the wrong way.

In a multithreaded application, you want to manage resources used in common in a list accessible by all the threads. That list needs to be managed in a multithread safe manner. This just means you need to lock a mutex, do things with the list, then unlock the mutex. Further, reading/writing to the files by more than one thread can be very complicated. Again, you need locking to do that safely. In most cases, it's much easier to mark the file as "busy" (a.k.a. a thread is using that file) and wait for the file to be "ready" (a.k.a. no thread is using it).

So assuming you have a form of linked list implementation, you can have a search of the list in a way similar to:

my_file *my_file_find(const char *filename)
{
    my_file *l, *result = NULL;

    pthread_mutex_lock(&fmutex);

    l = my_list_of_files;

    while(l != NULL)
    {
         if(strcmp(l->filename, filename) == 0)
         {
             result = l;
             break;
         }
         l = l->next;
    }

    pthread_mutex_unlock(&fmutex);

    return result;
}

If the function returns NULL, then no other threads had the file open while searching (since the mutex was unlocked, another thread could have opened the file before the function executed the return). If you need to open the file in a safe manner (i.e. only one thread can open file filename) then you need to have a my_file_open() function which locks, searches, adds a new my_file if not found, then return that new added my_file pointer. If the file already exists, then the my_file_open() probably returns NULL meaning that it could not open a file which that one thread can use (i.e. another thread is already using it).

Just remember that you can't unlock the mutex between the search and the add. So you can't use the my_file_find() function above without first getting a lock on your mutex (in which case you probably want to have recursive mutexes).

In other words, you can search the exiting list, grow the existing list, and shrink (a.k.a. close a file) only if you first lock the mutex, do ALL THE WORK, then unlock the mutex.

This is valid for any kind of resources, not just files. It could be memory buffers, a graphical interface widget, a USB port, etc.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156