mirror of
https://github.com/netwide-assembler/nasm
synced 2026-08-26 16:23:04 -04:00
nasmlib/file.c included <stringapiset.h> directly (for MultiByteToWideChar()/CompareStringOrdinal()) without first including <windows.h>. Windows SDK headers like <stringapiset.h> are only guaranteed to work when pulled in through the normal <windows.h> pipeline, which sets up SDK-internal architecture macros (_X86_, _AMD64_, ...) derived from the compiler's own _M_IX86/_M_X64/etc. Including them directly skips that setup and can fail with a 'No Target Architecture' #error from <winnt.h> -- which is exactly what happened building with a real cl.exe/nmake in CI. <windows.h> is deliberately *not* pulled in globally from compiler.h: it #defines NEAR and FAR as legacy no-op calling-convention keywords, which clash with NASM's own NEAR/FAR opflags bits (include/opflags.h). Keep the inclusion local to file.c, the only file that currently needs Windows API declarations, guarded by WIN32_LEAN_AND_MEAN to keep the exposed surface minimal. Confirmed fixed with a real MSVC (cl.exe/nmake) build in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
481 lines
11 KiB
C
481 lines
11 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
/* Copyright 1996-2017 The NASM Authors - All Rights Reserved */
|
|
|
|
#include "compiler.h"
|
|
#include "nasmlib.h"
|
|
#include "error.h"
|
|
|
|
#ifdef HAVE_FCNTL_H
|
|
# include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
# include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_IO_H
|
|
# include <io.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef R_OK
|
|
# define R_OK 4 /* Classic Unix constant, same on Windows */
|
|
#endif
|
|
|
|
#ifdef S_ISREG
|
|
/* all good */
|
|
#elif defined(HAVE_S_ISREG)
|
|
/* exists, but not a macro */
|
|
# define S_ISREG S_ISREG
|
|
#elif defined(S_IFMT) && defined(S_IFREG)
|
|
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
|
#elif defined(_S_IFMT) && defined(_S_IFREG)
|
|
# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
|
|
#endif
|
|
|
|
/*
|
|
* On Windows, we want to use _wfopen(), as fopen() has a much smaller limit
|
|
* on the path length that it supports.
|
|
*
|
|
* Previously we tried to prefix the path name with \\?\ in order to
|
|
* let the Windows kernel know that we are not limited to PATH_MAX
|
|
* characters, but it breaks relative paths among other things, and
|
|
* apparently Windows 10 contains a registry option to override this
|
|
* limit anyway... but only for the wide character interfaces.
|
|
*/
|
|
#ifdef _WIN32
|
|
#include <wchar.h>
|
|
/*
|
|
* <windows.h> is needed for MultiByteToWideChar()/CompareStringOrdinal()
|
|
* (normally declared via <stringapiset.h>). Windows SDK headers like
|
|
* <stringapiset.h> are only guaranteed to work when pulled in through the
|
|
* normal <windows.h> pipeline, which sets up SDK-internal architecture
|
|
* macros (_X86_, _AMD64_, ...) derived from the compiler's own
|
|
* _M_IX86/_M_X64/etc; including such a leaf header directly can fail with
|
|
* a "No Target Architecture" #error from <winnt.h>.
|
|
*
|
|
* <windows.h> is deliberately *not* included from compiler.h: it #defines
|
|
* NEAR and FAR (as legacy no-op calling-convention keywords), which clash
|
|
* with NASM's own NEAR/FAR opflags bits from opflags.h. Keep the
|
|
* inclusion local to the few files, like this one, that actually need
|
|
* Windows API declarations, and define WIN32_LEAN_AND_MEAN to keep the
|
|
* exposed surface (winsock, GDI, shell, DDE, RPC, crypto, ...) minimal.
|
|
*/
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
typedef wchar_t *os_filename;
|
|
typedef wchar_t os_fopenflag;
|
|
|
|
static os_filename os_mangle_filename(const char *filename)
|
|
{
|
|
size_t wclen;
|
|
wchar_t *buf;
|
|
|
|
wclen = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, filename,
|
|
-1, NULL, 0);
|
|
if (!wclen)
|
|
return NULL;
|
|
|
|
/* wclen is in "characters" (UTF-16 code points) */
|
|
buf = nasm_malloc(wclen << 1);
|
|
|
|
wclen = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, filename,
|
|
-1, buf, wclen);
|
|
if (!wclen) {
|
|
nasm_free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static inline void os_free_filename(os_filename filename)
|
|
{
|
|
nasm_free(filename);
|
|
}
|
|
|
|
# define os_fopen _wfopen
|
|
# define os_access _waccess
|
|
# define os_remove _wremove
|
|
|
|
/*
|
|
* On Win32/64, we have to use the _wstati64() function. Note that
|
|
* we can't use _wstat64() without depending on a needlessly new
|
|
* version os MSVCRT.
|
|
*/
|
|
|
|
typedef struct _stati64 os_struct_stat;
|
|
|
|
# define os_stat _wstati64
|
|
# define os_fstat _fstati64
|
|
|
|
static inline void os_set_binary_mode(FILE *f) {
|
|
int ret = _setmode(_fileno(f), _O_BINARY);
|
|
|
|
if (ret == -1) {
|
|
nasm_fatalf(ERR_NOFILE, "unable to set file mode to binary: %s",
|
|
strerror(errno));
|
|
}
|
|
}
|
|
|
|
static inline int os_compare_paths(const os_filename a, const os_filename b)
|
|
{
|
|
return CompareStringOrdinal(a, -1, b, -1, TRUE);
|
|
}
|
|
#define os_compare_paths os_compare_paths
|
|
|
|
#else /* not _WIN32 */
|
|
|
|
typedef const char *os_filename;
|
|
typedef char os_fopenflag;
|
|
|
|
static inline os_filename os_mangle_filename(const char *filename)
|
|
{
|
|
return filename;
|
|
}
|
|
static inline void os_free_filename(os_filename filename)
|
|
{
|
|
(void)filename; /* Nothing to do */
|
|
}
|
|
|
|
static inline void os_set_binary_mode(FILE *f) {
|
|
(void)f;
|
|
}
|
|
|
|
# define os_fopen fopen
|
|
# define os_remove remove
|
|
|
|
#if defined(HAVE_FACCESSAT) && defined(AT_EACCESS)
|
|
static inline int os_access(os_filename pathname, int mode)
|
|
{
|
|
return faccessat(AT_FDCWD, pathname, mode, AT_EACCESS);
|
|
}
|
|
# define os_access os_access
|
|
#elif defined(HAVE_ACCESS)
|
|
# define os_access access
|
|
#endif
|
|
|
|
#ifdef HAVE_STRUCT_STAT
|
|
typedef struct stat os_struct_stat;
|
|
# ifdef HAVE_STAT
|
|
# define os_stat stat
|
|
# endif
|
|
# ifdef HAVE_FSTAT
|
|
# define os_fstat fstat
|
|
# endif
|
|
#else
|
|
struct dummy_struct_stat {
|
|
int st_mode;
|
|
int st_size;
|
|
};
|
|
typedef struct dummy_struct_stat os_struct_stat;
|
|
#endif
|
|
|
|
#endif /* Not _WIN32 */
|
|
|
|
#ifdef fileno
|
|
/* all good */
|
|
#elif defined(HAVE_FILENO)
|
|
/* exists, but not a macro */
|
|
# define fileno fileno
|
|
#elif defined(_fileno) || defined(HAVE__FILENO)
|
|
# define fileno _fileno
|
|
#endif
|
|
|
|
#ifndef S_ISREG
|
|
# undef os_stat
|
|
# undef os_fstat
|
|
#endif
|
|
|
|
/* Disable these functions if they don't support something we need */
|
|
#ifndef fileno
|
|
# undef os_fstat
|
|
# undef os_ftruncate
|
|
# undef HAVE_MMAP
|
|
#endif
|
|
|
|
/*
|
|
* If we don't have functional versions of these functions,
|
|
* stub them out so we don't need so many #ifndefs
|
|
*/
|
|
#ifndef os_stat
|
|
static inline int os_stat(os_filename osfname, os_struct_stat *st)
|
|
{
|
|
(void)osfname;
|
|
(void)st;
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#ifndef os_fstat
|
|
static inline int os_fstat(int fd, os_struct_stat *st)
|
|
{
|
|
(void)fd;
|
|
(void)st;
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#ifndef S_ISREG
|
|
static inline bool S_ISREG(int m)
|
|
{
|
|
(void)m;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void nasm_set_binary_mode(FILE *f)
|
|
{
|
|
os_set_binary_mode(f);
|
|
}
|
|
|
|
FILE *nasm_open_read(const char *filename, enum file_flags flags)
|
|
{
|
|
FILE *f = NULL;
|
|
os_filename osfname;
|
|
|
|
osfname = os_mangle_filename(filename);
|
|
if (osfname) {
|
|
os_fopenflag fopen_flags[4];
|
|
memset(fopen_flags, 0, sizeof fopen_flags);
|
|
|
|
fopen_flags[0] = 'r';
|
|
fopen_flags[1] = (flags & NF_TEXT) ? 't' : 'b';
|
|
|
|
#if defined(__GLIBC__) || defined(__linux__)
|
|
/*
|
|
* Try to open this file with memory mapping for speed, unless we are
|
|
* going to do it "manually" with nasm_map_file()
|
|
*/
|
|
if (!(flags & NF_FORMAP))
|
|
fopen_flags[2] = 'm';
|
|
#endif
|
|
|
|
while (true) {
|
|
f = os_fopen(osfname, fopen_flags);
|
|
if (f || errno != EINVAL || !fopen_flags[2])
|
|
break;
|
|
|
|
/* We got EINVAL but with 'm'; try again without 'm' */
|
|
fopen_flags[2] = '\0';
|
|
}
|
|
|
|
os_free_filename(osfname);
|
|
}
|
|
|
|
if (!f && (flags & NF_FATAL))
|
|
nasm_fatalf(ERR_NOFILE, "unable to open input file: `%s': %s",
|
|
filename, strerror(errno));
|
|
|
|
return f;
|
|
}
|
|
|
|
FILE *nasm_open_write(const char *filename, enum file_flags flags)
|
|
{
|
|
FILE *f = NULL;
|
|
os_filename osfname;
|
|
|
|
osfname = os_mangle_filename(filename);
|
|
if (osfname) {
|
|
os_fopenflag fopen_flags[3];
|
|
|
|
fopen_flags[0] = 'w';
|
|
fopen_flags[1] = (flags & NF_TEXT) ? 't' : 'b';
|
|
fopen_flags[2] = '\0';
|
|
|
|
f = os_fopen(osfname, fopen_flags);
|
|
os_free_filename(osfname);
|
|
}
|
|
|
|
if (!f && (flags & NF_FATAL))
|
|
nasm_fatalf(ERR_NOFILE, "unable to open output file: `%s': %s",
|
|
filename, strerror(errno));
|
|
|
|
switch (flags & NF_BUF_MASK) {
|
|
case NF_IONBF:
|
|
setvbuf(f, NULL, _IONBF, 0);
|
|
break;
|
|
case NF_IOLBF:
|
|
setvbuf(f, NULL, _IOLBF, 0);
|
|
break;
|
|
case NF_IOFBF:
|
|
setvbuf(f, NULL, _IOFBF, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
/* The appropriate "rb" strings for os_fopen() */
|
|
static const os_fopenflag fopenflags_rb[3] = { 'r', 'b', 0 };
|
|
|
|
/*
|
|
* Report the existence of a file
|
|
*/
|
|
bool nasm_file_exists(const char *filename)
|
|
{
|
|
#ifndef os_access
|
|
FILE *f;
|
|
#endif
|
|
os_filename osfname;
|
|
bool exists;
|
|
|
|
osfname = os_mangle_filename(filename);
|
|
if (!osfname)
|
|
return false;
|
|
|
|
#ifdef os_access
|
|
exists = os_access(osfname, R_OK) == 0;
|
|
#else
|
|
f = os_fopen(osfname, fopenflags_rb);
|
|
exists = f != NULL;
|
|
if (f)
|
|
fclose(f);
|
|
#endif
|
|
|
|
os_free_filename(osfname);
|
|
return exists;
|
|
}
|
|
|
|
/*
|
|
* Report the file size of an open file. This MAY move the file pointer.
|
|
*/
|
|
off_t nasm_file_size(FILE *f)
|
|
{
|
|
off_t where, end;
|
|
os_struct_stat st;
|
|
|
|
if (!os_fstat(fileno(f), &st) && S_ISREG(st.st_mode))
|
|
return st.st_size;
|
|
|
|
/* Do it the hard way... this tests for seekability */
|
|
|
|
if (fseeko(f, 0, SEEK_CUR))
|
|
goto fail; /* Not seekable, don't even try */
|
|
|
|
where = ftello(f);
|
|
if (where == (off_t)-1)
|
|
goto fail;
|
|
|
|
if (fseeko(f, 0, SEEK_END))
|
|
goto fail;
|
|
|
|
end = ftello(f);
|
|
if (end == (off_t)-1)
|
|
goto fail;
|
|
|
|
/*
|
|
* Move the file pointer back. If this fails, this is probably
|
|
* not a plain file.
|
|
*/
|
|
if (fseeko(f, where, SEEK_SET))
|
|
goto fail;
|
|
|
|
return end;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Report file size given pathname
|
|
*/
|
|
off_t nasm_file_size_by_path(const char *pathname)
|
|
{
|
|
os_filename osfname;
|
|
off_t len = -1;
|
|
os_struct_stat st;
|
|
FILE *fp;
|
|
|
|
osfname = os_mangle_filename(pathname);
|
|
|
|
if (!os_stat(osfname, &st) && S_ISREG(st.st_mode))
|
|
len = st.st_size;
|
|
|
|
fp = os_fopen(osfname, fopenflags_rb);
|
|
if (fp) {
|
|
len = nasm_file_size(fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* Report the timestamp on a file, returns true if successful
|
|
*/
|
|
bool nasm_file_time(time_t *t, const char *pathname)
|
|
{
|
|
#ifdef os_stat
|
|
os_filename osfname;
|
|
os_struct_stat st;
|
|
bool rv = false;
|
|
|
|
osfname = os_mangle_filename(pathname);
|
|
if (!osfname)
|
|
return false;
|
|
|
|
rv = !os_stat(osfname, &st);
|
|
*t = st.st_mtime;
|
|
os_free_filename(osfname);
|
|
|
|
return rv;
|
|
#else
|
|
return false; /* No idea how to do this on this OS */
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Delete a file (allows NULL as input -> do nothing)
|
|
*/
|
|
int nasm_remove(const char *pathname)
|
|
{
|
|
int rv;
|
|
os_filename osfname;
|
|
|
|
if (!pathname || !*pathname)
|
|
return 0;
|
|
|
|
osfname = os_mangle_filename(pathname);
|
|
if (!osfname)
|
|
return false;
|
|
|
|
rv = os_remove(osfname);
|
|
os_free_filename(osfname);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Try to determine if two paths are the same. This test must not have
|
|
* false positives; false negatives are OK but (obviously) not ideal.
|
|
*/
|
|
#ifdef os_compare_paths
|
|
int nasm_compare_paths(const char *a, const char *b)
|
|
{
|
|
os_filename oa, ob;
|
|
/* A direct string comparison is usually quick */
|
|
int rv = strcmp(a, b);
|
|
|
|
if (!rv)
|
|
return rv;
|
|
|
|
oa = os_mangle_filename(a);
|
|
ob = os_mangle_filename(b);
|
|
|
|
rv = os_compare_paths(oa, ob);
|
|
|
|
os_free_filename(oa);
|
|
os_free_filename(ob);
|
|
|
|
return rv;
|
|
}
|
|
#else
|
|
int nasm_compare_paths(const char *a, const char *b)
|
|
{
|
|
return strcmp(a, b);
|
|
}
|
|
#endif
|