Make pty raw in adb shell when non-interactive.

The main goal here is fixing the line ending translation from \n to
\r\n, but we probably don't want any translation to happen.

Bug: http://b/19735063
Change-Id: I1d6d6c6b57cc741b046c2432cd864b344ce1f28a
diff --git a/adb/services.cpp b/adb/services.cpp
index b869479..8ce9f71 100644
--- a/adb/services.cpp
+++ b/adb/services.cpp
@@ -24,6 +24,11 @@
 #include <stdlib.h>
 #include <string.h>
 
+#if !ADB_HOST
+#include <pty.h>
+#include <termios.h>
+#endif
+
 #ifndef _WIN32
 #include <netdb.h>
 #include <netinet/in.h>
@@ -238,30 +243,14 @@
     }
 }
 
-static int create_subproc_pty(const char *cmd, const char *arg0, const char *arg1, pid_t *pid)
-{
+#if !ADB_HOST
+static int create_subproc_pty(const char* cmd, const char* arg0,
+                              const char* arg1, pid_t* pid) {
     D("create_subproc_pty(cmd=%s, arg0=%s, arg1=%s)\n", cmd, arg0, arg1);
-#if defined(_WIN32)
-    fprintf(stderr, "error: create_subproc_pty not implemented on Win32 (%s %s %s)\n", cmd, arg0, arg1);
-    return -1;
-#else
+    char pts_name[PATH_MAX];
     int ptm;
-
-    ptm = unix_open("/dev/ptmx", O_RDWR | O_CLOEXEC); // | O_NOCTTY);
-    if(ptm < 0){
-        printf("[ cannot open /dev/ptmx - %s ]\n",strerror(errno));
-        return -1;
-    }
-
-    char devname[64];
-    if(grantpt(ptm) || unlockpt(ptm) || ptsname_r(ptm, devname, sizeof(devname)) != 0) {
-        printf("[ trouble with /dev/ptmx - %s ]\n", strerror(errno));
-        adb_close(ptm);
-        return -1;
-    }
-
-    *pid = fork();
-    if(*pid < 0) {
+    *pid = forkpty(&ptm, pts_name, nullptr, nullptr);
+    if (*pid == -1) {
         printf("- fork failed: %s -\n", strerror(errno));
         adb_close(ptm);
         return -1;
@@ -270,12 +259,33 @@
     if (*pid == 0) {
         init_subproc_child();
 
-        int pts = unix_open(devname, O_RDWR | O_CLOEXEC);
-        if (pts < 0) {
-            fprintf(stderr, "child failed to open pseudo-term slave: %s\n", devname);
+        int pts = unix_open(pts_name, O_RDWR | O_CLOEXEC);
+        if (pts == -1) {
+            fprintf(stderr, "child failed to open pseudo-term slave %s: %s\n",
+                    pts_name, strerror(errno));
+            adb_close(ptm);
             exit(-1);
         }
 
+        // arg0 is "-c" in batch mode and "-" in interactive mode.
+        if (strcmp(arg0, "-c") == 0) {
+            termios tattr;
+            if (tcgetattr(pts, &tattr) == -1) {
+                fprintf(stderr, "tcgetattr failed: %s\n", strerror(errno));
+                adb_close(pts);
+                adb_close(ptm);
+                exit(-1);
+            }
+
+            cfmakeraw(&tattr);
+            if (tcsetattr(pts, TCSADRAIN, &tattr) == -1) {
+                fprintf(stderr, "tcsetattr failed: %s\n", strerror(errno));
+                adb_close(pts);
+                adb_close(ptm);
+                exit(-1);
+            }
+        }
+
         dup2(pts, STDIN_FILENO);
         dup2(pts, STDOUT_FILENO);
         dup2(pts, STDERR_FILENO);
@@ -283,15 +293,15 @@
         adb_close(pts);
         adb_close(ptm);
 
-        execl(cmd, cmd, arg0, arg1, NULL);
+        execl(cmd, cmd, arg0, arg1, nullptr);
         fprintf(stderr, "- exec '%s' failed: %s (%d) -\n",
                 cmd, strerror(errno), errno);
         exit(-1);
     } else {
         return ptm;
     }
-#endif /* !defined(_WIN32) */
 }
+#endif // !ADB_HOST
 
 static int create_subproc_raw(const char *cmd, const char *arg0, const char *arg1, pid_t *pid)
 {
diff --git a/adb/tests/test_adb.py b/adb/tests/test_adb.py
index 0ff87d2..4eccc8c 100755
--- a/adb/tests/test_adb.py
+++ b/adb/tests/test_adb.py
@@ -164,7 +164,8 @@
         return call_combined(self.adb_cmd + "shell " + cmd)
 
     def install(self, filename):
-        return call_checked(self.adb_cmd + "install {}".format(pipes.quote(filename)))
+        return call_checked(
+            self.adb_cmd + "install {}".format(pipes.quote(filename)))
 
     def push(self, local, remote):
         return call_checked(self.adb_cmd + "push {} {}".format(local, remote))
@@ -283,7 +284,7 @@
         result = adb.shell("sh -c 'echo hello; echo world'").splitlines()
         self.assertEqual(["", "world"], result)
         # If you really wanted "hello" and "world", here's what you'd do:
-        result = adb.shell("echo hello\;echo world").splitlines()
+        result = adb.shell(r"echo hello\;echo world").splitlines()
         self.assertEqual(["hello", "world"], result)
 
         # http://b/15479704
@@ -292,7 +293,7 @@
 
         # http://b/20564385
         self.assertEqual('t', adb.shell("FOO=a BAR=b echo t").strip())
-        self.assertEqual('123Linux', adb.shell("echo -n 123\;uname").strip())
+        self.assertEqual('123Linux', adb.shell(r"echo -n 123\;uname").strip())
 
     def test_install_argument_escaping(self):
         """Make sure that install argument escaping works."""
@@ -306,6 +307,13 @@
         tf = tempfile.NamedTemporaryFile("w", suffix="-Live Hold'em.apk")
         self.assertIn("-Live Hold'em.apk", adb.install(tf.name))
 
+    def test_line_endings(self):
+        """Ensure that line ending translation is not happening in the pty.
+
+        Bug: http://b/19735063
+        """
+        self.assertFalse(AdbWrapper().shell("uname").endswith("\r\n"))
+
 
 class AdbFile(unittest.TestCase):
     SCRATCH_DIR = "/data/local/tmp"