packages feed

MicroHs-0.16.0.0: src/runtime/windows/extra.c

/* Copyright 2025 Lennart Augustsson
 * See LICENSE file for full license.
 */
#include <conio.h>
#include <io.h>
#include <stdlib.h>
#include <windows.h>
#include <string.h>

/*
 * Find First Set
 * This macro must be defined.
 * It returns the number of the least significant bit that is set.
 * Numberings starts from 1.  If no bit is set, it should return 0.
 */
//#pragma warning(disable : 4996)
#if defined(_M_X64) || defined(_M_ARM64)
#pragma intrinsic(_BitScanForward64)
#pragma intrinsic(_BitScanReverse64)
static INLINE int
ffs(int64_t arg)
{
  unsigned long r;
  if (_BitScanForward64(&r, arg))
    return (int)(r+1);
  else
    return 0;
}
#define FFS ffs

#if defined(_M_X64)
#define POPCOUNT __popcnt64
#elif defined(_M_IX86)
#define POPCOUNT __popcnt
#endif

static INLINE uint64_t clz(uint64_t x) {
  unsigned long count;
  if (_BitScanReverse64(&count, x)) {
    return 63 - (uint64_t)count;
  } else {
    return 64;
  }
}
#define CLZ clz

static INLINE uint64_t ctz(uint64_t x) {
  unsigned long count;
  if (_BitScanForward64(&count, x)) {
    return (uint64_t)count;
  } else {
    return 64;
  }
}
#define CTZ ctz
#endif  /* #if defined(_M_X64) || defined(_M_ARM64) */

/*
 * This is the character used for comma-separation in printf.
 * Defaults to "'".
 * Windows does not support this.
 */
#define PCOMMA ""

/*
 * Get a raw input character.
 * If undefined, the default always returns -1
 */
#define GETRAW getraw
int
getraw(void)
{
  static char buf[2];
  int c;
  if (buf[0]) {
    c = buf[0];
    buf[0] = buf[1];
    buf[1] = 0;
  } else {
  tryagain:
    c = _getch();
    if (c == 0xe0 || c == 0x00) {
      switch(_getch()) {
      case 0x48: buf[1] = 'A'; break; /* Up arrow */
      case 0x50: buf[1] = 'B'; break; /* Down arrow */
      case 0x4d: buf[1] = 'C'; break; /* Right arrow */
      case 0x4b: buf[1] = 'D'; break; /* Left arrow */
      default: goto tryagain;
      }
      buf[0] = '[';
      c = 0x1b;                 /* ESC */
    }
  }
  return c;
}

/*
 * Get time since some epoch in milliseconds.
 * If undefined, return 0.
 */
#define GETTIMEMILLI gettimemilli

uint64_t
gettimemilli(void)
{
    static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);

    SYSTEMTIME  system_time;
    FILETIME    file_time;
    uint64_t    time, msec;

    GetSystemTime( &system_time );
    SystemTimeToFileTime( &system_time, &file_time );
    time =  ((uint64_t)file_time.dwLowDateTime )      ;
    time += ((uint64_t)file_time.dwHighDateTime) << 32;

    msec = (time - EPOCH) / 10000L;
    //msec = time + system_time.wMilliseconds;
    return msec;
}

/*
 * Create a unique file name.
 */
char*
tmpname(const char* prefix, const char* suffix)
{
  char tmpdir[MAX_PATH];
  DWORD len = GetTempPathA(sizeof(tmpdir), tmpdir);
  if (len == 0 || len > sizeof(tmpdir))
    return 0;

  /* Ensure prefix isn't too short */
  if (!prefix || strlen(prefix) < 3) prefix = "tmp";  // GetTempFileName needs at least 3 chars

  char tempFile[MAX_PATH];
  if (GetTempFileNameA(tmpdir, prefix, 0, tempFile) == 0)
    return 0;

  /* Delete the file created by GetTempFileName */
  DeleteFileA(tempFile);

  /* Append suffix */
  size_t total_len = strlen(tempFile) + strlen(suffix) + 1;
  char *filename = malloc(total_len);
  if (!filename)
    return 0;
  snprintf(filename, total_len, "%s%s", tempFile, suffix);

  // Create the file exclusively to ensure it exists and is unique
  int fd = _open(filename, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY,
                 _S_IREAD | _S_IWRITE);
  if (fd == -1) {
    free(filename);
    return 0;
  }
  _close(fd);

  return filename;
}
#define TMPNAME tmpname

/* Return path to executable as a null-terminated UTF-8 string. */
char*
get_executable_path(void)
{
    DWORD size = MAX_PATH;
    WCHAR *wbuf = malloc(size * sizeof(WCHAR));
    if (!wbuf) return NULL;

    DWORD len = GetModuleFileNameW(NULL, wbuf, size);
    while (len == size && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        size *= 2;
        WCHAR *new_buf = realloc(wbuf, size * sizeof(WCHAR));
        if (!new_buf) { free(wbuf); return NULL; }
        wbuf = new_buf;
        len = GetModuleFileNameW(NULL, wbuf, size);
    }

    int utf8_len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL, NULL);
    char *utf8_buf = malloc(utf8_len);
    if (utf8_buf) {
        WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, utf8_buf, utf8_len, NULL, NULL);
    }
    free(wbuf);
    return utf8_buf;
}
#define GET_EXECUTABLE_PATH get_executable_path

/* Emulate directory functions, partially written by Gemini */

struct dirent {
  char d_name[MAX_PATH * 3];
};

typedef struct {
  HANDLE hFind;
  WIN32_FIND_DATAW findData;
  struct dirent entry;
  int first;
} DIR;

DIR*
opendir(const char *path)
{
  DIR *d = (DIR*)malloc(sizeof(DIR));
  if (!d)
    return NULL;

  WCHAR wpath[MAX_PATH];
  MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);
  wcscat_s(wpath, MAX_PATH, L"\\*");

  d->hFind = FindFirstFileW(wpath, &d->findData);
  if (d->hFind == INVALID_HANDLE_VALUE) {
    free(d);
    return NULL;
  }
  d->first = 1;
  return d;
}

struct dirent*
readdir(DIR *d)
{
  if (d->hFind == INVALID_HANDLE_VALUE)
    return NULL;

  if (d->first) {
    d->first = 0;
  } else {
    if (!FindNextFileW(d->hFind, &d->findData))
      return NULL;
  }

  WideCharToMultiByte(CP_UTF8, 0, d->findData.cFileName, -1, 
                      d->entry.d_name, sizeof(d->entry.d_name), NULL, NULL);
  return &d->entry;
}

int
closedir(DIR *d)
{
  if (!d)
    return -1;
  if (d->hFind != INVALID_HANDLE_VALUE)
    FindClose(d->hFind);
  free(d);
  return 0;
}

#define MKDIR(s, m) mkdir(s)

int
setenv(const char *name, const char *value, int overwrite)
{
  // POSIX specifies that if name is NULL, points to an empty string, 
  // or contains an '=', the function should fail with EINVAL.
  if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) {
    errno = EINVAL;
    return -1;
  }

  // If overwrite is 0, we must check if the variable already exists.
  // If it does, we successfully do nothing.
  if (overwrite == 0 && getenv(name) != NULL) {
    return 0; 
  }

  // _putenv_s returns 0 on success, and an error code on failure.
  if (_putenv_s(name, value) != 0) {
    // _putenv_s sets its own error codes, but we return -1 to match POSIX
    return -1; 
  }

  return 0;
}

int
unsetenv(const char *name)
{
  // Standard POSIX validation for the name
  if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) {
    errno = EINVAL;
    return -1;
  }

  // In the Windows C Runtime, setting a variable to an empty string ("") 
  // effectively deletes it from the environment.
  if (_putenv_s(name, "") != 0) {
    return -1;
  }

  return 0;
}


#if defined(WANT_DIR)
#include <sys/stat.h>
#include <sys/types.h>
#include <io.h>

#define PERM_SEARCH 8
#define PERM_READ   4
#define PERM_WRITE  2
#define PERM_EXEC   1

int
get_permissions(const char *path)
{
  struct _stat st;
  int perms = 0;
    
  if (_stat(path, &st) == -1) {
    return -1;
  }

  if (st.st_mode & _S_IREAD)
    perms |= PERM_READ;
  if (st.st_mode & _S_IWRITE)
    perms |= PERM_WRITE;
  if (st.st_mode & _S_IFDIR) {
    perms |= PERM_SEARCH; 
  } else {
    if (st.st_mode & _S_IEXEC) {
      perms |= PERM_EXEC; 
    }
  }
  return perms;
}

int
set_permissions(const char *path, int perms)
{
  int wperms = 0;
    
  if (perms & PERM_READ)
    wperms |= _S_IREAD;
  if (perms & PERM_WRITE)
    wperms |= _S_IWRITE;
  if ((perms & PERM_EXEC) || (perms & PERM_SEARCH))
    wperms |= _S_IEXEC;

  return _chmod(path, wperms);
}
#endif  /* defined(WANT_DIR) */