/* Read an image and check that file handles are being closed on minimise.
 *
 * This will only work on linux: we signal success and do nothing if
 * /proc/self/fd does not exist.
 */

#include <stdio.h>
#include <unistd.h>

#define _GNU_SOURCE
#include <stdlib.h>

#include <vips/vips.h>

/**
 * get_open_files:
 *
 * Get a list of open files for this process.
 *
 * Returns (transfer full) (nullable): a new #GSList, or %NULL
 */
static GSList *
get_open_files()
{
	GSList *list = NULL;
	GDir *dir;
	const char *name;

	if( !(dir = g_dir_open( "/proc/self/fd", 0, NULL )) )
		return( NULL );

	while( (name = g_dir_read_name( dir )) ) {
		char *fullname = g_build_filename( "/proc/self/fd", name, NULL );

		list = g_slist_prepend( list, realpath( fullname, NULL ) );

		g_free( fullname );
	}

	g_dir_close( dir );

	return( list );
}

/**
 * fd_check:
 * @stage: the originating stage for the error message
 * @fds: a #GSList of file descriptors to check against
 *
 * Check for a leak by comparing the currently open files for this
 * process with the file descriptors in @fds. If there's a leak,
 * print an error message and return %FALSE.
 *
 * See also: get_open_files().
 * 
 * Returns: %TRUE if there are no leaks; %FALSE otherwise
 */
static gboolean
fd_check( const char *stage, GSList *fds )
{
	GSList *unique_list = NULL, *list, *iter;

	list = get_open_files();

	for( iter = list; iter; iter = iter->next )
		if( !g_slist_find_custom( fds, iter->data,
			(GCompareFunc) g_strcmp0 ) )
			unique_list = g_slist_prepend( unique_list, iter->data );

	if( unique_list == NULL ) {
		g_slist_free_full( list, g_free );
		return( TRUE );
	}

	fprintf( stderr, "%s: file descriptors not closed after %s:\n",
		vips_get_prgname(), stage );
	for( iter = unique_list; iter; iter = iter->next )
		fprintf( stderr, "%s\n", (char *) iter->data );

	g_slist_free( unique_list );
	g_slist_free_full( list, g_free );

	return( FALSE );
}

int
main( int argc, char **argv )
{
	VipsSource *source;
	VipsImage *image, *x;
	GSList *list;
	double average;

	if( VIPS_INIT( argv[0] ) )
		vips_error_exit( "unable to start" ); 

	if( argc != 2 ) 
		vips_error_exit( "usage: %s test-image", argv[0] ); 

	list = get_open_files();
	if( list == NULL )
		/* Probably not linux, silent success.
		 */
		return( 0 );

	/* This is usually a list of 4 files. stdout / stdin / stderr plus one
	 * more made for us by glib, I think, doing what I don't know.
	 */

	/* Opening an image should read the header, then close the fd.
	 */
	printf( "** rand open ..\n" );
	if( !(source = vips_source_new_from_file( argv[1] )) )
		goto error;
	if( !(image = vips_image_new_from_source( source, "",
		"access", VIPS_ACCESS_RANDOM,
		NULL )) )
		goto error;
	if( !fd_check( "header read", list ) )
		goto error;

	/* We should be able to read a chunk near the top, then have the fd
	 * closed again.
	 */
	printf( "** crop1, avg ..\n" );
	if( vips_crop( image, &x, 0, 0, image->Xsize, 10, NULL ) ||
		vips_avg( x, &average, NULL ) )
		goto error;

	g_object_unref( x );
	if( !fd_check( "first read", list ) )
		goto error;

	/* We should be able to read again, a little further down, and have
	 * the input restarted and closed again.
	 */
	printf( "** crop2, avg ..\n" );
	if( vips_crop( image, &x, 0, 20, image->Xsize, 10, NULL ) ||
		vips_avg( x, &average, NULL ) )
		goto error;

	g_object_unref( x );
	if( !fd_check( "second read", list ) )
		goto error;

	/* Clean up, and we should still just have three open.
	 */
	printf( "** unref ..\n" );
	g_object_unref( image );
	g_object_unref( source );
	printf( "** shutdown ..\n" );
	vips_shutdown();

	if( !fd_check( "shutdown", list ) )
		goto error;

	g_slist_free_full( list, g_free );
	return( 0 );

error:
	g_slist_free_full( list, g_free );
	return( 1 );
}
