__PREFIX_BEGIN__
//-----------------------------------------------------------------------------
// 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/ftr
// 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.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// file		ftr.h
//-----------------------------------------------------------------------------
#ifndef __FTR_H__
#define __FTR_H__

__PREFIX_END__

__INCLUDE_BEGIN__
#include <stdio.h>
#include <sys/stat.h>

#include <libh/avl.h>
__INCLUDE_END__

__DEFINE_BEGIN__
//-----------------------------------------------------------------------------
// struct	ftr_dir (internal)
//
// purpose	To hold the state of a particular directory level in file tree
//		recursion.
//-----------------------------------------------------------------------------
struct ftr_dir {
    // AVL struct of tree that holds names in this directory
    AVL			name_tree	;

    // pointer to directory node of parent, or NULL for top
    struct ftr_dir *	parent_dir	;

    // pointer to string comparison function to sort names within a directory
    int ( *		cmp_fun_p	)( const char *, const char * );

    // file descriptor open to directory for last name
    int			rdir_last	;

    // save descent state of name string
    char *		save_name_last	;
    char *		save_name_end	;
    int			save_rdir_last	;
};

//-----------------------------------------------------------------------------
// struct	ftr_s
//
// purpose	To hold the state of one instance of file tree recursion.
//-----------------------------------------------------------------------------
struct ftr_s {
    // count of objects (ascend is not counted)
    unsigned long long	count_objects	;

    // count of returns (ascend is counted)
    unsigned long long	count_returns	;

    // pointer to directory node that is current
    struct ftr_dir *	curr_dir	;

    // pointer to string comparison function to sort names within a directory
    int ( *		cmp_fun_p	)( const char *, const char * );

    // descent depth limit
    int			depth_limit	;

    // current descent depth
    int			depth_curr	;

    // descent depth deepest maximum
    int			depth_peak	;

    // file descriptor open to original ftr_new() current directory
    int			rdir_orig	;

    // pointer to next file tree recursion when this one ends
    struct ftr_s *	next_ftr	;

    // pointer to absolute root path name, including leading '/'.
    char *		name_root	;

    // pointer to full name including the specified name
    char *		name_full	;

    // pointer to partial name after the specified name
    char *		name_part	;

    // pointer to last name after the last parent directory
    char *		name_last	;

    // pointer to end of name at the terminator
    char *		name_end	;

    // allocated size of space name_root points to
    size_t		name_size	;

    // file type of most recent file, or 0 at end, or -1 for error
    int			file_type	;

    // file descriptor open to directory for full name
    int			rdir_full	;

    // file descriptor open to directory for part name
    int			rdir_part	;

    // file descriptor open to directory for last name
    int			rdir_last	;

    // file status buffer for most recent file
    struct stat		stat_buf	;
};
typedef struct ftr_s	ftr_s		;
typedef struct ftr_s *	ftr_p		;
typedef struct ftr_s *	FTR		;

//-----------------------------------------------------------------------------
// struct	ftr_node (internal)
//
// purpose	A node in an AVL tree of object names in a directory.
//
// note		The name of the object extends the length of this struct.
//		Allocation requires adding the length of the name string,
//		including its termination character, to the offset of the
//		name member (not to the size of the struct).
//-----------------------------------------------------------------------------
struct ftr_node {
    avl_link		link			; // AVL tree links
    char		name	[1]		; // name of this object
};

#define ftr_node_new(l) (malloc(offsetof(struct ftr_node,name)+(l)))
#define ftr_node_end(p) (free((p)))

//-----------------------------------------------------------------------------
// macro	while_ftr
//
// purpose	Code a while loop around a file tree recursion.
//
// argument	1 (FTR) reference to which file tree recursion
//
// example	while_ftr( this_ftr ) {
//			if ( ftr_type( this_ftr ) == 'f' ) {
//				puts( ftr_name_full( this_ftr ) );
//			}
//		}
//-----------------------------------------------------------------------------
#define while_ftr(o)		while ( ftr_get( (o) ) > 0 )

//-----------------------------------------------------------------------------
// macro	ftr_set_cmp
//
// purpose	Set a compare function for this FTR.  Only directories not yet
//		descended into will be affected.  This should be done before
//		the first call to ftr_get() to uniformly sort the whole tree.
//
// argument	1 (FTR) reference to which file tree recursion
//		2 (int(*)(const char*,const char*)) string compare function
//
// note		The string compare function must have the same prototype as
//		the C Standard Library function strcmp().
//
// note		If the sorting function pointer (argument 2) is given as NULL
//		then a default sorting function is used.
//
// note		The sorting function is only used to sort names within each
//		directory, not to sort the entire name tree.  This results in
//		the directory separator collation being determined by how the
//		function sorts shorter names compared to longer names that have
//		a beginning equal to the shorter name, rather than how the
//		function sorts the directory separator character.
//-----------------------------------------------------------------------------
#define ftr_set_cmp(o,f)	((o)->cmp_fun_p=(f))

//-----------------------------------------------------------------------------
// macro	ftr_type
//
// purpose	Yield the current file type returned from the last ftr_get.
//
// argument	1 (FTR) reference to which file tree recursion
//-----------------------------------------------------------------------------
#define ftr_type(o)		((o)->file_type)

//-----------------------------------------------------------------------------
// macro	ftr_name_root
// macro	ftr_name_full
// macro	ftr_name_part
// macro	ftr_name_last
// macro	ftr_name_end
//
// purpose	Provide access to the most recent name at various levels.
//		Macro ftr_name_end() provides a pointer to the termination
//		character at the end of each name.
//
// argument	1 (FTR) reference to which file tree recursion
//-----------------------------------------------------------------------------
#define ftr_name_root(o)	(0+((o)->name_root))
#define ftr_name_full(o)	(0+((o)->name_full))
#define ftr_name_part(o)	(0+((o)->name_part))
#define ftr_name_last(o)	(0+((o)->name_last))
#define ftr_name_end(o)		(0+((o)->name_end))

//-----------------------------------------------------------------------------
// macro	ftr_name_len_root
// macro	ftr_name_len_full
// macro	ftr_name_len_part
// macro	ftr_name_len_last
//
// purpose	Provide access to the length of the most recent name at
//		various levels.  This length is calculated directly and
//		is faster than calling strlen() on the name.
//
// argument	1 (FTR) reference to which file tree recursion
//-----------------------------------------------------------------------------
#define ftr_name_len_root(o)	((o)->name_end-(o)->name_root)
#define ftr_name_len_full(o)	((o)->name_end-(o)->name_full)
#define ftr_name_len_part(o)	((o)->name_end-(o)->name_part)
#define ftr_name_len_last(o)	((o)->name_end-(o)->name_last)

//-----------------------------------------------------------------------------
// macro	ftr_object_count
// macro	ftr_return_count
//
// purpose	Provide the count of number of objects or the number of returns
//		encountered so far in this FTR instance.
//
// argument	1 (FTR) reference to which file tree recursion
//-----------------------------------------------------------------------------
#define ftr_object_count(o)	(0+((o)->count_objects))
#define ftr_return_count(o)	(0+((o)->count_returns))

//-----------------------------------------------------------------------------
// macro	ftr_stat
//
// purpose	Provide direct access to the most recent stat buffer.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(struct stat) expression of stat buffer
//-----------------------------------------------------------------------------
#define ftr_stat(o)		((o)->stat_buf)

//-----------------------------------------------------------------------------
// macro	ftr_stat_ptr
//
// purpose	Provide a pointer to the most recent stat buffer.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(struct stat *) pointer to stat buffer
//-----------------------------------------------------------------------------
#define ftr_stat_ptr(o)		(&((o)->stat_buf))

//-----------------------------------------------------------------------------
// macro	ftr_dirfd_orig
// macro	ftr_dirfd_full
// macro	ftr_dirfd_part
// macro	ftr_dirfd_last
//
// purpose	Get an open file descriptor for the directory that is the
//		reference for a specific name pointer category.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(int) file descriptor
//-----------------------------------------------------------------------------
#define ftr_dirfd_orig(o)	(0+(o)->rdir_orig)
#define ftr_dirfd_full(o)	(0+(o)->rdir_full)
#define ftr_dirfd_part(o)	(0+(o)->rdir_part)
#define ftr_dirfd_last(o)	(0+(o)->rdir_last)

//-----------------------------------------------------------------------------
// macro	ftr_chdir_orig
// macro	ftr_chdir_full
// macro	ftr_chdir_part
// macro	ftr_chdir_last
//
// purpose	Change the process current directory to be the directory that
//		is the reference for a specific name pointer category.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(int) result from fchdir()
//-----------------------------------------------------------------------------
#define ftr_chdir_orig(o)	(fchdir((o)->rdir_orig))
#define ftr_chdir_full(o)	(fchdir((o)->rdir_full))
#define ftr_chdir_part(o)	(fchdir((o)->rdir_part))
#define ftr_chdir_last(o)	(fchdir((o)->rdir_last))

//-----------------------------------------------------------------------------
// macro	ftr_depth_curr
//
// purpose	Get the current depth for this FTR.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(int) current depth
//-----------------------------------------------------------------------------
#define ftr_depth_curr(o)	(0+(o)->depth_curr)

//-----------------------------------------------------------------------------
// macro	ftr_depth_peak
//
// purpose	Get the peak deepest depth for this FTR.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(int) peak deepest depth
//-----------------------------------------------------------------------------
#define ftr_depth_peak(o)	(0+(o)->depth_peak)

//-----------------------------------------------------------------------------
// macro	ftr_depth_limit
//
// purpose	Get the depth limit for this FTR.
//
// argument	1 (FTR) reference to which file tree recursion
//
// yields	(int) maximum depth
//-----------------------------------------------------------------------------
#define ftr_depth_limit(o)	(0+(o)->depth_limit)

//-----------------------------------------------------------------------------
// macro	ftr_set_depth_limit
//
// purpose	Set the depth limit for this FTR.
//
// argument	1 (FTR) reference to which file tree recursion
//		2 (int) new maximum depth to use
//
// yields	(int) updated maximum depth
//-----------------------------------------------------------------------------
#define ftr_set_depth_limit(o,d)	((o)->depth_limit=(d))

//-----------------------------------------------------------------------------
// macro	ftr_get_select
//
// purpose	Get the next name object in a file tree recursion that matches
//		the selected file types.
//
// arguments	1 (FTR) reference to which file tree recursion.
//		2 (const char *) string with list of requested types.
//
// returns	(int) return status
//		(int) ==  -1 : error
//		(int) ==   0 : end of recursion, no more names
//		(int) == '-' : no status available for this name
//		(int) == '?' : unrecognized file type
//		(int) == 'd' : directory before descending
//		(int) == 'a' : directory after ascending back
//		(int) == 'e' : directory not descended (cannot read)
//		(int) == 'm' : directory not descended (maximum depth)
//		(int) == 'f' : regular file
//		(int) == 'l' : symlink
//		(int) == 'b' : block device
//		(int) == 'c' : character device
//		(int) == 's' : socket
//		(int) == 'p' : pipe/fifo
//
// note		All character code return values are positive integers so a
//		valid loop could be coded like:
//
//		while ( ftr_get_select( this_ftr, type_list ) > 0 ) ...
//-----------------------------------------------------------------------------
#define ftr_get_select(o,t) ({							\
	FTR		libh__this_ftr;						\
	const char *	libh__type_list;					\
	int		libh__file_type;					\
	libh__this_ftr = (o);							\
	libh__type_list = (t);							\
	for (;;) {								\
		libh__file_type = ftr_get( libh__this_ftr );			\
		if ( libh__file_type <= 0 ) break;				\
		if ( strchr( libh__type_list, libh__file_type ) ) break;	\
	}									\
	libh__file_type;							\
})

//-----------------------------------------------------------------------------
// macro	ftr_open
//
// purpose	Open the filesystem object last found by ftr_get().
//
// arguments	1 (FTR) reference to which file tree recursion
//		2 (int) open flags for 2nd argument
//
// yields	(int)  < 0 : error
//		(int) >= 0 : open file descriptor
//-----------------------------------------------------------------------------
#define ftr_open(o,f) ({							\
	FTR libh__ftr;								\
	int libh__cwd;								\
	int libh__fd;								\
	libh__ftr=(o);								\
	libh__ftr->count_objects == 0						\
	? -1									\
	: ( libh__cwd = open( ".", O_RDONLY | O_DIRECTORY ) ) < 0		\
	? -1									\
	: ( fchdir( libh__ftr->rdir_last ) < 0 )				\
	? -1									\
	: ({	libh__fd = open( libh__ftr->name_last, (f) );			\
		fchdir( libh__cwd );						\
		libh__fd;							\
	})									\
})

//-----------------------------------------------------------------------------
// macro	ftr_open_mode
//
// purpose	Open the filesystem object last found by ftr_get() using the
//		three argument open call with a file mode setting.  This is
//		only needed when creating new files, which could be needed by
//		a caller that deleted or renamed the file found by ftr_get().
//
// arguments	1 (FTR) reference to which file tree recursion
//		2 (int) open flags for 2nd argument
//		3 (int) mode for new file
//
// yields	(int)  < 0 : error
//		(int) >= 0 : open file descriptor
//-----------------------------------------------------------------------------
#define ftr_open_mode(o,f,m) ({							\
	FTR libh__ftr;								\
	int libh__cwd;								\
	int libh__fd;								\
	libh__ftr=(o);								\
	libh__ftr->count_objects == 0						\
	? -1									\
	: ( libh__cwd = open( ".", O_RDONLY | O_DIRECTORY ) ) < 0		\
	? -1									\
	: ( fchdir( libh__ftr->rdir_last ) < 0 )				\
	? -1									\
	: ({	libh__fd = open( libh__ftr->name_last, (f), (m) );		\
		fchdir( libh__cwd );						\
		libh__fd;							\
	})									\
})

__DEFINE_END__

__SUFFIX_BEGIN__
#endif /* __FTR_H__ */
__SUFFIX_END__

