OSDN Git Service

Refactored pseudo-terminal code into pts.c
authorTan Chee Eng <me@tan-ce.com>
Fri, 30 Aug 2013 08:09:21 +0000 (16:09 +0800)
committerTan Chee Eng <me@tan-ce.com>
Fri, 30 Aug 2013 08:09:21 +0000 (16:09 +0800)
Superuser/jni/Android.mk
Superuser/jni/su/daemon.c
Superuser/jni/su/pts.c [new file with mode: 0644]
Superuser/jni/su/pts.h [new file with mode: 0644]

index 41542b9..4f2a24a 100644 (file)
@@ -6,7 +6,7 @@ LOCAL_MODULE := su
 LOCAL_LDFLAGS := -static
 LOCAL_STATIC_LIBRARIES := sqlite3
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/sqlite3
-LOCAL_SRC_FILES := su/su.c su/activity.c su/db.c su/utils.c su/daemon.c
+LOCAL_SRC_FILES := su/su.c su/activity.c su/db.c su/utils.c su/daemon.c su/pts.c
 include $(BUILD_EXECUTABLE)
 
 
index 0443507..0c76b64 100644 (file)
@@ -21,7 +21,6 @@
 #include <sys/wait.h>
 #include <sys/select.h>
 #include <sys/time.h>
-#include <sys/ioctl.h>
 #include <unistd.h>
 #include <limits.h>
 #include <fcntl.h>
 #include <stdarg.h>
 #include <sys/types.h>
 #include <pthread.h>
-#include <termios.h>
 #include <signal.h>
 #include <string.h>
 
 #include "su.h"
 #include "utils.h"
+#include "pts.h"
 
 int is_daemon = 0;
 int daemon_from_uid = 0;
@@ -382,71 +381,10 @@ err:
     return -1;
 }
 
-/**
- * pts_open
- *
- * open a pts device and returns the name of the slave tty device.
- *
- * arguments
- * slave_name       the name of the slave device
- * slave_name_size  the size of the buffer passed via slave_name
- *
- * return values
- * on failure either -2 or -1 (errno set) is returned.
- * on success, the file descriptor of the master device is returned.
- */
-static int pts_open(char *slave_name, size_t slave_name_size) {
-    int fdm;
-    char *sn_tmp;
-
-    // Open master ptmx device
-    fdm = open("/dev/ptmx", O_RDWR);
-    if (fdm == -1) return -1;
-
-    // Get the slave name
-    sn_tmp = ptsname(fdm);
-    if (!sn_tmp) {
-        close(fdm);
-        return -2;
-    }
-
-    strncpy(slave_name, sn_tmp, slave_name_size);
-    slave_name[slave_name_size - 1] = '\0';
-
-    // Grant, then unlock
-    if (grantpt(fdm) == -1) {
-        close(fdm);
-        return -1;
-    }
-    if (unlockpt(fdm) == -1) {
-        close(fdm);
-        return -1;
-    }
-
-    return fdm;
-}
-
-static struct termios old_termios;
-static int stdin_is_raw = 0;
 // List of signals which cause process termination
 static int quit_signals[] = { SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
 
-/**
- * Restore termios on stdin to the state it was before
- * set_stdin_raw() was called. Does nothing if set_stdin_raw()
- * was never called
- */
-static void restore_stdin(void) {
-    if (!stdin_is_raw) return;
-
-    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_termios) < 0) {
-        PLOGE("restore_stdin");
-    }
-
-    stdin_is_raw = 0;
-}
-
-static void restore_stdin_handler(int sig) {
+static void sighandler(int sig) {
     restore_stdin();
 
     // Assume we'll only be called before death
@@ -474,37 +412,11 @@ static void restore_stdin_handler(int sig) {
 }
 
 /**
- * Set stdin to raw mode
+ * Setup signal handlers trap signals which should result in program termination
+ * so that we can restore the terminal to its normal state and retrieve the 
+ * return code.
  */
-static void set_stdin_raw(void) {
-    struct termios new_termios;
-
-    // Save the current stdin termios
-    if (tcgetattr(STDIN_FILENO, &old_termios) < 0) {
-        PLOGE("set_stdin_raw: tcgetattr");
-        // Don't terminate - the connectoin could still be useful
-        return;
-    }
-
-    // Start from the current settings
-    new_termios = old_termios;
-
-    // Make the terminal like an SSH or telnet client
-    new_termios.c_iflag |= IGNPAR;
-    new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
-    new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
-    new_termios.c_oflag &= ~OPOST;
-    new_termios.c_cc[VMIN] = 1;
-    new_termios.c_cc[VTIME] = 0;
-
-    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
-        PLOGE("set_stdin_raw: tcsetattr");
-        return;
-    }
-
-    stdin_is_raw = 1;
-
-    // Make sure we restore the terminal in the event of sudden termination
+static void setup_sighandlers(void) {
     struct sigaction act;
     int i;
 
@@ -513,7 +425,7 @@ static void set_stdin_raw(void) {
     // If they are, we'll need to modify this code to save the previous handler and
     // call it after we restore stdin to its previous state.
     memset(&act, '\0', sizeof(act));
-    act.sa_handler = &restore_stdin_handler;
+    act.sa_handler = &sighandler;
     for (i = 0; quit_signals[i]; i++) {
         if (sigaction(quit_signals[i], &act, NULL) < 0) {
             PLOGE("Error installing signal handler");
@@ -522,75 +434,6 @@ static void set_stdin_raw(void) {
     }
 }
 
-volatile static int closing_time = 0;
-
-/**
- * Wait for a SIGWINCH to be receive, then update the terminal size when it is
- */
-static void *watch_sigwinch(void *data) {
-    sigset_t winch;
-    int sig;
-    int input = ((int *)data)[0];
-    int output = ((int *)data)[1];
-
-    sigemptyset(&winch);
-    sigaddset(&winch, SIGWINCH);
-
-    do {
-        // Wait for a SIGWINCH
-        sigwait(&winch, &sig);
-
-        if (closing_time) break;
-
-        // Get the new terminal size
-        struct winsize w;
-        if (ioctl(input, TIOCGWINSZ, &w) == -1) {
-            PLOGE("watch_sigwinch: unable to get terminal size");
-            continue;
-        }
-
-        // Set the new terminal size
-        if (ioctl(output, TIOCSWINSZ, &w) == -1) {
-            PLOGE("watch_sigwinch: unable to set terminal size");
-        }
-
-    } while (1);
-
-    free(data);
-    return NULL;
-}
-
-static void watch_sigwinch_async(int input, int output) {
-    pthread_t watcher;
-    int *files = (int *) malloc(sizeof(int) * 2);
-    if (files == NULL) {
-        LOGE("unable to watch_sigwinch_async");
-        // Not fatal, just that the screen won't resize
-        return;
-    }
-
-    // Block SIGWINCH so sigwait can later receive it
-    sigset_t winch;
-    sigemptyset(&winch);
-    sigaddset(&winch, SIGWINCH);
-    sigprocmask(SIG_BLOCK, &winch, NULL);
-
-    // Initialize some variables, then start the thread
-    closing_time = 0;
-    files[0] = input;
-    files[1] = output;
-    pthread_create(&watcher, NULL, &watch_sigwinch, files);
-
-    // Set the initial terminal size
-    raise(SIGWINCH);
-}
-
-static void watch_sigwinch_cleanup(void) {
-    // Make sure the thread terminates
-    closing_time = 1;
-    raise(SIGWINCH);
-}
-
 int connect_daemon(int argc, char *argv[]) {
     char errfile[PATH_MAX];
     char outfile[PATH_MAX];
@@ -684,6 +527,7 @@ int connect_daemon(int argc, char *argv[]) {
     // otherwise ncurses apps are going to misbehave
     if (isatty(STDIN_FILENO)) {
         set_stdin_raw();
+        setup_sighandlers();
     }
 
     // Forward SIGWINCH
diff --git a/Superuser/jni/su/pts.c b/Superuser/jni/su/pts.c
new file mode 100644 (file)
index 0000000..7adb10c
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2013, Tan Chee Eng (@tan-ce)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /*
+ * pts.c
+ *
+ * Manages the pseudo-terminal driver on Linux/Android and provides some
+ * helper functions to handle raw input mode and terminal window resizing
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <termios.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "pts.h"
+
+/**
+ * pts_open
+ *
+ * Opens a pts device and returns the name of the slave tty device.
+ *
+ * Arguments
+ * slave_name       the name of the slave device
+ * slave_name_size  the size of the buffer passed via slave_name
+ *
+ * Return Values
+ * on failure either -2 or -1 (errno set) is returned.
+ * on success, the file descriptor of the master device is returned.
+ */
+int pts_open(char *slave_name, size_t slave_name_size) {
+    int fdm;
+    char *sn_tmp;
+
+    // Open master ptmx device
+    fdm = open("/dev/ptmx", O_RDWR);
+    if (fdm == -1) return -1;
+
+    // Get the slave name
+    sn_tmp = ptsname(fdm);
+    if (!sn_tmp) {
+        close(fdm);
+        return -2;
+    }
+
+    strncpy(slave_name, sn_tmp, slave_name_size);
+    slave_name[slave_name_size - 1] = '\0';
+
+    // Grant, then unlock
+    if (grantpt(fdm) == -1) {
+        close(fdm);
+        return -1;
+    }
+    if (unlockpt(fdm) == -1) {
+        close(fdm);
+        return -1;
+    }
+
+    return fdm;
+}
+
+// Stores the previous termios of stdin
+static struct termios old_stdin;
+static int stdin_is_raw = 0;
+
+/**
+ * set_stdin_raw
+ *
+ * Changes stdin to raw unbuffered mode, disables echo, 
+ * auto carriage return, etc.
+ *
+ * Return Value
+ * on failure -1, and errno is set
+ * on success 0
+ */
+int set_stdin_raw(void) {
+    struct termios new_termios;
+
+    // Save the current stdin termios
+    if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
+        return -1;
+    }
+
+    // Start from the current settings
+    new_termios = old_stdin;
+
+    // Make the terminal like an SSH or telnet client
+    new_termios.c_iflag |= IGNPAR;
+    new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
+    new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
+    new_termios.c_oflag &= ~OPOST;
+    new_termios.c_cc[VMIN] = 1;
+    new_termios.c_cc[VTIME] = 0;
+
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
+        return -1;
+    }
+
+    stdin_is_raw = 1;
+
+    return 0;
+}
+
+/**
+ * restore_stdin
+ *
+ * Restore termios on stdin to the state it was before
+ * set_stdin_raw() was called. If set_stdin_raw() was
+ * never called, does nothing and doesn't return an error.
+ *
+ * This function is async-safe.
+ *
+ * Return Value
+ * on failure, -1 and errno is set
+ * on success, 0
+ */
+int restore_stdin(void) {
+    if (!stdin_is_raw) return 0;
+
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
+        return -1;
+    }
+
+    stdin_is_raw = 0;
+
+    return 0;
+}
+
+// Flag indicating whether the sigwinch watcher should terminate.
+volatile static int closing_time = 0;
+
+/**
+ * Thread process. Wait for a SIGWINCH to be received, then update 
+ * the terminal size.
+ */
+static void *watch_sigwinch(void *data) {
+    sigset_t winch;
+    int sig;
+    int master = ((int *)data)[0];
+    int slave = ((int *)data)[1];
+
+    sigemptyset(&winch);
+    sigaddset(&winch, SIGWINCH);
+
+    do {
+        // Wait for a SIGWINCH
+        sigwait(&winch, &sig);
+
+        if (closing_time) break;
+
+        // Get the new terminal size
+        struct winsize w;
+        if (ioctl(master, TIOCGWINSZ, &w) == -1) {
+            continue;
+        }
+
+        // Set the new terminal size
+        ioctl(slave, TIOCSWINSZ, &w);
+
+    } while (1);
+
+    free(data);
+    return NULL;
+}
+
+/**
+ * watch_sigwinch_async
+ *
+ * After calling this function, if the application receives
+ * SIGWINCH, the terminal window size will be read from 
+ * "input" and set on "output".
+ *
+ * NOTE: This function blocks SIGWINCH and spawns a thread.
+ *
+ * Arguments
+ * master   A file descriptor of the TTY window size to follow
+ * slave    A file descriptor of the TTY window size which is
+ *          to be set on SIGWINCH
+ *
+ * Return Value
+ * on failure, -1 and errno will be set. In this case, no
+ *      thread has been spawned and SIGWINCH will not be 
+ *      blocked.
+ * on success, 0
+ */
+int watch_sigwinch_async(int master, int slave) {
+    pthread_t watcher;
+    int *files = (int *) malloc(sizeof(int) * 2);
+    if (files == NULL) {
+        return -1;
+    }
+
+    // Block SIGWINCH so sigwait can later receive it
+    sigset_t winch;
+    sigemptyset(&winch);
+    sigaddset(&winch, SIGWINCH);
+    if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
+        free(files);
+        return -1;
+    }
+
+    // Initialize some variables, then start the thread
+    closing_time = 0;
+    files[0] = master;
+    files[1] = slave;
+    int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
+    if (ret != 0) {
+        free(files);
+        errno = ret;
+        return -1;
+    }
+
+    // Set the initial terminal size
+    raise(SIGWINCH);
+}
+
+/**
+ * watch_sigwinch_cleanup
+ *
+ * Cause the SIGWINCH watcher thread to terminate
+ */
+void watch_sigwinch_cleanup(void) {
+    closing_time = 1;
+    raise(SIGWINCH);
+}
diff --git a/Superuser/jni/su/pts.h b/Superuser/jni/su/pts.h
new file mode 100644 (file)
index 0000000..73bd01b
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013, Tan Chee Eng (@tan-ce)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /*
+ * pts.h
+ *
+ * Manages the pseudo-terminal driver on Linux/Android and provides some
+ * helper functions to handle raw input mode and terminal window resizing
+ */
+
+#ifndef _PTS_H_
+#define _PTS_H_
+
+/**
+ * pts_open
+ *
+ * Opens a pts device and returns the name of the slave tty device.
+ *
+ * Arguments
+ * slave_name       the name of the slave device
+ * slave_name_size  the size of the buffer passed via slave_name
+ *
+ * Return Values
+ * on failure either -2 or -1 (errno set) is returned.
+ * on success, the file descriptor of the master device is returned.
+ */
+int pts_open(char *slave_name, size_t slave_name_size);
+
+/**
+ * set_stdin_raw
+ *
+ * Changes stdin to raw unbuffered mode, disables echo, 
+ * auto carriage return, etc.
+ *
+ * Return Value
+ * on failure -1, and errno is set
+ * on success 0
+ */
+int set_stdin_raw(void);
+
+/**
+ * restore_stdin
+ *
+ * Restore termios on stdin to the state it was before
+ * set_stdin_raw() was called. If set_stdin_raw() was
+ * never called, does nothing and doesn't return an error.
+ *
+ * This function is async-safe.
+ *
+ * Return Value
+ * on failure, -1 and errno is set
+ * on success, 0
+ */
+int restore_stdin(void);
+
+/**
+ * watch_sigwinch_async
+ *
+ * After calling this function, if the application receives
+ * SIGWINCH, the terminal window size will be read from 
+ * "input" and set on "output".
+ *
+ * NOTE: This function blocks SIGWINCH and spawns a thread.
+ *
+ * Arguments
+ * master   A file descriptor of the TTY window size to follow
+ * slave    A file descriptor of the TTY window size which is
+ *          to be set on SIGWINCH
+ *
+ * Return Value
+ * on failure, -1 and errno will be set. In this case, no
+ *      thread has been spawned and SIGWINCH will not be 
+ *      blocked.
+ * on success, 0
+ */
+int watch_sigwinch_async(int master, int slave);
+
+/**
+ * watch_sigwinch_cleanup
+ *
+ * Cause the SIGWINCH watcher thread to terminate
+ */
+void watch_sigwinch_cleanup(void);
+#endif