<commit_before>/*
===========================================================================

Daemon GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.

This file is part of the Daemon GPL Source Code (Daemon Source Code).

Daemon Source Code 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.

Daemon Source Code 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 Daemon Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Daemon Source Code is also subject to certain additional terms.
You should have received a copy of these additional terms immediately following the
terms and conditions of the GNU General Public License which accompanied the Daemon
Source Code.  If not, please request a copy in writing from id Software at the address
below.

If you have questions concerning this license or the applicable additional terms, you
may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville,
Maryland 20850 USA.

===========================================================================
*/

#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "sys_local.h"

#include <stdarg.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <pwd.h>
#include <libgen.h>
#include <fcntl.h>
#include <fenv.h>


#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
#include <SDL.h>
#include <SDL_syswm.h>
#endif

#ifndef SIGIOT
#define SIGIOT SIGABRT
#endif

qboolean    stdinIsATTY;

// Used to determine where to store user-specific files
static char homePath[ MAX_OSPATH ] = { 0 };

/*
==================
Sys_DefaultHomePath
==================
*/
char *Sys_DefaultHomePath( void )
{
	char *p;

	if ( !*homePath )
	{
		if ( ( p = getenv( "HOME" ) ) != NULL )
		{
			Q_strncpyz( homePath, p, sizeof( homePath ) );
#ifdef MACOS_X
			Q_strcat( homePath, sizeof( homePath ),
			          "/Library/Application Support/" PRODUCT_NAME );
#else
			Q_strcat( homePath, sizeof( homePath ), "/." PRODUCT_NAME );
#endif
		}
	}

	return homePath;
}

/*
================
Sys_Milliseconds
================
*/

/* base time in seconds, that's our origin
  timeval:tv_sec is an int:
  assuming this wraps every 0x7fffffff - ~68 years since the Epoch (1970) - we're safe till 2038
*/

unsigned long sys_timeBase = 0;

/* current time in ms, using sys_timeBase as origin
  NOTE: sys_timeBase*1000 + curtime -> ms since the Epoch
    0x7fffffff ms - ~24 days
  although timeval:tv_usec is an int, I'm not sure wether it is actually used as an unsigned int
  (which would affect the wrap period)
*/

int Sys_Milliseconds( void )
{
	struct timeval tp;

	gettimeofday( &tp, NULL );

	if ( !sys_timeBase )
	{
		sys_timeBase = tp.tv_sec;
		return tp.tv_usec/1000;
	}

	return ( tp.tv_sec - sys_timeBase ) * 1000 + tp.tv_usec/1000;
}

/*
==================
Sys_RandomBytes
==================
*/
qboolean Sys_RandomBytes( byte *string, int len )
{
	FILE *fp;

	fp = fopen( "/dev/urandom", "r" );

	if ( !fp )
	{
		return qfalse;
	}

	if ( fread( string, sizeof( byte ), len, fp ) != len )
	{
		fclose( fp );
		return qfalse;
	}

	fclose( fp );
	return qtrue;
}

/*
==================
Sys_GetCurrentUser
==================
*/
char *Sys_GetCurrentUser( void )
{
	struct passwd *p;

	if ( ( p = getpwuid( getuid() ) ) == NULL )
	{
		return "player";
	}

	return p->pw_name;
}


/*
==================
Sys_GetClipboardData
==================
*/
#ifndef MACOS_X
#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
static struct {
	Display *display;
	Window  window;
	void  ( *lockDisplay )( void );
	void  ( *unlockDisplay )( void );
	Atom    utf8;
} x11 = { NULL };
#endif

char *Sys_GetClipboardData( clipboard_t clip )
{
#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
	// derived from SDL clipboard code (http://hg.assembla.com/SDL_Clipboard)
	Window        owner;
	Atom          selection;
	Atom          converted;
	Atom          selectionType;
	int           selectionFormat;
	unsigned long nbytes;
	unsigned long overflow;
	char          *src;

	if ( x11.display == NULL )
	{
		SDL_SysWMinfo info;

		SDL_VERSION( &info.version );
		if ( SDL_GetWMInfo( &info ) != 1 || info.subsystem != SDL_SYSWM_X11 )
		{
			Com_Printf("Not on X11? (%d)\n",info.subsystem);
			return NULL;
		}

		x11.display = info.info.x11.display;
		x11.window = info.info.x11.window;
		x11.lockDisplay = info.info.x11.lock_func;
		x11.unlockDisplay = info.info.x11.unlock_func;
		x11.utf8 = XInternAtom( x11.display, "UTF8_STRING", False );

		SDL_EventState( SDL_SYSWMEVENT, SDL_ENABLE );
		//SDL_SetEventFilter( Sys_ClipboardFilter );
	}

	x11.lockDisplay();

	switch ( clip )
	{
	// preference order; we use fall-through
	default: // SELECTION_CLIPBOARD
		selection = XInternAtom( x11.display, "CLIPBOARD", False );
		owner = XGetSelectionOwner( x11.display, selection );
		if ( owner != None && owner != x11.window )
		{
			break;
		}

	case SELECTION_PRIMARY:
		selection = XA_PRIMARY;
		owner = XGetSelectionOwner( x11.display, selection );
		if ( owner != None && owner != x11.window )
		{
			break;
		}

	case SELECTION_SECONDARY:
		selection = XA_SECONDARY;
		owner = XGetSelectionOwner( x11.display, selection );
	}

	converted = XInternAtom( x11.display, "UNVANQUISHED_SELECTION", False );
	x11.unlockDisplay();

	if ( owner == None || owner == x11.window )
	{
		selection = XA_CUT_BUFFER0;
		owner = RootWindow( x11.display, DefaultScreen( x11.display ) );
	}
	else
	{
		SDL_Event event;

		x11.lockDisplay();
		owner = x11.window;
		//FIXME: when we can respond to clipboard requests, don't alter selection
		XConvertSelection( x11.display, selection, x11.utf8, converted, owner, CurrentTime );
		x11.unlockDisplay();

		for (;;)
		{
			SDL_WaitEvent( &event );

			if ( event.type == SDL_SYSWMEVENT )
			{
				XEvent xevent = event.syswm.msg->event.xevent;

				if ( xevent.type == SelectionNotify &&
				     xevent.xselection.requestor == owner )
				{
					break;
				}
			}
		}
	}

	x11.lockDisplay ();

	if ( XGetWindowProperty( x11.display, owner, converted, 0, INT_MAX / 4,
	                         False, x11.utf8, &selectionType, &selectionFormat,
	                         &nbytes, &overflow, (unsigned char **) &src ) == Success )
	{
		char *dest = NULL;

		if ( selectionType == x11.utf8 )
		{
			dest = Z_Malloc( nbytes + 1 );
			memcpy( dest, src, nbytes );
			dest[ nbytes ] = 0;
		}
		XFree( src );

		x11.unlockDisplay();
		return dest;
	}

	x11.unlockDisplay();
#endif // !DEDICATED
	return NULL;
}
#endif // !MACOSX

#define MEM_THRESHOLD 96 * 1024 * 1024

/*
==================
Sys_LowPhysicalMemory

TODO
==================
*/
qboolean Sys_LowPhysicalMemory( void )
{
	return qfalse;
}

/*
==================
Sys_Basename
==================
*/
char *Sys_Basename( char *path )
{
	return basename( path );
}

/*
==================
Sys_Dirname
==================
*/
char *Sys_Dirname( char *path )
{
	return dirname( path );
}

/*
==============
Sys_FOpen
==============
*/
FILE *Sys_FOpen( const char *ospath, const char *mode )
{
	struct stat buf;

	// check if path exists and is a directory
	if ( !stat( ospath, &buf ) && S_ISDIR( buf.st_mode ) )
	{
		return NULL;
	}

	return fopen( ospath, mode );
}

/*
==============
Sys_Chmod
==============
*/
void Sys_Chmod( const char *ospath, int mode )
{
	chmod( ospath, (mode_t) mode );
}

/*
==============
Sys_FChmod
==============
*/
void Sys_FChmod( FILE *f, int mode )
{
	fchmod( fileno( f ), (mode_t) mode );
}

/*
==================
Sys_Mkdir
==================
*/
qboolean Sys_Mkdir( const char *path )
{
	int result = mkdir( path, 0750 );

	if ( result != 0 )
	{
		return errno == EEXIST;
	}

	return qtrue;
}

/*
==================
Sys_Mkfifo
==================
*/
FILE *Sys_Mkfifo( const char *ospath )
{
	FILE	*fifo;
	int	result;
	int	fn;
	struct	stat buf;

	// if file already exists AND is a pipefile, remove it
	if( !stat( ospath, &buf ) && S_ISFIFO( buf.st_mode ) )
		FS_Remove( ospath );

	result = mkfifo( ospath, 0600 );
	if( result != 0 )
		return NULL;

	fn = open( ospath, O_RDWR | O_NONBLOCK );
	if( fn == -1 )
		return NULL;

	fifo = fdopen( fn, "w+" );
	if( fifo == NULL )
		close( fn );

	return fifo;
}


/*
==================
Sys_Cwd
==================
*/
char *Sys_Cwd( void )
{
	static char cwd[ MAX_OSPATH ];
	char        *result = getcwd( cwd, sizeof( cwd ) - 1 );

#ifdef MACOS_X
	char *apppath = Sys_DefaultAppPath();

	if ( apppath[ 0 ] && apppath[ 0 ] != '.' )
	{
		return apppath;
	}

#endif

	if ( result != cwd )
	{
		return NULL;
	}

	cwd[ MAX_OSPATH - 1 ] = 0;

	return cwd;
}

/*
==============================================================

DIRECTORY SCANNING

==============================================================
*/

#define MAX_FOUND_FILES 0x1000

/*
==================
Sys_ListFilteredFiles
==================
*/
void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles )
{
	char          search[ MAX_OSPATH ], newsubdirs[ MAX_OSPATH ];
	char          filename[ MAX_OSPATH ];
	DIR           *fdir;
	struct dirent *d;

	struct stat   st;

	if ( *numfiles >= MAX_FOUND_FILES - 1 )
	{
		return;
	}

	if ( strlen( subdirs ) )
	{
		Com_sprintf( search, sizeof( search ), "%s/%s", basedir, subdirs );
	}
	else
	{
		Com_sprintf( search, sizeof( search ), "%s", basedir );
	}

	if ( ( fdir = opendir( search ) ) == NULL )
	{
		return;
	}

	while ( ( d = readdir( fdir ) ) != NULL )
	{
		Com_sprintf( filename, sizeof( filename ), "%s/%s", search, d->d_name );

		if ( stat( filename, &st ) == -1 )
		{
			continue;
		}

		if ( st.st_mode & S_IFDIR )
		{
			if ( Q_stricmp( d->d_name, "." ) && Q_stricmp( d->d_name, ".." ) )
			{
				if ( strlen( subdirs ) )
				{
					Com_sprintf( newsubdirs, sizeof( newsubdirs ), "%s/%s", subdirs, d->d_name );
				}
				else
				{
					Com_sprintf( newsubdirs, sizeof( newsubdirs ), "%s", d->d_name );
				}

				Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles );
			}
		}

		if ( *numfiles >= MAX_FOUND_FILES - 1 )
		{
			break;
		}

		Com_sprintf( filename, sizeof( filename ), "%s/%s", subdirs, d->d_name );

		if ( !Com_FilterPath( filter, filename, qfalse ) )
		{
			continue;
		}

		list[ *numfiles ] = CopyString( filename );
		( *numfiles ) ++;
	}

	closedir( fdir );
}

/*
==================
Sys_ListFiles
==================
*/
char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs )
{
	struct dirent *d;

	DIR           *fdir;
	qboolean      dironly = wantsubs;
	char          search[ MAX_OSPATH ];
	int           nfiles;
	char          **listCopy;
	char          *list[ MAX_FOUND_FILES ];
	int           i;
	struct stat   st;

//	int           extLen;

	if ( filter )
	{
		nfiles = 0;
		Sys_ListFilteredFiles( directory, "", filter, list, &nfiles );

		list[ nfiles ] = NULL;
		*numfiles = nfiles;

		if ( !nfiles )
		{
			return NULL;
		}

		listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );

		for ( i = 0; i < nfiles; i++ )
		{
			listCopy[ i ] = list[ i ];
		}

		listCopy[ i ] = NULL;

		return listCopy;
	}

	if ( !extension )
	{
		extension = "";
	}

	if ( extension[ 0 ] == '/' && extension[ 1 ] == 0 )
	{
		extension = "";
		dironly = qtrue;
	}

//	extLen = strlen( extension );

	// search
	nfiles = 0;

	if ( ( fdir = opendir( directory ) ) == NULL )
	{
		*numfiles = 0;
		return NULL;
	}

	while ( ( d = readdir( fdir ) ) != NULL )
	{
		Com_sprintf( search, sizeof( search ), "%s/%s", directory, d->d_name );

		if ( stat( search, &st ) == -1 )
		{
			continue;
		}

		if ( ( dironly && !( st.st_mode & S_IFDIR ) ) ||
		     ( !dironly && ( st.st_mode & S_IFDIR ) ) )
		{
			continue;
		}

		if ( *extension )
		{
			if ( strlen( d->d_name ) < strlen( extension ) ||
			     Q_stricmp(
			       d->d_name + strlen( d->d_name ) - strlen( extension ),
			       extension ) )
			{
				continue; // didn't match
			}
		}

		if ( nfiles == MAX_FOUND_FILES - 1 )
		{
			break;
		}

		list[ nfiles ] = CopyString( d->d_name );
		nfiles++;
	}

	list[ nfiles ] = NULL;

	closedir( fdir );

	// return a copy of the list
	*numfiles = nfiles;

	if ( !nfiles )
	{
		return NULL;
	}

	listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );

	for ( i = 0; i < nfiles; i++ )
	{
		listCopy[ i ] = list[ i ];
	}

	listCopy[ i ] = NULL;

	return listCopy;
}

/*
==================
Sys_FreeFileList
==================
*/
void Sys_FreeFileList( char **list )
{
	int i;

	if ( !list )
	{
		return;
	}

	for ( i = 0; list[ i ]; i++ )
	{
		Z_Free( list[ i ] );
	}

	Z_Free( list );
}

/*
==================
Sys_Sleep

Block execution for msec or until input is received.
==================
*/
void Sys_Sleep( int msec )
{
	if ( msec == 0 )
	{
		return;
	}

	if ( stdinIsATTY )
	{
		fd_set fdset;

		FD_ZERO( &fdset );
		FD_SET( STDIN_FILENO, &fdset );

		if ( msec < 0 )
		{
			select( STDIN_FILENO + 1, &fdset, NULL, NULL, NULL );
		}
		else
		{
			struct timeval timeout;

			timeout.tv_sec = msec / 1000;
			timeout.tv_usec = ( msec % 1000 ) * 1000;
			select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout );
		}
	}
	else
	{
		// With nothing to select() on, we can't wait indefinitely
		if ( msec < 0 )
		{
			msec = 10;
		}

		usleep( msec * 1000 );
	}
}

/*
==============
Sys_ErrorDialog

Display an error message
==============
*/
void Sys_ErrorDialog( const char *error )
{
	char         buffer[ 1024 ];
	unsigned int size;
	int          f;
	const char   *homepath = Cvar_VariableString( "fs_homepath" );
	const char   *gamedir = Cvar_VariableString( "fs_game" );
	const char   *fileName = "crashlog.txt";
	char         *ospath = FS_BuildOSPath( homepath, gamedir, fileName );

	Sys_Print( va( "%s\n", error ) );

#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
	// We may have grabbed input devices. Need to release.
	if ( SDL_WasInit( SDL_INIT_VIDEO ) )
	{
		SDL_WM_GrabInput( SDL_GRAB_OFF );
	}

	Sys_Dialog( DT_ERROR, va( "%s. See \"%s\" for details.", error, ospath ), "Error" );
#endif

	// Make sure the write path for the crashlog exists...
	if ( FS_CreatePath( ospath ) )
	{
		Com_Printf( "ERROR: couldn't create path '%s' for crash log.\n", ospath );
		return;
	}

	// We might be crashing because we maxed out the Quake MAX_FILE_HANDLES,
	// which will come through here, so we don't want to recurse forever by
	// calling FS_FOpenFileWrite()...use the Unix system APIs instead.
	f = open( ospath, O_CREAT | O_TRUNC | O_WRONLY, 0640 );

	if ( f == -1 )
	{
		Com_Printf( "ERROR: couldn't open %s\n", fileName );
		return;
	}

	// We're crashing, so we don't care much if write() or close() fails.
	while ( ( size = CON_LogRead( buffer, sizeof( buffer ) ) ) > 0 )
	{<commit_msg>Fix recursive crash when home path cannot be created<commit_after>/*
===========================================================================

Daemon GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.

This file is part of the Daemon GPL Source Code (Daemon Source Code).

Daemon Source Code 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.

Daemon Source Code 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 Daemon Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Daemon Source Code is also subject to certain additional terms.
You should have received a copy of these additional terms immediately following the
terms and conditions of the GNU General Public License which accompanied the Daemon
Source Code.  If not, please request a copy in writing from id Software at the address
below.

If you have questions concerning this license or the applicable additional terms, you
may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville,
Maryland 20850 USA.

===========================================================================
*/

#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "sys_local.h"

#include <stdarg.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <pwd.h>
#include <libgen.h>
#include <fcntl.h>
#include <fenv.h>


#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
#include <SDL.h>
#include <SDL_syswm.h>
#endif

#ifndef SIGIOT
#define SIGIOT SIGABRT
#endif

qboolean    stdinIsATTY;

// Used to determine where to store user-specific files
static char homePath[ MAX_OSPATH ] = { 0 };

/*
==================
Sys_DefaultHomePath
==================
*/
char *Sys_DefaultHomePath( void )
{
	char *p;

	if ( !*homePath )
	{
		if ( ( p = getenv( "HOME" ) ) != NULL )
		{
			Q_strncpyz( homePath, p, sizeof( homePath ) );
#ifdef MACOS_X
			Q_strcat( homePath, sizeof( homePath ),
			          "/Library/Application Support/" PRODUCT_NAME );
#else
			Q_strcat( homePath, sizeof( homePath ), "/." PRODUCT_NAME );
#endif
		}
	}

	return homePath;
}

/*
================
Sys_Milliseconds
================
*/

/* base time in seconds, that's our origin
  timeval:tv_sec is an int:
  assuming this wraps every 0x7fffffff - ~68 years since the Epoch (1970) - we're safe till 2038
*/

unsigned long sys_timeBase = 0;

/* current time in ms, using sys_timeBase as origin
  NOTE: sys_timeBase*1000 + curtime -> ms since the Epoch
    0x7fffffff ms - ~24 days
  although timeval:tv_usec is an int, I'm not sure wether it is actually used as an unsigned int
  (which would affect the wrap period)
*/

int Sys_Milliseconds( void )
{
	struct timeval tp;

	gettimeofday( &tp, NULL );

	if ( !sys_timeBase )
	{
		sys_timeBase = tp.tv_sec;
		return tp.tv_usec/1000;
	}

	return ( tp.tv_sec - sys_timeBase ) * 1000 + tp.tv_usec/1000;
}

/*
==================
Sys_RandomBytes
==================
*/
qboolean Sys_RandomBytes( byte *string, int len )
{
	FILE *fp;

	fp = fopen( "/dev/urandom", "r" );

	if ( !fp )
	{
		return qfalse;
	}

	if ( fread( string, sizeof( byte ), len, fp ) != len )
	{
		fclose( fp );
		return qfalse;
	}

	fclose( fp );
	return qtrue;
}

/*
==================
Sys_GetCurrentUser
==================
*/
char *Sys_GetCurrentUser( void )
{
	struct passwd *p;

	if ( ( p = getpwuid( getuid() ) ) == NULL )
	{
		return "player";
	}

	return p->pw_name;
}


/*
==================
Sys_GetClipboardData
==================
*/
#ifndef MACOS_X
#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
static struct {
	Display *display;
	Window  window;
	void  ( *lockDisplay )( void );
	void  ( *unlockDisplay )( void );
	Atom    utf8;
} x11 = { NULL };
#endif

char *Sys_GetClipboardData( clipboard_t clip )
{
#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
	// derived from SDL clipboard code (http://hg.assembla.com/SDL_Clipboard)
	Window        owner;
	Atom          selection;
	Atom          converted;
	Atom          selectionType;
	int           selectionFormat;
	unsigned long nbytes;
	unsigned long overflow;
	char          *src;

	if ( x11.display == NULL )
	{
		SDL_SysWMinfo info;

		SDL_VERSION( &info.version );
		if ( SDL_GetWMInfo( &info ) != 1 || info.subsystem != SDL_SYSWM_X11 )
		{
			Com_Printf("Not on X11? (%d)\n",info.subsystem);
			return NULL;
		}

		x11.display = info.info.x11.display;
		x11.window = info.info.x11.window;
		x11.lockDisplay = info.info.x11.lock_func;
		x11.unlockDisplay = info.info.x11.unlock_func;
		x11.utf8 = XInternAtom( x11.display, "UTF8_STRING", False );

		SDL_EventState( SDL_SYSWMEVENT, SDL_ENABLE );
		//SDL_SetEventFilter( Sys_ClipboardFilter );
	}

	x11.lockDisplay();

	switch ( clip )
	{
	// preference order; we use fall-through
	default: // SELECTION_CLIPBOARD
		selection = XInternAtom( x11.display, "CLIPBOARD", False );
		owner = XGetSelectionOwner( x11.display, selection );
		if ( owner != None && owner != x11.window )
		{
			break;
		}

	case SELECTION_PRIMARY:
		selection = XA_PRIMARY;
		owner = XGetSelectionOwner( x11.display, selection );
		if ( owner != None && owner != x11.window )
		{
			break;
		}

	case SELECTION_SECONDARY:
		selection = XA_SECONDARY;
		owner = XGetSelectionOwner( x11.display, selection );
	}

	converted = XInternAtom( x11.display, "UNVANQUISHED_SELECTION", False );
	x11.unlockDisplay();

	if ( owner == None || owner == x11.window )
	{
		selection = XA_CUT_BUFFER0;
		owner = RootWindow( x11.display, DefaultScreen( x11.display ) );
	}
	else
	{
		SDL_Event event;

		x11.lockDisplay();
		owner = x11.window;
		//FIXME: when we can respond to clipboard requests, don't alter selection
		XConvertSelection( x11.display, selection, x11.utf8, converted, owner, CurrentTime );
		x11.unlockDisplay();

		for (;;)
		{
			SDL_WaitEvent( &event );

			if ( event.type == SDL_SYSWMEVENT )
			{
				XEvent xevent = event.syswm.msg->event.xevent;

				if ( xevent.type == SelectionNotify &&
				     xevent.xselection.requestor == owner )
				{
					break;
				}
			}
		}
	}

	x11.lockDisplay ();

	if ( XGetWindowProperty( x11.display, owner, converted, 0, INT_MAX / 4,
	                         False, x11.utf8, &selectionType, &selectionFormat,
	                         &nbytes, &overflow, (unsigned char **) &src ) == Success )
	{
		char *dest = NULL;

		if ( selectionType == x11.utf8 )
		{
			dest = Z_Malloc( nbytes + 1 );
			memcpy( dest, src, nbytes );
			dest[ nbytes ] = 0;
		}
		XFree( src );

		x11.unlockDisplay();
		return dest;
	}

	x11.unlockDisplay();
#endif // !DEDICATED
	return NULL;
}
#endif // !MACOSX

#define MEM_THRESHOLD 96 * 1024 * 1024

/*
==================
Sys_LowPhysicalMemory

TODO
==================
*/
qboolean Sys_LowPhysicalMemory( void )
{
	return qfalse;
}

/*
==================
Sys_Basename
==================
*/
char *Sys_Basename( char *path )
{
	return basename( path );
}

/*
==================
Sys_Dirname
==================
*/
char *Sys_Dirname( char *path )
{
	return dirname( path );
}

/*
==============
Sys_FOpen
==============
*/
FILE *Sys_FOpen( const char *ospath, const char *mode )
{
	struct stat buf;

	// check if path exists and is a directory
	if ( !stat( ospath, &buf ) && S_ISDIR( buf.st_mode ) )
	{
		return NULL;
	}

	return fopen( ospath, mode );
}

/*
==============
Sys_Chmod
==============
*/
void Sys_Chmod( const char *ospath, int mode )
{
	chmod( ospath, (mode_t) mode );
}

/*
==============
Sys_FChmod
==============
*/
void Sys_FChmod( FILE *f, int mode )
{
	fchmod( fileno( f ), (mode_t) mode );
}

/*
==================
Sys_Mkdir
==================
*/
qboolean Sys_Mkdir( const char *path )
{
	int result = mkdir( path, 0750 );

	if ( result != 0 )
	{
		return errno == EEXIST;
	}

	return qtrue;
}

/*
==================
Sys_Mkfifo
==================
*/
FILE *Sys_Mkfifo( const char *ospath )
{
	FILE	*fifo;
	int	result;
	int	fn;
	struct	stat buf;

	// if file already exists AND is a pipefile, remove it
	if( !stat( ospath, &buf ) && S_ISFIFO( buf.st_mode ) )
		FS_Remove( ospath );

	result = mkfifo( ospath, 0600 );
	if( result != 0 )
		return NULL;

	fn = open( ospath, O_RDWR | O_NONBLOCK );
	if( fn == -1 )
		return NULL;

	fifo = fdopen( fn, "w+" );
	if( fifo == NULL )
		close( fn );

	return fifo;
}


/*
==================
Sys_Cwd
==================
*/
char *Sys_Cwd( void )
{
	static char cwd[ MAX_OSPATH ];
	char        *result = getcwd( cwd, sizeof( cwd ) - 1 );

#ifdef MACOS_X
	char *apppath = Sys_DefaultAppPath();

	if ( apppath[ 0 ] && apppath[ 0 ] != '.' )
	{
		return apppath;
	}

#endif

	if ( result != cwd )
	{
		return NULL;
	}

	cwd[ MAX_OSPATH - 1 ] = 0;

	return cwd;
}

/*
==============================================================

DIRECTORY SCANNING

==============================================================
*/

#define MAX_FOUND_FILES 0x1000

/*
==================
Sys_ListFilteredFiles
==================
*/
void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles )
{
	char          search[ MAX_OSPATH ], newsubdirs[ MAX_OSPATH ];
	char          filename[ MAX_OSPATH ];
	DIR           *fdir;
	struct dirent *d;

	struct stat   st;

	if ( *numfiles >= MAX_FOUND_FILES - 1 )
	{
		return;
	}

	if ( strlen( subdirs ) )
	{
		Com_sprintf( search, sizeof( search ), "%s/%s", basedir, subdirs );
	}
	else
	{
		Com_sprintf( search, sizeof( search ), "%s", basedir );
	}

	if ( ( fdir = opendir( search ) ) == NULL )
	{
		return;
	}

	while ( ( d = readdir( fdir ) ) != NULL )
	{
		Com_sprintf( filename, sizeof( filename ), "%s/%s", search, d->d_name );

		if ( stat( filename, &st ) == -1 )
		{
			continue;
		}

		if ( st.st_mode & S_IFDIR )
		{
			if ( Q_stricmp( d->d_name, "." ) && Q_stricmp( d->d_name, ".." ) )
			{
				if ( strlen( subdirs ) )
				{
					Com_sprintf( newsubdirs, sizeof( newsubdirs ), "%s/%s", subdirs, d->d_name );
				}
				else
				{
					Com_sprintf( newsubdirs, sizeof( newsubdirs ), "%s", d->d_name );
				}

				Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles );
			}
		}

		if ( *numfiles >= MAX_FOUND_FILES - 1 )
		{
			break;
		}

		Com_sprintf( filename, sizeof( filename ), "%s/%s", subdirs, d->d_name );

		if ( !Com_FilterPath( filter, filename, qfalse ) )
		{
			continue;
		}

		list[ *numfiles ] = CopyString( filename );
		( *numfiles ) ++;
	}

	closedir( fdir );
}

/*
==================
Sys_ListFiles
==================
*/
char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs )
{
	struct dirent *d;

	DIR           *fdir;
	qboolean      dironly = wantsubs;
	char          search[ MAX_OSPATH ];
	int           nfiles;
	char          **listCopy;
	char          *list[ MAX_FOUND_FILES ];
	int           i;
	struct stat   st;

//	int           extLen;

	if ( filter )
	{
		nfiles = 0;
		Sys_ListFilteredFiles( directory, "", filter, list, &nfiles );

		list[ nfiles ] = NULL;
		*numfiles = nfiles;

		if ( !nfiles )
		{
			return NULL;
		}

		listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );

		for ( i = 0; i < nfiles; i++ )
		{
			listCopy[ i ] = list[ i ];
		}

		listCopy[ i ] = NULL;

		return listCopy;
	}

	if ( !extension )
	{
		extension = "";
	}

	if ( extension[ 0 ] == '/' && extension[ 1 ] == 0 )
	{
		extension = "";
		dironly = qtrue;
	}

//	extLen = strlen( extension );

	// search
	nfiles = 0;

	if ( ( fdir = opendir( directory ) ) == NULL )
	{
		*numfiles = 0;
		return NULL;
	}

	while ( ( d = readdir( fdir ) ) != NULL )
	{
		Com_sprintf( search, sizeof( search ), "%s/%s", directory, d->d_name );

		if ( stat( search, &st ) == -1 )
		{
			continue;
		}

		if ( ( dironly && !( st.st_mode & S_IFDIR ) ) ||
		     ( !dironly && ( st.st_mode & S_IFDIR ) ) )
		{
			continue;
		}

		if ( *extension )
		{
			if ( strlen( d->d_name ) < strlen( extension ) ||
			     Q_stricmp(
			       d->d_name + strlen( d->d_name ) - strlen( extension ),
			       extension ) )
			{
				continue; // didn't match
			}
		}

		if ( nfiles == MAX_FOUND_FILES - 1 )
		{
			break;
		}

		list[ nfiles ] = CopyString( d->d_name );
		nfiles++;
	}

	list[ nfiles ] = NULL;

	closedir( fdir );

	// return a copy of the list
	*numfiles = nfiles;

	if ( !nfiles )
	{
		return NULL;
	}

	listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );

	for ( i = 0; i < nfiles; i++ )
	{
		listCopy[ i ] = list[ i ];
	}

	listCopy[ i ] = NULL;

	return listCopy;
}

/*
==================
Sys_FreeFileList
==================
*/
void Sys_FreeFileList( char **list )
{
	int i;

	if ( !list )
	{
		return;
	}

	for ( i = 0; list[ i ]; i++ )
	{
		Z_Free( list[ i ] );
	}

	Z_Free( list );
}

/*
==================
Sys_Sleep

Block execution for msec or until input is received.
==================
*/
void Sys_Sleep( int msec )
{
	if ( msec == 0 )
	{
		return;
	}

	if ( stdinIsATTY )
	{
		fd_set fdset;

		FD_ZERO( &fdset );
		FD_SET( STDIN_FILENO, &fdset );

		if ( msec < 0 )
		{
			select( STDIN_FILENO + 1, &fdset, NULL, NULL, NULL );
		}
		else
		{
			struct timeval timeout;

			timeout.tv_sec = msec / 1000;
			timeout.tv_usec = ( msec % 1000 ) * 1000;
			select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout );
		}
	}
	else
	{
		// With nothing to select() on, we can't wait indefinitely
		if ( msec < 0 )
		{
			msec = 10;
		}

		usleep( msec * 1000 );
	}
}

/*
==============
Sys_ErrorDialog

Display an error message
==============
*/
void Sys_ErrorDialog( const char *error )
{
	char         buffer[ 1024 ];
	unsigned int size;
	int          f;
	const char   *homepath = Cvar_VariableString( "fs_homepath" );
	const char   *gamedir = Cvar_VariableString( "fs_game" );
	const char   *fileName = "crashlog.txt";
	char         *ospath = FS_BuildOSPath( homepath, gamedir, fileName );

	Sys_Print( va( "%s\n", error ) );

#if !defined(DEDICATED) && !defined(BUILD_TTY_CLIENT)
	// We may have grabbed input devices. Need to release.
	if ( SDL_WasInit( SDL_INIT_VIDEO ) )
	{
		SDL_WM_GrabInput( SDL_GRAB_OFF );
	}

	Sys_Dialog( DT_ERROR, va( "%s. See \"%s\" for details.", error, ospath ), "Error" );
#endif

	// Make sure the write path for the crashlog exists...
	if ( !Sys_Mkdir( ospath ) )
	{
		Com_Printf( "ERROR: couldn't create path '%s' for crash log.\n", ospath );
		return;
	}

	// We might be crashing because we maxed out the Quake MAX_FILE_HANDLES,
	// which will come through here, so we don't want to recurse forever by
	// calling FS_FOpenFileWrite()...use the Unix system APIs instead.
	f = open( ospath, O_CREAT | O_TRUNC | O_WRONLY, 0640 );

	if ( f == -1 )
	{
		Com_Printf( "ERROR: couldn't open %s\n", fileName );
		return;
	}

	// We're crashing, so we don't care much if write() or close() fails.
	while ( ( size = CON_LogRead( buffer, sizeof( buffer ) ) ) > 0 )
	{
{'fraction_non_alphanumeric': 0.18651442779558988, 'fraction_numerical': 0.014198782961460446, 'mean_word_length': 3.17069177241097, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 73, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3069523', 'n_tokens_mistral': 12203, 'n_tokens_neox': 10932, 'n_words': 3003}