2009-03-22 13:06:52 +03:00
/*
* VFS module to provide a sorted directory list .
*
* Copyright ( C ) Andy Kelk ( andy @ mopoke . co . uk ) , 2009
*
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 3 of the License , or
* ( at your option ) any later version .
*
* This program 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
*/
# include "includes.h"
2011-03-23 00:34:22 +03:00
# include "smbd/smbd.h"
2011-02-26 01:20:06 +03:00
# include "system/filesys.h"
2009-03-22 13:06:52 +03:00
2012-03-28 06:18:14 +04:00
static int compare_dirent ( const struct dirent * da , const struct dirent * db )
2010-02-14 02:01:17 +03:00
{
2011-05-13 22:21:30 +04:00
return strcasecmp_m ( da - > d_name , db - > d_name ) ;
2009-03-22 13:06:52 +03:00
}
struct dirsort_privates {
2014-01-30 05:01:30 +04:00
struct dirsort_privates * prev , * next ;
2009-03-22 13:06:52 +03:00
long pos ;
2012-03-28 06:18:14 +04:00
struct dirent * directory_list ;
2013-04-09 03:40:35 +04:00
unsigned int number_of_entries ;
2013-04-09 21:43:53 +04:00
struct timespec mtime ;
2012-03-28 06:22:03 +04:00
DIR * source_directory ;
2013-04-09 21:50:55 +04:00
files_struct * fsp ; /* If open via FDOPENDIR. */
struct smb_filename * smb_fname ; /* If open via OPENDIR */
2009-03-22 13:06:52 +03:00
} ;
2013-04-09 21:43:53 +04:00
static bool get_sorted_dir_mtime ( vfs_handle_struct * handle ,
struct dirsort_privates * data ,
struct timespec * ret_mtime )
{
int ret ;
2013-04-09 21:50:55 +04:00
struct timespec mtime ;
2013-04-09 21:43:53 +04:00
2013-04-09 21:50:55 +04:00
if ( data - > fsp ) {
ret = fsp_stat ( data - > fsp ) ;
mtime = data - > fsp - > fsp_name - > st . st_ex_mtime ;
} else {
ret = SMB_VFS_STAT ( handle - > conn , data - > smb_fname ) ;
mtime = data - > smb_fname - > st . st_ex_mtime ;
}
2013-04-09 21:43:53 +04:00
if ( ret = = - 1 ) {
return false ;
}
2013-04-09 21:50:55 +04:00
* ret_mtime = mtime ;
2013-04-09 21:43:53 +04:00
return true ;
}
2013-04-09 21:29:47 +04:00
static bool open_and_sort_dir ( vfs_handle_struct * handle ,
struct dirsort_privates * data )
2009-03-22 13:06:52 +03:00
{
2017-02-09 17:05:01 +03:00
uint32_t total_count = 0 ;
/* This should be enough for most use cases */
uint32_t dirent_allocated = 64 ;
struct dirent * dp ;
2009-03-22 13:06:52 +03:00
data - > number_of_entries = 0 ;
2013-04-09 21:43:53 +04:00
if ( get_sorted_dir_mtime ( handle , data , & data - > mtime ) = = false ) {
return false ;
2009-03-22 13:06:52 +03:00
}
2017-02-09 17:05:01 +03:00
dp = SMB_VFS_NEXT_READDIR ( handle , data - > source_directory , NULL ) ;
if ( dp = = NULL ) {
2013-04-09 03:31:53 +04:00
return false ;
}
2009-03-22 13:06:52 +03:00
/* Set up an array and read the directory entries into it */
2013-04-09 02:11:28 +04:00
TALLOC_FREE ( data - > directory_list ) ; /* destroy previous cache if needed */
data - > directory_list = talloc_zero_array ( data ,
2017-02-09 17:05:01 +03:00
struct dirent ,
dirent_allocated ) ;
if ( data - > directory_list = = NULL ) {
2010-09-10 22:56:26 +04:00
return false ;
}
2017-02-09 17:05:01 +03:00
do {
if ( total_count > = dirent_allocated ) {
struct dirent * dlist ;
/*
* Be memory friendly .
*
* We should not double the amount of memory . With a lot
* of files we reach easily 50 MB , and doubling will
* get much bigger just for a few files more .
*
* For 200 k files this means 50 memory reallocations .
*/
dirent_allocated + = 4096 ;
dlist = talloc_realloc ( data ,
data - > directory_list ,
struct dirent ,
dirent_allocated ) ;
if ( dlist = = NULL ) {
break ;
}
data - > directory_list = dlist ;
2013-04-09 03:40:35 +04:00
}
2017-02-09 17:05:01 +03:00
data - > directory_list [ total_count ] = * dp ;
total_count + + ;
dp = SMB_VFS_NEXT_READDIR ( handle , data - > source_directory , NULL ) ;
} while ( dp ! = NULL ) ;
2009-03-22 13:06:52 +03:00
2017-02-09 17:05:01 +03:00
data - > number_of_entries = total_count ;
2013-04-09 03:40:35 +04:00
2009-03-22 13:06:52 +03:00
/* Sort the directory entries by name */
2010-02-14 02:01:17 +03:00
TYPESAFE_QSORT ( data - > directory_list , data - > number_of_entries , compare_dirent ) ;
2010-09-10 22:56:26 +04:00
return true ;
2009-03-22 13:06:52 +03:00
}
2012-03-28 06:22:03 +04:00
static DIR * dirsort_opendir ( vfs_handle_struct * handle ,
2016-02-27 01:53:12 +03:00
const struct smb_filename * smb_fname ,
const char * mask ,
uint32_t attr )
2009-03-22 13:06:52 +03:00
{
2014-01-30 05:01:30 +04:00
struct dirsort_privates * list_head = NULL ;
2009-03-22 13:06:52 +03:00
struct dirsort_privates * data = NULL ;
2014-01-30 05:01:30 +04:00
if ( SMB_VFS_HANDLE_TEST_DATA ( handle ) ) {
/* Find the list head of all open directories. */
SMB_VFS_HANDLE_GET_DATA ( handle , list_head , struct dirsort_privates ,
return NULL ) ;
}
2009-03-22 13:06:52 +03:00
/* set up our private data about this directory */
2013-04-09 02:11:28 +04:00
data = talloc_zero ( handle - > conn , struct dirsort_privates ) ;
2010-09-10 22:56:26 +04:00
if ( ! data ) {
return NULL ;
}
2016-02-27 01:53:12 +03:00
data - > smb_fname = cp_smb_filename ( data , smb_fname ) ;
2013-04-15 13:42:38 +04:00
if ( data - > smb_fname = = NULL ) {
2013-04-09 21:50:55 +04:00
TALLOC_FREE ( data ) ;
return NULL ;
}
2017-01-05 23:38:07 +03:00
if ( ISDOT ( data - > smb_fname - > base_name ) ) {
data - > smb_fname - > base_name = vfs_GetWd ( data , handle - > conn ) ;
}
2009-03-22 13:06:52 +03:00
/* Open the underlying directory and count the number of entries */
2016-02-27 01:53:12 +03:00
data - > source_directory = SMB_VFS_NEXT_OPENDIR ( handle , smb_fname , mask ,
2009-03-22 13:06:52 +03:00
attr ) ;
2013-04-09 21:38:24 +04:00
if ( data - > source_directory = = NULL ) {
TALLOC_FREE ( data ) ;
return NULL ;
}
2013-04-09 21:29:47 +04:00
if ( ! open_and_sort_dir ( handle , data ) ) {
2010-09-10 22:56:26 +04:00
SMB_VFS_NEXT_CLOSEDIR ( handle , data - > source_directory ) ;
2013-04-09 21:29:47 +04:00
TALLOC_FREE ( data ) ;
2010-09-10 22:56:26 +04:00
return NULL ;
}
2009-03-22 13:06:52 +03:00
2014-01-30 05:01:30 +04:00
/* Add to the private list of all open directories. */
DLIST_ADD ( list_head , data ) ;
SMB_VFS_HANDLE_SET_DATA ( handle , list_head , NULL ,
2013-04-09 21:29:47 +04:00
struct dirsort_privates , return NULL ) ;
2009-03-22 13:06:52 +03:00
return data - > source_directory ;
}
2012-03-28 06:22:03 +04:00
static DIR * dirsort_fdopendir ( vfs_handle_struct * handle ,
2011-02-09 02:07:48 +03:00
files_struct * fsp ,
const char * mask ,
2015-05-03 06:11:02 +03:00
uint32_t attr )
2011-02-09 02:07:48 +03:00
{
2014-01-30 05:01:30 +04:00
struct dirsort_privates * list_head = NULL ;
2011-02-09 02:07:48 +03:00
struct dirsort_privates * data = NULL ;
2014-01-30 05:01:30 +04:00
if ( SMB_VFS_HANDLE_TEST_DATA ( handle ) ) {
/* Find the list head of all open directories. */
SMB_VFS_HANDLE_GET_DATA ( handle , list_head , struct dirsort_privates ,
return NULL ) ;
}
2011-02-09 02:07:48 +03:00
/* set up our private data about this directory */
2013-04-09 02:11:28 +04:00
data = talloc_zero ( handle - > conn , struct dirsort_privates ) ;
2011-02-09 02:07:48 +03:00
if ( ! data ) {
return NULL ;
}
2013-04-09 21:50:55 +04:00
data - > fsp = fsp ;
2011-02-09 02:07:48 +03:00
/* Open the underlying directory and count the number of entries */
data - > source_directory = SMB_VFS_NEXT_FDOPENDIR ( handle , fsp , mask ,
attr ) ;
if ( data - > source_directory = = NULL ) {
2013-04-09 02:11:28 +04:00
TALLOC_FREE ( data ) ;
2011-02-09 02:07:48 +03:00
return NULL ;
}
2013-04-09 21:29:47 +04:00
if ( ! open_and_sort_dir ( handle , data ) ) {
2011-02-09 02:07:48 +03:00
SMB_VFS_NEXT_CLOSEDIR ( handle , data - > source_directory ) ;
2013-04-09 21:29:47 +04:00
TALLOC_FREE ( data ) ;
2011-02-09 02:07:48 +03:00
/* fd is now closed. */
fsp - > fh - > fd = - 1 ;
return NULL ;
}
2014-01-30 05:01:30 +04:00
/* Add to the private list of all open directories. */
DLIST_ADD ( list_head , data ) ;
SMB_VFS_HANDLE_SET_DATA ( handle , list_head , NULL ,
2013-04-09 21:29:47 +04:00
struct dirsort_privates , return NULL ) ;
2011-02-09 02:07:48 +03:00
return data - > source_directory ;
}
2012-03-28 06:18:14 +04:00
static struct dirent * dirsort_readdir ( vfs_handle_struct * handle ,
2012-03-28 06:22:03 +04:00
DIR * dirp ,
2009-07-19 04:32:44 +04:00
SMB_STRUCT_STAT * sbuf )
2009-03-22 13:06:52 +03:00
{
struct dirsort_privates * data = NULL ;
2013-04-09 21:43:53 +04:00
struct timespec current_mtime ;
2009-03-22 13:06:52 +03:00
SMB_VFS_HANDLE_GET_DATA ( handle , data , struct dirsort_privates ,
return NULL ) ;
2014-01-30 05:01:30 +04:00
while ( data & & ( data - > source_directory ! = dirp ) ) {
data = data - > next ;
}
if ( data = = NULL ) {
return NULL ;
}
2013-04-09 21:43:53 +04:00
if ( get_sorted_dir_mtime ( handle , data , & current_mtime ) = = false ) {
2009-03-22 13:06:52 +03:00
return NULL ;
}
/* throw away cache and re-read the directory if we've changed */
2014-01-30 05:01:30 +04:00
if ( timespec_compare ( & current_mtime , & data - > mtime ) ) {
SMB_VFS_NEXT_REWINDDIR ( handle , data - > source_directory ) ;
2013-04-09 21:29:47 +04:00
open_and_sort_dir ( handle , data ) ;
2009-03-22 13:06:52 +03:00
}
if ( data - > pos > = data - > number_of_entries ) {
return NULL ;
}
return & data - > directory_list [ data - > pos + + ] ;
}
2012-03-28 06:22:03 +04:00
static void dirsort_seekdir ( vfs_handle_struct * handle , DIR * dirp ,
2009-03-22 13:06:52 +03:00
long offset )
{
2014-01-30 05:01:30 +04:00
struct timespec current_mtime ;
2009-03-22 13:06:52 +03:00
struct dirsort_privates * data = NULL ;
2014-01-30 05:01:30 +04:00
2009-03-22 13:06:52 +03:00
SMB_VFS_HANDLE_GET_DATA ( handle , data , struct dirsort_privates , return ) ;
2014-01-30 05:01:30 +04:00
/* Find the entry holding dirp. */
while ( data & & ( data - > source_directory ! = dirp ) ) {
data = data - > next ;
}
if ( data = = NULL ) {
return ;
}
2014-09-03 18:54:51 +04:00
if ( offset > = data - > number_of_entries ) {
2014-01-30 05:01:30 +04:00
return ;
}
2009-03-22 13:06:52 +03:00
data - > pos = offset ;
2014-01-30 05:01:30 +04:00
if ( get_sorted_dir_mtime ( handle , data , & current_mtime ) = = false ) {
return ;
}
if ( timespec_compare ( & current_mtime , & data - > mtime ) ) {
/* Directory changed. We must re-read the
cache and search for the name that was
previously stored at the offset being
requested , otherwise after the re - sort
we will point to the wrong entry . The
OS / 2 incremental delete code relies on
this . */
unsigned int i ;
char * wanted_name = talloc_strdup ( handle - > conn ,
data - > directory_list [ offset ] . d_name ) ;
if ( wanted_name = = NULL ) {
return ;
}
SMB_VFS_NEXT_REWINDDIR ( handle , data - > source_directory ) ;
open_and_sort_dir ( handle , data ) ;
/* Now search for where we were. */
data - > pos = 0 ;
for ( i = 0 ; i < data - > number_of_entries ; i + + ) {
if ( strcmp ( wanted_name , data - > directory_list [ i ] . d_name ) = = 0 ) {
data - > pos = i ;
break ;
}
}
TALLOC_FREE ( wanted_name ) ;
}
2009-03-22 13:06:52 +03:00
}
2012-03-28 06:22:03 +04:00
static long dirsort_telldir ( vfs_handle_struct * handle , DIR * dirp )
2009-03-22 13:06:52 +03:00
{
struct dirsort_privates * data = NULL ;
SMB_VFS_HANDLE_GET_DATA ( handle , data , struct dirsort_privates ,
return - 1 ) ;
2014-01-30 05:01:30 +04:00
/* Find the entry holding dirp. */
while ( data & & ( data - > source_directory ! = dirp ) ) {
data = data - > next ;
}
if ( data = = NULL ) {
return - 1 ;
}
2009-03-22 13:06:52 +03:00
return data - > pos ;
}
2012-03-28 06:22:03 +04:00
static void dirsort_rewinddir ( vfs_handle_struct * handle , DIR * dirp )
2009-03-22 13:06:52 +03:00
{
struct dirsort_privates * data = NULL ;
SMB_VFS_HANDLE_GET_DATA ( handle , data , struct dirsort_privates , return ) ;
2014-01-30 05:01:30 +04:00
/* Find the entry holding dirp. */
while ( data & & ( data - > source_directory ! = dirp ) ) {
data = data - > next ;
}
if ( data = = NULL ) {
return ;
}
2009-03-22 13:06:52 +03:00
data - > pos = 0 ;
}
2014-01-30 05:01:30 +04:00
static int dirsort_closedir ( vfs_handle_struct * handle , DIR * dirp )
{
struct dirsort_privates * list_head = NULL ;
struct dirsort_privates * data = NULL ;
int ret ;
SMB_VFS_HANDLE_GET_DATA ( handle , list_head , struct dirsort_privates , return - 1 ) ;
/* Find the entry holding dirp. */
for ( data = list_head ; data & & ( data - > source_directory ! = dirp ) ; data = data - > next ) {
;
}
if ( data = = NULL ) {
return - 1 ;
}
/* Remove from the list and re-store the list head. */
DLIST_REMOVE ( list_head , data ) ;
SMB_VFS_HANDLE_SET_DATA ( handle , list_head , NULL ,
struct dirsort_privates , return - 1 ) ;
ret = SMB_VFS_NEXT_CLOSEDIR ( handle , dirp ) ;
TALLOC_FREE ( data ) ;
return ret ;
}
2009-07-24 04:28:58 +04:00
static struct vfs_fn_pointers vfs_dirsort_fns = {
2011-12-04 08:45:04 +04:00
. opendir_fn = dirsort_opendir ,
. fdopendir_fn = dirsort_fdopendir ,
. readdir_fn = dirsort_readdir ,
. seekdir_fn = dirsort_seekdir ,
. telldir_fn = dirsort_telldir ,
. rewind_dir_fn = dirsort_rewinddir ,
2014-01-30 05:01:30 +04:00
. closedir_fn = dirsort_closedir ,
2009-03-22 13:06:52 +03:00
} ;
2015-08-13 19:16:20 +03:00
static_decl_vfs ;
2009-03-22 13:06:52 +03:00
NTSTATUS vfs_dirsort_init ( void )
{
return smb_register_vfs ( SMB_VFS_INTERFACE_VERSION , " dirsort " ,
2009-07-24 04:28:58 +04:00
& vfs_dirsort_fns ) ;
2009-03-22 13:06:52 +03:00
}