summaryrefslogtreecommitdiff
path: root/rts/posix/FileLock.c
blob: a6052c73819aee5ff2c4fb729638a6be1cbc2d76 (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
/* -----------------------------------------------------------------------------
 *
 * (c) The GHC Team, 2007
 *
 * File locking support as required by Haskell 98
 *
 * ---------------------------------------------------------------------------*/
 
#include "PosixSource.h"
#include "Rts.h"

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

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

typedef struct {
    dev_t device;
    ino_t 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;

#ifdef THREADED_RTS
static Mutex file_lock_mutex;
#endif

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

static int hashLock(HashTable *table, StgWord w)
{
    Lock *l = (Lock *)w;
    // Just xor the dev_t with the ino_t, hope this is good enough.
    return hashWord(table, (StgWord)l->inode ^ (StgWord)l->device);
}

void
initFileLocking(void)
{
    obj_hash = allocHashTable_(hashLock, cmpLocks);
    fd_hash  = allocHashTable(); /* ordinary word-based table */
#ifdef 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);
#ifdef THREADED_RTS
    closeMutex(&file_lock_mutex);
#endif
}

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

    ACQUIRE_LOCK(&file_lock_mutex);

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

    lock = lookupHashTable(obj_hash, (StgWord)&key);

    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);
        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);
        stgFree(lock);
    }
    removeHashTable(fd_hash, fd, NULL);

    RELEASE_LOCK(&file_lock_mutex);
    return 0;
}