Implemented support for executing external commands and creating symlinks.
@@ -1,3 +1,15 @@
+#ifdef _WIN32 +// Must be defined before any system header pulls in <_mingw.h>, which +// otherwise defaults _WIN32_WINNT to a pre-Vista value and hides +// CreateSymbolicLinkA's declaration in <windows.h>. +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Vista+ - required for CreateSymbolicLinkA +#endif +#ifndef WINVER +#define WINVER 0x0600 +#endif +#endif + #include <stdio.h> #include <string.h> #include <sys/stat.h>@@ -19,7 +31,7 @@ // file device
#define FILE_PATH 0x0 #define FILE_TYPE 0x2 #define FILE_SIZE 0x3 -#define FILE_SUCCESS 0x5 +#define FILE_RESULT 0x5 #define FILE_LENGTH 0x7 #define FILE_READ 0x9 #define FILE_WRITE 0xB@@ -32,6 +44,8 @@ #define FILE_OP_APPEND 0x03
#define FILE_OP_DELETE 0x04 #define FILE_OP_COPY 0x05 #define FILE_OP_MOVE 0x06 +#define FILE_OP_SYMLINK 0x07 +#define FILE_OP_EXEC 0x08 // File types #define XYW_FILE_TYPE_UNKNOWN 0x00@@ -45,6 +59,8 @@ #define FILE_ERROR_INVALID_OP 0x33
#define FILE_ERROR_IO_ERROR 0x34 #define FILE_ERROR_PATH_TOO_LONG 0x35 #define FILE_ERROR_BUFFER_OUT_OF_RANGE 0x36 +#define FILE_ERROR_SYMLINK_FAILED 0x37 +#define FILE_ERROR_EXEC_FAILED 0x38 // Maximum path length #define FILE_MAX_PATH 256@@ -143,7 +159,7 @@ xyw_word length = XYW_PEEKW(&data[FILE_LENGTH]);
char path[FILE_MAX_PATH]; // Clear success - XYW_POKEW(&data[FILE_SUCCESS], 0); + XYW_POKEW(&data[FILE_RESULT], 0); if (path_addr == 0 || buffer_addr == 0 || length == 0) {@@ -301,7 +317,7 @@ {
xyw_memory[buffer_addr + offset] = 0; } - XYW_POKEW(&data[FILE_SUCCESS], offset); + XYW_POKEW(&data[FILE_RESULT], offset); return; }@@ -345,7 +361,7 @@ xyw_memory[buffer_addr + bytes_read] = 0;
} XYW_DBG(" Read %zu bytes, cursor now at %ld\n", bytes_read, file_cursor); - XYW_POKEW(&data[FILE_SUCCESS], (xyw_word)bytes_read); + XYW_POKEW(&data[FILE_RESULT], (xyw_word)bytes_read); } // Write memory buffer to file with cursor support@@ -357,7 +373,7 @@ xyw_word length = XYW_PEEKW(&data[FILE_LENGTH]);
char path[FILE_MAX_PATH]; // Clear success - XYW_POKEW(&data[FILE_SUCCESS], 0); + XYW_POKEW(&data[FILE_RESULT], 0); if (path_addr == 0 || buffer_addr == 0 || length == 0) {@@ -456,7 +472,7 @@ // Advance cursor
file_cursor += written; XYW_DBG(" Wrote %zu bytes (of %u max), cursor now at %ld\n", written, length, file_cursor); - XYW_POKEW(&data[FILE_SUCCESS], (xyw_word)written); + XYW_POKEW(&data[FILE_RESULT], (xyw_word)written); // Update file size data[FILE_TYPE] = FILE_TYPE_FILE;@@ -469,7 +485,7 @@ {
xyw_word path_addr = XYW_PEEKW(&data[FILE_PATH]); char path[FILE_MAX_PATH]; - XYW_POKEW(&data[FILE_SUCCESS], 0); + XYW_POKEW(&data[FILE_RESULT], 0); if (path_addr == 0) {@@ -500,7 +516,7 @@ return;
} // Success = 1 for successful delete - XYW_POKEW(&data[FILE_SUCCESS], 1); + XYW_POKEW(&data[FILE_RESULT], 1); // Update stats after delete update_file_stats(data, error);@@ -539,7 +555,7 @@ static void file_copy(xyw_byte *data, xyw_byte *error)
{ char src[FILE_MAX_PATH], dst[FILE_MAX_PATH]; - XYW_POKEW(&data[FILE_SUCCESS], 0); + XYW_POKEW(&data[FILE_RESULT], 0); if (split_path_pair(data, src, dst, FILE_MAX_PATH) != 0) {@@ -589,7 +605,7 @@
fclose(in); fclose(out); - XYW_POKEW(&data[FILE_SUCCESS], (xyw_word)(total > 0xFFFF ? 0xFFFF : total)); + XYW_POKEW(&data[FILE_RESULT], (xyw_word)(total > 0xFFFF ? 0xFFFF : total)); } // Move (rename) a file@@ -597,7 +613,7 @@ static void file_move(xyw_byte *data, xyw_byte *error)
{ char src[FILE_MAX_PATH], dst[FILE_MAX_PATH]; - XYW_POKEW(&data[FILE_SUCCESS], 0); + XYW_POKEW(&data[FILE_RESULT], 0); if (split_path_pair(data, src, dst, FILE_MAX_PATH) != 0) {@@ -625,7 +641,159 @@ return;
} } - XYW_POKEW(&data[FILE_SUCCESS], 1); + XYW_POKEW(&data[FILE_RESULT], 1); +} + +static void file_symlink(xyw_byte *data, xyw_byte *error) +{ + char target[FILE_MAX_PATH], linkpath[FILE_MAX_PATH]; + + XYW_POKEW(&data[FILE_RESULT], 0); + + if (split_path_pair(data, target, linkpath, FILE_MAX_PATH) != 0) + { + XYW_DBG("Error: Incorrect path pair specified\n"); + *error = FILE_ERROR_INVALID_OP; + return; + } + + if (file_handle && strcmp(file_current_path, linkpath) == 0) + file_close(); + + XYW_DBG(" Symlinking: %s -> %s\n", linkpath, target); + +#ifdef _WIN32 + #ifndef SYMBOLIC_LINK_FLAG_DIRECTORY + #define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1 + #endif + #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE + #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 + #endif + + DWORD attrs = GetFileAttributesA(target); + DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) + flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; + + if (!CreateSymbolicLinkA(linkpath, target, flags)) + { + XYW_DBG("Error: Failed to create symlink '%s' -> '%s'\n", linkpath, target); + *error = FILE_ERROR_SYMLINK_FAILED; + return; + } +#else + if (symlink(target, linkpath) != 0) + { + XYW_DBG("Error: Failed to create symlink '%s' -> '%s'\n", linkpath, target); + *error = FILE_ERROR_SYMLINK_FAILED; + return; + } +#endif + + XYW_POKEW(&data[FILE_RESULT], 1); +} + +static void file_exec(xyw_byte *data, xyw_byte *error) +{ + xyw_word path_addr = XYW_PEEKW(&data[FILE_PATH]); + xyw_word buffer_addr = XYW_PEEKW(&data[FILE_READ]); + xyw_word length = XYW_PEEKW(&data[FILE_LENGTH]); + char cmd[FILE_MAX_PATH]; + + XYW_POKEW(&data[FILE_RESULT], 0); + + if (path_addr == 0) + { + *error = FILE_ERROR_INVALID_OP; + return; + } + if (get_path_string(path_addr, cmd, FILE_MAX_PATH) != 0) + { + *error = FILE_ERROR_PATH_TOO_LONG; + return; + } + + int capture = (buffer_addr != 0 && length != 0); + + if (capture && !file_buffer_in_range(buffer_addr, length)) + { + *error = FILE_ERROR_BUFFER_OUT_OF_RANGE; + return; + } + + int status; + + if (capture) + { + // Merge stderr into the captured stream ("2>&1" works under sh, + // bash, and cmd.exe alike) + char merged[FILE_MAX_PATH + 8]; + int n = snprintf(merged, sizeof(merged), "%s 2>&1", cmd); + if (n < 0 || (size_t)n >= sizeof(merged)) + { + *error = FILE_ERROR_PATH_TOO_LONG; + return; + } + + XYW_DBG(" Executing (captured): %s\n", cmd); + +#ifdef _WIN32 + FILE *pipe = _popen(merged, "r"); +#else + FILE *pipe = popen(merged, "r"); +#endif + if (!pipe) + { + XYW_DBG("Error: Failed to execute command '%s'\n", cmd); + *error = FILE_ERROR_EXEC_FAILED; + return; + } + + // Reserve one byte for the null terminator so captured output is + // always a valid C string, regardless of how much was written. + size_t capture_len = length - 1; + size_t n_read = fread(&xyw_memory[buffer_addr], 1, capture_len, pipe); + xyw_memory[buffer_addr + n_read] = 0; + + // Drain any remaining output so the child doesn't block on a full pipe + char scratch[512]; + while (fread(scratch, 1, sizeof(scratch), pipe) > 0) {} + +#ifdef _WIN32 + status = _pclose(pipe); +#else + status = pclose(pipe); +#endif + } + else + { + // Passthrough mode: child inherits stdout/stderr/stdin directly, + // so interactive output, colors, and progress bars just work. + XYW_DBG(" Executing (passthrough): %s\n", cmd); +#ifndef _WIN32 + fflush(stdout); +#endif + status = system(cmd); + } + + xyw_word exit_code = 0xFFFF; +#ifdef _WIN32 + if (status != -1) + exit_code = (xyw_word)(status & 0xFFFF); +#else + if (status != -1 && WIFEXITED(status)) + exit_code = (xyw_word)WEXITSTATUS(status); +#endif + + if (status == -1) + { + XYW_DBG("Error: Failed to execute command '%s'\n", cmd); + *error = FILE_ERROR_EXEC_FAILED; + return; + } + + XYW_DBG(" Exec finished, exit code %u\n", exit_code); + XYW_POKEW(&data[FILE_RESULT], exit_code); } void file_setup(xyw_byte *data, xyw_byte *error)@@ -678,6 +846,12 @@ file_copy(data, error);
break; case FILE_OP_MOVE: file_move(data, error); + break; + case FILE_OP_SYMLINK: + file_symlink(data, error); + break; + case FILE_OP_EXEC: + file_exec(data, error); break; default: if (op != 0)
@@ -0,0 +1,27 @@
+.include "lib/macros.xyw" + +; Set command to execute +cmd file.path STW +; Capture output to buffer +output file.read STW +$0100 file.length STW +; Execute command +file.op.exec file.operation STB +; Print captured output +execution-label puts JSRw +output puts JSRw +; Print: Result: <code>\n +result puts JSRw +file.result LDW putdw JSRw NL +HLT + +.include "lib/putdw.xyw" +.include "lib/puts.xyw" + +.label execution-label +.string "Output: " +.label result +.string "Result: " +.label cmd +.string "uname -s" +.label output
@@ -29,7 +29,7 @@ ; file
.macro file.path $30 .macro file.type $32 .macro file.size $33 -.macro file.success $35 +.macro file.result $35 .macro file.length $37 .macro file.read $39 .macro file.write $3B@@ -78,6 +78,8 @@ .macro error.file.invalid_op $33
.macro error.file.io_error $34 .macro error.file.path_too_long $35 .macro error.file.buffer_out_of_range $36 +.macro error.file.symlink_failed $37 +.macro error.file.exec_failed $38 ; beeper .macro error.beeper.device $41@@ -85,12 +87,14 @@ .macro error.beeper.memory $42
.macro error.beeper.playback $43 ;;; File operations -.macro file.op.read $01 -.macro file.op.write $02 -.macro file.op.append $03 -.macro file.op.delete $04 -.macro file.op.copy $05 -.macro file.op.move $06 +.macro file.op.read $01 +.macro file.op.write $02 +.macro file.op.append $03 +.macro file.op.delete $04 +.macro file.op.copy $05 +.macro file.op.move $06 +.macro file.op.symlink $07 +.macro file.op.exec $08 ;;; File types .macro file.type.unspecified $00