//-----------------------------------------------------------------------------
// Copyright © 2006 - Philip Howard - All rights reserved
//
// 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 2
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//-----------------------------------------------------------------------------
// package	libh/io
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#ifndef __USE_GNU
#define __USE_GNU
#endif

#ifndef __USE_LARGEFILE64
#define __USE_LARGEFILE64
#endif

#include "io_lib.h"

#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <utime.h>

#include <libh/map.h>

#define FT_COPY_BUFFER_SIZE	0x00400000UL
#define FT_COPY_PROGRAM_NAME	"ft_copy"

#define FT_COPY_DEFAULT_CONT	0
#define FT_COPY_DEFAULT_LIST	0
#define FT_COPY_DEFAULT_MMAP	1
#define FT_COPY_DEFAULT_SORT	1

//-----------------------------------------------------------------------------
// If madvise() is available, use it to speed up memory mapping.
// The name "madvise" will be undefined to allow its use as a function or
// will be defined to replace the function call with 0.
//-----------------------------------------------------------------------------
#if !defined(NO_MADVISE) && defined(MADV_WILLNEED) && defined(MADV_SEQUENTIAL) && defined(MADV_DONTNEED)
#else
#undef madvise
#define madvise(a,b,c)	0
#endif


//-----------------------------------------------------------------------------
// struct	name_part
//
// purpose	Hold one part of a full path name.
//
// note		This struct is a node in a simple doubly linked list and is
//		variable in size to match the name used.
//-----------------------------------------------------------------------------
struct name_part {
    struct name_part *	next_part	;
    struct name_part *	prev_part	;
    const char *	src_name	;
    const char *	tgt_name	;
};

//-----------------------------------------------------------------------------
// Define static variables.
//-----------------------------------------------------------------------------
static struct name_part *	dir_name_front	= NULL;
static struct name_part *	dir_name_back	= NULL;

static const char *		program_name	= FT_COPY_PROGRAM_NAME "()";

static size_t			buffer_size	= FT_COPY_BUFFER_SIZE;

static size_t			page_size	= 0;
static size_t			page_mask	= 0;

static uid_t			this_euid	= -1;

#if defined(ONLYUSE_CONT)
#define opt_cont 1
#elif defined(DONTUSE_CONT)
#define opt_cont 0
#else
static char			opt_cont	= FT_COPY_DEFAULT_CONT;
#endif

#if defined(ONLYUSE_LIST)
#define opt_list 1
#elif defined(DONTUSE_LIST)
#define opt_list 0
#else
static char			opt_list	= FT_COPY_DEFAULT_LIST;
#endif

#if defined(ONLYUSE_MMAP)
#define opt_mmap 1
#elif defined(DONTUSE_MMAP)
#define opt_mmap 0
#else
static char			opt_mmap	= FT_COPY_DEFAULT_MMAP;
#endif

#if defined(ONLYUSE_SORT)
#define opt_sort 1
#elif defined(DONTUSE_SORT)
#define opt_sort 0
#else
static char			opt_sort	= FT_COPY_DEFAULT_SORT;
#endif


__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy_set_program_name
//
// purpose	Set the program name for error messages in ft_copy().
//		Otherwise the error messages will indicate the function name.
//
// arguments	1 (const char *) name of program
//
// returns	(int)  0 : OK
//		(int) -1 : error
//-----------------------------------------------------------------------------
int
ft_copy_set_program_name (
    const char *	arg_program_name
    )
__PROTO_END__
{
    program_name = arg_program_name ? arg_program_name : FT_COPY_PROGRAM_NAME "()";
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy_set_buffer_size
//
// purpose	Set the buffer size for regular files to be copied with.  If
//		the specified size is not exactly a multiple of the page size,
//		then the size will be rounded up to such a multiple.  If no
//		page size is specified, or if 0 is specified, a default value
//		is used.
//
// arguments	1 (size_t) buffer size
//
// returns	(int)  0 : OK
//		(int) -1 : error
//-----------------------------------------------------------------------------
int
ft_copy_set_buffer_size (
    size_t		arg_buffer_size
    )
__PROTO_END__
{
    buffer_size =
	arg_buffer_size == 0 ? FT_COPY_BUFFER_SIZE :
	arg_buffer_size <= 0 ? 1UL << arg_buffer_size :
	arg_buffer_size;
    if ( page_mask != 0 )
	buffer_size = ( buffer_size | page_mask ) + 1UL;
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy_set_continue
//
// purpose	Set the continue option to on or off.
//
// arguments	1 (int) 0 for no continue, 1 for continue
//
// returns	(int)  0 : OK
//		(int) -1 : error
//-----------------------------------------------------------------------------
#define ft_copy_set_continue_off()	ft_copy_set_continue(0)
#define ft_copy_set_continue_on()	ft_copy_set_continue(1)
int
ft_copy_set_continue (
    int			arg_cont
    )
__PROTO_END__
{
#ifdef opt_cont
    if ( !arg_cont == opt_cont ) return -1;
#else
    opt_cont = arg_cont;
#endif
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy_set_list
//
// purpose	Set the list option to on or off.
//
// arguments	1 (int) 0 for no list, 1 for list
//
// returns	(int)  0 : OK
//		(int) -1 : error
//-----------------------------------------------------------------------------
#define ft_copy_set_list_off()	ft_copy_set_list(0)
#define ft_copy_set_list_on()	ft_copy_set_list(1)
int
ft_copy_set_list (
    int			arg_list
    )
__PROTO_END__
{
#ifdef opt_list
    if ( !arg_list == opt_list ) return -1;
#else
    opt_list = arg_list;
#endif
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy_set_sort
//
// purpose	Set the sort option to on or off.
//
// arguments	1 (int) 0 for no sort, 1 for sort
//
// returns	(int)  0 : OK
//		(int) -1 : error
//-----------------------------------------------------------------------------
#define ft_copy_set_sort_off()	ft_copy_set_sort(0)
#define ft_copy_set_sort_on()	ft_copy_set_sort(1)
int
ft_copy_set_sort (
    int			arg_sort
    )
__PROTO_END__
{
#ifdef opt_sort
    if ( !arg_sort == opt_sort ) return -1;
#else
    opt_sort = arg_sort;
#endif
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy_set_mmap
//
// purpose	Set the mmap option to on or off.
//
// arguments	1 (int) 0 for no mmap, 1 for mmap
//
// returns	(int)  0 : OK
//		(int) -1 : error
//-----------------------------------------------------------------------------
#define ft_copy_set_mmap_off()	ft_copy_set_mmap(0)
#define ft_copy_set_mmap_on()	ft_copy_set_mmap(1)
int
ft_copy_set_mmap (
    int			arg_mmap
    )
__PROTO_END__
{
#ifdef opt_mmap
    if ( !arg_mmap == opt_mmap ) return -1;
#else
    opt_mmap = arg_mmap;
#endif
    return 0;
}

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ft_copy
//
// purpose	Copy a file tree from source to target.
//
// arguments	1 (int) open file descriptor of source directory
//		2 (const char *) name of source object
//		3 (int) open file descriptor of target directory
//		4 (const char *) name of target object
//		
// returns	(int)  0 : object copied successfully
//		(int) -1 : error copying object
//
// WARNING	This function is NOT thread safe for POSIX threads because
//		it changes the current directory and POSIX threads does not
//		provide for each thread to have its own current directory.
//-----------------------------------------------------------------------------
int
ft_copy (
    int			arg_src_fd
    ,
    const char *	arg_src_name
    ,
    int			arg_tgt_fd
    ,
    const char *	arg_tgt_name
    )
__PROTO_END__
{


    struct stat64		stat_buf	;
    struct utimbuf		time_buf	;
    struct name_part *		this_node	;

    uint64_t			file_pos	;

    MAP				map_p		;
    DIR *			dir_p		;
    struct dirent *		dirent_p	;

    const char *		ptr		;

    char *			data_buf	;
    char *			data_ptr	;

    size_t			part_size	;
    size_t			data_size	;
    size_t			data_rem	;
    ssize_t			data_len	;

    int				result		;
    int				tgt_fd		;
    int				src_fd		;

    char			path_space	[PATH_MAX];


    //------------------------------
    // Do first time initialization.
    //------------------------------
    if ( page_size == 0 ) {

	//---------------------------------------------
	// Get the system page size one way or another.
	//---------------------------------------------
#if defined(USE_PAGESIZE)
	page_size = USE_PAGESIZE;
#elif defined(USE_GETPAGESIZE)
	page_size = getpagesize();
#elif defined(_SC_PAGESIZE)
	page_size = sysconf( _SC_PAGESIZE );
#elif defined(_SC_PAGE_SIZE)
	page_size = sysconf( _SC_PAGE_SIZE );
#else
	page_size = getpagesize();
#endif

	//---------------------------------------------------------
	// Set up a page mask to access just the offset in a page.
	// Round the buffer size up to a multiple of the page size.
	//---------------------------------------------------------
	page_mask = page_size - 1UL;
	buffer_size = ( buffer_size | page_mask ) + 1UL;

	//----------------------------
	// Cache the effective userid.
	//----------------------------
	this_euid = geteuid();
    }

    //---------------------------------------------------
    // If no open descriptor, open the current directory.
    //---------------------------------------------------
    if ( arg_src_fd == -1 ) {

	//-- Make sure a name is given in this case.
	if ( ! arg_src_name || ( arg_src_name[0] == '.' && arg_src_name[1] == 0 ) ) {
	    fprintf( stderr, "%s: %s descriptor and name both not given\n", program_name, "source" );
	    return -1;
	}

	//-- Open the current directory as source descriptor.
	arg_src_fd = open( ".", O_RDONLY );
	if ( arg_src_fd < 0 ) {
	    fprintf( stderr, "%s: open( \".\" ) for %s failed: %s\n",
		     program_name, "source", strerror( errno ) );
	    return -1;
	}

	//-- Share current directory descriptor with target.
	if ( arg_tgt_fd == -1 ) arg_tgt_fd = arg_src_fd;

    } else if ( arg_tgt_fd == -1 ) {

	//-- Make sure a name is given in this case.
	if ( ! arg_tgt_name || ( arg_tgt_name[0] == '.' && arg_tgt_name[1] == 0 ) ) {
	    fprintf( stderr, "%s: %s descriptor and name both not given\n", program_name, "target" );
	    return -1;
	}

	//-- Open the current directory as target descriptor.
	arg_tgt_fd = open( ".", O_RDONLY );
	if ( arg_tgt_fd < 0 ) {
	    fprintf( stderr, "%s: open( \".\" ) for %s failed: %s\n",
		     program_name, "target", strerror( errno ) );
	    return -1;
	}
    }

    //-----------------------------------------------------
    // If no name given, use "." to refer to the directory.
    //-----------------------------------------------------
    if ( ! arg_src_name ) arg_src_name = ".";
    if ( ! arg_tgt_name ) arg_tgt_name = ".";

    //---------------------------------------------------
    // Output the current directory and this object name.
    // This is not done for the very first call.
    //---------------------------------------------------
    if ( opt_list ) {
	this_node = dir_name_front;
	if ( this_node ) {
	    unsigned int count;
	    count = 0;
	    while ( this_node ) {
		++ count;
		this_node = this_node->next_part;
	    }
	    fprintf( stdout, "%2u ", count );
	}
	this_node = dir_name_front;
	if ( this_node ) {
	    while ( this_node ) {
		fputs( this_node->src_name, stdout );
		fputc( '/', stdout );
		this_node = this_node->next_part;
	    }
	    fputs( arg_src_name, stdout );
	    fputc( '\n', stdout );
	    fflush( stdout );
	}
    }

    //------------------------------------------
    // In source directory get file object info.
    //------------------------------------------
    if ( fchdir( arg_src_fd ) < 0 ) {
	fprintf( stderr, "%s: fchdir( %d ) to %s tree failed: %s\n",
		 program_name, arg_tgt_fd, "source", strerror( errno ) );
	return -1;
    }
    if ( lstat64( arg_src_name, & stat_buf ) < 0 ) {
	fprintf( stderr, "%s: lstat( \"%s\", %p ) failed: %s\n",
		 program_name, arg_src_name, & stat_buf, strerror( errno ) );
	return -1;
    }

    //--------------------------------------------
    // If not root, set the uid to not be changed.
    //--------------------------------------------
    if ( this_euid != 0 ) stat_buf.st_uid = -1;

    //----------------
    // Handle symlink.
    //----------------
    if ( S_ISLNK( stat_buf.st_mode ) ) {
	data_len = readlink( arg_src_name, path_space, sizeof path_space - 1 );
	if ( data_len < 0 ) {
	    fprintf( stderr, "%s: readlink( \"%s\", %p, %zu ) failed: %s\n",
		     program_name, arg_src_name, path_space, sizeof path_space - 1,
		     strerror( errno ) );
	    return -1;
	}

	//-- Terminate the string because readlink() does not.
	path_space[data_len] = 0;

	//-- Switch to target tree.
        if ( fchdir( arg_tgt_fd ) < 0 ) {
	    fprintf( stderr, "%s: fchdir( %d ) to %s tree failed: %s\n",
		     program_name, arg_tgt_fd, "target", strerror( errno ) );
	    return -1;
	}

	//-- Make the actual symlink.
	if ( symlink( path_space, arg_tgt_name ) < 0 ) {
	    fprintf( stderr, "%s: symlink( \"%s\", \"%s\" ) failed: %s\n",
		     program_name, path_space, arg_tgt_name, strerror( errno ) );
	    return -1;
	}

	//-- Change the symlink owner.
	if ( lchown( arg_tgt_name, stat_buf.st_uid, stat_buf.st_gid ) < 0 ) {
	    fprintf( stderr, "%s: lchown( \"%s\", %d, %d ) failed: %s\n",
		     program_name, arg_tgt_name, stat_buf.st_uid, stat_buf.st_gid, strerror( errno ) );
	    return -1;
	}

	return 0;
    }

    //---------------------
    // Handle regular file.
    //---------------------
    else if ( S_ISREG( stat_buf.st_mode ) ) {

	//-- Open source file.
	src_fd = open( arg_src_name, O_RDONLY );
	if ( src_fd < 0 ) {
	    fprintf( stderr, "%s: open( \"%s\", O_RDONLY ) failed: %s\n",
		     program_name, arg_src_name, strerror( errno ) );
	    return 1;
	}

	//-- Switch to target tree.
        if ( fchdir( arg_tgt_fd ) < 0 ) {
	    fprintf( stderr, "%s: fchdir( %d ) to %s tree failed: %s\n",
		     program_name, arg_tgt_fd, "target", strerror( errno ) );
	    return -1;
	}

	//-- Open target file.
	tgt_fd = open( arg_tgt_name, O_WRONLY | O_CREAT | O_LARGEFILE, 0600 );
	if ( tgt_fd < 0 ) {
	    fprintf( stderr, "%s: open( \"%s\", O_WRONLY|O_CREAT|O_LARGEFILE, 0600 ) failed: %s\n",
		     program_name, arg_tgt_name, strerror( errno ) );
	    close( src_fd );
	    return -1;
	}

	//-----------------------------------
	// Start with the default buffer size.
	//-----------------------------------
	data_size = buffer_size;

	//---------------------------------------------------------
	// If MMAP is not being used, allocate a read/write buffer.
	//---------------------------------------------------------
	data_buf = NULL;
	if ( ! opt_mmap ) {
	    data_size = buffer_size;
	    for (;;) {
		data_buf = alloca( data_size );
		if ( data_buf ) break;
		if ( data_size <= page_size ) break;
		data_size = ( ( ( data_size / 2 ) - 1UL ) | page_mask ) + 1UL;
	    }
	    if ( ! data_buf ) {
		fprintf( stderr, "%s: alloca( %zu ) failed\n", program_name, buffer_size );
		return -1;
	    }
	}

	//---------------------------------------
	// Run loop to copy all of file contents.
	//---------------------------------------
	file_pos = 0;
	while ( file_pos < stat_buf.st_size ) {

	    //------------------------------------------------
	    // Determine how much of the input file to access.
	    //------------------------------------------------
	    part_size = data_size;
	    if ( ( file_pos + part_size ) > stat_buf.st_size ) part_size = stat_buf.st_size - file_pos;

	    //-----------------------------------------------------
	    // Obtain a buffer of input file by mapping or reading.
	    //-----------------------------------------------------
	    if ( opt_mmap ) {

		//--------------------------------------------------------
		// Map a buffer size part of the file into virtual memory.
		//--------------------------------------------------------
		data_buf = (char *) mmap( 0, part_size, PROT_READ, MAP_SHARED, src_fd, file_pos );
		if ( data_buf == (char *) MAP_FAILED ) {
		    fprintf( stderr, "%s: mmap( 0, %zu, PROT_READ, MAP_SHARED, %d, %lu ) failed: %s\n",
			     program_name, part_size, src_fd, (unsigned long) file_pos, strerror( errno ) );
		    close( src_fd );
		    close( tgt_fd );
		    return -1;
		}
		madvise( data_buf, part_size, MADV_WILLNEED | MADV_SEQUENTIAL );

	    } else {

		//------------------------------------------
		// Read one buffer full from the input file.
		//------------------------------------------
		data_ptr = data_buf;
		data_rem = part_size;
		for (;;) {
		    data_len = read( src_fd, data_ptr, data_rem );
		    if ( data_len < 0 ) {
			fprintf( stderr, "%s: read( %d, %p, %lu ) failed: %s\n",
				 program_name, src_fd, data_ptr, (unsigned long) data_rem,
				 strerror( errno ) );
			close( src_fd );
			close( tgt_fd );
			return -1;
		    }
		    data_ptr += data_len;
		    data_rem -= data_len;
		}
	    }

	    //--------------------
	    // Write all the data.
	    //--------------------
	    data_ptr = data_buf;
	    data_rem = part_size;
	    while ( data_rem > 0 ) {
		data_len = write( tgt_fd, data_ptr, data_rem );
		if ( data_len < 0 ) {
		    fprintf( stderr, "%s: write( %d, %p, %zu ) failed: %s\n",
			     program_name, tgt_fd, data_ptr, data_rem, strerror( errno ) );
		    close( src_fd );
		    close( tgt_fd );
		    return -1;
		}
		if ( data_len == 0 ) break;
		data_ptr += data_len;
		data_rem -= data_len;
#if 0
		if ( data_rem ) {
		    fprintf( stderr, "write incomplete, %zu written, %zu remains, continuing\n", data_len, data_rem );
		}
#endif
	    }

	    //-----------------------------------------------
	    // Indicate the data can now be removed from RAM.
	    //-----------------------------------------------
	    if ( opt_mmap ) {
		madvise( data_buf, part_size, MADV_DONTNEED );
		munmap( data_buf, part_size );
	    }

	    //----------------------------
	    // Adjust input file position.
	    //----------------------------
	    file_pos += part_size;
	}

	//---------------------------------------------------
	// Set owner, group, and permissions from input file.
	//---------------------------------------------------
	if ( fchown( tgt_fd, stat_buf.st_uid, stat_buf.st_gid ) < 0 ) {
	    fprintf( stderr, "%s: fchown( %d, %d, %d ) failed: %s\n",
		     program_name, tgt_fd, (int) stat_buf.st_uid, (int) stat_buf.st_gid,
		     strerror( errno ) );
	    close( src_fd );
	    close( tgt_fd );
	    return -1;
	}

	if ( fchmod( tgt_fd, stat_buf.st_mode & 07777 ) < 0 ) {
	    fprintf( stderr, "%s: fchmod( %d, %08lx ) failed: %s\n",
		     program_name, tgt_fd, (unsigned long) ( stat_buf.st_mode & 07777 ),
		     strerror( errno ) );
	    close( src_fd );
	    close( tgt_fd );
	    return -1;
	}

	//--------------------------
	// All done with both files.
	//--------------------------
	close( src_fd );
	close( tgt_fd );
    }

    //------------------------------
    // Handle directory recursively.
    //------------------------------
    else if ( S_ISDIR( stat_buf.st_mode ) ) {

	//-- Open the source directory to hold and to read.
        src_fd = open( arg_src_name, O_RDONLY );
	if ( src_fd < 0 ) {
	    fprintf( stderr, "%s: open( \"%s\", O_RDONLY ) in %s tree failed: %s\n",
		     program_name, arg_src_name, "source", strerror( errno ) );
	    return -1;
	}

	//-- Open the source directory for reading its members.
	dir_p = opendir( arg_src_name );
	if ( ! dir_p ) {
	    fprintf( stderr, "%s: opendir( \"%s\" ) in source tree failed: %s\n",
		     program_name, arg_src_name, strerror( errno ) );
	    close( src_fd );
	    return -1;
	}

	//-- Switch to the target tree.
        if ( fchdir( arg_tgt_fd ) < 0 ) {
	    fprintf( stderr, "%s: fchdir( %d ) to %s tree failed: %s\n",
		     program_name, arg_tgt_fd, "target", strerror( errno ) );
	    closedir( dir_p );
	    close( src_fd );
	    return -1;
	}

	//-- Make the target directory.
	if ( mkdir( arg_tgt_name, 0700 ) < 0 ) {
	    lstat64( arg_tgt_name, & stat_buf );
	    if ( ! S_ISDIR( stat_buf.st_mode ) ) {
		fprintf( stderr, "%s: mkdir( \"%s\", 0700 ) in target tree failed: %s\n",
			 program_name, arg_tgt_name, strerror( errno ) );
		closedir( dir_p );
		close( src_fd );
		return -1;
	    }
	}

	//-- Open the target directory.
        tgt_fd = open( arg_tgt_name, O_RDONLY );
	if ( tgt_fd < 0 ) {
	    fprintf( stderr, "%s: open( \"%s\", O_RDONLY ) in %s tree failed: %s\n",
		     program_name, arg_tgt_name, "target", strerror( errno ) );
	    closedir( dir_p );
	    close( src_fd );
	    return -1;
	}

#if 1
	map_p = NULL;
#endif
	if ( opt_sort ) {

	    //-- Create a map to sort the names.
	    map_p = map_str_new();
	    if ( ! map_p ) {
		fprintf( stderr, "%s: map_str_new() failed\n", program_name );
		closedir( dir_p );
		close( tgt_fd );
		close( src_fd );
		return -1;
	    }

	    //-- Collect all the names in the directory into the map.
	    for (;;) {
		errno = 0;
		dirent_p = readdir( dir_p );
		if ( ! dirent_p ) {
		    if ( errno == 0 ) break;
		    fprintf( stderr, "%s: readdir( %p /* \"%s\" */ ) failed: %s\n",
			     program_name, dir_p, arg_src_name, strerror( errno ) );
		    map_destroy( map_p );
		    closedir( dir_p );
		    close( tgt_fd );
		    close( src_fd );
		    return -1;
		}
		if ( dirent_p->d_name[0] == '.' ) {
		    if ( dirent_p->d_name[1] == 0 ) continue;
		    if ( dirent_p->d_name[1] == '.' && dirent_p->d_name[2] == 0 ) continue;
		}
		if ( map_str_insert( map_p, dirent_p->d_name ) < 0 ) {
		    fprintf( stderr, "%s: map_str_insert( %p, \"%s\" ) failed\n",
			     program_name, map_p, dirent_p->d_name );
		    map_destroy( map_p );
		    closedir( dir_p );
		    close( tgt_fd );
		    close( src_fd );
		    return -1;
		}
	    }

	    //-- Done with directory reading.
	    closedir( dir_p );
	}

	//-- Add a node for this directory's name.
#if 1
	this_node = NULL;
#endif
	if ( opt_list ) {
	    this_node = alloca( sizeof (struct name_part) );
	    if ( ! this_node ) {
		fprintf( stderr, "%s: alloca( %zu ) failed\n", program_name, sizeof (struct name_part) );
	    } else {
		this_node->next_part = NULL;
		this_node->prev_part = dir_name_back;
		this_node->src_name = arg_src_name;
		this_node->tgt_name = arg_tgt_name;
		dir_name_back = this_node;
		if ( ! dir_name_front ) dir_name_front = this_node;
	    }
	}

	//-- Copy every name in the directory.
	if ( opt_sort ) {
	    //-- Copy every name in sorted order from the map.
	    map_loop_forward( map_p ) {
		ptr = map_str_key_ptr( map_p );
		result = ft_copy( src_fd, ptr, tgt_fd, ptr );
		if ( ! opt_cont && result < 0 ) break;
	    }
	    map_destroy( map_p );
	} else {
	    //-- Copy every name as read from the directory.
	    for (;;) {
		errno = 0;
		dirent_p = readdir( dir_p );
		if ( ! dirent_p ) {
		    if ( errno == 0 ) break;
		    fprintf( stderr, "%s: readdir( %p /* \"%s\" */ ) failed: %s\n",
			     program_name, dir_p, arg_src_name, strerror( errno ) );
		    map_destroy( map_p );
		    closedir( dir_p );
		    close( tgt_fd );
		    close( src_fd );
		    return -1;
		}
		if ( dirent_p->d_name[0] == '.' ) {
		    if ( dirent_p->d_name[1] == 0 ) continue;
		    if ( dirent_p->d_name[1] == '.' && dirent_p->d_name[2] == 0 ) continue;
		}
		result = ft_copy( src_fd, dirent_p->d_name, tgt_fd, dirent_p->d_name );
		if ( ! opt_cont && result < 0 ) break;
	    }

	    //-- Done with directory reading.
	    closedir( dir_p );
	}

	//-- Done with the source directory.
	close( src_fd );

	//-- Remove the full path node.
	if ( opt_list && this_node ) {
	    dir_name_back = this_node->prev_part;
	    if ( dir_name_front == this_node ) dir_name_front = NULL;
	}

	//-- Switch to the target tree.
        if ( fchdir( arg_tgt_fd ) < 0 ) {
	    fprintf( stderr, "%s: fchdir( %d ) to %s tree failed: %s\n",
		     program_name, arg_tgt_fd, "target", strerror( errno ) );
	    close( tgt_fd );
	    return -1;
	}

	//-- Copy the owner of the new directory.
	if ( fchown( tgt_fd, stat_buf.st_uid, stat_buf.st_gid ) < 0 ) {
	    fprintf( stderr, "%s: fchown( %d, %d, %d ) failed: %s\n",
		     program_name, tgt_fd, (int) stat_buf.st_uid, (int) stat_buf.st_gid,
		     strerror( errno ) );
	    close( tgt_fd );
	    return -1;
	}

	//-- Copy the mode of the new directory.
	if ( fchmod( tgt_fd, stat_buf.st_mode & 07777 ) < 0 ) {
	    fprintf( stderr, "%s: fchmod( %d, %08lx ) failed: %s\n",
		     program_name, tgt_fd, (unsigned long) ( stat_buf.st_mode & 07777 ),
		     strerror( errno ) );
	    close( tgt_fd );
	    return -1;
	}

	//-- Done with the new directory.
	close( tgt_fd );
    }

    //----------------------------------------------------
    // Handle node objects (block, char, pipe, or socket).
    //----------------------------------------------------
    else if ( S_ISBLK(  stat_buf.st_mode ) ||
	      S_ISCHR(  stat_buf.st_mode ) ||
	      S_ISFIFO( stat_buf.st_mode ) ||
	      S_ISSOCK( stat_buf.st_mode ) ) {

	//-- Switch to the target tree.
        if ( fchdir( arg_tgt_fd ) < 0 ) {
	    fprintf( stderr, "%s: fchdir( %d ) to %s tree failed: %s\n",
		     program_name, arg_tgt_fd, "target", strerror( errno ) );
	    return -1;
	}

	//-- Make an identical node in the target tree.
	if ( mknod( arg_tgt_name, stat_buf.st_mode & S_IFMT, stat_buf.st_rdev ) < 0 ) {
	    fprintf( stderr, "%s: mknod( \"%s\", %08lx, %08lx ) failed: %s\n",
		     program_name, arg_tgt_name, (unsigned long) ( stat_buf.st_mode & S_IFMT ),
		     (unsigned long) stat_buf.st_rdev, strerror( errno ) );
	    return -1;
	}

	//-- Copy the owner of the new node.
	if ( chown( arg_tgt_name, stat_buf.st_uid, stat_buf.st_gid ) < 0 ) {
	    fprintf( stderr, "%s: chown( \"%s\", %d, %d ) failed: %s\n",
		     program_name, arg_tgt_name, (int) stat_buf.st_uid, (int) stat_buf.st_gid,
		     strerror( errno ) );
	    return -1;
	}

	//-- Copy the mode of the new node.
	if ( chmod( arg_tgt_name, stat_buf.st_mode & 07777 ) < 0 ) {
	    fprintf( stderr, "%s: chmod( \"%s\", %08lx ) failed: %s\n",
		     program_name, arg_tgt_name, (unsigned long) ( stat_buf.st_mode & 07777 ),
		     strerror( errno ) );
	    return -1;
	}
    }

    //-----------------------------------
    // Not a recognized file object type.
    //-----------------------------------
    else {
	fprintf( stderr, "%s: unrecognized file type (%08lx) for \"%s\"\n",
		 program_name, (unsigned long) stat_buf.st_mode, arg_src_name );
	return -1;
    }

    //-----------------------------------------------
    // For all file object types other than symlinks,
    // set the time from the source.
    //-----------------------------------------------
    time_buf.actime = stat_buf.st_atime;
    time_buf.modtime = stat_buf.st_mtime;
    if ( utime( arg_tgt_name, & time_buf ) < 0 ) {
	fprintf( stderr, "%s: utime( \"%s\", { %lu , %lu } ) failed: %s\n",
		 program_name, arg_tgt_name, time_buf.actime, time_buf.modtime,
		 strerror( errno ) );
	return -1;
    }

    return 0;
}

