@ -21,9 +21,11 @@
# include <config.h>
# endif
# include <dirent.h>
# include <errno.h>
# include <fcntl.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include <sys/param.h>
# include <sys/stat.h>
@ -260,6 +262,8 @@ syntax error: not inside a locale definition section"));
}
/* Semantic checking of locale specifications. */
static void ( * const check_funcs [ ] ) ( struct localedef_t * ,
struct charmap_t * ) =
{
@ -277,7 +281,6 @@ static void (*const check_funcs[]) (struct localedef_t *,
[ LC_IDENTIFICATION ] = identification_finish
} ;
void
check_all_categories ( struct localedef_t * definitions ,
struct charmap_t * charmap )
@ -290,6 +293,8 @@ check_all_categories (struct localedef_t *definitions,
}
/* Writing the locale data files. All files use the same output_path. */
static void ( * const write_funcs [ ] ) ( struct localedef_t * , struct charmap_t * ,
const char * ) =
{
@ -307,7 +312,6 @@ static void (*const write_funcs[]) (struct localedef_t *, struct charmap_t *,
[ LC_IDENTIFICATION ] = identification_output
} ;
void
write_all_categories ( struct localedef_t * definitions ,
struct charmap_t * charmap ,
@ -320,7 +324,189 @@ write_all_categories (struct localedef_t *definitions,
write_funcs [ cnt ] ( definitions , charmap , output_path ) ;
}
/* Return a NULL terminated list of the directories next to output_path
that have the same owner , group , permissions and device as output_path . */
static const char * *
siblings_uncached ( const char * output_path )
{
size_t len ;
char * base , * p ;
struct stat output_stat ;
DIR * dirp ;
int nelems ;
const char * * elems ;
/* Remove trailing slashes and trailing pathname component. */
len = strlen ( output_path ) ;
base = ( char * ) alloca ( len ) ;
memcpy ( base , output_path , len ) ;
p = base + len ;
while ( p > base & & p [ - 1 ] = = ' / ' )
p - - ;
if ( p = = base )
return NULL ;
do
p - - ;
while ( p > base & & p [ - 1 ] ! = ' / ' ) ;
if ( p = = base )
return NULL ;
* - - p = ' \0 ' ;
len = p - base ;
/* Get the properties of output_path. */
if ( lstat ( output_path , & output_stat ) < 0 | | ! S_ISDIR ( output_stat . st_mode ) )
return NULL ;
/* Iterate through the directories in base directory. */
dirp = opendir ( base ) ;
if ( dirp = = NULL )
return NULL ;
nelems = 0 ;
elems = NULL ;
for ( ; ; )
{
struct dirent * other_dentry ;
const char * other_name ;
char * other_path ;
struct stat other_stat ;
other_dentry = readdir ( dirp ) ;
if ( other_dentry = = NULL )
break ;
other_name = other_dentry - > d_name ;
if ( strcmp ( other_name , " . " ) = = 0 | | strcmp ( other_name , " .. " ) = = 0 )
continue ;
other_path = ( char * ) xmalloc ( len + 1 + strlen ( other_name ) + 2 ) ;
memcpy ( other_path , base , len ) ;
other_path [ len ] = ' / ' ;
strcpy ( other_path + len + 1 , other_name ) ;
if ( lstat ( other_path , & other_stat ) > = 0
& & S_ISDIR ( other_stat . st_mode )
& & other_stat . st_uid = = output_stat . st_uid
& & other_stat . st_gid = = output_stat . st_gid
& & other_stat . st_mode = = output_stat . st_mode
& & other_stat . st_dev = = output_stat . st_dev )
{
/* Found a subdirectory. Add a trailing slash and store it. */
p = other_path + len + 1 + strlen ( other_name ) ;
* p + + = ' / ' ;
* p = ' \0 ' ;
elems = ( const char * * ) xrealloc ( ( char * ) elems ,
( nelems + 2 ) * sizeof ( char * * ) ) ;
elems [ nelems + + ] = other_path ;
}
else
free ( other_path ) ;
}
closedir ( dirp ) ;
if ( elems ! = NULL )
elems [ nelems ] = NULL ;
return elems ;
}
/* Return a NULL terminated list of the directories next to output_path
that have the same owner , group , permissions and device as output_path .
Cache the result for future calls . */
static const char * *
siblings ( const char * output_path )
{
static const char * last_output_path ;
static const char * * last_result ;
if ( output_path ! = last_output_path )
{
if ( last_result ! = NULL )
{
const char * * p ;
for ( p = last_result ; * p ! = NULL ; p + + )
free ( ( char * ) * p ) ;
free ( last_result ) ;
}
last_output_path = output_path ;
last_result = siblings_uncached ( output_path ) ;
}
return last_result ;
}
/* Read as many bytes from a file descriptor as possible. */
static ssize_t
full_read ( int fd , void * bufarea , size_t nbyte )
{
char * buf = ( char * ) bufarea ;
while ( nbyte > 0 )
{
ssize_t retval = read ( fd , buf , nbyte ) ;
if ( retval = = 0 )
break ;
else if ( retval > 0 )
{
buf + = retval ;
nbyte - = retval ;
}
else if ( errno ! = EINTR )
return retval ;
}
return buf - ( char * ) bufarea ;
}
/* Compare the contents of two regular files of the same size. Return 0
if they are equal , 1 if they are different , or - 1 if an error occurs . */
static int
compare_files ( const char * filename1 , const char * filename2 , size_t size ,
size_t blocksize )
{
int fd1 , fd2 ;
int ret = - 1 ;
fd1 = open ( filename1 , O_RDONLY ) ;
if ( fd1 > = 0 )
{
fd2 = open ( filename2 , O_RDONLY ) ;
if ( fd2 > = 0 )
{
char * buf1 = ( char * ) xmalloc ( 2 * blocksize ) ;
char * buf2 = buf1 + blocksize ;
ret = 0 ;
while ( size > 0 )
{
size_t bytes = ( size < blocksize ? size : blocksize ) ;
if ( full_read ( fd1 , buf1 , bytes ) < ( ssize_t ) bytes )
{
ret = - 1 ;
break ;
}
if ( full_read ( fd2 , buf2 , bytes ) < ( ssize_t ) bytes )
{
ret = - 1 ;
break ;
}
if ( memcmp ( buf1 , buf2 , bytes ) ! = 0 )
{
ret = 1 ;
break ;
}
size - = bytes ;
}
free ( buf1 ) ;
close ( fd2 ) ;
}
close ( fd1 ) ;
}
return ret ;
}
/* Write a locale file, with contents given by N_ELEM and VEC. */
void
write_locale_data ( const char * output_path , const char * category ,
size_t n_elem , struct iovec * vec )
@ -328,10 +514,9 @@ write_locale_data (const char *output_path, const char *category,
size_t cnt , step , maxiov ;
int fd ;
char * fname ;
const char * * other_paths ;
fname = malloc ( strlen ( output_path ) + 2 * strlen ( category ) + 7 ) ;
if ( fname = = NULL )
error ( 5 , errno , _ ( " memory exhausted " ) ) ;
fname = xmalloc ( strlen ( output_path ) + 2 * strlen ( category ) + 7 ) ;
/* Normally we write to the directory pointed to by the OUTPUT_PATH.
But for LC_MESSAGES we have to take care for the translation
@ -359,7 +544,8 @@ write_locale_data (const char *output_path, const char *category,
}
/* Create the locale file with nlinks == 1; this avoids crashing processes
which currently use the locale . */
which currently use the locale and damaging files belonging to other
locales as well . */
if ( fd = = - 2 )
{
unlink ( fname ) ;
@ -389,7 +575,6 @@ cannot open output file `%s' for category `%s'"),
return ;
}
}
free ( fname ) ;
# ifdef UIO_MAXIOV
maxiov = UIO_MAXIOV ;
@ -415,4 +600,116 @@ cannot open output file `%s' for category `%s'"),
}
close ( fd ) ;
/* Compare the file with the locale data files for the same category in
other locales , and see if we can reuse it , to save disk space . */
other_paths = siblings ( output_path ) ;
if ( other_paths ! = NULL )
{
struct stat fname_stat ;
if ( lstat ( fname , & fname_stat ) > = 0
& & S_ISREG ( fname_stat . st_mode ) )
{
const char * fname_tail = fname + strlen ( output_path ) ;
const char * * other_p ;
int seen_count ;
ino_t * seen_inodes ;
seen_count = 0 ;
for ( other_p = other_paths ; * other_p ; other_p + + )
seen_count + + ;
seen_inodes = ( ino_t * ) xmalloc ( seen_count * sizeof ( ino_t ) ) ;
seen_count = 0 ;
for ( other_p = other_paths ; * other_p ; other_p + + )
{
const char * other_path = * other_p ;
size_t other_path_len = strlen ( other_path ) ;
char * other_fname ;
struct stat other_fname_stat ;
other_fname =
( char * ) xmalloc ( other_path_len + strlen ( fname_tail ) + 1 ) ;
memcpy ( other_fname , other_path , other_path_len ) ;
strcpy ( other_fname + other_path_len , fname_tail ) ;
if ( lstat ( other_fname , & other_fname_stat ) > = 0
& & S_ISREG ( other_fname_stat . st_mode )
/* Consider only files on the same device.
Otherwise hard linking won ' t work anyway . */
& & other_fname_stat . st_dev = = fname_stat . st_dev
/* Consider only files with the same permissions.
Otherwise there are security risks . */
& & other_fname_stat . st_uid = = fname_stat . st_uid
& & other_fname_stat . st_gid = = fname_stat . st_gid
& & other_fname_stat . st_mode = = fname_stat . st_mode
/* Don't compare fname with itself. */
& & other_fname_stat . st_ino ! = fname_stat . st_ino
/* Files must have the same size, otherwise they
cannot be the same . */
& & other_fname_stat . st_size = = fname_stat . st_size )
{
/* Skip this file if we have already read it (under a
different name ) . */
int i ;
for ( i = seen_count - 1 ; i > = 0 ; i - - )
if ( seen_inodes [ i ] = = other_fname_stat . st_ino )
break ;
if ( i < 0 )
{
/* Now compare fname and other_fname for real. */
blksize_t blocksize ;
# ifdef _STATBUF_ST_BLKSIZE
blocksize = MAX ( fname_stat . st_blksize ,
other_fname_stat . st_blksize ) ;
if ( blocksize > 8 * 1024 )
blocksize = 8 * 1024 ;
# else
blocksize = 8 * 1024 ;
# endif
if ( compare_files ( fname , other_fname ,
fname_stat . st_size , blocksize ) = = 0 )
{
/* Found! other_fname is identical to fname. */
/* Link other_fname to fname. But use a temporary
file , in case hard links don ' t work on the
particular filesystem . */
char * tmp_fname =
( char * ) xmalloc ( strlen ( fname ) + 4 + 1 ) ;
strcpy ( tmp_fname , fname ) ;
strcat ( tmp_fname , " .tmp " ) ;
if ( link ( other_fname , tmp_fname ) > = 0 )
{
unlink ( fname ) ;
if ( rename ( tmp_fname , fname ) < 0 )
{
if ( ! be_quiet )
error ( 0 , errno , _ ( " \
cannot create output file ` % s ' for category ` % s ' " ),
fname , category ) ;
}
free ( tmp_fname ) ;
free ( other_fname ) ;
break ;
}
free ( tmp_fname ) ;
}
/* Don't compare with this file a second time. */
seen_inodes [ seen_count + + ] = other_fname_stat . st_ino ;
}
}
free ( other_fname ) ;
}
free ( seen_inodes ) ;
}
}
free ( fname ) ;
}