diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64f3cbb..a1132ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -133,63 +133,3 @@ jobs: with: name: ${{ env.RESULT_NAME }} path: ${{ env.RESULT_PATH }} - - build-windows: - runs-on: windows-latest - timeout-minutes: 90 - strategy: - fail-fast: false - matrix: - arch: [x86_64, i686] - include: - - arch: x86_64 - arch2: x86_64 - bit: 64 - - arch: i686 - arch2: x86 - bit: 32 - env: - OS_NAME: windows - ARCH_NAME: ${{ matrix.arch2 }} - DLL_NAME: libasyncprocess.dll - RESULT_NAME: result-windows-${{ matrix.arch }} - RESULT_PATH: result-windows-${{ matrix.arch }} - RESULT_PATH_SUB: result-async/static - steps: - - run: git config --global core.autocrlf false - - uses: actions/checkout@v2 - - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW${{ matrix.bit }} - path-type: inherit - release: true - update: true - install: 'base-devel mingw-w64-${{ matrix.arch }}-toolchain' - - name: Run MSYS2 once - shell: msys2 {0} - run: | - pwd - echo $MSYSTEM - echo $MSYS2_PATH_TYPE - echo $PATH - uname - uname -m - - name: Build - shell: msys2 {0} - run: | - gcc -v - rm static/$ARCH_NAME/$OS_NAME/$DLL_NAME - ./bootstrap - - name: Copy Result - if: always() - shell: msys2 {0} - run: | - mkdir -p $RESULT_PATH/$RESULT_PATH_SUB/$ARCH_NAME/$OS_NAME - cp static/$ARCH_NAME/$OS_NAME/$DLL_NAME $RESULT_PATH/$RESULT_PATH_SUB/$ARCH_NAME/$OS_NAME - - name: Upload Result - if: always() - uses: actions/upload-artifact@v1 - with: - name: ${{ env.RESULT_NAME }} - path: ${{ env.RESULT_PATH }} - diff --git a/src/async-process.asd b/src/async-process.asd index 98f25d2..9f506ec 100644 --- a/src/async-process.asd +++ b/src/async-process.asd @@ -5,4 +5,7 @@ :license "MIT" :depends-on ("cffi") :serial t - :components ((:file "async-process"))) + :components ((:file "async-process_windows" + :if-feature (:or :win32 :windows)) + (:file "async-process" + :if-feature (:not (:or :win32 :windows))))) diff --git a/src/async-process.c b/src/async-process.c index f6b871f..ad58157 100644 --- a/src/async-process.c +++ b/src/async-process.c @@ -1,7 +1,5 @@ #include "async-process.h" -#ifndef HAVE_WINDOWS_H - static const char* open_pty(int *out_fd) { int fd = posix_openpt(O_RDWR | O_CLOEXEC | O_NOCTTY); @@ -133,4 +131,3 @@ int process_alive_p(struct process *process) { return kill(process->pid, 0) == 0; } -#endif diff --git a/src/async-process.h b/src/async-process.h index 38cafb1..dd2b26f 100644 --- a/src/async-process.h +++ b/src/async-process.h @@ -5,17 +5,12 @@ # include "config.h" #endif - -#ifdef HAVE_WINDOWS_H -# include -#else -# define _GNU_SOURCE -# include -# include -# include -# include -# include -#endif +#define _GNU_SOURCE +#include +#include +#include +#include +#include #include #include @@ -25,16 +20,9 @@ struct process { char buffer[1024*4]; -#ifdef HAVE_WINDOWS_H - PROCESS_INFORMATION pi; - HANDLE hInputWrite; - HANDLE hOutputRead; - bool nonblock; -#else int fd; char *pty_name; pid_t pid; -#endif }; struct process* create_process(char *const command[], bool nonblock, const char *path); diff --git a/src/async-process_windows.c b/src/async-process_windows.c deleted file mode 100644 index c763a79..0000000 --- a/src/async-process_windows.c +++ /dev/null @@ -1,121 +0,0 @@ -#include "async-process.h" -#ifdef HAVE_WINDOWS_H - -__declspec(dllexport) -struct process* create_process(char *const command[], bool nonblock, const char *path) -{ - struct process* ret=malloc(sizeof(struct process)); - HANDLE hErrorWrite = INVALID_HANDLE_VALUE; - HANDLE hOutputReadTmp = INVALID_HANDLE_VALUE; - HANDLE hOutputWrite = INVALID_HANDLE_VALUE; - HANDLE hInputWriteTmp = INVALID_HANDLE_VALUE; - HANDLE hInputRead = INVALID_HANDLE_VALUE; - - SECURITY_ATTRIBUTES sa; - ret->nonblock=nonblock; - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.lpSecurityDescriptor = 0; - sa.bInheritHandle = TRUE; - - HANDLE currproc = GetCurrentProcess(); - - if (!CreatePipe(&hOutputReadTmp, &hOutputWrite, &sa, 0)) - return NULL; - if (!DuplicateHandle(currproc, hOutputWrite, currproc, &hErrorWrite, 0, TRUE, DUPLICATE_SAME_ACCESS)) - return NULL; - if (!CreatePipe(&hInputRead, &hInputWriteTmp, &sa, 0)) - return NULL; - if (!DuplicateHandle(currproc, hOutputReadTmp, currproc, &(ret->hOutputRead), 0, FALSE, DUPLICATE_SAME_ACCESS)) - return NULL; - if (!DuplicateHandle(currproc, hInputWriteTmp, currproc, &(ret->hInputWrite), 0, FALSE, DUPLICATE_SAME_ACCESS)) - return NULL; - - CloseHandle(hOutputReadTmp); - CloseHandle(hInputWriteTmp); - - STARTUPINFOA si; - - ZeroMemory(&si, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdInput = hInputRead; - si.hStdOutput = hOutputWrite; - si.hStdError = hErrorWrite; - { - char *tmp; - int size = 0; - for(int i=0;command[i]!=NULL;++i) - size+=strlen(command[i])+1; - tmp=alloca(size); - for(int i=0,j=0,k=0;;++j,++k) { - if(command[i][j]=='\0') { - j=-1; - if(command[++i]) { - tmp[k]=' '; - }else { - tmp[k]='\0'; - break; - } - }else - tmp[k]=command[i][j]; - } - if (!CreateProcessA(0, tmp, 0, 0, TRUE, CREATE_NO_WINDOW, 0, path, &si, &(ret->pi))) - return NULL; - } - CloseHandle(hOutputWrite); - CloseHandle(hInputRead); - CloseHandle(hErrorWrite); - - return ret; -} - -__declspec(dllexport) -void delete_process(struct process *process) -{ - TerminateProcess(process->pi.hProcess,2); - CloseHandle(process->hInputWrite); - CloseHandle(process->hOutputRead); - CloseHandle(process->pi.hThread); - free(process); -} - -__declspec(dllexport) -int process_pid(struct process *process) -{ - return process->pi.dwProcessId; -} - -__declspec(dllexport) -void process_send_input(struct process *process, const char *string) -{ - DWORD n = 0; - WriteFile(process->hInputWrite,string,strlen(string),&n,NULL); -} - -__declspec(dllexport) -const char* process_receive_output(struct process *process) -{ - DWORD n = 0; - DWORD avail; - if(process->nonblock) { - if (!PeekNamedPipe (process->hOutputRead, 0, 0, 0, &avail, 0)) - return NULL; - if(!avail) - return NULL; - } - if (ReadFile(process->hOutputRead, process->buffer, sizeof(process->buffer)-1, &n, NULL)) { - process->buffer[n] = '\0'; - return process->buffer; - } - return NULL; -} - -__declspec(dllexport) -int process_alive_p(struct process *process) -{ - DWORD dwExitCode; - GetExitCodeProcess(process->pi.hProcess, &dwExitCode); - return (dwExitCode == STILL_ACTIVE); -} -#endif diff --git a/src/async-process_windows.lisp b/src/async-process_windows.lisp new file mode 100644 index 0000000..f8275f3 --- /dev/null +++ b/src/async-process_windows.lisp @@ -0,0 +1,285 @@ +(defpackage :async-process + (:use :cl) + (:export + :delete-process + :process-send-input + :process-receive-output + :process-alive-p + :create-process)) +(in-package :async-process) + +;; Windows API definitions via CFFI +(cffi:define-foreign-library kernel32 + (:windows "kernel32.dll")) + +(cffi:use-foreign-library kernel32) + +;; Constants +(defconstant +invalid-handle-value+ #xFFFFFFFF) +(defconstant +create-no-window+ #x08000000) +(defconstant +startf-usestdhandles+ #x00000100) +(defconstant +startf-useshowwindow+ #x00000001) +(defconstant +sw-hide+ 0) +(defconstant +duplicate-same-access+ #x00000002) +(defconstant +still-active+ 259) + +;; Structures +(cffi:defcstruct security-attributes + (length :uint32) + (security-descriptor :pointer) + (inherit-handle :boolean)) + +(cffi:defcstruct startup-info + (cb :uint32) + (reserved :pointer) + (desktop :pointer) + (title :pointer) + (x :uint32) + (y :uint32) + (x-size :uint32) + (y-size :uint32) + (x-count-chars :uint32) + (y-count-chars :uint32) + (fill-attribute :uint32) + (flags :uint32) + (show-window :uint16) + (cb-reserved2 :uint16) + (reserved2 :pointer) + (std-input :pointer) + (std-output :pointer) + (std-error :pointer)) + +(cffi:defcstruct process-information + (process :pointer) + (thread :pointer) + (process-id :uint32) + (thread-id :uint32)) + +;; Windows API functions +(cffi:defcfun ("CreatePipe" create-pipe) :boolean + (read-pipe (:pointer :pointer)) + (write-pipe (:pointer :pointer)) + (pipe-attributes (:pointer (:struct security-attributes))) + (size :uint32)) + +(cffi:defcfun ("CreateProcessW" create-process-w) :boolean + (application-name :pointer) + (command-line :pointer) + (process-attributes :pointer) + (thread-attributes :pointer) + (inherit-handles :boolean) + (creation-flags :uint32) + (environment :pointer) + (current-directory :pointer) + (startup-info (:pointer (:struct startup-info))) + (process-information (:pointer (:struct process-information)))) + +(cffi:defcfun ("DuplicateHandle" duplicate-handle) :boolean + (source-process :pointer) + (source-handle :pointer) + (target-process :pointer) + (target-handle (:pointer :pointer)) + (desired-access :uint32) + (inherit-handle :boolean) + (options :uint32)) + +(cffi:defcfun ("GetCurrentProcess" get-current-process) :pointer) + +(cffi:defcfun ("CloseHandle" close-handle) :boolean + (object :pointer)) + +(cffi:defcfun ("WriteFile" write-file) :boolean + (file :pointer) + (buffer :string) + (number-of-bytes-to-write :uint32) + (number-of-bytes-written (:pointer :uint32)) + (overlapped :pointer)) + +(cffi:defcfun ("ReadFile" read-file) :boolean + (file :pointer) + (buffer :pointer) + (number-of-bytes-to-read :uint32) + (number-of-bytes-read (:pointer :uint32)) + (overlapped :pointer)) + +(cffi:defcfun ("PeekNamedPipe" peek-named-pipe) :boolean + (pipe :pointer) + (buffer :pointer) + (buffer-size :uint32) + (bytes-read (:pointer :uint32)) + (total-bytes-avail (:pointer :uint32)) + (bytes-left-this-message (:pointer :uint32))) + +(cffi:defcfun ("GetExitCodeProcess" get-exit-code-process) :boolean + (process :pointer) + (exit-code (:pointer :uint32))) + +(cffi:defcfun ("TerminateProcess" terminate-process) :boolean + (process :pointer) + (exit-code :uint32)) + +;; Process class +(defclass process () + ((process-info :accessor process-process-info :initarg :process-info) + (input-handle :accessor process-input-handle :initarg :input-handle) + (output-handle :accessor process-output-handle :initarg :output-handle) + (nonblock :accessor process-nonblock :initarg :nonblock) + (encode :accessor process-encode :initarg :encode))) + +(defun create-process (command &key nonblock (encode cffi:*default-foreign-encoding*) directory) + "Create a new process with the given command" + (when (and directory (not (uiop:directory-exists-p directory))) + (error "Directory ~S does not exist" directory)) + + (let ((command-string (if (listp command) + (format nil "~{~A~^ ~}" command) + command))) + + (cffi:with-foreign-objects ((sa '(:struct security-attributes)) + (output-read-tmp :pointer) + (output-write :pointer) + (input-read :pointer) + (input-write-tmp :pointer) + (output-read :pointer) + (input-write :pointer) + (si '(:struct startup-info)) + (pi- '(:struct process-information))) + + ;; Initialize security attributes + (setf (cffi:foreign-slot-value sa '(:struct security-attributes) 'length) + (cffi:foreign-type-size '(:struct security-attributes))) + (setf (cffi:foreign-slot-value sa '(:struct security-attributes) 'security-descriptor) + (cffi:null-pointer)) + (setf (cffi:foreign-slot-value sa '(:struct security-attributes) 'inherit-handle) + t) + + ;; Create pipes + (unless (create-pipe output-read-tmp output-write sa 0) + (error "Failed to create output pipe")) + + (unless (create-pipe input-read input-write-tmp sa 0) + (error "Failed to create input pipe")) + + (let ((curr-process (get-current-process))) + ;; Duplicate handles + (unless (duplicate-handle curr-process + (cffi:mem-ref output-read-tmp :pointer) + curr-process + output-read + 0 nil +duplicate-same-access+) + (error "Failed to duplicate output read handle")) + + (unless (duplicate-handle curr-process + (cffi:mem-ref input-write-tmp :pointer) + curr-process + input-write + 0 nil +duplicate-same-access+) + (error "Failed to duplicate input write handle"))) + + ;; Close temporary handles + (close-handle (cffi:mem-ref output-read-tmp :pointer)) + (close-handle (cffi:mem-ref input-write-tmp :pointer)) + + ;; Initialize startup info + (cffi:foreign-funcall "memset" :pointer si :int 0 :uint32 + (cffi:foreign-type-size '(:struct startup-info)) :void) + (setf (cffi:foreign-slot-value si '(:struct startup-info) 'cb) + (cffi:foreign-type-size '(:struct startup-info))) + (setf (cffi:foreign-slot-value si '(:struct startup-info) 'flags) + (logior +startf-usestdhandles+ +startf-useshowwindow+)) + (setf (cffi:foreign-slot-value si '(:struct startup-info) 'show-window) + +sw-hide+) + (setf (cffi:foreign-slot-value si '(:struct startup-info) 'std-input) + (cffi:mem-ref input-read :pointer)) + (setf (cffi:foreign-slot-value si '(:struct startup-info) 'std-output) + (cffi:mem-ref output-write :pointer)) + (setf (cffi:foreign-slot-value si '(:struct startup-info) 'std-error) + (cffi:mem-ref output-write :pointer)) + + ;; Create process + (cffi:with-foreign-strings ((cmd-wide command-string :encoding :utf-16le) + (dir-wide (if directory (namestring directory) ""))) + (unless (create-process-w (cffi:null-pointer) + cmd-wide + (cffi:null-pointer) + (cffi:null-pointer) + t + +create-no-window+ + (cffi:null-pointer) + (if directory dir-wide (cffi:null-pointer)) + si + pi-) + (error "Failed to create process: ~A" command-string))) + + ;; Close handles we don't need + (close-handle (cffi:mem-ref output-write :pointer)) + (close-handle (cffi:mem-ref input-read :pointer)) + + ;; Create and return process object + (make-instance 'process + :process-info pi- + :input-handle (cffi:mem-ref input-write :pointer) + :output-handle (cffi:mem-ref output-read :pointer) + :nonblock nonblock + :encode encode)))) + +(defun delete-process (process) + "Terminate and clean up the process" + (let ((pi- (process-process-info process))) + (terminate-process (cffi:foreign-slot-value pi- '(:struct process-information) 'process) 2) + (close-handle (process-input-handle process)) + (close-handle (process-output-handle process)) + (close-handle (cffi:foreign-slot-value pi- '(:struct process-information) 'thread)) + (close-handle (cffi:foreign-slot-value pi- '(:struct process-information) 'process)))) + +(defun process-pid (process) + "Get the process ID" + (cffi:foreign-slot-value (process-process-info process) '(:struct process-information) 'process-id)) + +(defun process-send-input (process string) + "Send input to the process" + (cffi:with-foreign-object (bytes-written :uint32) + (let ((cffi:*default-foreign-encoding* (process-encode process))) + (write-file (process-input-handle process) + string + (length string) + bytes-written + (cffi:null-pointer))))) + +(defun process-receive-output (process) + "Receive output from the process" + (let ((buffer-size 4096)) + (cffi:with-foreign-objects ((buffer :char buffer-size) + (bytes-read :uint32) + (bytes-avail :uint32)) + + ;; Check if data is available (for non-blocking mode) + (when (process-nonblock process) + (unless (peek-named-pipe (process-output-handle process) + (cffi:null-pointer) 0 + (cffi:null-pointer) + bytes-avail + (cffi:null-pointer)) + (return-from process-receive-output nil)) + (when (zerop (cffi:mem-ref bytes-avail :uint32)) + (return-from process-receive-output nil))) + + ;; Read data + (when (read-file (process-output-handle process) + buffer + (1- buffer-size) + bytes-read + (cffi:null-pointer)) + (let ((num-bytes (cffi:mem-ref bytes-read :uint32))) + (when (> num-bytes 0) + (setf (cffi:mem-aref buffer :char num-bytes) 0) + (let ((cffi:*default-foreign-encoding* (process-encode process))) + (cffi:foreign-string-to-lisp buffer :count num-bytes)))))))) + +(defun process-alive-p (process) + "Check if the process is still running" + (cffi:with-foreign-object (exit-code :uint32) + (let ((pi- (process-process-info process))) + (when (get-exit-code-process (cffi:foreign-slot-value pi- '(:struct process-information) 'process) + exit-code) + (= (cffi:mem-ref exit-code :uint32) +still-active+))))) \ No newline at end of file diff --git a/static/x86/windows/libasyncprocess.dll b/static/x86/windows/libasyncprocess.dll deleted file mode 100644 index ad80b4c..0000000 Binary files a/static/x86/windows/libasyncprocess.dll and /dev/null differ diff --git a/static/x86_64/windows/libasyncprocess.dll b/static/x86_64/windows/libasyncprocess.dll deleted file mode 100644 index 4472501..0000000 Binary files a/static/x86_64/windows/libasyncprocess.dll and /dev/null differ