2014-02-10 01:10:30 +00:00
/**************************************************************************/
/* file_access_unix.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-04 23:50:27 +00:00
2014-02-10 01:10:30 +00:00
# include "file_access_unix.h"
2022-10-03 09:43:20 +00:00
# if defined(UNIX_ENABLED)
2014-02-10 01:10:30 +00:00
# include "core/os/os.h"
2020-11-07 22:33:38 +00:00
# include "core/string/print_string.h"
2018-09-11 16:13:45 +00:00
2022-10-03 09:43:20 +00:00
# include <errno.h>
# include <fcntl.h>
2014-02-10 01:10:30 +00:00
# include <sys/stat.h>
# include <sys/types.h>
2017-09-04 19:29:59 +00:00
# include <unistd.h>
2014-02-10 01:10:30 +00:00
2019-01-09 09:24:23 +00:00
# if defined(TOOLS_ENABLED)
# include <limits.h>
# include <stdlib.h>
# endif
2014-02-10 01:10:30 +00:00
void FileAccessUnix : : check_errors ( ) const {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_MSG ( f , " File must be opened before use. " ) ;
2014-02-10 01:10:30 +00:00
if ( feof ( f ) ) {
last_error = ERR_FILE_EOF ;
}
}
2022-09-05 11:01:31 +00:00
Error FileAccessUnix : : open_internal ( const String & p_path , int p_mode_flags ) {
2022-04-12 08:15:02 +00:00
_close ( ) ;
2014-02-10 01:10:30 +00:00
2018-03-09 14:45:21 +00:00
path_src = p_path ;
2015-09-12 13:54:47 +00:00
path = fix_path ( p_path ) ;
2020-07-27 10:43:20 +00:00
//printf("opening %s, %i\n", path.utf8().get_data(), Memory::get_static_mem_usage());
2014-02-10 01:10:30 +00:00
2019-09-25 08:28:50 +00:00
ERR_FAIL_COND_V_MSG ( f , ERR_ALREADY_IN_USE , " File is already in use. " ) ;
2014-02-10 01:10:30 +00:00
const char * mode_string ;
2017-03-05 15:44:50 +00:00
2020-05-14 14:41:43 +00:00
if ( p_mode_flags = = READ ) {
2014-02-10 01:10:30 +00:00
mode_string = " rb " ;
2020-05-14 14:41:43 +00:00
} else if ( p_mode_flags = = WRITE ) {
2014-02-10 01:10:30 +00:00
mode_string = " wb " ;
2020-05-14 14:41:43 +00:00
} else if ( p_mode_flags = = READ_WRITE ) {
2016-01-10 18:15:04 +00:00
mode_string = " rb+ " ;
2020-05-14 14:41:43 +00:00
} else if ( p_mode_flags = = WRITE_READ ) {
2016-01-02 15:03:33 +00:00
mode_string = " wb+ " ;
2020-05-14 14:41:43 +00:00
} else {
2014-02-10 01:10:30 +00:00
return ERR_INVALID_PARAMETER ;
2020-05-14 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
/* pretty much every implementation that uses fopen as primary
backend ( unix - compatible mostly ) supports utf8 encoding */
//printf("opening %s as %s\n", p_path.utf8().get_data(), path.utf8().get_data());
2022-10-01 19:09:22 +00:00
struct stat st = { } ;
2017-09-04 19:29:59 +00:00
int err = stat ( path . utf8 ( ) . get_data ( ) , & st ) ;
2017-09-04 23:14:14 +00:00
if ( ! err ) {
switch ( st . st_mode & S_IFMT ) {
case S_IFLNK :
case S_IFREG :
break ;
default :
return ERR_FILE_CANT_OPEN ;
}
2017-09-04 19:29:59 +00:00
}
2014-02-10 01:10:30 +00:00
2019-01-09 09:24:23 +00:00
# if defined(TOOLS_ENABLED)
if ( p_mode_flags & READ ) {
String real_path = get_real_path ( ) ;
if ( real_path ! = path ) {
// Don't warn on symlinks, since they can be used to simply share addons on multiple projects.
if ( real_path . to_lower ( ) = = path . to_lower ( ) ) {
// The File system is case insensitive, but other platforms can be sensitive to it
// To ease cross-platform development, we issue a warning if users try to access
// a file using the wrong case (which *works* on Windows and macOS, but won't on other
// platforms).
WARN_PRINT ( vformat ( " Case mismatch opening requested file '%s', stored as '%s' in the filesystem. This file will not open when exported to other case-sensitive platforms. " , path , real_path ) ) ;
}
}
}
# endif
2021-07-23 19:01:18 +00:00
if ( is_backup_save_enabled ( ) & & ( p_mode_flags = = WRITE ) ) {
2014-02-10 01:10:30 +00:00
save_path = path ;
2023-02-12 13:05:48 +00:00
// Create a temporary file in the same directory as the target file.
path = path + " -XXXXXX " ;
2023-03-18 18:13:11 +00:00
CharString cs = path . utf8 ( ) ;
int fd = mkstemp ( cs . ptrw ( ) ) ;
if ( fd = = - 1 ) {
last_error = ERR_FILE_CANT_OPEN ;
return last_error ;
2023-02-12 13:05:48 +00:00
}
2023-07-25 00:02:06 +00:00
fchmod ( fd , 0666 ) ;
2023-03-18 18:13:11 +00:00
path = String : : utf8 ( cs . ptr ( ) ) ;
f = fdopen ( fd , mode_string ) ;
if ( f = = nullptr ) {
// Delete temp file and close descriptor if open failed.
: : unlink ( cs . ptr ( ) ) ;
: : close ( fd ) ;
last_error = ERR_FILE_CANT_OPEN ;
return last_error ;
}
} else {
f = fopen ( path . utf8 ( ) . get_data ( ) , mode_string ) ;
2014-02-10 01:10:30 +00:00
}
2020-04-01 23:20:12 +00:00
if ( f = = nullptr ) {
2019-08-20 11:59:46 +00:00
switch ( errno ) {
case ENOENT : {
last_error = ERR_FILE_NOT_FOUND ;
} break ;
default : {
last_error = ERR_FILE_CANT_OPEN ;
} break ;
}
return last_error ;
2014-02-10 01:10:30 +00:00
}
2019-10-07 09:31:20 +00:00
// Set close on exec to avoid leaking it to subprocesses.
int fd = fileno ( f ) ;
if ( fd ! = - 1 ) {
int opts = fcntl ( fd , F_GETFD ) ;
fcntl ( fd , F_SETFD , opts | FD_CLOEXEC ) ;
}
last_error = OK ;
flags = p_mode_flags ;
return OK ;
2014-02-10 01:10:30 +00:00
}
2017-09-04 19:29:59 +00:00
2022-04-12 08:15:02 +00:00
void FileAccessUnix : : _close ( ) {
2020-05-14 14:41:43 +00:00
if ( ! f ) {
2014-02-10 01:10:30 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2017-09-04 19:29:59 +00:00
2014-02-10 01:10:30 +00:00
fclose ( f ) ;
2020-04-01 23:20:12 +00:00
f = nullptr ;
2017-09-04 19:29:59 +00:00
2015-09-12 13:54:47 +00:00
if ( close_notification_func ) {
close_notification_func ( path , flags ) ;
}
2017-09-04 19:29:59 +00:00
2021-12-09 09:42:46 +00:00
if ( ! save_path . is_empty ( ) ) {
2023-02-12 13:05:48 +00:00
int rename_error = rename ( path . utf8 ( ) . get_data ( ) , save_path . utf8 ( ) . get_data ( ) ) ;
2016-06-13 13:10:50 +00:00
if ( rename_error & & close_fail_notify ) {
close_fail_notify ( save_path ) ;
}
2014-02-10 01:10:30 +00:00
save_path = " " ;
ERR_FAIL_COND ( rename_error ! = 0 ) ;
}
}
2017-09-04 19:29:59 +00:00
2014-02-10 01:10:30 +00:00
bool FileAccessUnix : : is_open ( ) const {
2020-04-01 23:20:12 +00:00
return ( f ! = nullptr ) ;
2014-02-10 01:10:30 +00:00
}
2017-09-04 19:29:59 +00:00
2018-03-09 14:45:21 +00:00
String FileAccessUnix : : get_path ( ) const {
return path_src ;
}
String FileAccessUnix : : get_path_absolute ( ) const {
return path ;
}
2019-01-09 09:24:23 +00:00
# if defined(TOOLS_ENABLED)
String FileAccessUnix : : get_real_path ( ) const {
char * resolved_path = : : realpath ( path . utf8 ( ) . get_data ( ) , nullptr ) ;
if ( ! resolved_path ) {
return path ;
}
String result ;
Error parse_ok = result . parse_utf8 ( resolved_path ) ;
: : free ( resolved_path ) ;
if ( parse_ok ! = OK ) {
return path ;
}
return result . simplify_path ( ) ;
}
# endif
2019-03-26 17:51:13 +00:00
void FileAccessUnix : : seek ( uint64_t p_position ) {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_MSG ( f , " File must be opened before use. " ) ;
2014-02-10 01:10:30 +00:00
last_error = OK ;
2019-03-26 17:51:13 +00:00
if ( fseeko ( f , p_position , SEEK_SET ) ) {
2014-02-10 01:10:30 +00:00
check_errors ( ) ;
2020-05-14 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
}
2017-09-04 19:29:59 +00:00
2014-02-10 01:10:30 +00:00
void FileAccessUnix : : seek_end ( int64_t p_position ) {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_MSG ( f , " File must be opened before use. " ) ;
2017-09-04 19:29:59 +00:00
2019-03-26 17:51:13 +00:00
if ( fseeko ( f , p_position , SEEK_END ) ) {
2014-02-10 01:10:30 +00:00
check_errors ( ) ;
2020-05-14 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
}
2017-09-04 19:29:59 +00:00
2019-03-26 17:51:13 +00:00
uint64_t FileAccessUnix : : get_position ( ) const {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_V_MSG ( f , 0 , " File must be opened before use. " ) ;
2017-09-04 19:29:59 +00:00
2019-03-26 17:51:13 +00:00
int64_t pos = ftello ( f ) ;
2017-09-04 19:29:59 +00:00
if ( pos < 0 ) {
2014-02-10 01:10:30 +00:00
check_errors ( ) ;
2017-09-04 19:29:59 +00:00
ERR_FAIL_V ( 0 ) ;
}
return pos ;
2014-02-10 01:10:30 +00:00
}
2017-09-04 19:29:59 +00:00
2021-05-25 06:58:49 +00:00
uint64_t FileAccessUnix : : get_length ( ) const {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_V_MSG ( f , 0 , " File must be opened before use. " ) ;
2014-02-10 01:10:30 +00:00
2019-03-26 17:51:13 +00:00
int64_t pos = ftello ( f ) ;
2017-09-04 19:29:59 +00:00
ERR_FAIL_COND_V ( pos < 0 , 0 ) ;
2019-03-26 17:51:13 +00:00
ERR_FAIL_COND_V ( fseeko ( f , 0 , SEEK_END ) , 0 ) ;
int64_t size = ftello ( f ) ;
2017-09-04 19:29:59 +00:00
ERR_FAIL_COND_V ( size < 0 , 0 ) ;
2019-03-26 17:51:13 +00:00
ERR_FAIL_COND_V ( fseeko ( f , pos , SEEK_SET ) , 0 ) ;
2014-02-10 01:10:30 +00:00
return size ;
}
bool FileAccessUnix : : eof_reached ( ) const {
return last_error = = ERR_FILE_EOF ;
}
2019-03-26 17:51:13 +00:00
uint64_t FileAccessUnix : : get_buffer ( uint8_t * p_dst , uint64_t p_length ) const {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_V_MSG ( f , - 1 , " File must be opened before use. " ) ;
2024-05-20 16:07:27 +00:00
ERR_FAIL_COND_V ( ! p_dst & & p_length > 0 , - 1 ) ;
2019-03-26 17:51:13 +00:00
uint64_t read = fread ( p_dst , 1 , p_length , f ) ;
2014-02-10 01:10:30 +00:00
check_errors ( ) ;
2024-05-20 16:07:27 +00:00
2014-02-10 01:10:30 +00:00
return read ;
2022-02-16 12:56:32 +00:00
}
2014-02-10 01:10:30 +00:00
Error FileAccessUnix : : get_error ( ) const {
return last_error ;
}
2024-04-08 19:09:34 +00:00
Error FileAccessUnix : : resize ( int64_t p_length ) {
ERR_FAIL_NULL_V_MSG ( f , FAILED , " File must be opened before use. " ) ;
int res = : : ftruncate ( fileno ( f ) , p_length ) ;
switch ( res ) {
case 0 :
return OK ;
case EBADF :
return ERR_FILE_CANT_OPEN ;
case EFBIG :
return ERR_OUT_OF_MEMORY ;
case EINVAL :
return ERR_INVALID_PARAMETER ;
default :
return FAILED ;
}
}
2017-09-22 05:56:02 +00:00
void FileAccessUnix : : flush ( ) {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_MSG ( f , " File must be opened before use. " ) ;
2017-09-22 05:56:02 +00:00
fflush ( f ) ;
}
2019-03-26 17:51:13 +00:00
void FileAccessUnix : : store_buffer ( const uint8_t * p_src , uint64_t p_length ) {
2023-09-09 15:46:44 +00:00
ERR_FAIL_NULL_MSG ( f , " File must be opened before use. " ) ;
2021-06-07 16:31:50 +00:00
ERR_FAIL_COND ( ! p_src & & p_length > 0 ) ;
2019-03-26 17:51:13 +00:00
ERR_FAIL_COND ( fwrite ( p_src , 1 , p_length , f ) ! = p_length ) ;
2018-02-04 12:23:23 +00:00
}
2014-02-10 01:10:30 +00:00
bool FileAccessUnix : : file_exists ( const String & p_path ) {
2017-09-04 19:29:59 +00:00
int err ;
2022-10-01 19:09:22 +00:00
struct stat st = { } ;
2014-02-10 01:10:30 +00:00
String filename = fix_path ( p_path ) ;
2017-09-04 19:29:59 +00:00
// Does the name exist at all?
err = stat ( filename . utf8 ( ) . get_data ( ) , & st ) ;
2020-05-14 14:41:43 +00:00
if ( err ) {
2014-02-10 01:10:30 +00:00
return false ;
2020-05-14 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
2017-09-04 19:29:59 +00:00
// See if we have access to the file
2020-05-14 14:41:43 +00:00
if ( access ( filename . utf8 ( ) . get_data ( ) , F_OK ) ) {
2017-09-04 19:29:59 +00:00
return false ;
2020-05-14 14:41:43 +00:00
}
2017-09-04 19:29:59 +00:00
// See if this is a regular file
switch ( st . st_mode & S_IFMT ) {
case S_IFLNK :
case S_IFREG :
return true ;
default :
return false ;
2014-02-10 01:10:30 +00:00
}
}
uint64_t FileAccessUnix : : _get_modified_time ( const String & p_file ) {
String file = fix_path ( p_file ) ;
2023-05-11 10:32:23 +00:00
struct stat status = { } ;
int err = stat ( file . utf8 ( ) . get_data ( ) , & status ) ;
2014-02-10 01:10:30 +00:00
2017-09-04 19:29:59 +00:00
if ( ! err ) {
2023-05-11 10:32:23 +00:00
return status . st_mtime ;
2014-02-10 01:10:30 +00:00
} else {
2021-12-01 20:47:09 +00:00
return 0 ;
2022-02-16 12:56:32 +00:00
}
2014-02-10 01:10:30 +00:00
}
2023-08-08 06:53:41 +00:00
BitField < FileAccess : : UnixPermissionFlags > FileAccessUnix : : _get_unix_permissions ( const String & p_file ) {
2019-04-07 18:46:52 +00:00
String file = fix_path ( p_file ) ;
2023-05-11 10:32:23 +00:00
struct stat status = { } ;
int err = stat ( file . utf8 ( ) . get_data ( ) , & status ) ;
2019-04-07 18:46:52 +00:00
if ( ! err ) {
2023-08-08 06:53:41 +00:00
return status . st_mode & 0xFFF ; //only permissions
2019-04-07 18:46:52 +00:00
} else {
2019-08-15 02:57:49 +00:00
ERR_FAIL_V_MSG ( 0 , " Failed to get unix permissions for: " + p_file + " . " ) ;
2022-02-16 12:56:32 +00:00
}
2019-04-07 18:46:52 +00:00
}
2023-08-08 06:53:41 +00:00
Error FileAccessUnix : : _set_unix_permissions ( const String & p_file , BitField < FileAccess : : UnixPermissionFlags > p_permissions ) {
2019-04-07 18:46:52 +00:00
String file = fix_path ( p_file ) ;
int err = chmod ( file . utf8 ( ) . get_data ( ) , p_permissions ) ;
2017-09-17 17:40:58 +00:00
if ( ! err ) {
return OK ;
}
return FAILED ;
}
2023-08-08 06:53:41 +00:00
bool FileAccessUnix : : _get_hidden_attribute ( const String & p_file ) {
2024-03-01 19:33:49 +00:00
# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
2023-08-08 06:53:41 +00:00
String file = fix_path ( p_file ) ;
struct stat st = { } ;
int err = stat ( file . utf8 ( ) . get_data ( ) , & st ) ;
ERR_FAIL_COND_V_MSG ( err , false , " Failed to get attributes for: " + p_file ) ;
return ( st . st_flags & UF_HIDDEN ) ;
# else
return false ;
# endif
}
Error FileAccessUnix : : _set_hidden_attribute ( const String & p_file , bool p_hidden ) {
2024-03-01 19:33:49 +00:00
# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
2023-08-08 06:53:41 +00:00
String file = fix_path ( p_file ) ;
struct stat st = { } ;
int err = stat ( file . utf8 ( ) . get_data ( ) , & st ) ;
ERR_FAIL_COND_V_MSG ( err , FAILED , " Failed to get attributes for: " + p_file ) ;
if ( p_hidden ) {
err = chflags ( file . utf8 ( ) . get_data ( ) , st . st_flags | UF_HIDDEN ) ;
} else {
err = chflags ( file . utf8 ( ) . get_data ( ) , st . st_flags & ~ UF_HIDDEN ) ;
}
ERR_FAIL_COND_V_MSG ( err , FAILED , " Failed to set attributes for: " + p_file ) ;
return OK ;
# else
return ERR_UNAVAILABLE ;
# endif
}
bool FileAccessUnix : : _get_read_only_attribute ( const String & p_file ) {
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
String file = fix_path ( p_file ) ;
struct stat st = { } ;
int err = stat ( file . utf8 ( ) . get_data ( ) , & st ) ;
ERR_FAIL_COND_V_MSG ( err , false , " Failed to get attributes for: " + p_file ) ;
return st . st_flags & UF_IMMUTABLE ;
# else
return false ;
# endif
}
Error FileAccessUnix : : _set_read_only_attribute ( const String & p_file , bool p_ro ) {
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
String file = fix_path ( p_file ) ;
struct stat st = { } ;
int err = stat ( file . utf8 ( ) . get_data ( ) , & st ) ;
ERR_FAIL_COND_V_MSG ( err , FAILED , " Failed to get attributes for: " + p_file ) ;
if ( p_ro ) {
err = chflags ( file . utf8 ( ) . get_data ( ) , st . st_flags | UF_IMMUTABLE ) ;
} else {
err = chflags ( file . utf8 ( ) . get_data ( ) , st . st_flags & ~ UF_IMMUTABLE ) ;
}
ERR_FAIL_COND_V_MSG ( err , FAILED , " Failed to set attributes for: " + p_file ) ;
return OK ;
# else
return ERR_UNAVAILABLE ;
# endif
}
2023-02-16 13:25:32 +00:00
void FileAccessUnix : : close ( ) {
_close ( ) ;
}
2020-04-01 23:20:12 +00:00
CloseNotificationFunc FileAccessUnix : : close_notification_func = nullptr ;
2014-02-10 01:10:30 +00:00
FileAccessUnix : : ~ FileAccessUnix ( ) {
2022-04-12 08:15:02 +00:00
_close ( ) ;
2014-02-10 01:10:30 +00:00
}
2022-10-03 09:43:20 +00:00
# endif // UNIX_ENABLED