# File Helpers

```c
#define FIO_FILES
#include "fio-stl.h"
```

POSIX-style file helpers: open, read, write, remove, folder creation, size, type, path safety, filename parsing, and temporary files. Nothing fancy — just the usual paperwork. Implemented in [`./004 files.h`](https://facil.io/0.8.x/api/004-files/).

### Configuration Macros

#### `FIO_FD_FIND_BLOCK`

```c
#ifndef FIO_FD_FIND_BLOCK
#define FIO_FD_FIND_BLOCK 4096
#endif
```

Stack buffer size used by `fio_fd_find_next` for each read cycle. Override before inclusion if you want larger or smaller scans.

#### `FIO_FILENAME_PATH_CAPA`

```c
#ifndef FIO_FILENAME_PATH_CAPA
#define FIO_FILENAME_PATH_CAPA (PATH_MAX | 4094)
#endif
```

Stack buffer capacity used for path manipulation (`"~/"` expansion in `fio_filename_open`, recursive `fio_filename_remove`, nested `fio_filename_make_path`). These functions allocate nothing — paths longer than this fail with `errno == ENAMETOOLONG`. The default guarantees at least ~4KB.

### Types

#### `fio_filename_s`

```c
typedef struct {
  fio_buf_info_s folder;
  fio_buf_info_s basename;
  fio_buf_info_s ext;
} fio_filename_s;
```

Zero-copy result from `fio_filename_parse` / `fio_filename_parse2`. All members point into the original path string.

**Members:**
- `folder` - directory path including trailing separator, or `{NULL,0}` if none
- `basename` - file name without extension, or `{NULL,0}` if empty
- `ext` - extension without leading `.`, or `{NULL,0}` if none

### Macros

#### `FIO_FOLDER_SEPARATOR`

```c
#if FIO_OS_WIN
#define FIO_FOLDER_SEPARATOR '\\'
#else
#define FIO_FOLDER_SEPARATOR '/'
#endif
```

Native path separator. On Windows the parser also accepts `/`.

#### `fio_file_dup`

```c
#if FIO_OS_WIN
#define fio_file_dup(fd) _dup(fd)
#else
#define fio_file_dup(fd) dup(fd)
#endif
```

Duplicates a file descriptor.

#### `fio_filename_is_folder`

```c
#define fio_filename_is_folder(filename) \
  (fio_filename_type((filename)) == S_IFDIR)
```

Returns non-zero if `filename` is a directory, `0` otherwise (including errors).

#### `FIO_FD_FIND_EOF`

```c
#define FIO_FD_FIND_EOF ((size_t)-1)
```

Sentinel returned by `fio_fd_find_next` when the token is not found before EOF.

### Opening and Creating Files

#### `fio_filename_open`

```c
SFUNC int fio_filename_open(const char *filename, int flags);
```

Opens `filename` with POSIX `open` semantics. If `filename` starts with `"~/"` (or `"~\\"` on Windows), it is resolved relative to `$HOME`.

**Parameters:**
- `filename` - path to open
- `flags` - `O_RDONLY`, `O_RDWR | O_CREAT | O_TRUNC`, etc.

**Returns:** file descriptor on success, `-1` on error.

**Note:** uses a fixed size stack buffer (zero allocations); over-long `"~/..."` paths fail with `errno == ENAMETOOLONG` (see `FIO_FILENAME_PATH_CAPA`).

#### `fio_filename_tmp`

```c
SFUNC int fio_filename_tmp(void);
```

Creates a temporary file and returns its descriptor. Tries `TMPDIR`, `TMP`, `TEMP`, `P_tmpdir`, then the current directory. On Linux it attempts `O_TMPFILE` first; otherwise it builds a name prefixed with `facil_io_tmp_` and opens with `O_CREAT | O_EXCL`.

**Returns:** file descriptor on success, `-1` on error.

#### `fio_filename_overwrite`

```c
FIO_IFUNC int fio_filename_overwrite(const char *filename,
                                     const void *buf,
                                     size_t len);
```

Opens `filename` with `O_RDWR | O_CREAT | O_TRUNC`, writes `len` bytes from `buf`, and closes the file.

**Returns:** `0` on success, `-1` on error. On error the file state is undefined.

### Removing Files and Folders

#### `fio_filename_remove`

```c
typedef struct {
  const char *path;
  uint8_t folder;
  uint8_t recursive;
} fio_filename_remove_args_s;

SFUNC int fio_filename_remove(fio_filename_remove_args_s args);
#define fio_filename_remove(...) \
  fio_filename_remove((fio_filename_remove_args_s){__VA_ARGS__})
```

Removes the file / link or folder at `path`, abstracting OS differences (`unlink` / `rmdir` vs. `DeleteFileA` / `RemoveDirectoryA`).

**Parameters:**
- `path` - NUL-terminated path of the file / folder to remove
- `folder` - set to allow removal of an empty folder (like `rmdir`)
- `recursive` - set to remove a folder and all of its content (like `rm -r`); implies `folder`. If `path` isn't a folder, the flag is ignored and `path` is removed as a file / link

**Returns:** `0` on success, `-1` on error. Recursive removal stops on the first error (some content may remain). Uses a fixed size stack buffer (zero allocations); over-long paths fail with `errno == ENAMETOOLONG` (see `FIO_FILENAME_PATH_CAPA`).

**Note:** links (symlinks / reparse points) are removed, never followed into.

**Example:**

```c
fio_filename_remove(.path = "old.log");
fio_filename_remove(.path = "empty_dir", .folder = 1);
fio_filename_remove(.path = "build_tree", .recursive = 1);
```

### Creating Folders

#### `fio_filename_make_path`

```c
typedef struct {
  const char *path;
  uint32_t mode;
} fio_filename_make_path_args_s;

SFUNC int fio_filename_make_path(fio_filename_make_path_args_s args);
#define fio_filename_make_path(...) \
  fio_filename_make_path((fio_filename_make_path_args_s){__VA_ARGS__})
```

Creates the folder at `path`, including any missing parent folders (similar to `mkdir -p`). Abstracts OS differences (`mkdir` vs. `CreateDirectoryA`).

**Parameters:**
- `path` - NUL-terminated folder path to create; nested folders are created as needed, trailing separators are allowed
- `mode` - creation mode (POSIX only, ignored on Windows); zero defaults to `0755`

**Returns:** `0` on success, `-1` on error. An existing folder is NOT an error. A non-folder component along the way IS an error (`errno == ENOTDIR`). Uses a fixed size stack buffer (zero allocations); over-long paths fail with `errno == ENAMETOOLONG` (see `FIO_FILENAME_PATH_CAPA`).

**Note:** root / drive / UNC `\\server\share` prefixes are never created, only the components after them.

**Example:**

```c
if (fio_filename_make_path(.path = "./build/out/artifacts"))
  perror("mkdir -p");
```

### Reading and Writing

#### `fio_fd_write`

```c
FIO_IFUNC ssize_t fio_fd_write(int fd, const void *buf, size_t len);
```

Writes `len` bytes to `fd`, fragmenting the syscall into `write` blocks of up to `1 << 17` bytes. Retries on `EINTR`.

**Returns:** number of bytes written, or `-1` on error. If `fd == -1`, `buf == NULL`, or `len == 0`, returns `-1`.

**Note:** for non-blocking descriptors, check `errno` for `EAGAIN` / `EWOULDBLOCK`.

#### `fio_fd_read`

```c
FIO_IFUNC size_t fio_fd_read(int fd, void *buf, size_t len, off_t start_at);
```

Reads up to `len` bytes from `fd` starting at `start_at`. On POSIX systems with `pread`, the file offset is preserved; otherwise it seeks. Reads are fragmented to `1 << 27` byte chunks. Retries on `EINTR`.

**Parameters:**
- `fd` - file descriptor
- `buf` - destination buffer
- `len` - maximum bytes to read
- `start_at` - offset; negative values seek from end

**Returns:** bytes read, or `0` on error / EOF. Sets `errno = ENOENT` if `fd == -1`, `buf == NULL`, or `len == 0`.

### File Information

#### `fio_filename_size`

```c
FIO_IFUNC size_t fio_filename_size(const char *filename);
```

Returns file size in bytes, or `0` on error or empty file.

#### `fio_fd_size`

```c
FIO_IFUNC size_t fio_fd_size(int fd);
```

Returns file size for an open descriptor, or `0` on error or empty file.

#### `fio_filename_type`

```c
FIO_IFUNC size_t fio_filename_type(const char *filename);
```

Returns the file type bits (`st_mode & S_IFMT`), e.g. `S_IFREG`, `S_IFDIR`, or `0` on error.

#### `fio_fd_type`

```c
FIO_IFUNC size_t fio_fd_type(int fd);
```

Same as `fio_filename_type`, but for an open descriptor.

#### `fio_filename_stat`

```c
FIO_IFUNC int fio_filename_stat(const char *filename, struct stat *stat_buf);
```

Populates `stat_buf` via `stat`. Returns `0` on success, `-1` on error (including `filename == NULL` or `stat_buf == NULL`).

### Path Safety

#### `fio_filename_is_unsafe`

```c
SFUNC int fio_filename_is_unsafe(const char *path);
```

Returns `1` if `path` possibly folds backwards or contains double separators:
- leading `../`
- `/../` or trailing `/..`
- `//`

Uses the OS separator (`\` on Windows, `/` elsewhere). Returns `0` for `NULL` paths.

#### `fio_filename_is_unsafe_url`

```c
SFUNC int fio_filename_is_unsafe_url(const char *path);
```

Same check as `fio_filename_is_unsafe`, but always uses `/` as the separator. Use this for URL paths.

### Filename Parsing

#### `fio_filename_parse`

```c
SFUNC fio_filename_s fio_filename_parse(const char *filename);
```

Splits a NUL-terminated path into folder, basename, and extension without copying.

**Returns:** `fio_filename_s` with pointers into `filename`.

#### `fio_filename_parse2`

```c
SFUNC fio_filename_s fio_filename_parse2(const char *filename, size_t len);
```

Same as `fio_filename_parse`, but reads at most `len` bytes. Use when the input is not NUL-terminated.

### File Search

#### `fio_fd_find_next`

```c
SFUNC size_t fio_fd_find_next(int fd, char token, size_t start_at);
```

Scans `fd` for the next occurrence of `token` starting at `start_at`. Reads in `FIO_FD_FIND_BLOCK` byte chunks.

**Returns:** byte offset of the match, or `FIO_FD_FIND_EOF` if not found.

### Example

```c
#define FIO_FILES
#define FIO_LOG
#include "fio-stl.h"

int main(void) {
  fio_filename_s parts = fio_filename_parse("/var/log/app.log");
  printf("folder=%.*s base=%.*s ext=%.*s\n",
         (int)parts.folder.len, parts.folder.buf,
         (int)parts.basename.len, parts.basename.buf,
         (int)parts.ext.len, parts.ext.buf);

  const char *unsafe = "/var/www/../etc/passwd";
  printf("unsafe? %s\n", fio_filename_is_unsafe(unsafe) ? "yes" : "no");

  const char *msg = "hello, files";
  if (fio_filename_overwrite("/tmp/fio_test.txt", msg, strlen(msg)) == 0) {
    printf("wrote %zu bytes\n", strlen(msg));
  }

  size_t sz = fio_filename_size("/tmp/fio_test.txt");
  printf("size = %zu\n", sz);

  int tmp = fio_filename_tmp();
  if (tmp != -1) {
    fio_fd_write(tmp, "temp", 4);
    close(tmp);
  }
  return 0;
}
```

------------------------------------------------------------
