1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *  * Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 *  * Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in
12 *    the documentation and/or other materials provided with the
13 *    distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29/* implement flockfile(), ftrylockfile() and funlockfile()
30 *
31 * we can't use the OpenBSD implementation which uses kernel-specific
32 * APIs not available on Linux.
33 *
34 * Ideally, this would be trivially implemented by adding a
35 * pthread_mutex_t field to struct __sFILE as defined in
36 * <stdio.h>.
37 *
38 * However, since we don't want to bring pthread into the mix
39 * as well as change the size of a public API/ABI structure,
40 * we're going to store the data out-of-band.
41 *
42 * we use a hash-table to map FILE* pointers to recursive mutexes
43 * fclose() will call __fremovelock() defined below to remove
44 * a pointer from the table.
45 *
46 * the behaviour, if fclose() is called while the corresponding
47 * file is locked is totally undefined.
48 */
49#include <stdio.h>
50#include <pthread.h>
51#include <string.h>
52
53/* a node in the hash table */
54typedef struct FileLock {
55    struct FileLock*  next;
56    FILE*             file;
57    pthread_mutex_t   mutex;
58} FileLock;
59
60/* use a static hash table. We assume that we're not going to
61 * lock a really large number of FILE* objects on an embedded
62 * system.
63 */
64#define  FILE_LOCK_BUCKETS  32
65
66typedef struct {
67    pthread_mutex_t   lock;
68    FileLock*         buckets[ FILE_LOCK_BUCKETS ];
69} LockTable;
70
71static LockTable*      _lockTable;
72static pthread_once_t  _lockTable_once = PTHREAD_ONCE_INIT;
73
74static void
75lock_table_init( void )
76{
77    _lockTable = malloc(sizeof(*_lockTable));
78    if (_lockTable != NULL) {
79        pthread_mutex_init(&_lockTable->lock, NULL);
80        memset(_lockTable->buckets, 0, sizeof(_lockTable->buckets));
81    }
82}
83
84static LockTable*
85lock_table_lock( void )
86{
87    pthread_once( &_lockTable_once, lock_table_init );
88    pthread_mutex_lock( &_lockTable->lock );
89    return _lockTable;
90}
91
92static void
93lock_table_unlock( LockTable*  t )
94{
95    pthread_mutex_unlock( &t->lock );
96}
97
98static FileLock**
99lock_table_lookup( LockTable*  t, FILE*  f )
100{
101    uint32_t    hash = (uint32_t)(void*)f;
102    FileLock**  pnode;
103
104    hash = (hash >> 2) ^ (hash << 17);
105    pnode = &t->buckets[hash % FILE_LOCK_BUCKETS];
106    for (;;) {
107        FileLock*  node = *pnode;
108        if (node == NULL || node->file == f)
109            break;
110        pnode = &node->next;
111    }
112    return pnode;
113}
114
115void
116flockfile(FILE * fp)
117{
118    LockTable*  t = lock_table_lock();
119
120    if (t != NULL) {
121        FileLock**  lookup = lock_table_lookup(t, fp);
122        FileLock*   lock   = *lookup;
123
124        if (lock == NULL) {
125            pthread_mutexattr_t  attr;
126
127            /* create a new node in the hash table */
128            lock = malloc(sizeof(*lock));
129            if (lock == NULL) {
130                lock_table_unlock(t);
131                return;
132            }
133            lock->next        = NULL;
134            lock->file        = fp;
135
136            pthread_mutexattr_init(&attr);
137            pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
138            pthread_mutex_init( &lock->mutex, &attr );
139
140            *lookup           = lock;
141        }
142        lock_table_unlock(t);
143
144        /* we assume that another thread didn't destroy 'lock'
145        * by calling fclose() on the FILE*. This can happen if
146        * the client is *really* buggy, but we don't care about
147        * such code here.
148        */
149        pthread_mutex_lock(&lock->mutex);
150    }
151}
152
153
154int
155ftrylockfile(FILE *fp)
156{
157    int         ret = -1;
158    LockTable*  t   = lock_table_lock();
159
160    if (t != NULL) {
161        FileLock**  lookup = lock_table_lookup(t, fp);
162        FileLock*   lock   = *lookup;
163
164        lock_table_unlock(t);
165
166        /* see above comment about why we assume that 'lock' can
167        * be accessed from here
168        */
169        if (lock != NULL && !pthread_mutex_trylock(&lock->mutex)) {
170            ret = 0;  /* signal success */
171        }
172    }
173    return ret;
174}
175
176void
177funlockfile(FILE * fp)
178{
179    LockTable*  t = lock_table_lock();
180
181    if (t != NULL) {
182        FileLock**  lookup = lock_table_lookup(t, fp);
183        FileLock*   lock   = *lookup;
184
185        if (lock != NULL)
186            pthread_mutex_unlock(&lock->mutex);
187
188        lock_table_unlock(t);
189    }
190}
191
192
193/* called from fclose() to remove the file lock */
194void
195__fremovelock(FILE*  fp)
196{
197    LockTable*  t = lock_table_lock();
198
199    if (t != NULL) {
200        FileLock**  lookup = lock_table_lookup(t, fp);
201        FileLock*   lock   = *lookup;
202
203        if (lock != NULL) {
204            *lookup   = lock->next;
205            lock->file = NULL;
206        }
207        lock_table_unlock(t);
208        free(lock);
209    }
210}
211