2017-09-21 22:26:06 +03:00
/*
2015-06-02 05:34:14 +03:00
* Copyright ( C ) 2015 Colin Walters < walters @ verbum . org > .
*
2018-01-30 22:26:26 +03:00
* SPDX - License - Identifier : LGPL - 2.0 +
*
2015-06-02 05:34:14 +03:00
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
2021-12-07 04:20:55 +03:00
* License along with this library . If not , see < https : //www.gnu.org/licenses/>.
2015-06-02 05:34:14 +03:00
*/
# include "config.h"
# include <dirent.h>
# include <stdio.h>
# include <unistd.h>
# include <dlfcn.h>
# include <errno.h>
# include <stdlib.h>
# include <fcntl.h>
# include <sys/stat.h>
# include <glib.h>
2015-06-03 22:34:37 +03:00
/* Glibc uses readdir64 when _FILE_OFFSET_BITS == 64 as set by
* AC_SYS_LARGEFILE on 32 bit systems .
*/
# if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)
# define READDIR "readdir64"
# define READDIR_R "readdir64_r"
# else
# define READDIR "readdir"
# define READDIR_R "readdir_r"
# endif
2015-06-02 05:34:14 +03:00
static GHashTable * direntcache ;
static GMutex direntcache_lock ;
static gsize initialized ;
typedef struct {
GPtrArray * entries ;
guint offset ;
} DirEntries ;
static void
dir_entries_free ( gpointer data )
{
DirEntries * d = data ;
g_ptr_array_unref ( d - > entries ) ;
g_free ( d ) ;
}
static DirEntries *
dir_entries_new ( void )
{
DirEntries * d = g_new0 ( DirEntries , 1 ) ;
d - > entries = g_ptr_array_new_with_free_func ( g_free ) ;
return d ;
}
static void
ensure_initialized ( void )
{
if ( g_once_init_enter ( & initialized ) )
{
direntcache = g_hash_table_new_full ( NULL , NULL , NULL , dir_entries_free ) ;
g_mutex_init ( & direntcache_lock ) ;
g_once_init_leave ( & initialized , 1 ) ;
}
}
struct dirent *
readdir ( DIR * dirp )
{
2015-06-03 22:34:37 +03:00
struct dirent * ( * real_readdir ) ( DIR * dirp ) = dlsym ( RTLD_NEXT , READDIR ) ;
2015-06-02 05:34:14 +03:00
struct dirent * ret ;
gboolean cache_another = TRUE ;
2017-05-11 17:59:21 +03:00
2015-06-02 05:34:14 +03:00
ensure_initialized ( ) ;
/* The core idea here is that each time through the loop, we read a
* directory entry . If there is one , we choose whether to cache it
* or to return it . Because multiple entries can be cached ,
* ordering is randomized . Statistically , the order will still be
* * weighted * towards the ordering returned from the
* kernel / filesystem , but the goal here is just to provide some
* randomness in order to trigger bugs , not to be perfectly random .
*/
while ( cache_another )
{
DirEntries * de ;
errno = 0 ;
ret = real_readdir ( dirp ) ;
if ( ret = = NULL & & errno ! = 0 )
2017-05-11 17:59:21 +03:00
goto out ;
2015-06-02 05:34:14 +03:00
g_mutex_lock ( & direntcache_lock ) ;
de = g_hash_table_lookup ( direntcache , dirp ) ;
if ( ret )
2017-05-11 17:59:21 +03:00
{
if ( g_random_boolean ( ) )
{
struct dirent * copy ;
if ( ! de )
{
de = dir_entries_new ( ) ;
g_hash_table_insert ( direntcache , dirp , de ) ;
}
copy = g_memdup ( ret , sizeof ( struct dirent ) ) ;
g_ptr_array_add ( de - > entries , copy ) ;
}
else
{
cache_another = FALSE ;
}
}
2015-06-02 05:34:14 +03:00
else
2017-05-11 17:59:21 +03:00
{
if ( de & & de - > offset < de - > entries - > len )
{
ret = de - > entries - > pdata [ de - > offset ] ;
de - > offset + + ;
}
cache_another = FALSE ;
}
2015-06-02 05:34:14 +03:00
g_mutex_unlock ( & direntcache_lock ) ;
}
2017-05-11 17:59:21 +03:00
2015-06-02 05:34:14 +03:00
out :
return ret ;
}
int
closedir ( DIR * dirp )
{
int ( * real_closedir ) ( DIR * dirp ) = dlsym ( RTLD_NEXT , " closedir " ) ;
ensure_initialized ( ) ;
2017-05-11 17:59:21 +03:00
2015-06-02 05:34:14 +03:00
g_mutex_lock ( & direntcache_lock ) ;
g_hash_table_remove ( direntcache , dirp ) ;
g_mutex_unlock ( & direntcache_lock ) ;
return real_closedir ( dirp ) ;
}
static void
assert_no_cached_entries ( DIR * dirp )
{
DirEntries * de ;
g_mutex_lock ( & direntcache_lock ) ;
de = g_hash_table_lookup ( direntcache , dirp ) ;
g_assert ( ! de | | de - > entries - > len = = 0 ) ;
g_mutex_unlock ( & direntcache_lock ) ;
}
void
seekdir ( DIR * dirp , long loc )
{
void ( * real_seekdir ) ( DIR * dirp , long loc ) = dlsym ( RTLD_NEXT , " seekdir " ) ;
ensure_initialized ( ) ;
2017-05-11 17:59:21 +03:00
/* For now, crash if seekdir is called when we have cached entries.
2015-06-02 05:34:14 +03:00
* If some app wants to use this and seekdir ( ) we can implement it .
*/
assert_no_cached_entries ( dirp ) ;
real_seekdir ( dirp , loc ) ;
}
void
rewinddir ( DIR * dirp )
{
void ( * real_rewinddir ) ( DIR * dirp ) = dlsym ( RTLD_NEXT , " rewinddir " ) ;
ensure_initialized ( ) ;
/* Blow away the cache */
g_mutex_lock ( & direntcache_lock ) ;
g_hash_table_remove ( direntcache , dirp ) ;
g_mutex_unlock ( & direntcache_lock ) ;
real_rewinddir ( dirp ) ;
}
int
readdir_r ( DIR * dirp , struct dirent * entry , struct dirent * * result )
{
2015-06-03 22:34:37 +03:00
int ( * real_readdir_r ) ( DIR * dirp , struct dirent * entry , struct dirent * * result ) = dlsym ( RTLD_NEXT , READDIR_R ) ;
2015-06-02 05:34:14 +03:00
ensure_initialized ( ) ;
/* For now, assert that no one is mixing readdir_r() with readdir().
* It ' d be broken to do so , and very few programs use readdir_r ( )
* anyways . */
assert_no_cached_entries ( dirp ) ;
return real_readdir_r ( dirp , entry , result ) ;
}