Eina: add eina_file_access() API

this addition is motivated by the fact that the access() API on
Windows just check if a file is read only or read/write. See

https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess?view=msvc-170#remarks

This API now also manage if a file/dir is executable or not.

On Unix, access() is just called.
This commit is contained in:
Vincent Torri 2024-01-18 03:54:03 +01:00
parent 97845c1ee3
commit 34e25ac568
4 changed files with 253 additions and 1 deletions

View File

@ -148,6 +148,21 @@ typedef enum {
EINA_FILE_REMOVE /**< This memory is to be released and any content will be lost. Subsequent accesses will succeed but return fresh memory as if accessed for the first time. This may not succeed if the filesystem does not support it. @since 1.8 */
} Eina_File_Populate;
/**
* @typedef Eina_File_Access_Mode
* @brief Type for enumeration of a file access mode.
* @details This type is used with eina_file_access(). Enumerations can be
* combined bitwise with the OR operator.
* @since 1.28
*/
typedef enum
{
EINA_FILE_ACCESS_MODE_EXIST = 0, /**< existence test: F_OK */
EINA_FILE_ACCESS_MODE_EXEC = 1 << 0, /**< exec permission: X_OK */
EINA_FILE_ACCESS_MODE_WRITE = 1 << 1, /**< write permission: W_OK */
EINA_FILE_ACCESS_MODE_READ = 1 << 2, /**< read permission: R_OK */
} Eina_File_Access_Mode;
/* Why do this? Well PATH_MAX may vary from when eina itself is compiled
* to when the app using eina is compiled. Exposing the path buffer below
* can't safely and portably vary based on how/when you compile. It should
@ -830,6 +845,30 @@ EINA_API void eina_file_statgen_enable(void);
*/
EINA_API void eina_file_statgen_disable(void);
/**
* @brief Determine the accessibility of a file or path.
*
* @param[in] path The path to check.
* @param[in] mode Access permissions to be checked, or existence test.
* @return #EINA_TRUE it @p path satisfies the tests, #EINA_FALSE otherwise.
*
* On Linux, this function just calls the access() function. On Windows, it
* mimics as best as possible the behavior of access():
* - Existence is always checked.
* - As on Windows, a file is either read only or read/write, read permission
* is equivalent to existence. so Write permission is equivalent to not
* being read only.
* - A directory is always executable, except if greater privilege is needed.
*
* The @p mode has the same values than F_OK, X_OK, W_OK and R_OK, and the
* usage is the same than the access() function.
*
* If @p path is NULL or the epty string, this function returns #EINA_FALSE.
*
* @since 1.28
*/
EINA_API Eina_Bool eina_file_access(const char *path, Eina_File_Access_Mode mode);
/**
* @}
*/

View File

@ -1573,3 +1573,13 @@ eina_file_mkdtemp(const char *templatename, Eina_Tmpstr **path)
if (path) *path = eina_tmpstr_add(tmpdirname);
return EINA_TRUE;
}
EINA_API Eina_Bool
eina_file_access(const char *path, Eina_File_Access_Mode mode)
{
if (!path || !*path)
return EINA_FALSE;
return access(path, mode) == 0;
}

View File

@ -549,6 +549,93 @@ eina_file_cleanup(Eina_Tmpstr *path)
return result;
}
static Eina_Bool
_eina_file_is_binary(const char *path)
{
HANDLE h;
HANDLE fm;
unsigned char *base;
unsigned char *base_nt;
LARGE_INTEGER sz;
WORD characteristics;
Eina_Bool res;
res = EINA_FALSE;
/*
* we parse the file to check if it is a PE file (EXE or DLL)
* and we finally check whether it's a DLL or not.
* Reference :
* https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
*/
h = CreateFile(path,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (h == INVALID_HANDLE_VALUE)
return EINA_FALSE;
if (!GetFileSizeEx(h, &sz))
goto close_h;
/* a PE file must contain at least the DOS and NT headers */
if (sz.QuadPart < (LONGLONG)(sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)))
goto close_h;
fm = CreateFileMapping(h, NULL, PAGE_READONLY, 0, 0, NULL);
if (fm == NULL)
goto close_h;
base = (unsigned char *)MapViewOfFile(fm, FILE_MAP_READ, 0, 0, 0);
CloseHandle(fm);
if (base == NULL)
goto close_h;
/*
* the PE file begins with the DOS header.
* First magic number : the DOS header must begin with a DOS magic
* number, that is "MZ", that is 0x5a4d, stored in a WORD.
*/
if (*((WORD *)base) != 0x5a4d)
goto unmap_view;
/*
* The position of the NT header is located at the offset 0x3c.
*/
base_nt = base + *((DWORD *)(base + 0x3c));
/*
* The NT header begins with the magic number "PE\0\0", that is
* 0x00004550, stored in a DWORD.
*/
if (*((DWORD *)base_nt) != 0x00004550)
goto unmap_view;
/*
* to get informations about executable (EXE or DLL), we look at
* the 'Characteristics' member of the NT header, located at the offset
* 22 (4 for the magic number, 18 for the offset) from base_nt.
* https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics
*/
characteristics = *((WORD *)(base_nt + 4 + 18));
/*
* 0x0002 : if set, EXE or DLL
* 0x2000 : if set, DLL
*/
res = (characteristics & 0x0002) && !(characteristics & 0x2000);
unmap_view:
UnmapViewOfFile(base);
close_h:
CloseHandle(h);
return res;
}
/*============================================================================*
* API *
*============================================================================*/
@ -1286,3 +1373,76 @@ eina_file_mkdtemp(const char *templatename, Eina_Tmpstr **path)
if (path) *path = eina_tmpstr_add(tmpdirname);
return EINA_TRUE;
}
EINA_API Eina_Bool
eina_file_access(const char *path, Eina_File_Access_Mode mode)
{
DWORD attr;
if (!path || !*path)
return EINA_FALSE;
if ((mode != EINA_FILE_ACCESS_MODE_EXIST) &&
((mode >> 3) != 0))
return EINA_FALSE;
/*
* Always check for existence for both files and directories
*/
attr = GetFileAttributes(path);
if (attr == INVALID_FILE_ATTRIBUTES)
return EINA_FALSE;
/*
* On Windows a file or path is either read/write or read only.
* So if it exists, it has at least read access.
* So do something only if mode is EXEC or WRITE
*/
if (mode & EINA_FILE_ACCESS_MODE_EXEC)
{
if (attr & FILE_ATTRIBUTE_DIRECTORY)
{
/*
* Some directories have restricted access, like
* c:\Windows\System32\WDI
*/
HANDLE h;
Eina_Bool ret = EINA_FALSE;
h = CreateFile(path,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
ret = (h == INVALID_HANDLE_VALUE);
CloseHandle(h);
if (ret)
return EINA_FALSE;
}
else
{
/*
* For files, check if it is a binary.
*/
if (!_eina_file_is_binary(path))
return EINA_FALSE;
}
}
if (mode & EINA_FILE_ACCESS_MODE_WRITE)
{
DWORD attr;
attr = GetFileAttributes(path);
if (attr == INVALID_FILE_ATTRIBUTES)
return EINA_FALSE;
if (attr & FILE_ATTRIBUTE_READONLY)
return EINA_FALSE;
}
return EINA_TRUE;
}

View File

@ -897,6 +897,49 @@ EFL_START_TEST(eina_test_file_unlink)
}
EFL_END_TEST
EFL_START_TEST(eina_test_file_access)
{
typedef struct
{
const char *path;
Eina_File_Access_Mode mode;
Eina_Bool expected;
} Paths;
Paths paths[] = {
#ifdef _WIN32
{ "c:\\Windows", EINA_FILE_ACCESS_MODE_EXIST, EINA_TRUE },
{ "c:\\Windows", EINA_FILE_ACCESS_MODE_EXEC, EINA_TRUE },
{ "c:\\Windows\\System32\\WDI", EINA_FILE_ACCESS_MODE_EXEC, EINA_FALSE },
{ "c:\\Windows\\notepad.exe", EINA_FILE_ACCESS_MODE_EXIST, EINA_TRUE },
{ "c:\\Windows\\notepad.exe", EINA_FILE_ACCESS_MODE_EXEC, EINA_TRUE },
{ "c:\\Windows\\notepad.exe", EINA_FILE_ACCESS_MODE_WRITE, EINA_TRUE },
{ "c:\\Windows\\notepad.exe", EINA_FILE_ACCESS_MODE_READ, EINA_TRUE },
#else
{ "/usr", EINA_FILE_ACCESS_MODE_EXIST, EINA_TRUE },
{ "/usr", EINA_FILE_ACCESS_MODE_EXEC, EINA_TRUE },
{ "/usr", EINA_FILE_ACCESS_MODE_READ, EINA_TRUE },
{ "/root", EINA_FILE_ACCESS_MODE_WRITE, EINA_FALSE },
#endif
{ NULL, EINA_FILE_ACCESS_MODE_EXIST, EINA_FALSE },
};
size_t i;
Eina_Bool result;
result = eina_file_access(NULL, EINA_FILE_ACCESS_MODE_EXIST);
fail_if(result == EINA_TRUE);
result = eina_file_access("", EINA_FILE_ACCESS_MODE_EXIST);
fail_if(result == EINA_TRUE);
for (i = 0; paths[i].path; i++)
{
result = eina_file_access(paths[i].path, paths[i].mode);
fail_if(result != paths[i].expected);
}
}
EFL_END_TEST
void
eina_test_file(TCase *tc)
{
@ -914,5 +957,5 @@ eina_test_file(TCase *tc)
tcase_add_test(tc, eina_test_file_statat);
tcase_add_test(tc, eina_test_file_mktemp);
tcase_add_test(tc, eina_test_file_unlink);
tcase_add_test(tc, eina_test_file_access);
}