Home | History | Annotate | Download | only in stdio
      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 */
     54 typedef 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 
     66 typedef struct {
     67     pthread_mutex_t   lock;
     68     FileLock*         buckets[ FILE_LOCK_BUCKETS ];
     69 } LockTable;
     70 
     71 static LockTable*      _lockTable;
     72 static pthread_once_t  _lockTable_once = PTHREAD_ONCE_INIT;
     73 
     74 static void
     75 lock_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 
     84 static LockTable*
     85 lock_table_lock( void )
     86 {
     87     pthread_once( &_lockTable_once, lock_table_init );
     88     pthread_mutex_lock( &_lockTable->lock );
     89     return _lockTable;
     90 }
     91 
     92 static void
     93 lock_table_unlock( LockTable*  t )
     94 {
     95     pthread_mutex_unlock( &t->lock );
     96 }
     97 
     98 static FileLock**
     99 lock_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 
    115 void
    116 flockfile(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 
    154 int
    155 ftrylockfile(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 
    176 void
    177 funlockfile(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 */
    194 __LIBC_HIDDEN__ void
    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