├── test ├── bash-test.sh ├── php-test.sh ├── groovy-test.sh ├── python-test.sh ├── ruby-test.sh ├── bash-test-script.sh ├── setup.sh ├── svn-test.sh └── ruby-nonblock-test.sh └── src ├── die.h ├── process_name.h ├── textmate.h ├── plist.h ├── dialog.h ├── mode.h ├── stringutil.h ├── die.c ├── stdin_fd_tracker.h ├── debug.h ├── textmate.c ├── intset.h ├── stringutil.c ├── plist.c ├── buffer.h ├── mode.c ├── intset.c ├── system_function_overrides.h ├── process_name.c ├── stdin_fd_tracker.c ├── buffer.c ├── dialog.c └── system_function_overrides.c /test/bash-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . "$(dirname "$0")/setup.sh" 4 | TM_INTERACTIVE_INPUT='AUTO|ECHO' ./bash-test-script.sh -------------------------------------------------------------------------------- /test/php-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . "$(dirname "$0")/setup.sh" 4 | TM_INTERACTIVE_INPUT=AUTO php -r "echo file_get_contents('php://stdin');" -------------------------------------------------------------------------------- /src/die.h: -------------------------------------------------------------------------------- 1 | #ifndef DIE_H_U3C78NAP 2 | #define DIE_H_U3C78NAP 3 | 4 | void die(char *, ...); 5 | 6 | #endif /* end of include guard: DIE_H_U3C78NAP */ 7 | -------------------------------------------------------------------------------- /test/groovy-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . "$(dirname "$0")/setup.sh" 4 | TM_INTERACTIVE_INPUT=AUTO groovy -e "System.in.withReader { println it.readLine() }" -------------------------------------------------------------------------------- /test/python-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . "$(dirname "$0")/setup.sh" 4 | TM_INTERACTIVE_INPUT=AUTO python -c "import sys ; sys.stdout.write(sys.stdin.readline())" -------------------------------------------------------------------------------- /src/process_name.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_NAME_H_6XWET0N 2 | #define PROCESS_NAME_H_6XWET0N 3 | 4 | char* create_process_name(); 5 | 6 | #endif /* end of include guard: PROCESS_NAME_H_6XWET0N */ 7 | -------------------------------------------------------------------------------- /src/textmate.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTMATE_H_ZD09ZIDY 2 | #define TEXTMATE_H_ZD09ZIDY 3 | 4 | #include 5 | 6 | bool fd_is_owned_by_tm(int); 7 | 8 | #endif /* end of include guard: TEXTMATE_H_ZD09ZIDY */ 9 | -------------------------------------------------------------------------------- /test/ruby-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . "$(dirname "$0")/setup.sh" 4 | TM_INTERACTIVE_INPUT=AUTO ruby -e "puts 'decoy line' 5 | print 'Enter Something: ' 6 | STDOUT.flush 7 | puts gets 8 | " -------------------------------------------------------------------------------- /src/plist.h: -------------------------------------------------------------------------------- 1 | #ifndef PLIST_H_VBWFGMGP 2 | #define PLIST_H_VBWFGMGP 3 | 4 | #include "buffer.h" 5 | #include 6 | 7 | CFPropertyListRef create_plist_from_buffer(buffer_t*); 8 | 9 | #endif /* end of include guard: PLIST_H_VBWFGMGP */ 10 | -------------------------------------------------------------------------------- /src/dialog.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOG_H_7T36A9MD 2 | #define DIALOG_H_7T36A9MD 3 | 4 | #include 5 | 6 | ssize_t tm_dialog_read(void *, size_t); 7 | void capture_for_prompt(const void *buffer, size_t buffer_length); 8 | 9 | #endif /* end of include guard: DIALOG_H_7T36A9MD */ 10 | -------------------------------------------------------------------------------- /test/bash-test-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo -n "Enter Username: " 4 | read x 5 | echo -n "Enter Password: " 6 | read x 7 | 8 | # This should not bring up the dialog… 9 | # this tests dialog only comes up when stdin 10 | # is owned by TM 11 | { sleep .1; echo; } | cat 12 | 13 | -------------------------------------------------------------------------------- /src/mode.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_H_ZFBHBHH0 2 | #define MODE_H_ZFBHBHH0 3 | 4 | #include 5 | 6 | bool tm_interactive_input_is_active(); 7 | bool tm_interactive_input_is_in_always_mode(); 8 | bool tm_interactive_input_is_in_echo_mode(); 9 | 10 | #endif /* end of include guard: MODE_H_ZFBHBHH0 */ 11 | -------------------------------------------------------------------------------- /src/stringutil.h: -------------------------------------------------------------------------------- 1 | #ifndef STRINGUTIL_H_UPCIVA7G 2 | #define STRINGUTIL_H_UPCIVA7G 3 | 4 | #include 5 | #include 6 | 7 | char* cfstr_2_cstr(CFStringRef); 8 | CFStringRef cstr_2_cfstr(char*); 9 | 10 | #endif /* end of include guard: STRINGUTIL_H_UPCIVA7G */ 11 | -------------------------------------------------------------------------------- /test/setup.sh: -------------------------------------------------------------------------------- 1 | TM_INTERACTIVE_INPUT_DYLIB="$(dirname "$0")/../build/tm_interactive_input.dylib" 2 | 3 | if [ ! -f "$TM_INTERACTIVE_INPUT_DYLIB" ] 4 | then 5 | echo "$TM_INTERACTIVE_INPUT_DYLIB doesn't exist, build it first" 6 | exit 1 7 | fi 8 | 9 | export DYLD_INSERT_LIBRARIES="$TM_INTERACTIVE_INPUT_DYLIB${DYLD_INSERT_LIBRARIES:+:$DYLD_INSERT_LIBRARIES}" 10 | export DYLD_FORCE_FLAT_NAMESPACE=1 -------------------------------------------------------------------------------- /test/svn-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # In svn 1.5.0 this does not work. It does in < 1.5.0 though. 4 | # It never returns from the log command. It appears to be something to do with the select() impl. 5 | 6 | # Note: it's not supposed to bring up an input dialog, it's just supposed to complete. 7 | 8 | SVN=svn 9 | 10 | . "$(dirname "$0")/setup.sh" 11 | "$SVN" --version | head -1 12 | TM_INTERACTIVE_INPUT=AUTO "$SVN" --no-auth-cache log http://svn.textmate.org/ --limit 1 -------------------------------------------------------------------------------- /test/ruby-nonblock-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # When running this, we are expecting the dialog *not* to appear. 4 | # We are testing that we don't invoke for non blocking reads. 5 | # 6 | # The reason for the begin/rescue is that read_nonblock errors if it gets EOF 7 | # which it will. 8 | 9 | . "$(dirname "$0")/setup.sh" 10 | TM_INTERACTIVE_INPUT=AUTO ruby -e ' 11 | begin 12 | puts STDIN.read_nonblock(1) 13 | rescue 14 | puts "non blocking read done" 15 | end' 16 | -------------------------------------------------------------------------------- /src/die.c: -------------------------------------------------------------------------------- 1 | #include "die.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void die(char *fmt, ...) { 8 | va_list ap; 9 | char *msg; 10 | 11 | va_start(ap, fmt); 12 | if (vasprintf(&msg, fmt, ap) == -1) { 13 | fputs("tm_interactive_input failure: FAILED TO ALLOCATE ERROR STRING", stderr); 14 | exit(1); 15 | } 16 | va_end(ap); 17 | 18 | fprintf(stderr, "tm_interactive_input failure: %s\n", msg); 19 | exit(1); 20 | } -------------------------------------------------------------------------------- /src/stdin_fd_tracker.h: -------------------------------------------------------------------------------- 1 | #ifndef STDIN_FD_TRACKER_H_TJF398AZ 2 | #define STDIN_FD_TRACKER_H_TJF398AZ 3 | 4 | #include 5 | #include 6 | 7 | bool stdin_fd_tracker_is_stdin(int); 8 | void stdin_fd_tracker_did_dup(int,int); 9 | void stdin_fd_tracker_did_close(int); 10 | int stdin_fd_tracker_augment_select_result(int, fd_set * __restrict, fd_set * __restrict); 11 | int stdin_fd_tracker_count_stdins_in_fdset(int max, fd_set *fds); 12 | 13 | #endif /* end of include guard: STDIN_FD_TRACKER_H_TJF398AZ */ 14 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H_8I80DRD8 2 | #define DEBUG_H_8I80DRD8 3 | 4 | #include 5 | 6 | #ifndef NDEBUG 7 | #define D(format, args...) ; if (true) { FILE *f = fopen("/tmp/tm_interactive_read.log","a"); fprintf(f, "%s(): ", __FUNCTION__); fprintf (f, format,## args); fclose(f); } 8 | #else 9 | #define D(format, args...) 10 | #endif 11 | 12 | #ifndef NDEBUG 13 | #define DB(buffer) write(STDERR_FILENO, buffer->data, buffer->size) 14 | #else 15 | #define DB(buffer) 16 | #endif 17 | 18 | #endif /* end of include guard: DEBUG_H_8I80DRD8 */ 19 | -------------------------------------------------------------------------------- /src/textmate.c: -------------------------------------------------------------------------------- 1 | #include "textmate.h" 2 | #include "die.h" 3 | #include "debug.h" 4 | #include 5 | #include 6 | #include 7 | 8 | int get_tm_pid() { 9 | char* value = getenv("TM_PID"); 10 | if (value == NULL) return -1; 11 | return atoi(value); 12 | } 13 | 14 | bool fd_is_owned_by_tm(int fd) { 15 | int tm_pid = get_tm_pid(); 16 | 17 | if (tm_pid < 0) return false; 18 | 19 | int fd_pid = fcntl(fd, F_GETOWN); 20 | D("fd_pid = %d, tm_pid = %d\n", fd_pid, tm_pid); 21 | return (abs(fd_pid) == tm_pid); 22 | } -------------------------------------------------------------------------------- /src/intset.h: -------------------------------------------------------------------------------- 1 | #ifndef INTSET_H_FW2SWPAB 2 | #define INTSET_H_FW2SWPAB 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | int* ints; 9 | size_t size; 10 | size_t capacity; 11 | } intset_t ; 12 | 13 | intset_t* create_intset(); 14 | void destroy_intset(intset_t*); 15 | void add_to_intset(intset_t*, int); 16 | bool remove_from_intset(intset_t*, int); 17 | bool intset_contains(intset_t*, int); 18 | size_t intset_size(intset_t* set); 19 | int intset_get(intset_t* set, ssize_t i); 20 | 21 | #endif /* end of include guard: INTSET_H_FW2SWPAB */ 22 | -------------------------------------------------------------------------------- /src/stringutil.c: -------------------------------------------------------------------------------- 1 | #include "stringutil.h" 2 | #include "die.h" 3 | #include "debug.h" 4 | 5 | char* cfstr_2_cstr(CFStringRef cfstr) { 6 | size_t cstr_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8) + 1; 7 | char *cstr = malloc(cstr_size); 8 | if (cstr == NULL) die("failed to allocate to CFStringRef to C string conversion"); 9 | 10 | if (!CFStringGetCString(NULL, cstr, cstr_size, kCFStringEncodingUTF8)) { 11 | die("failed to convert CFStringRef to C string"); 12 | } 13 | return cstr; 14 | } 15 | 16 | CFStringRef cstr_2_cfstr(char* cstr) { 17 | CFStringRef cfstr = CFStringCreateWithCString(kCFAllocatorDefault, cstr, kCFStringEncodingUTF8); 18 | if (cfstr == NULL) die("failed to create CFStringRef from %s", cstr); 19 | return cfstr; 20 | } -------------------------------------------------------------------------------- /src/plist.c: -------------------------------------------------------------------------------- 1 | #include "plist.h" 2 | #include "debug.h" 3 | #include "stringutil.h" 4 | #include "die.h" 5 | 6 | CFPropertyListRef create_plist_from_buffer(buffer_t *buffer) { 7 | 8 | D("creating data ref of buffer\n"); 9 | 10 | CFDataRef buffer_as_data = CFDataCreate(kCFAllocatorDefault, (UInt8*)get_buffer_data(buffer), buffer->size); 11 | if (buffer_as_data == NULL) die("failed to allocate tm_dialog_output_data"); 12 | 13 | D("creating property list from data\n"); 14 | 15 | CFStringRef error = NULL; 16 | CFPropertyListRef plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, buffer_as_data, kCFPropertyListImmutable, &error); 17 | 18 | if (error) { 19 | D("creating property list failed\n"); 20 | die(cfstr_2_cstr(error)); 21 | } 22 | 23 | CFRelease(buffer_as_data); 24 | 25 | return plist; 26 | } -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H_Z38W1DOX 2 | #define BUFFER_H_Z38W1DOX 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | char* data; 9 | size_t size; 10 | size_t capacity; 11 | } buffer_t; 12 | 13 | buffer_t* create_buffer(); 14 | void destroy_buffer(buffer_t*); 15 | size_t get_buffer_size(buffer_t*); 16 | size_t get_buffer_capacity(buffer_t*); 17 | char* get_buffer_data(buffer_t*); 18 | char get_buffer_byte_at(buffer_t*, size_t); 19 | buffer_t* create_buffer_with(char*, size_t); 20 | buffer_t* create_buffer_from_cfstr(CFStringRef); 21 | buffer_t * create_buffer_from_file_descriptor(int); 22 | buffer_t* create_buffer_from_dictionary_as_xml(CFDictionaryRef); 23 | char* create_cstr_from_buffer(buffer_t*); 24 | void add_to_buffer(buffer_t*, char*, size_t); 25 | size_t consume_from_head_of_buffer(buffer_t*, char*, size_t); 26 | int write_buffer_to_fd(buffer_t*, int); 27 | 28 | #endif /* end of include guard: BUFFER_H_Z38W1DOX */ 29 | -------------------------------------------------------------------------------- /src/mode.c: -------------------------------------------------------------------------------- 1 | #include "mode.h" 2 | #include "debug.h" 3 | 4 | #include 5 | #include 6 | 7 | char* get_tm_interactive_input_mode_mask() { 8 | return getenv("TM_INTERACTIVE_INPUT"); 9 | } 10 | 11 | bool mode_contains(char *target) { 12 | 13 | // Because strsep modifies the string in place, we need to make a copy. 14 | char *mode_mask = get_tm_interactive_input_mode_mask(); 15 | if (mode_mask == NULL) return false; 16 | char *mode_mask_copy = strdup(mode_mask); 17 | char *strsep_index = mode_mask_copy; 18 | 19 | char *mode_flag = NULL; 20 | bool contains = false; 21 | 22 | while ((mode_flag = strsep(&strsep_index, "|")) != NULL) { 23 | if (strcmp(mode_flag, target) == 0) { 24 | contains = true; 25 | break; 26 | } 27 | } 28 | 29 | free(mode_mask_copy); 30 | return contains; 31 | } 32 | 33 | bool tm_interactive_input_is_active() { 34 | return (get_tm_interactive_input_mode_mask() != NULL && mode_contains("NEVER") == false); 35 | } 36 | 37 | bool tm_interactive_input_is_in_always_mode() { 38 | return mode_contains("ALWAYS"); 39 | } 40 | 41 | bool tm_interactive_input_is_in_echo_mode() { 42 | return mode_contains("ECHO"); 43 | } -------------------------------------------------------------------------------- /src/intset.c: -------------------------------------------------------------------------------- 1 | #include "intset.h" 2 | #include 3 | #include "die.h" 4 | #include "debug.h" 5 | #include 6 | 7 | static const size_t grow_factor = 8; 8 | 9 | intset_t* create_intset() { 10 | intset_t* res = malloc(sizeof(intset_t)); 11 | if (res == NULL) die("create_intset() malloc failed"); 12 | res->ints = NULL; 13 | res->size = 0; 14 | res->capacity = 0; 15 | return res; 16 | } 17 | 18 | void destroy_intset(intset_t* is) { 19 | if (is->ints != NULL) free(is->ints); 20 | free(is); 21 | } 22 | 23 | void add_to_intset(intset_t* is, int i) { 24 | if (intset_contains(is, i) == false) { 25 | if (is->capacity == is->size) { 26 | is->capacity += grow_factor; 27 | is->ints = realloc(is->ints, is->capacity); 28 | if (is->ints == NULL) die("reallocation of ints failed"); 29 | } 30 | is->ints[is->size++] = i; 31 | } 32 | } 33 | 34 | bool remove_from_intset(intset_t* set, int target) { 35 | size_t i = 0; 36 | for (i = 0; i < set->size; ++i) { 37 | if (set->ints[i] == target) { 38 | memmove(set->ints + i, set->ints + i + 1, set->size - i); 39 | --set->size; 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | bool intset_contains(intset_t* set, int target) { 47 | size_t i = 0; 48 | for (i = 0; i < set->size; ++i) { 49 | if (set->ints[i] == target) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | 56 | size_t intset_size(intset_t* set) { 57 | return set->size; 58 | } 59 | 60 | int intset_get(intset_t* set, ssize_t i) { 61 | if (i < 0 || (i + 1) > set->size) die("%d is out of range (size = %d)", i, set->size); 62 | return set->ints[i]; 63 | } -------------------------------------------------------------------------------- /src/system_function_overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEM_FUNCTION_OVERRIDES_H_OD5QRJC5 2 | #define SYSTEM_FUNCTION_OVERRIDES_H_OD5QRJC5 3 | 4 | #include 5 | #include 6 | 7 | ssize_t system_read(char*, int, void *, size_t); 8 | ssize_t read(int, void *, size_t) __asm("_read"); 9 | ssize_t read_nocancel(int, void *, size_t) __asm("_read$NOCANCEL"); 10 | ssize_t read_unix2003(int, void *, size_t) __asm("_read$UNIX2003"); 11 | ssize_t read_nocancel_unix2003(int, void *, size_t) __asm("_read$NOCANCEL$UNIX2003"); 12 | 13 | ssize_t system_write(char*, int, const void *, size_t); 14 | ssize_t write(int, const void*, size_t) __asm("_write"); 15 | ssize_t write_nocancel(int, const void*, size_t) __asm("_write$NOCANCEL"); 16 | ssize_t write_unix2003(int, const void*, size_t) __asm("_write$UNIX2003"); 17 | ssize_t write_nocancel_unix2003(int, const void*, size_t) __asm("_write$NOCANCEL$UNIX2003"); 18 | 19 | int dup(int); 20 | 21 | int close(int); 22 | 23 | int system_select(char * symbol, int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout); 24 | 25 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 26 | int select(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) __asm("_select"); 27 | #else 28 | int select(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict); 29 | #endif 30 | int select_darwinextsn(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) __asm("_select$DARWIN_EXTSN"); 31 | int select_darwinextsn_nocancel(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) __asm("_select$DARWIN_EXTSN$NOCANCEL"); 32 | int select_nocancel(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) __asm("_select$NOCANCEL"); 33 | int select_unix2003(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) __asm("_select$UNIX2003"); 34 | int select_nocancel_unix2003(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) __asm("_select$NOCANCEL$UNIX2003"); 35 | 36 | #endif /* end of include guard: SYSTEM_FUNCTION_OVERRIDES_H_OD5QRJC5 */ 37 | -------------------------------------------------------------------------------- /src/process_name.c: -------------------------------------------------------------------------------- 1 | #include "process_name.h" 2 | #include "buffer.h" 3 | #include "die.h" 4 | #include "debug.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | buffer_t* get_ps_output() { 14 | pid_t pid = getpid(); 15 | D("pid = %d\n", pid); 16 | 17 | char* get_process_name_command; 18 | asprintf(&get_process_name_command, "ps -p %i -o ucomm", pid); 19 | if (get_process_name_command == NULL) die("failed to create ps command"); 20 | D("ps command = %s\n", get_process_name_command); 21 | sig_t previous_sigchld_handler = signal(SIGCHLD, SIG_DFL); 22 | FILE* out = popen(get_process_name_command, "r"); 23 | free(get_process_name_command); 24 | 25 | buffer_t* ps_output = create_buffer_from_file_descriptor(fileno(out)); 26 | 27 | int ps_result = pclose(out); 28 | signal(SIGCHLD, previous_sigchld_handler); 29 | 30 | if (ps_result != 0) die("ps returned %d, output = %*.s", ps_result, get_buffer_size(ps_output), get_buffer_data(ps_output)); 31 | if (get_buffer_size(ps_output) == 0) die("ps did not return any output"); 32 | 33 | return ps_output; 34 | } 35 | 36 | char* create_process_name() { 37 | buffer_t* ps_output = get_ps_output(); 38 | 39 | // ps output has a column name header line, so we need ignore it 40 | int index_of_process_name_line = strlen("UCOMM\n"); 41 | 42 | // Ignore any whitespace before the process name 43 | int first_non_space_char_index; 44 | for (first_non_space_char_index = index_of_process_name_line; first_non_space_char_index < get_buffer_size(ps_output); ++first_non_space_char_index) { 45 | if (isspace(get_buffer_byte_at(ps_output, first_non_space_char_index)) == false) break; 46 | } 47 | 48 | // Ignore any whitespace after the process name 49 | int last_non_space_char_index; 50 | for (last_non_space_char_index = get_buffer_size(ps_output) - 1; last_non_space_char_index >= index_of_process_name_line; --last_non_space_char_index) { 51 | if (isspace(get_buffer_byte_at(ps_output, last_non_space_char_index)) == false) break; 52 | } 53 | 54 | size_t process_name_length = last_non_space_char_index - first_non_space_char_index + 1; 55 | char* process_name = malloc(process_name_length + 1); // +1 for \0 56 | strncpy(process_name, get_buffer_data(ps_output) + index_of_process_name_line, process_name_length); 57 | process_name[process_name_length] = '\0'; 58 | 59 | destroy_buffer(ps_output); 60 | 61 | D("process name = %s\n", process_name); 62 | return process_name; 63 | } 64 | -------------------------------------------------------------------------------- /src/stdin_fd_tracker.c: -------------------------------------------------------------------------------- 1 | #include "stdin_fd_tracker.h" 2 | #include "intset.h" 3 | #include "debug.h" 4 | #include "textmate.h" 5 | #include 6 | 7 | pthread_mutex_t storage_mutex = PTHREAD_MUTEX_INITIALIZER; 8 | 9 | // Only call this if you have locked the storage mutex 10 | intset_t* get_storage() { 11 | static intset_t* storage = NULL; 12 | if (storage == NULL) { 13 | storage = create_intset(); 14 | add_to_intset(storage, 0); 15 | } 16 | return storage; 17 | } 18 | 19 | bool stdin_fd_tracker_is_stdin(int target) { 20 | pthread_mutex_lock(&storage_mutex); 21 | intset_t* fds = get_storage(); 22 | bool is = intset_contains(fds, target); 23 | pthread_mutex_unlock(&storage_mutex); 24 | return is; 25 | } 26 | 27 | void stdin_fd_tracker_did_dup(int orig, int dup) { 28 | pthread_mutex_lock(&storage_mutex); 29 | intset_t* fds = get_storage(); 30 | if (intset_contains(fds, orig)) { 31 | D("adding %d as dup of %d\n", dup, orig); 32 | add_to_intset(fds, dup); 33 | } 34 | pthread_mutex_unlock(&storage_mutex); 35 | } 36 | 37 | void stdin_fd_tracker_did_close(int target) { 38 | pthread_mutex_lock(&storage_mutex); 39 | intset_t* fds = get_storage(); 40 | if (intset_contains(fds, target)) { 41 | D("removing %d\n as dup of stdin", target); 42 | remove_from_intset(fds, target); 43 | } 44 | pthread_mutex_unlock(&storage_mutex); 45 | } 46 | 47 | int stdin_fd_tracker_augment_select_result(int max, fd_set *orig_fds __restrict, fd_set *changed_fds) { 48 | pthread_mutex_lock(&storage_mutex); 49 | intset_t* storage = get_storage(); 50 | int count = 0; 51 | 52 | int i; 53 | for (i = 0; i < intset_size(storage); ++i) { 54 | int fd = intset_get(storage, i); 55 | if(fd >= max) 56 | continue; 57 | 58 | if (FD_ISSET(fd, orig_fds) && !FD_ISSET(fd, changed_fds) && fd_is_owned_by_tm(fd)) { 59 | ++count; 60 | FD_SET(fd, changed_fds); 61 | } 62 | } 63 | 64 | pthread_mutex_unlock(&storage_mutex); 65 | 66 | return count; 67 | } 68 | 69 | int stdin_fd_tracker_count_stdins_in_fdset(int max, fd_set *fds) { 70 | pthread_mutex_lock(&storage_mutex); 71 | intset_t* storage = get_storage(); 72 | int count = 0; 73 | 74 | int i; 75 | for (i = 0; i < intset_size(storage); ++i) { 76 | int fd = intset_get(storage, i); 77 | if(fd >= max) 78 | continue; 79 | 80 | if (FD_ISSET(fd, fds) && fd_is_owned_by_tm(fd)) { 81 | ++count; 82 | } 83 | } 84 | 85 | pthread_mutex_unlock(&storage_mutex); 86 | 87 | return count; 88 | } -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | #include "die.h" 3 | #include "debug.h" 4 | #include "stringutil.h" 5 | #include "system_function_overrides.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef ALLOC_SIZE 13 | #define ALLOC_SIZE 128 14 | #endif 15 | 16 | #ifndef READ_BLOCK_SIZE 17 | #define READ_BLOCK_SIZE 1024 18 | #endif 19 | 20 | buffer_t* create_buffer() { 21 | buffer_t* res = malloc(sizeof(buffer_t)); 22 | if (res == NULL) die("create_buffer() malloc failed"); 23 | res->data = NULL; 24 | res->size = 0; 25 | res->capacity = 0; 26 | return res; 27 | } 28 | 29 | size_t get_buffer_size(buffer_t *b) { 30 | return b->size; 31 | } 32 | size_t get_buffer_capacity(buffer_t *b) { 33 | return b->capacity; 34 | } 35 | char* get_buffer_data(buffer_t *b) { 36 | return b->data; 37 | } 38 | 39 | char get_buffer_byte_at(buffer_t *b, size_t at) { 40 | if (at >= b->size) die("buffer access out of range: %d for buffer of size %d", at, b->size); 41 | return b->data[at]; 42 | } 43 | 44 | buffer_t* create_buffer_with(char* bytes, size_t len) { 45 | buffer_t* b = create_buffer(); 46 | b->data = bytes; 47 | b->size = len; 48 | b->capacity = len; 49 | return b; 50 | } 51 | 52 | buffer_t* create_buffer_from_cfstr(CFStringRef cfstr) { 53 | buffer_t* b = create_buffer(); 54 | CFIndex cfstr_length = CFStringGetLength(cfstr); 55 | CFIndex storage_max_length = CFStringGetMaximumSizeForEncoding(cfstr_length, kCFStringEncodingUTF8); 56 | b->data = malloc(storage_max_length); 57 | 58 | CFRange copy_range; 59 | copy_range.location = 0; 60 | copy_range.length = cfstr_length; 61 | 62 | CFIndex copy_count; 63 | 64 | CFStringGetBytes(cfstr, copy_range, kCFStringEncodingUTF8, 0, false, (UInt8 *)b->data, storage_max_length, ©_count); 65 | b->size = copy_count; 66 | b->capacity = storage_max_length; 67 | return b; 68 | } 69 | 70 | buffer_t * create_buffer_from_file_descriptor(int fd) { 71 | 72 | char intermediary_buffer[READ_BLOCK_SIZE]; 73 | buffer_t *buffer = create_buffer(); 74 | 75 | ssize_t bytes_read; 76 | do { 77 | bytes_read = system_read("read", fd, intermediary_buffer, sizeof(intermediary_buffer)); 78 | D("got %zd bytes from fd\n", bytes_read); 79 | if (bytes_read > 0) { 80 | add_to_buffer(buffer, intermediary_buffer, bytes_read); 81 | } else if (bytes_read < 0) { 82 | D("read error = '%s'\n", strerror(errno)); 83 | } 84 | 85 | } while(bytes_read != 0); 86 | 87 | return buffer; 88 | } 89 | 90 | buffer_t* create_buffer_from_dictionary_as_xml(CFDictionaryRef dictionary) { 91 | 92 | CFStringRef error; 93 | CFWriteStreamRef stream = CFWriteStreamCreateWithAllocatedBuffers(kCFAllocatorDefault, kCFAllocatorDefault); 94 | if (stream == NULL) die("failed to allocate write stream to write dictionary representation to"); 95 | if (!CFWriteStreamOpen(stream)) die("failed to open write stream to write dictionary representation to"); 96 | 97 | CFPropertyListWriteToStream(dictionary, stream, kCFPropertyListXMLFormat_v1_0, &error); 98 | 99 | if (error) { 100 | die("Writing dictionary to stream failed: %s", cfstr_2_cstr(error)); 101 | } 102 | 103 | CFDataRef data = CFWriteStreamCopyProperty(stream, kCFStreamPropertyDataWritten); 104 | if (data == NULL) die("failed to extract data from dictionary write stream"); 105 | 106 | CFIndex data_length = CFDataGetLength(data); 107 | char *bytes = malloc(data_length); 108 | if (bytes == NULL) die("failed to allocate buffer for dictionary representation"); 109 | 110 | CFDataGetBytes(data, CFRangeMake(0, data_length), (UInt8 *)bytes); 111 | buffer_t* buffer = create_buffer_with(bytes, data_length); 112 | 113 | D("buffer = %*s\n", (int)buffer->size, buffer->data); 114 | 115 | return buffer; 116 | } 117 | 118 | char* create_cstr_from_buffer(buffer_t* buffer) { 119 | char *cstr = malloc(buffer->size + 1); 120 | memcpy(cstr, buffer->data, buffer->size); 121 | cstr[buffer->size] = '\0'; 122 | return cstr; 123 | } 124 | 125 | void add_to_buffer(buffer_t* buffer, char* bytes, size_t len) { 126 | if (buffer->capacity < buffer->size + len) { 127 | 128 | D("Resizing buffer (need to add %d) from %d\n", (int)len, (int)buffer->capacity); 129 | buffer->capacity = ceil((buffer->size + len) / ALLOC_SIZE + 1) * ALLOC_SIZE; 130 | D("New target size = %d\n", (int)buffer->capacity); 131 | buffer->data = realloc(buffer->data, buffer->capacity); 132 | if (buffer->data == NULL) die("reallocation of buffer failed"); 133 | } 134 | memcpy(buffer->data + buffer->size, bytes, len); 135 | buffer->size += len; 136 | } 137 | 138 | size_t consume_from_head_of_buffer(buffer_t* buffer, char* dest, size_t dest_size) { 139 | D("consuming %d from buffer of size %d\n", (int)dest_size, (int)buffer->size); 140 | if (buffer->size == 0 || dest_size == 0) return 0; 141 | 142 | size_t consumption_size = (buffer->size < dest_size) ? buffer->size : dest_size; 143 | D("consumption_size = %d\n", (int)consumption_size); 144 | 145 | if (dest != NULL) { 146 | strncpy(dest, buffer->data, consumption_size); 147 | } 148 | 149 | buffer->size -= consumption_size; 150 | D("new_data_size = %d\n", (int)buffer->size); 151 | memmove(buffer->data, buffer->data + consumption_size, buffer->size); 152 | return consumption_size; 153 | } 154 | 155 | void destroy_buffer(buffer_t* buffer) { 156 | if (buffer->data != NULL) free(buffer->data); 157 | free(buffer); 158 | } 159 | 160 | int write_buffer_to_fd(buffer_t* b, int fd) { 161 | return write(fd, b->data, b->size); 162 | } -------------------------------------------------------------------------------- /src/dialog.c: -------------------------------------------------------------------------------- 1 | #include "die.h" 2 | #include "debug.h" 3 | #include "stringutil.h" 4 | #include "plist.h" 5 | #include "buffer.h" 6 | #include "process_name.h" 7 | #include "mode.h" 8 | #include "system_function_overrides.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | buffer_t* input_buffer = NULL; 20 | pthread_mutex_t input_mutex = PTHREAD_MUTEX_INITIALIZER; 21 | 22 | static char prompt[128]; 23 | pthread_mutex_t prompt_mutex = PTHREAD_MUTEX_INITIALIZER; 24 | 25 | char* create_prompt_copy() { 26 | pthread_mutex_lock(&prompt_mutex); 27 | char* prompt_copy = malloc(strlen(prompt) + 1); 28 | strcpy(prompt_copy, prompt); 29 | pthread_mutex_unlock(&prompt_mutex); 30 | return prompt_copy; 31 | } 32 | 33 | buffer_t* get_input_buffer() { 34 | if (input_buffer == NULL) input_buffer = create_buffer(); 35 | return input_buffer; 36 | } 37 | 38 | CFDictionaryRef create_input_dictionary() { 39 | 40 | CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(kCFAllocatorDefault, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 41 | if (parameters == NULL) die("failed to allocate dict for dialog parameters"); 42 | 43 | CFStringRef cf_title_key = CFSTR("title"); 44 | char* process_name = create_process_name(); 45 | CFStringRef cf_title = cstr_2_cfstr(process_name); 46 | CFDictionaryAddValue(parameters, cf_title_key, cf_title); 47 | free(process_name); 48 | CFRelease(cf_title); 49 | 50 | char* prompt_copy = create_prompt_copy(); 51 | CFStringRef dialog_prompt = cstr_2_cfstr((strlen(prompt) == 0) ? "The processing is requesting input:" : prompt); 52 | free(prompt_copy); 53 | CFDictionaryAddValue(parameters, CFSTR("prompt"), dialog_prompt); 54 | CFRelease(dialog_prompt); 55 | 56 | CFDictionaryAddValue(parameters, CFSTR("string"), CFSTR("")); 57 | 58 | CFDictionaryAddValue(parameters, CFSTR("button1"), CFSTR("Send")); 59 | CFDictionaryAddValue(parameters, CFSTR("button2"), CFSTR("Send EOF")); 60 | 61 | return parameters; 62 | } 63 | 64 | char * get_path() { 65 | 66 | char *path = getenv("DIALOG"); 67 | if (path == NULL) 68 | die("Cannot execute tm_dialog, DIALOG is empty or not set"); 69 | 70 | return path; 71 | } 72 | 73 | bool use_secure_nib() { 74 | pthread_mutex_lock(&prompt_mutex); 75 | bool should = (strcasestr(prompt, "password") != NULL); 76 | pthread_mutex_unlock(&prompt_mutex); 77 | return should; 78 | } 79 | 80 | char* get_nib() { 81 | return (use_secure_nib()) ? "RequestSecureString" : "RequestString"; 82 | } 83 | 84 | int get_echo_fd() { 85 | char *echo_fd = getenv("TM_INTERACTIVE_INPUT_ECHO_FD"); 86 | return (echo_fd == NULL) ? STDERR_FILENO : atoi(echo_fd); 87 | } 88 | 89 | CFStringRef get_return_argument_from_output_plist(CFPropertyListRef plist) { 90 | 91 | if (CFGetTypeID(plist) != CFDictionaryGetTypeID()) 92 | die("root element of plist is not dictionary"); 93 | 94 | CFDictionaryRef results; 95 | CFStringRef results_key = CFSTR("result"); 96 | 97 | bool has_results = CFDictionaryGetValueIfPresent(plist, results_key, (void *)&results); 98 | 99 | if (!has_results) { 100 | D("plist has no result key, so returning nothing\n"); 101 | return NULL; 102 | } 103 | 104 | if (CFGetTypeID(results) != CFDictionaryGetTypeID()) 105 | die("results entry of output is not a dictionary"); 106 | 107 | CFStringRef return_argument_key = CFSTR("returnArgument"); 108 | CFStringRef return_argument; 109 | 110 | if (CFDictionaryGetValueIfPresent(results, return_argument_key, (void *)&return_argument)) { 111 | if (CFGetTypeID(return_argument) != CFStringGetTypeID()) 112 | die("return value entry in results entry of output is not a string"); 113 | } else { 114 | return_argument = CFSTR(""); 115 | } 116 | 117 | return return_argument; 118 | } 119 | 120 | void open_tm_dialog(int in[], int out[]) { 121 | 122 | enum {R,W,N}; 123 | 124 | close(0); 125 | dup(in[R]); 126 | close(in[0]); 127 | close(in[1]); 128 | close(1); 129 | dup(out[W]); 130 | close(out[0]); 131 | close(out[1]); 132 | 133 | // Prevent tm_dialog from using our read() implementation 134 | unsetenv("DYLD_INSERT_LIBRARIES"); 135 | unsetenv("DYLD_FORCE_FLAT_NAMESPACE"); 136 | 137 | if (execl(get_path(), get_path(), "-m", get_nib(), NULL) < 0) 138 | die("execl() failed, %s", strerror(errno)); 139 | } 140 | 141 | buffer_t* create_user_input_from_output(buffer_t* output) { 142 | 143 | CFPropertyListRef plist = create_plist_from_buffer(output); 144 | buffer_t* input = NULL; 145 | 146 | CFStringRef return_argument = get_return_argument_from_output_plist(plist); 147 | if (return_argument != NULL) { 148 | input = create_buffer_from_cfstr(return_argument); 149 | add_to_buffer(input, "\n", 1); 150 | } 151 | 152 | CFRelease(plist); 153 | return input; 154 | } 155 | 156 | void get_input_from_user() { 157 | 158 | assert(input_buffer == NULL); 159 | 160 | // We do this now so we hit any errors before we attempt a fork. 161 | CFDictionaryRef parameters = create_input_dictionary(); 162 | buffer_t* parameters_buffer = create_buffer_from_dictionary_as_xml(parameters); 163 | CFRelease(parameters); 164 | 165 | enum {R,W,N}; 166 | int input[N],output[N]; 167 | 168 | if (pipe(input) < 0) die("failed to create input pipe for tm_dialog"); 169 | if (pipe(output) < 0) die("failed to create output pipe for tm_dialog"); 170 | 171 | sig_t previous_sigchld_handler = signal(SIGCHLD, SIG_DFL); 172 | 173 | int child = fork(); 174 | if (child < 0) die("failed to fork() for tm_dialog"); 175 | 176 | if (child == 0) open_tm_dialog(input, output); 177 | 178 | // These aren't used. 179 | close(input[R]); 180 | close(output[W]); 181 | 182 | // Write our parameters to tm_dialog's input 183 | size_t bytes_written = write_buffer_to_fd(parameters_buffer, input[W]); 184 | if (bytes_written < get_buffer_size(parameters_buffer)) die("failed to write all of parameter input to tm_dialog"); 185 | close(input[W]); 186 | destroy_buffer(parameters_buffer); 187 | 188 | // Read all of tm_dialog's output 189 | D("about to consume tm_dialog's output\n"); 190 | buffer_t* output_buffer = create_buffer_from_file_descriptor(output[R]); 191 | close(output[R]); 192 | 193 | // Wait for tm_dialog to finish 194 | int return_code; 195 | 196 | D("about to wait ...\n"); 197 | int waitError = wait(&return_code); 198 | if(waitError == -1) 199 | die("tm_dialog wait() failed: %s\n", strerror(errno)); 200 | if (WEXITSTATUS(return_code) != 0) 201 | die("tm_dialog returned with code %d, output ... \n%s", WEXITSTATUS(return_code), create_cstr_from_buffer(output_buffer)); 202 | 203 | signal(SIGCHLD, previous_sigchld_handler); 204 | 205 | input_buffer = create_user_input_from_output(output_buffer); 206 | destroy_buffer(output_buffer); 207 | } 208 | 209 | ssize_t tm_dialog_read(void *buffer, size_t buffer_length) { 210 | size_t consumed; 211 | 212 | pthread_mutex_lock(&input_mutex); 213 | if (input_buffer == NULL) { 214 | D("input_buffer == NULL, getting input from user\n"); 215 | get_input_from_user(); 216 | 217 | 218 | if (tm_interactive_input_is_in_echo_mode() && input_buffer != NULL) { 219 | int echo_fd = get_echo_fd(); 220 | if (use_secure_nib()) { 221 | locale_t l = newlocale(LC_CTYPE_MASK, "", NULL); 222 | char const* str = get_buffer_data(input_buffer); 223 | int i, len = get_buffer_size(input_buffer); 224 | for(i = 0; i < len - 1; i += mblen_l(str + i, len - i, l)) 225 | { 226 | system_write("write", echo_fd, "*", 1); 227 | if(mblen_l(str + i, len - i, l) <= 0) // encoding error 228 | break; 229 | } 230 | system_write("write", echo_fd, "\n", 1); 231 | freelocale(l); 232 | } else { 233 | write_buffer_to_fd(input_buffer, echo_fd); 234 | } 235 | } 236 | } 237 | 238 | if (input_buffer == NULL) { 239 | D("input_buffer still == NULL, user entered nothing\n"); 240 | consumed = 0; 241 | } else { 242 | D("reading %d into read buffer from input buffer\n", (int)buffer_length); 243 | consumed = consume_from_head_of_buffer(input_buffer, buffer, buffer_length); 244 | if (get_buffer_size(input_buffer) == 0) { 245 | destroy_buffer(input_buffer); 246 | input_buffer = NULL; 247 | } 248 | } 249 | 250 | pthread_mutex_unlock(&input_mutex); 251 | return consumed; 252 | } 253 | 254 | void capture_for_prompt(const void *buffer, size_t buffer_length) { 255 | char const* cbuffer = buffer; 256 | D("buffer_length = %d\n", (int)buffer_length); 257 | 258 | // First skip trailing whitespace (including newlines) 259 | char const* to = cbuffer + buffer_length; 260 | while(to != cbuffer && isspace(to[-1])) 261 | --to; 262 | 263 | // Second search back for the begin-of-(last)-line 264 | char const* from = to; 265 | while(from != cbuffer && from[-1] != '\n') 266 | --from; 267 | 268 | // If we end with an empty string, do nothing (we probably have a prompt from a previous write) 269 | if(from == to) 270 | return; 271 | 272 | // Third, truncate line (by cutting head) if larger than prompt capacity 273 | if(to - from > sizeof(prompt)-1) 274 | from = to - (sizeof(prompt)-1); 275 | 276 | pthread_mutex_lock(&prompt_mutex); 277 | strncpy(prompt, from, to - from); 278 | prompt[to - from] = '\0'; 279 | D("copied %i bytes to prompt\n", ((int)(to - from))); 280 | pthread_mutex_unlock(&prompt_mutex); 281 | 282 | D("prompt = '%s'\n", prompt); 283 | } 284 | -------------------------------------------------------------------------------- /src/system_function_overrides.c: -------------------------------------------------------------------------------- 1 | #include "system_function_overrides.h" 2 | #include "dialog.h" 3 | #include "mode.h" 4 | #include "die.h" 5 | #include "debug.h" 6 | #include "stdin_fd_tracker.h" 7 | #include "textmate.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static int initialised = 0; 20 | 21 | pthread_key_t override_in_use_flag_tls_key = 0; 22 | 23 | void __attribute__ ((constructor)) tls_init(void) { 24 | int e = pthread_key_create(&override_in_use_flag_tls_key, NULL); 25 | if (e != 0) { 26 | die("tls_init() failed: %s", strerror(e)); 27 | } 28 | initialised = 1; 29 | } 30 | 31 | #define INITIALISED_AND_NOT_IN_OVERRIDE() (initialised != 0 && (pthread_getspecific((override_in_use_flag_tls_key)) == NULL)) 32 | #define MARK_IN_OVERRIDE() pthread_setspecific((override_in_use_flag_tls_key), (void *)1) 33 | #define UNMARK_IN_OVERRIDE() pthread_setspecific((override_in_use_flag_tls_key) ,NULL) 34 | 35 | ssize_t read_override(char* system_symbol, int d, void *buffer, size_t buffer_length) { 36 | // Only interested in STDIN 37 | if (!tm_interactive_input_is_active() || !stdin_fd_tracker_is_stdin(d) || !fd_is_owned_by_tm(d)) 38 | return system_read(system_symbol, d, buffer, buffer_length); 39 | 40 | // It doesn't make sense to invoke tm_dialog if the caller wanted a non blocking read 41 | int oldFlags = fcntl(d, F_GETFL); 42 | if (oldFlags & O_NONBLOCK) return system_read(system_symbol, d, buffer, buffer_length); 43 | 44 | if (tm_interactive_input_is_in_always_mode()) { 45 | return tm_dialog_read(buffer, buffer_length); 46 | } else { 47 | fcntl(d, F_SETFL, oldFlags | O_NONBLOCK); 48 | ssize_t bytes_read = system_read(system_symbol, d, buffer, buffer_length); 49 | fcntl(d, F_SETFL, oldFlags); 50 | 51 | /* 52 | If reading from stdin produced an error, then we just return the result 53 | of the syscall (previously we died fatally). Except when the error is EAGAIN. 54 | Processes running under TM may have their stdin closed, and that will cause 55 | EAGAIN which in our context is not really an error. 56 | */ 57 | 58 | D("syscall returned %d bytes\n", (int)bytes_read); 59 | 60 | static bool did_see_data = false, did_see_eof = false; 61 | 62 | if(bytes_read > 0) 63 | { 64 | did_see_data = true; 65 | return bytes_read; 66 | } 67 | 68 | if(bytes_read == 0) 69 | { 70 | if(!did_see_data || did_see_eof) 71 | return tm_dialog_read(buffer, buffer_length); 72 | 73 | did_see_eof = true; 74 | } 75 | 76 | if(bytes_read == -1 && errno == EAGAIN) 77 | return tm_dialog_read(buffer, buffer_length); 78 | 79 | return bytes_read; 80 | } 81 | } 82 | 83 | ssize_t read_override_wrapper(char* system_symbol, int d, void *buffer, size_t buffer_length) { 84 | if (INITIALISED_AND_NOT_IN_OVERRIDE()) { 85 | MARK_IN_OVERRIDE(); 86 | ssize_t r = read_override(system_symbol, d, buffer, buffer_length); 87 | UNMARK_IN_OVERRIDE(); 88 | return r; 89 | } else { 90 | return system_read(system_symbol, d, buffer, buffer_length); 91 | } 92 | } 93 | 94 | ssize_t system_read(char *symbol, int d, void *buffer, size_t buffer_length) { 95 | int (*read_impl)(int, const void*, size_t) = dlsym(RTLD_NEXT, symbol); 96 | return read_impl(d, buffer, buffer_length); 97 | } 98 | 99 | ssize_t read(int d, void *buffer, size_t buffer_length) { 100 | return read_override_wrapper("read", d, buffer, buffer_length); 101 | } 102 | 103 | ssize_t read_nocancel(int d, void *buffer, size_t buffer_length) { 104 | return read_override_wrapper("read$NOCANCEL", d, buffer, buffer_length); 105 | } 106 | 107 | ssize_t read_unix2003(int d, void *buffer, size_t buffer_length) { 108 | return read_override_wrapper("read$UNIX2003", d, buffer, buffer_length); 109 | } 110 | 111 | ssize_t read_nocancel_unix2003(int d, void *buffer, size_t buffer_length) { 112 | return read_override_wrapper("read$NOCANCEL$UNIX2003", d, buffer, buffer_length); 113 | } 114 | 115 | ssize_t write_override(char *system_symbol, int d, const void *buffer, size_t buffer_length) { 116 | if (tm_interactive_input_is_active() && (d == STDOUT_FILENO || d == STDERR_FILENO)) { 117 | capture_for_prompt(buffer, buffer_length); 118 | } 119 | return system_write(system_symbol, d, buffer, buffer_length); 120 | } 121 | 122 | ssize_t write_override_wrapper(char *system_symbol, int d, const void *buffer, size_t buffer_length) { 123 | if (INITIALISED_AND_NOT_IN_OVERRIDE()) { 124 | MARK_IN_OVERRIDE(); 125 | ssize_t r = write_override(system_symbol, d, buffer, buffer_length); 126 | UNMARK_IN_OVERRIDE(); 127 | return r; 128 | } else { 129 | return system_write(system_symbol, d, buffer, buffer_length); 130 | } 131 | } 132 | ssize_t system_write(char *symbol, int d, const void *buffer, size_t buffer_length) { 133 | int (*write_impl)(int, const void*, size_t) = dlsym(RTLD_NEXT, symbol); 134 | return write_impl(d, buffer, buffer_length); 135 | } 136 | 137 | ssize_t write(int d, const void *buffer, size_t buffer_length) { 138 | return write_override_wrapper("write", d, buffer, buffer_length); 139 | } 140 | 141 | ssize_t write_nocancel(int d, const void *buffer, size_t buffer_length) { 142 | return write_override_wrapper("write$NOCANCEL", d, buffer, buffer_length); 143 | } 144 | 145 | ssize_t write_unix2003(int d, const void *buffer, size_t buffer_length) { 146 | return write_override_wrapper("write$UNIX2003", d, buffer, buffer_length); 147 | } 148 | 149 | ssize_t write_nocancel_unix2003(int d, const void *buffer, size_t buffer_length) { 150 | return write_override_wrapper("write$NOCANCEL$UNIX2003", d, buffer, buffer_length); 151 | } 152 | 153 | int dup(int orig) { 154 | int (*system_dup)(int) = dlsym(RTLD_NEXT, "dup"); 155 | 156 | if (INITIALISED_AND_NOT_IN_OVERRIDE()) { 157 | MARK_IN_OVERRIDE(); 158 | int dup = system_dup(orig); 159 | if (tm_interactive_input_is_active()) stdin_fd_tracker_did_dup(orig, dup); 160 | UNMARK_IN_OVERRIDE(); 161 | return dup; 162 | } else { 163 | return system_dup(orig); 164 | } 165 | } 166 | 167 | int close(int fd) { 168 | int (*system_close)(int) = dlsym(RTLD_NEXT, "close"); 169 | 170 | if (INITIALISED_AND_NOT_IN_OVERRIDE()) { 171 | MARK_IN_OVERRIDE(); 172 | int res = system_close(fd); 173 | if (tm_interactive_input_is_active()) 174 | stdin_fd_tracker_did_close(fd); 175 | UNMARK_IN_OVERRIDE(); 176 | return res; 177 | } else { 178 | return system_close(fd); 179 | } 180 | } 181 | 182 | int system_select(char * symbol, int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 183 | int (*select_impl)(int, fd_set * __restrict, fd_set * __restrict, fd_set * __restrict, struct timeval * __restrict) = dlsym(RTLD_NEXT, symbol); 184 | return select_impl(nfds, readfds, writefds, errorfds, timeout); 185 | } 186 | 187 | FILE* open_log () 188 | { 189 | char* logfile; 190 | FILE* fp; 191 | 192 | asprintf(&logfile, "%s/Library/Logs/tm_interactive_read.log", getenv("HOME")); 193 | fp = fopen(logfile, "a"); 194 | free(logfile); 195 | 196 | return fp; 197 | } 198 | 199 | int select_override(char * system_symbol, int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 200 | int result; 201 | 202 | if (readfds == NULL) { 203 | result = system_select(system_symbol, nfds, readfds, writefds, errorfds, timeout); 204 | } else { 205 | fd_set orig_readfds; FD_ZERO(&orig_readfds); FD_COPY(readfds, &orig_readfds); 206 | struct timeval t = { }; 207 | 208 | if (t.tv_sec != 0 || t.tv_usec != 0) 209 | { 210 | FILE* logfile = open_log(); 211 | fprintf(logfile, "timeout had values: %ld, %d\n", t.tv_sec, t.tv_usec); 212 | fclose(logfile); 213 | 214 | t.tv_sec = 0; 215 | t.tv_usec = 0; 216 | } 217 | 218 | if (stdin_fd_tracker_count_stdins_in_fdset(nfds, readfds) > 0) 219 | timeout = &t; 220 | 221 | result = system_select(system_symbol, nfds, readfds, writefds, errorfds, timeout); 222 | if (result != -1) result += stdin_fd_tracker_augment_select_result(nfds, &orig_readfds, readfds); 223 | } 224 | 225 | return result; 226 | } 227 | 228 | int select_override_wrapper(char * system_symbol, int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 229 | if (INITIALISED_AND_NOT_IN_OVERRIDE()) { 230 | MARK_IN_OVERRIDE(); 231 | int r = select_override(system_symbol, nfds, readfds, writefds, errorfds, timeout); 232 | UNMARK_IN_OVERRIDE(); 233 | return r; 234 | } else { 235 | return system_select(system_symbol, nfds, readfds, writefds, errorfds, timeout); 236 | } 237 | } 238 | 239 | int select(int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 240 | return select_override_wrapper("select", nfds, readfds, writefds, errorfds, timeout); 241 | } 242 | 243 | int select_darwinextsn(int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 244 | return select_override_wrapper("select$DARWIN_EXTSN", nfds, readfds, writefds, errorfds, timeout); 245 | } 246 | 247 | int select_darwinextsn_nocancel(int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 248 | return select_override_wrapper("select$DARWIN_EXTSN$NOCANCEL", nfds, readfds, writefds, errorfds, timeout); 249 | } 250 | 251 | int select_nocancel(int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 252 | return select_override_wrapper("select$NOCANCEL", nfds, readfds, writefds, errorfds, timeout); 253 | } 254 | 255 | int select_unix2003(int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 256 | return select_override_wrapper("select$UNIX2003", nfds, readfds, writefds, errorfds, timeout); 257 | } 258 | 259 | int select_nocancel_unix2003(int nfds, fd_set * __restrict readfds, fd_set * __restrict writefds, fd_set * __restrict errorfds, struct timeval * __restrict timeout) { 260 | return select_override_wrapper("select$NOCANCEL$UNIX2003", nfds, readfds, writefds, errorfds, timeout); 261 | } --------------------------------------------------------------------------------