OSDN Git Service

ext4_utils: Yet another MMC discard pain in the ass
authorSteve Kondik <shade@chemlab.org>
Sat, 16 Nov 2013 15:36:33 +0000 (07:36 -0800)
committerMichael Bestas <mikeioannina@gmail.com>
Sun, 1 Jan 2017 23:29:05 +0000 (01:29 +0200)
 * Secure discard on this device is painfully slow. Use regular
   discard until the issue is sorted out.

Change-Id: Ib8ccb12829385aa267253f772e243bef8850f455

ext4_utils: Add NO_SECURE_DISCARD boardconfig option

* In N CFLAGS in device tress are prohibited

Change-Id: Iea6a4145d1e460d61115b7d6f5a754996c7c9b28

14 files changed:
ext4_utils/Android.mk
ext4_utils/wipe.c
su/.gitignore [new file with mode: 0644]
su/LICENSE [new file with mode: 0644]
su/binder/appops-wrapper.cpp [new file with mode: 0644]
su/binder/pm-wrapper.c [new file with mode: 0644]
su/binder/pm-wrapper.h [new file with mode: 0644]
su/daemon.c [new file with mode: 0644]
su/pts.c [new file with mode: 0644]
su/pts.h [new file with mode: 0644]
su/su.h [new file with mode: 0644]
su/superuser.rc [new file with mode: 0644]
su/utils.c [new file with mode: 0644]
su/utils.h [new file with mode: 0644]

index a8362b2..9f86507 100644 (file)
@@ -80,6 +80,11 @@ LOCAL_SHARED_LIBRARIES := \
     libsparse \
     libz
 LOCAL_CFLAGS := -DREAL_UUID
+
+ifeq ($(BOARD_NO_SECURE_DISCARD),true)
+    LOCAL_CFLAGS += -DNO_SECURE_DISCARD
+endif
+
 include $(BUILD_SHARED_LIBRARY)
 
 
@@ -95,6 +100,11 @@ LOCAL_STATIC_LIBRARIES := \
     libsparse_static \
     libselinux \
     libbase
+
+ifeq ($(BOARD_NO_SECURE_DISCARD),true)
+    LOCAL_CFLAGS += -DNO_SECURE_DISCARD
+endif
+
 include $(BUILD_STATIC_LIBRARY)
 
 
index 5766632..002e021 100644 (file)
@@ -42,10 +42,12 @@ int wipe_block_device(int fd, s64 len)
                return 0;
        }
 
+#ifndef NO_SECURE_DISCARD
        range[0] = 0;
        range[1] = len;
        ret = ioctl(fd, BLKSECDISCARD, &range);
        if (ret < 0) {
+#endif
                range[0] = 0;
                range[1] = len;
                ret = ioctl(fd, BLKDISCARD, &range);
@@ -56,8 +58,9 @@ int wipe_block_device(int fd, s64 len)
                        warn("Wipe via secure discard failed, used discard instead\n");
                        return 0;
                }
+#ifndef NO_SECURE_DISCARD
        }
-
+#endif
        return 0;
 }
 
diff --git a/su/.gitignore b/su/.gitignore
new file mode 100644 (file)
index 0000000..8fbf940
--- /dev/null
@@ -0,0 +1,9 @@
+bin
+armeabi
+x86
+obj
+local.properties
+gen
+.DS_Store
+.settings
+libs
diff --git a/su/LICENSE b/su/LICENSE
new file mode 100644 (file)
index 0000000..0392aea
--- /dev/null
@@ -0,0 +1,13 @@
+Copyright 2013 Koushik Dutta (2013)
+
+   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.
\ No newline at end of file
diff --git a/su/binder/appops-wrapper.cpp b/su/binder/appops-wrapper.cpp
new file mode 100644 (file)
index 0000000..acc26b3
--- /dev/null
@@ -0,0 +1,35 @@
+#define LOG_TAG "su"
+
+#include <binder/AppOpsManager.h>
+#include <utils/Log.h>
+
+using namespace android;
+
+extern "C" {
+
+int appops_start_op_su(int uid, const char *pkgName) {
+    ALOGD("Checking whether app [uid:%d, pkgName: %s] is allowed to be root", uid, pkgName);
+    AppOpsManager *ops = new AppOpsManager();
+
+    int mode = ops->startOp(AppOpsManager::OP_SU, uid, String16(pkgName));
+
+    switch (mode) {
+        case AppOpsManager::MODE_ALLOWED:
+          ALOGD("Privilege elevation allowed by appops");
+          return 0;
+        default:
+          ALOGD("Privilege elevation denied by appops");
+          return 1;
+    }
+
+    delete ops;
+}
+
+void appops_finish_op_su(int uid, const char *pkgName) {
+    ALOGD("Finishing su operation for app [uid:%d, pkgName: %s]", uid, pkgName);
+    AppOpsManager *ops = new AppOpsManager();
+    ops->finishOp(AppOpsManager::OP_SU, uid, String16(pkgName));
+    delete ops;
+}
+
+}
diff --git a/su/binder/pm-wrapper.c b/su/binder/pm-wrapper.c
new file mode 100644 (file)
index 0000000..0f5a5bc
--- /dev/null
@@ -0,0 +1,50 @@
+#include "../utils.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <utils/Log.h>
+
+#define PACKAGE_LIST_PATH "/data/system/packages.list"
+#define PACKAGE_NAME_MAX_LEN (1<<16)
+
+/* Tries to resolve a package name from a uid via the packages list file.
+ *
+ * If there is no matching uid, it will return an empty string which can
+ * be resolved by appops in some cases (i.e. apps with uid = 0, uid = AID_SHELL).
+ *
+ * Since packages may share UID, this function will return the first present
+ * in packages.list.
+ */
+const char* resolve_package_name(int uid) {
+    char *packages = read_file(PACKAGE_LIST_PATH);
+
+    if (packages == NULL) {
+        goto notfound;
+    }
+
+    char *p = packages;
+    while (*p) {
+        char *line_end = strstr(p, "\n");
+        if (line_end == NULL)
+            break;
+
+        char *token;
+        char *pkgName = strtok_r(p, " ", &token);
+        if (pkgName != NULL) {
+            char *pkgUid = strtok_r(NULL, " ", &token);
+            if (pkgUid != NULL) {
+                char *endptr;
+                errno = 0;
+                int pkgUidInt = strtoul(pkgUid, &endptr, 10);
+                if ((errno == 0 && endptr != NULL && !(*endptr)) && pkgUidInt == uid)
+                    return strdup(pkgName);
+            }
+        }
+        p = ++line_end;
+    }
+    free(packages);
+
+notfound:
+    return "";
+}
diff --git a/su/binder/pm-wrapper.h b/su/binder/pm-wrapper.h
new file mode 100644 (file)
index 0000000..10c1da6
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef _HAS_PM_WRAPPER_H
+#define _HAS_PM_WRAPPER_H
+
+const char* resolve_package_name(int uid);
+
+#endif
diff --git a/su/daemon.c b/su/daemon.c
new file mode 100644 (file)
index 0000000..a09aad6
--- /dev/null
@@ -0,0 +1,677 @@
+/*
+** Copyright 2010, Adam Shanks (@ChainsDD)
+** Copyright 2008, Zinx Verituse (@zinxv)
+**
+** 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.
+*/
+
+#define _GNU_SOURCE /* for unshare() */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <pwd.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <sched.h>
+#include <termios.h>
+#include <signal.h>
+#include <string.h>
+#include <log/log.h>
+
+#include <cutils/multiuser.h>
+
+#include "su.h"
+#include "utils.h"
+#include "pts.h"
+
+int is_daemon = 0;
+int daemon_from_uid = 0;
+int daemon_from_pid = 0;
+
+// Constants for the atty bitfield
+#define ATTY_IN     1
+#define ATTY_OUT    2
+#define ATTY_ERR    4
+
+/*
+ * Receive a file descriptor from a Unix socket.
+ * Contributed by @mkasick
+ *
+ * Returns the file descriptor on success, or -1 if a file
+ * descriptor was not actually included in the message
+ *
+ * On error the function terminates by calling exit(-1)
+ */
+static int recv_fd(int sockfd) {
+    // Need to receive data from the message, otherwise don't care about it.
+    char iovbuf;
+
+    struct iovec iov = {
+        .iov_base = &iovbuf,
+        .iov_len  = 1,
+    };
+
+    char cmsgbuf[CMSG_SPACE(sizeof(int))];
+
+    struct msghdr msg = {
+        .msg_iov        = &iov,
+        .msg_iovlen     = 1,
+        .msg_control    = cmsgbuf,
+        .msg_controllen = sizeof(cmsgbuf),
+    };
+
+    if (recvmsg(sockfd, &msg, MSG_WAITALL) != 1) {
+        goto error;
+    }
+
+    // Was a control message actually sent?
+    switch (msg.msg_controllen) {
+    case 0:
+        // No, so the file descriptor was closed and won't be used.
+        return -1;
+    case sizeof(cmsgbuf):
+        // Yes, grab the file descriptor from it.
+        break;
+    default:
+        goto error;
+    }
+
+    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+    if (cmsg             == NULL                  ||
+        cmsg->cmsg_len   != CMSG_LEN(sizeof(int)) ||
+        cmsg->cmsg_level != SOL_SOCKET            ||
+        cmsg->cmsg_type  != SCM_RIGHTS) {
+error:
+        ALOGE("unable to read fd");
+        exit(-1);
+    }
+
+    return *(int *)CMSG_DATA(cmsg);
+}
+
+/*
+ * Send a file descriptor through a Unix socket.
+ * Contributed by @mkasick
+ *
+ * On error the function terminates by calling exit(-1)
+ *
+ * fd may be -1, in which case the dummy data is sent,
+ * but no control message with the FD is sent.
+ */
+static void send_fd(int sockfd, int fd) {
+    // Need to send some data in the message, this will do.
+    struct iovec iov = {
+        .iov_base = "",
+        .iov_len  = 1,
+    };
+
+    struct msghdr msg = {
+        .msg_iov        = &iov,
+        .msg_iovlen     = 1,
+    };
+
+    char cmsgbuf[CMSG_SPACE(sizeof(int))];
+
+    if (fd != -1) {
+        // Is the file descriptor actually open?
+        if (fcntl(fd, F_GETFD) == -1) {
+            if (errno != EBADF) {
+                goto error;
+            }
+            // It's closed, don't send a control message or sendmsg will EBADF.
+        } else {
+            // It's open, send the file descriptor in a control message.
+            msg.msg_control    = cmsgbuf;
+            msg.msg_controllen = sizeof(cmsgbuf);
+
+            struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+            cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
+            cmsg->cmsg_level = SOL_SOCKET;
+            cmsg->cmsg_type  = SCM_RIGHTS;
+
+            *(int *)CMSG_DATA(cmsg) = fd;
+        }
+    }
+
+    if (sendmsg(sockfd, &msg, 0) != 1) {
+error:
+        PLOGE("unable to send fd");
+        exit(-1);
+    }
+}
+
+static int read_int(int fd) {
+    int val;
+    int len = read(fd, &val, sizeof(int));
+    if (len != sizeof(int)) {
+        ALOGE("unable to read int: %d", len);
+        exit(-1);
+    }
+    return val;
+}
+
+static void write_int(int fd, int val) {
+    int written = write(fd, &val, sizeof(int));
+    if (written != sizeof(int)) {
+        PLOGE("unable to write int");
+        exit(-1);
+    }
+}
+
+static char* read_string(int fd) {
+    int len = read_int(fd);
+    if (len > PATH_MAX || len < 0) {
+        ALOGE("invalid string length %d", len);
+        exit(-1);
+    }
+    char* val = malloc(sizeof(char) * (len + 1));
+    if (val == NULL) {
+        ALOGE("unable to malloc string");
+        exit(-1);
+    }
+    val[len] = '\0';
+    int amount = read(fd, val, len);
+    if (amount != len) {
+        ALOGE("unable to read string");
+        exit(-1);
+    }
+    return val;
+}
+
+static void write_string(int fd, char* val) {
+    int len = strlen(val);
+    write_int(fd, len);
+    int written = write(fd, val, len);
+    if (written != len) {
+        PLOGE("unable to write string");
+        exit(-1);
+    }
+}
+
+static void mount_emulated_storage(int user_id) {
+    const char *emulated_source = getenv("EMULATED_STORAGE_SOURCE");
+    const char *emulated_target = getenv("EMULATED_STORAGE_TARGET");
+    const char* legacy = getenv("EXTERNAL_STORAGE");
+
+    if (!emulated_source || !emulated_target) {
+        // No emulated storage is present
+        return;
+    }
+
+    // Create a second private mount namespace for our process
+    if (unshare(CLONE_NEWNS) < 0) {
+        PLOGE("unshare");
+        return;
+    }
+
+    if (mount("rootfs", "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
+        PLOGE("mount rootfs as slave");
+        return;
+    }
+
+    // /mnt/shell/emulated -> /storage/emulated
+    if (mount(emulated_source, emulated_target, NULL, MS_BIND, NULL) < 0) {
+        PLOGE("mount emulated storage");
+    }
+
+    char target_user[PATH_MAX];
+    snprintf(target_user, PATH_MAX, "%s/%d", emulated_target, user_id);
+
+    // /mnt/shell/emulated/<user> -> /storage/emulated/legacy
+    if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) < 0) {
+        PLOGE("mount legacy path");
+    }
+}
+
+static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) {
+    if (-1 == dup2(outfd, STDOUT_FILENO)) {
+        PLOGE("dup2 child outfd");
+        exit(-1);
+    }
+
+    if (-1 == dup2(errfd, STDERR_FILENO)) {
+        PLOGE("dup2 child errfd");
+        exit(-1);
+    }
+
+    if (-1 == dup2(infd, STDIN_FILENO)) {
+        PLOGE("dup2 child infd");
+        exit(-1);
+    }
+
+    close(infd);
+    close(outfd);
+    close(errfd);
+
+    return su_main(argc, argv, 0);
+}
+
+static int daemon_accept(int fd) {
+    char mypath[PATH_MAX], remotepath[PATH_MAX];
+    int caller_is_self = 0;
+
+    is_daemon = 1;
+    int pid = read_int(fd);
+    int child_result;
+    ALOGD("remote pid: %d", pid);
+    char *pts_slave = read_string(fd);
+    ALOGD("remote pts_slave: %s", pts_slave);
+    daemon_from_pid = read_int(fd);
+    ALOGV("remote req pid: %d", daemon_from_pid);
+
+    struct ucred credentials;
+    socklen_t ucred_length = sizeof(struct ucred);
+    /* fill in the user data structure */
+    if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
+        ALOGE("could obtain credentials from unix domain socket");
+        exit(-1);
+    }
+
+    daemon_from_uid = credentials.uid;
+
+    int mount_storage = read_int(fd);
+    // The the FDs for each of the streams
+    int infd  = recv_fd(fd);
+    int outfd = recv_fd(fd);
+    int errfd = recv_fd(fd);
+
+    int argc = read_int(fd);
+    if (argc < 0 || argc > 512) {
+        ALOGE("unable to allocate args: %d", argc);
+        exit(-1);
+    }
+    ALOGV("remote args: %d", argc);
+    char** argv = (char**)malloc(sizeof(char*) * (argc + 1));
+    argv[argc] = NULL;
+    int i;
+    for (i = 0; i < argc; i++) {
+        argv[i] = read_string(fd);
+    }
+
+    // ack
+    write_int(fd, 1);
+
+    // Fork the child process. The fork has to happen before calling
+    // setsid() and opening the pseudo-terminal so that the parent
+    // is not affected
+    int child = fork();
+    if (child < 0) {
+        for (i = 0; i < argc; i++) {
+            free(argv[i]);
+        }
+        free(argv);
+
+        // fork failed, send a return code and bail out
+        PLOGE("unable to fork");
+        write(fd, &child, sizeof(int));
+        close(fd);
+        return child;
+    }
+
+    if (child != 0) {
+        for (i = 0; i < argc; i++) {
+            free(argv[i]);
+        }
+        free(argv);
+
+        // In parent, wait for the child to exit, and send the exit code
+        // across the wire.
+        int status, code;
+
+        free(pts_slave);
+
+        ALOGD("waiting for child exit");
+        if (waitpid(child, &status, 0) > 0) {
+            code = WEXITSTATUS(status);
+        }
+        else {
+            code = -1;
+        }
+
+        // Is the file descriptor actually open?
+        if (fcntl(fd, F_GETFD) == -1) {
+            if (errno != EBADF) {
+                goto error;
+            }
+        }
+
+        // Pass the return code back to the client
+        ALOGD("sending code");
+        if (write(fd, &code, sizeof(int)) != sizeof(int)) {
+            PLOGE("unable to write exit code");
+        }
+
+        close(fd);
+error:
+        ALOGD("child exited");
+        return code;
+    }
+
+    // We are in the child now
+    // Close the unix socket file descriptor
+    close (fd);
+
+    // Become session leader
+    if (setsid() == (pid_t) -1) {
+        PLOGE("setsid");
+    }
+
+    int ptsfd;
+    if (pts_slave[0]) {
+        // Opening the TTY has to occur after the
+        // fork() and setsid() so that it becomes
+        // our controlling TTY and not the daemon's
+        ptsfd = open(pts_slave, O_RDWR);
+        if (ptsfd == -1) {
+            PLOGE("open(pts_slave) daemon");
+            exit(-1);
+        }
+
+        struct stat st;
+        if (fstat(ptsfd, &st)) {
+            PLOGE("failed to stat pts_slave");
+            exit(-1);
+        }
+
+        if (st.st_uid != credentials.uid) {
+            PLOGE("caller doesn't own proposed PTY");
+            exit(-1);
+        }
+
+        if (!S_ISCHR(st.st_mode)) {
+            PLOGE("proposed PTY isn't a chardev");
+            exit(-1);
+        }
+
+        if (infd < 0)  {
+            ALOGD("daemon: stdin using PTY");
+            infd  = ptsfd;
+        }
+        if (outfd < 0) {
+            ALOGD("daemon: stdout using PTY");
+            outfd = ptsfd;
+        }
+        if (errfd < 0) {
+            ALOGD("daemon: stderr using PTY");
+            errfd = ptsfd;
+        }
+    } else {
+        // TODO: Check system property, if PTYs are disabled,
+        // made infd the CTTY using:
+        // ioctl(infd, TIOCSCTTY, 1);
+    }
+    free(pts_slave);
+
+    if (mount_storage) {
+        mount_emulated_storage(multiuser_get_user_id(daemon_from_uid));
+    }
+
+    child_result = run_daemon_child(infd, outfd, errfd, argc, argv);
+    for (i = 0; i < argc; i++) {
+        free(argv[i]);
+    }
+    free(argv);
+    return child_result;
+}
+
+int run_daemon() {
+    if (getuid() != 0 || getgid() != 0) {
+        PLOGE("daemon requires root. uid/gid not root");
+        return -1;
+    }
+
+    int fd;
+    struct sockaddr_un sun;
+
+    fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (fd < 0) {
+        PLOGE("socket");
+        return -1;
+    }
+    if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
+        PLOGE("fcntl FD_CLOEXEC");
+        goto err;
+    }
+
+    memset(&sun, 0, sizeof(sun));
+    sun.sun_family = AF_LOCAL;
+    sprintf(sun.sun_path, "%s/su-daemon", DAEMON_SOCKET_PATH);
+
+    /*
+     * Delete the socket to protect from situations when
+     * something bad occured previously and the kernel reused pid from that process.
+     * Small probability, isn't it.
+     */
+    unlink(sun.sun_path);
+    unlink(DAEMON_SOCKET_PATH);
+
+    int previous_umask = umask(027);
+    mkdir(DAEMON_SOCKET_PATH, 0711);
+
+    if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
+        PLOGE("daemon bind");
+        goto err;
+    }
+
+    chmod(DAEMON_SOCKET_PATH, 0711);
+    chmod(sun.sun_path, 0666);
+
+    umask(previous_umask);
+
+    if (listen(fd, 10) < 0) {
+        PLOGE("daemon listen");
+        goto err;
+    }
+
+    int client;
+    while ((client = accept(fd, NULL, NULL)) > 0) {
+        if (fork_zero_fucks() == 0) {
+            close(fd);
+            return daemon_accept(client);
+        }
+        else {
+            close(client);
+        }
+    }
+
+    ALOGE("daemon exiting");
+err:
+    close(fd);
+    return -1;
+}
+
+// List of signals which cause process termination
+static int quit_signals[] = { SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
+
+static void sighandler(__attribute__ ((unused)) int sig) {
+    restore_stdin();
+
+    // Assume we'll only be called before death
+    // See note before sigaction() in set_stdin_raw()
+    //
+    // Now, close all standard I/O to cause the pumps
+    // to exit so we can continue and retrieve the exit
+    // code
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+
+    // Put back all the default handlers
+    struct sigaction act;
+    int i;
+
+    memset(&act, '\0', sizeof(act));
+    act.sa_handler = SIG_DFL;
+    for (i = 0; quit_signals[i]; i++) {
+        if (sigaction(quit_signals[i], &act, NULL) < 0) {
+            PLOGE("Error removing signal handler");
+            continue;
+        }
+    }
+}
+
+/**
+ * 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 setup_sighandlers(void) {
+    struct sigaction act;
+    int i;
+
+    // Install the termination handlers
+    // Note: we're assuming that none of these signal handlers are already trapped.
+    // 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 = &sighandler;
+    for (i = 0; quit_signals[i]; i++) {
+        if (sigaction(quit_signals[i], &act, NULL) < 0) {
+            PLOGE("Error installing signal handler");
+            continue;
+        }
+    }
+}
+
+int connect_daemon(int argc, char *argv[], int ppid) {
+    int ptmx = -1;
+    char pts_slave[PATH_MAX];
+
+    struct sockaddr_un sun;
+
+    // Open a socket to the daemon
+    int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (socketfd < 0) {
+        PLOGE("socket");
+        exit(-1);
+    }
+    if (fcntl(socketfd, F_SETFD, FD_CLOEXEC)) {
+        PLOGE("fcntl FD_CLOEXEC");
+        exit(-1);
+    }
+
+    memset(&sun, 0, sizeof(sun));
+    sun.sun_family = AF_LOCAL;
+    sprintf(sun.sun_path, "%s/su-daemon", DAEMON_SOCKET_PATH);
+
+    if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) {
+        PLOGE("connect");
+        exit(-1);
+    }
+
+    ALOGV("connecting client %d", getpid());
+
+    int mount_storage = getenv("MOUNT_EMULATED_STORAGE") != NULL;
+
+    // Determine which one of our streams are attached to a TTY
+    int atty = 0;
+
+    // TODO: Check a system property and never use PTYs if
+    // the property is set.
+    if (isatty(STDIN_FILENO))  atty |= ATTY_IN;
+    if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
+    if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
+
+    if (atty) {
+        // We need a PTY. Get one.
+        ptmx = pts_open(pts_slave, sizeof(pts_slave));
+        if (ptmx < 0) {
+            PLOGE("pts_open");
+            exit(-1);
+        }
+    } else {
+        pts_slave[0] = '\0';
+    }
+
+    // Send some info to the daemon, starting with our PID
+    write_int(socketfd, getpid());
+    // Send the slave path to the daemon
+    // (This is "" if we're not using PTYs)
+    write_string(socketfd, pts_slave);
+    // Parent PID
+    write_int(socketfd, ppid);
+    write_int(socketfd, mount_storage);
+
+    // Send stdin
+    if (atty & ATTY_IN) {
+        // Using PTY
+        send_fd(socketfd, -1);
+    } else {
+        send_fd(socketfd, STDIN_FILENO);
+    }
+
+    // Send stdout
+    if (atty & ATTY_OUT) {
+        // Forward SIGWINCH
+        watch_sigwinch_async(STDOUT_FILENO, ptmx);
+
+        // Using PTY
+        send_fd(socketfd, -1);
+    } else {
+        send_fd(socketfd, STDOUT_FILENO);
+    }
+
+    // Send stderr
+    if (atty & ATTY_ERR) {
+        // Using PTY
+        send_fd(socketfd, -1);
+    } else {
+        send_fd(socketfd, STDERR_FILENO);
+    }
+
+    // Number of command line arguments
+    write_int(socketfd, mount_storage ? argc - 1 : argc);
+
+    // Command line arguments
+    int i;
+    for (i = 0; i < argc; i++) {
+        if (i == 1 && mount_storage) {
+            continue;
+        }
+        write_string(socketfd, argv[i]);
+    }
+
+    // Wait for acknowledgement from daemon
+    read_int(socketfd);
+
+    if (atty & ATTY_IN) {
+        setup_sighandlers();
+        pump_stdin_async(ptmx);
+    }
+    if (atty & ATTY_OUT) {
+        pump_stdout_blocking(ptmx);
+    }
+
+    // Get the exit code
+    int code = read_int(socketfd);
+    close(socketfd);
+    ALOGD("client exited %d", code);
+
+    return code;
+}
diff --git a/su/pts.c b/su/pts.c
new file mode 100644 (file)
index 0000000..26927a9
--- /dev/null
+++ b/su/pts.c
@@ -0,0 +1,336 @@
+/*
+ * 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 <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <termios.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "pts.h"
+
+/**
+ * Helper functions
+ */
+// Ensures all the data is written out
+static int write_blocking(int fd, char *buf, size_t bufsz) {
+    ssize_t ret, written;
+
+    written = 0;
+    do {
+        ret = write(fd, buf + written, bufsz - written);
+        if (ret == -1) return -1;
+        written += ret;
+    } while (written < (ssize_t)bufsz);
+
+    return 0;
+}
+
+/**
+ * Pump data from input FD to output FD. If close_output is
+ * true, then close the output FD when we're done.
+ */
+static void pump_ex(int input, int output, int close_output) {
+    char buf[4096];
+    int len;
+    while ((len = read(input, buf, 4096)) > 0) {
+        if (write_blocking(output, buf, len) == -1) break;
+    }
+    close(input);
+    if (close_output) close(output);
+}
+
+/**
+ * Pump data from input FD to output FD. Will close the
+ * output FD when done.
+ */
+static void pump(int input, int output) {
+    pump_ex(input, output, 1);
+}
+
+static void* pump_thread(void* data) {
+    int* files = (int*)data;
+    int input = files[0];
+    int output = files[1];
+    pump(input, output);
+    free(data);
+    return NULL;
+}
+
+static void pump_async(int input, int output) {
+    pthread_t writer;
+    int* files = (int*)malloc(sizeof(int) * 2);
+    if (files == NULL) {
+        exit(-1);
+    }
+    files[0] = input;
+    files[1] = output;
+    pthread_create(&writer, NULL, pump_thread, files);
+}
+
+
+/**
+ * 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[slave_name_size];
+
+    // Open master ptmx device
+    fdm = open("/dev/ptmx", O_RDWR);
+    if (fdm == -1) return -1;
+
+    // Get the slave name
+    if (ptsname_r(fdm, sn_tmp, slave_name_size) != 0) {
+        close(fdm);
+        return -2;
+    }
+
+    if (strlcpy(slave_name, sn_tmp, slave_name_size) >= slave_name_size) {
+        return -1;
+    }
+
+    // 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.
+static volatile 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.
+ * NOTE 2: This function must be called before any of the
+ *         pump functions.
+ *
+ * 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);
+    return 0;
+}
+
+/**
+ * watch_sigwinch_cleanup
+ *
+ * Cause the SIGWINCH watcher thread to terminate
+ */
+void watch_sigwinch_cleanup(void) {
+    closing_time = 1;
+    raise(SIGWINCH);
+}
+
+/**
+ * pump_stdin_async
+ *
+ * Forward data from STDIN to the given FD
+ * in a seperate thread
+ */
+void pump_stdin_async(int outfd) {
+    // Put stdin into raw mode
+    set_stdin_raw();
+
+    // Pump data from stdin to the PTY
+    pump_async(STDIN_FILENO, outfd);
+}
+
+/**
+ * pump_stdout_blocking
+ *
+ * Forward data from the FD to STDOUT.
+ * Returns when the remote end of the FD closes.
+ *
+ * Before returning, restores stdin settings.
+ */
+void pump_stdout_blocking(int infd) {
+    // Pump data from stdout to PTY
+    pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */);
+
+    // Cleanup
+    restore_stdin();
+    watch_sigwinch_cleanup();
+}
diff --git a/su/pts.h b/su/pts.h
new file mode 100644 (file)
index 0000000..c323643
--- /dev/null
+++ b/su/pts.h
@@ -0,0 +1,116 @@
+/*
+ * 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);
+
+/**
+ * pump_stdin_async
+ *
+ * Forward data from STDIN to the given FD
+ * in a seperate thread
+ */
+void pump_stdin_async(int outfd);
+
+/**
+ * pump_stdout_blocking
+ *
+ * Forward data from the FD to STDOUT.
+ * Returns when the remote end of the FD closes.
+ *
+ * Before returning, restores stdin settings.
+ */
+void pump_stdout_blocking(int infd);
+
+#endif
diff --git a/su/su.h b/su/su.h
new file mode 100644 (file)
index 0000000..6c21f51
--- /dev/null
+++ b/su/su.h
@@ -0,0 +1,114 @@
+/*
+** Copyright 2010, Adam Shanks (@ChainsDD)
+** Copyright 2008, Zinx Verituse (@zinxv)
+**
+** 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.
+*/
+
+#ifndef SU_h 
+#define SU_h 1
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "su"
+
+// CyanogenMod-specific behavior
+#define CM_ROOT_ACCESS_DISABLED      0
+#define CM_ROOT_ACCESS_APPS_ONLY     1
+#define CM_ROOT_ACCESS_ADB_ONLY      2
+#define CM_ROOT_ACCESS_APPS_AND_ADB  3
+
+#define DAEMON_SOCKET_PATH "/dev/socket/su-daemon/"
+
+#define DEFAULT_SHELL "/system/bin/sh"
+
+#define xstr(a) str(a)
+#define str(a) #a
+
+#ifndef VERSION_CODE
+#define VERSION_CODE 16
+#endif
+#define VERSION xstr(VERSION_CODE) " cm-su"
+
+#define PROTO_VERSION 1
+
+struct su_initiator {
+    pid_t pid;
+    unsigned uid;
+    unsigned user;
+    char name[64];
+    char bin[PATH_MAX];
+    char args[4096];
+};
+
+struct su_request {
+    unsigned uid;
+    char name[64];
+    int login;
+    int keepenv;
+    char *shell;
+    char *command;
+    char **argv;
+    int argc;
+    int optind;
+};
+
+struct su_context {
+    struct su_initiator from;
+    struct su_request to;
+    mode_t umask;
+    char sock_path[PATH_MAX];
+};
+
+typedef enum {
+    INTERACTIVE = 0,
+    DENY = 1,
+    ALLOW = 2,
+} policy_t;
+
+extern void set_identity(unsigned int uid);
+
+static inline char *get_command(const struct su_request *to)
+{
+  if (to->command)
+    return to->command;
+  if (to->shell)
+    return to->shell;
+  char* ret = to->argv[to->optind];
+  if (ret)
+    return ret;
+  return DEFAULT_SHELL;
+}
+
+int appops_start_op_su(int uid, const char *pkgName);
+int appops_finish_op_su(int uid, const char *pkgName);
+
+int run_daemon();
+int connect_daemon(int argc, char *argv[], int ppid);
+int su_main(int argc, char *argv[], int need_client);
+// for when you give zero fucks about the state of the child process.
+// this version of fork understands you don't care about the child.
+// deadbeat dad fork.
+int fork_zero_fucks();
+
+#ifndef LOG_NDEBUG
+#define LOG_NDEBUG 1
+#endif
+
+#include <errno.h>
+#include <string.h>
+#define PLOGE(fmt,args...) ALOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
+#define PLOGEV(fmt,err,args...) ALOGE(fmt " failed with %d: %s", ##args, err, strerror(err))
+
+#endif
diff --git a/su/superuser.rc b/su/superuser.rc
new file mode 100644 (file)
index 0000000..ea96be6
--- /dev/null
@@ -0,0 +1,18 @@
+# su daemon
+service su_daemon /system/xbin/su --daemon
+    user root
+    group root
+    disabled
+    seclabel u:r:sudaemon:s0
+
+on property:persist.sys.root_access=0
+    stop su_daemon
+
+on property:persist.sys.root_access=2
+    start su_daemon
+
+on property:persist.sys.root_access=1
+    start su_daemon
+
+on property:persist.sys.root_access=3
+    start su_daemon
diff --git a/su/utils.c b/su/utils.c
new file mode 100644 (file)
index 0000000..a65f547
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+** Copyright 2012, The CyanogenMod Project
+**
+** 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.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+
+/* reads a file, making sure it is terminated with \n \0 */
+char* read_file(const char *fn)
+{
+    struct stat st;
+    char *data = NULL;
+
+    int fd = open(fn, O_RDONLY);
+    if (fd < 0) return data;
+
+    if (fstat(fd, &st)) goto oops;
+
+    data = malloc(st.st_size + 2);
+    if (!data) goto oops;
+
+    if (read(fd, data, st.st_size) != st.st_size) goto oops;
+    close(fd);
+    data[st.st_size] = '\n';
+    data[st.st_size + 1] = 0;
+    return data;
+
+oops:
+    close(fd);
+    if (data) free(data);
+    return NULL;
+}
+
+int get_property(const char *data, char *found, const char *searchkey, const char *not_found)
+{
+    char *key, *value, *eol, *sol, *tmp;
+    if (data == NULL) goto defval;
+    int matched = 0;
+    char *dup = strdup(data);
+
+    sol = dup;
+    while((eol = strchr(sol, '\n'))) {
+        key = sol;
+        *eol++ = 0;
+        sol = eol;
+
+        value = strchr(key, '=');
+        if(value == 0) continue;
+        *value++ = 0;
+
+        while(isspace(*key)) key++;
+        if(*key == '#') continue;
+        tmp = value - 2;
+        while((tmp > key) && isspace(*tmp)) *tmp-- = 0;
+
+        while(isspace(*value)) value++;
+        tmp = eol - 2;
+        while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
+
+        if (strncmp(searchkey, key, strlen(searchkey)) == 0) {
+            matched = 1;
+            break;
+        }
+    }
+    free(dup);
+    int len;
+    if (matched) {
+        len = strlen(value);
+        if (len >= PROPERTY_VALUE_MAX)
+            return -1;
+        memcpy(found, value, len + 1);
+    } else goto defval;
+    return len;
+
+defval:
+    len = strlen(not_found);
+    memcpy(found, not_found, len + 1);
+    return len;
+}
+
+/*
+ * Fast version of get_property which purpose is to check
+ * whether the property with given prefix exists.
+ *
+ * Assume nobody is stupid enough to put a propery with prefix ro.cm.version
+ * in his build.prop on a non-CM ROM and comment it out.
+ */
+int check_property(const char *data, const char *prefix)
+{
+    if (!data)
+        return 0;
+    return strstr(data, prefix) != NULL;
+}
diff --git a/su/utils.h b/su/utils.h
new file mode 100644 (file)
index 0000000..a9d4a8f
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+** Copyright 2012, The CyanogenMod Project
+**
+** 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.
+*/
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#ifndef PROPERTY_VALUE_MAX
+#define PROPERTY_VALUE_MAX  92
+#endif
+
+/* reads a file, making sure it is terminated with \n \0 */
+extern char* read_file(const char *fn);
+
+extern int get_property(const char *data, char *found, const char *searchkey,
+                        const char *not_found);
+extern int check_property(const char *data, const char *prefix);
+#endif