summaryrefslogtreecommitdiff
path: root/rts/FileLock.c
blob: 351d2a58f76b8427806e397a2a0420857e371d95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/* -----------------------------------------------------------------------------
 *
 * (c) The GHC Team, 2007
 *
 * File locking support as required by Haskell
 *
 * ---------------------------------------------------------------------------*/

#include "PosixSource.h"
#include "Rts.h"

#include "FileLock.h"
#include "Hash.h"
#include "RtsUtils.h"

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

typedef struct {
    StgWord64 device;
    StgWord64 inode;
    int   readers; // >0 : readers,  <0 : writers
} Lock;

// Two hash tables.  The first maps objects (device/inode pairs) to
// Lock objects containing the number of active readers or writers.  The
// second maps file descriptors to lock objects, so that we can unlock
// by FD without needing to fstat() again.
static HashTable *obj_hash;
static HashTable *fd_hash;

#if defined(THREADED_RTS)
static Mutex file_lock_mutex;
#endif

STATIC_INLINE int cmpLocks(StgWord w1, StgWord w2)
{
    Lock *l1 = (Lock *)w1;
    Lock *l2 = (Lock *)w2;
    return (l1->device == l2->device && l1->inode == l2->inode);
}

STATIC_INLINE int hashLock(const HashTable *table, StgWord w)
{
    Lock *l = (Lock *)w;
    StgWord key = l->inode ^ (l->inode >> 32) ^ l->device ^ (l->device >> 32);
    // Just xor all 32-bit words of inode and device, hope this is good enough.
    return hashWord(table, key);
}

void
initFileLocking(void)
{
    obj_hash = allocHashTable();
    fd_hash  = allocHashTable(); /* ordinary word-based table */
#if defined(THREADED_RTS)
    initMutex(&file_lock_mutex);
#endif
}

static void
freeLock(void *lock)
{
    stgFree(lock);
}

void
freeFileLocking(void)
{
    freeHashTable(obj_hash, freeLock);
    freeHashTable(fd_hash,  NULL);
#if defined(THREADED_RTS)
    closeMutex(&file_lock_mutex);
#endif
}

int
lockFile(int fd, StgWord64 dev, StgWord64 ino, int for_writing)
{
    Lock key, *lock;

    ACQUIRE_LOCK(&file_lock_mutex);

    key.device = dev;
    key.inode  = ino;

    lock = lookupHashTable_(obj_hash, (StgWord)&key, hashLock, cmpLocks);

    if (lock == NULL)
    {
        lock = stgMallocBytes(sizeof(Lock), "lockFile");
        lock->device = dev;
        lock->inode  = ino;
        lock->readers = for_writing ? -1 : 1;
        insertHashTable_(obj_hash, (StgWord)lock, (void *)lock, hashLock);
        insertHashTable(fd_hash, fd, lock);
        RELEASE_LOCK(&file_lock_mutex);
        return 0;
    }
    else
    {
        // single-writer/multi-reader locking:
        if (for_writing || lock->readers < 0) {
            RELEASE_LOCK(&file_lock_mutex);
            return -1;
        }
        insertHashTable(fd_hash, fd, lock);
        lock->readers++;
        RELEASE_LOCK(&file_lock_mutex);
        return 0;
    }
}

int
unlockFile(int fd)
{
    Lock *lock;

    ACQUIRE_LOCK(&file_lock_mutex);

    lock = lookupHashTable(fd_hash, fd);
    if (lock == NULL) {
        // errorBelch("unlockFile: fd %d not found", fd);
        // This is normal: we didn't know when calling unlockFile
        // whether this FD referred to a locked file or not.
        RELEASE_LOCK(&file_lock_mutex);
        return 1;
    }

    if (lock->readers < 0) {
        lock->readers++;
    } else {
        lock->readers--;
    }

    if (lock->readers == 0) {
        removeHashTable_(obj_hash, (StgWord)lock, NULL, hashLock, cmpLocks);
        stgFree(lock);
    }
    removeHashTable(fd_hash, fd, NULL);

    RELEASE_LOCK(&file_lock_mutex);
    return 0;
}