diff --git a/NOTES b/NOTES index cf8e337..a06893c 100644 --- a/NOTES +++ b/NOTES @@ -176,5 +176,25 @@ SIOCGIFCONF, just like ifconfig does, but it works fine with java.net.NetworkInterface. +June 20, 2015. + +At some points, rsync is only write()ing, and assumes that the other end +will receive it all. The other end does a little bit of write()ing of +its own, and then is happy to read() all it wants. So this written stuff +may sit in a buffer somewhere indefinitely. If that happens, an +infelicity in the design of SuperSU causes everything to wedge. + +Of course, this is only if you set the shell to /system/xbin/su as a way +of having root access for rsync. + +Anyways, I made a new program, "buffersu", which is just a +stdin/stdout-buffering wrapper for rsync() that is guaranteed to always +perform any read() that is possible at any time, no matter how many +write()s are outstanding. That seems to do the trick. + + +XXX - document rsync buffer and supersu integration +XXX - new release + XXX - if you remove it from the recent apps list, does it stop the service?? XXX - support password-based logins? diff --git a/doit b/doit index 8bc1558..4ff7844 100755 --- a/doit +++ b/doit @@ -2,6 +2,7 @@ ndk-build -j8 && mv libs/armeabi/scp libs/armeabi/libscp.so && mv libs/armeabi/sftp-server libs/armeabi/libsftp-server.so && mv libs/armeabi/rsync libs/armeabi/librsync.so && +mv libs/armeabi/buffersu libs/armeabi/libbuffersu.so && ant release && pbinst bin/SimpleSSHD-release.apk # cat bin/SimpleSSHD-release.apk | ssh roach 'cat > /sdcard/buh.apk; source .profile; am start --user 0 -d file:///sdcard/buh.apk' diff --git a/dropbear/dbutil.c b/dropbear/dbutil.c index 729b537..62379fd 100644 --- a/dropbear/dbutil.c +++ b/dropbear/dbutil.c @@ -587,7 +587,11 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { cmd = t; } else if (cmd && !strncmp(cmd, "rsync ", 6)) { char *t = malloc(strlen(cmd)+strlen(NDK_EXECUTABLES_PATH)+80); - sprintf(t, "%s/lib%s.so %s", NDK_EXECUTABLES_PATH, "rsync", + char *x = "rsync"; + if (conf_rsyncbuffer) { + x = "buffersu"; + } + sprintf(t, "%s/lib%s.so %s", NDK_EXECUTABLES_PATH, x, cmd+6); cmd = t; } diff --git a/jni/Android.mk b/jni/Android.mk index 10def87..70fe873 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -597,3 +597,17 @@ LOCAL_LDLIBS := LOCAL_LDFLAGS := -static include $(BUILD_EXECUTABLE) + + +# build separate sftp executable + +include $(CLEAR_VARS) + +LOCAL_CFLAGS := +LOCAL_MODULE := buffersu + +LOCAL_SRC_FILES := buffersu.c +# LOCAL_LDLIBS := +LOCAL_LDFLAGS := -static + +include $(BUILD_EXECUTABLE) diff --git a/jni/buffersu.c b/jni/buffersu.c new file mode 100644 index 0000000..c030528 --- /dev/null +++ b/jni/buffersu.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define WRAPPED_CMD "/data/data/org.galexander.sshd/lib/librsync.so" +#define WRAPPED_ARG0 "rsync" + + +static void +die(void) +{ + int status; + waitpid(-1, &status, WNOHANG); + exit(0); +} + +static volatile int child_dead = 0; +static volatile pid_t child_pid = 0; + +static void +sigchld(int sig) +{ + int status; + waitpid(-1, &status, WNOHANG); + /* don't exit, in case child has unprocessed output */ + child_dead = 1; +} + +static void +sigpipe(int sig) +{ + kill(child_pid, SIGPIPE); + die(); +} + +#define BLKSZ 1024 +struct block { + struct block *next; + struct block *prev; + char buf[BLKSZ]; + int ofs,len; +}; +struct buf { + struct block *head; /* read here */ + struct block *tail; /* write here */ +}; + +static int +buf_waiting(struct buf *b) +{ + return (b->head != NULL); +} + +static int +buf_read(struct buf *b, int fd) +{ + struct block *p; + char t[BLKSZ]; + int n; + + n = read(fd, t, BLKSZ); + if (n < 0) { + perror("read"); + die(); + } + if (n == 0) { + /* EOF */ + return 0; + } + p = malloc(sizeof *p); + memcpy(p->buf, t, BLKSZ); + p->next = NULL; + p->prev = b->tail; + if (b->tail) { + b->tail->next = p; + } + p->ofs = 0; + p->len = n; + b->tail = p; + if (!b->head) { + b->head = p; + } + return n; +} + +static void +buf_write(struct buf *b, int fd) +{ + struct block *p; + int n; + + if (!b->head) { + return; + } + p = b->head; + + n = write(fd, p->buf+p->ofs, p->len); + if (n <= 0) { + perror("write"); + return; + } + p->ofs += n; + p->len -= n; + if (p->len <= 0) { + if (p->next) { + b->head = p->next; + p->next->prev = NULL; + } else { + b->head = NULL; + b->tail = NULL; + } + free(p); + } +} + +int +main(int argc, char **argv) +{ + int p0[2], p1[2]; + pid_t pid; + pipe(p0); + pipe(p1); + if ((pid=fork())) { + /* parent */ + fd_set ifds, ofds; + int child_stdin, child_stdout; + struct buf buf0 = { 0 }; + struct buf buf1 = { 0 }; + int nfd = 2; + + close(p0[0]); + close(p1[1]); + + if (pid == -1) { + perror("fork"); + return -1; + } + child_pid = pid; + + signal(SIGPIPE, sigpipe); + signal(SIGCHLD, sigchld); + + child_stdin = p0[1]; + child_stdout = p1[0]; + if (child_stdin > nfd) nfd = child_stdin; + if (child_stdout > nfd) nfd = child_stdout; + nfd++; + while (1) { + int s; + FD_ZERO(&ifds); + FD_SET(0, &ifds); + FD_SET(child_stdout, &ifds); + FD_ZERO(&ofds); + if (buf_waiting(&buf1)) { + FD_SET(1, &ofds); + } else if (child_dead) { + die(); + } + if (!child_dead && buf_waiting(&buf0)) { + FD_SET(child_stdin, &ofds); + } + s = select(nfd, &ifds, &ofds, NULL, NULL); + if (s < 0) { + perror("select"); + } +#if 0 +fprintf(stderr, "select %d\n", s); +#define T(x) \ +if (FD_ISSET(0, &x##s)) fprintf(stderr, #x " 0\n"); \ +if (FD_ISSET(1, &x##s)) fprintf(stderr, #x " 1\n"); \ +if (FD_ISSET(child_stdin, &x##s)) fprintf(stderr, #x " child_stdin\n"); \ +if (FD_ISSET(child_stdout, &x##s)) fprintf(stderr, #x " child_stdout\n"); +T(ifd) +T(ofd) +#endif + if (FD_ISSET(0, &ifds)) { + buf_read(&buf0, 0); + } + if (FD_ISSET(child_stdout, &ifds)) { + if (!buf_read(&buf1, child_stdout)) { + child_dead = 1; + } + } + if (FD_ISSET(1, &ofds)) { + buf_write(&buf1, 1); + } + if (!child_dead && FD_ISSET(child_stdin, &ofds)) { + buf_write(&buf0, child_stdin); + } + } + die(); + } else { + /* child */ + char **child_argv; + close(0); + close(1); + dup2(p0[0], 0); + dup2(p1[1], 1); + close(p0[0]); + close(p0[1]); + close(p1[0]); + close(p1[1]); + child_argv = malloc((argc+1) * sizeof *child_argv); + memcpy(child_argv, argv, argc*sizeof *child_argv); + child_argv[0] = WRAPPED_ARG0; + child_argv[argc] = NULL; + execv(WRAPPED_CMD, child_argv); + perror("execv"); + return -1; + } + return 0; +} diff --git a/jni/config.h b/jni/config.h index 73a4e23..cfa8877 100644 --- a/jni/config.h +++ b/jni/config.h @@ -38,6 +38,7 @@ extern const char *conf_path; extern const char *conf_shell; extern const char *conf_home; const char *conf_path_file(const char *fn); +extern int conf_rsyncbuffer; #endif /* __CONFIG_H__ */ diff --git a/jni/interface.c b/jni/interface.c index f6a2590..0ae62f6 100644 --- a/jni/interface.c +++ b/jni/interface.c @@ -10,6 +10,7 @@ #include const char *conf_path = "", *conf_shell = "", *conf_home = ""; +int conf_rsyncbuffer = 0; /* NB - this will leak memory like crazy if called often.... */ const char * @@ -117,7 +118,8 @@ from_java_string(jobject s) JNIEXPORT void JNICALL Java_org_galexander_sshd_SimpleSSHDService_start_1sshd(JNIEnv *env_, jclass cl, - jint port, jobject jpath, jobject jshell, jobject jhome, jobject jextra) + jint port, jobject jpath, jobject jshell, jobject jhome, jobject jextra, + jint rsyncbuffer) { pid_t pid; const char *extra; @@ -129,6 +131,7 @@ Java_org_galexander_sshd_SimpleSSHDService_start_1sshd(JNIEnv *env_, conf_shell = from_java_string(jshell); conf_home = from_java_string(jhome); extra = from_java_string(jextra); + conf_rsyncbuffer = rsyncbuffer; pid = fork(); if (pid == 0) { diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 407b5a6..3cead97 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -20,6 +20,10 @@ android:title="Login Shell" android:summary="Location of sh binary" android:defaultValue="/system/bin/sh" /> +