diff --git a/activity.c b/activity.c index fb2c79c..2bc9f1e 100644 --- a/activity.c +++ b/activity.c @@ -15,62 +15,107 @@ ** limitations under the License. */ +#include #include +#include +#include +#include #include "su.h" -int send_intent(const struct su_context *ctx, - const char *socket_path, int allow, const char *action) +static void kill_child(pid_t pid) { - char command[PATH_MAX]; + LOGD("killing child %d", pid); + if (pid) { + sigset_t set, old; - sprintf(command, "/system/bin/am broadcast -a '%s' --es socket '%s' --ei caller_uid '%d' --ei allow '%d' --ei version_code '%d' > /dev/null", - action, socket_path, ctx->from.uid, allow, VERSION_CODE); + sigemptyset(&set); + sigaddset(&set, SIGCHLD); + if (sigprocmask(SIG_BLOCK, &set, &old)) { + PLOGE("sigprocmask(SIG_BLOCK)"); + return; + } + if (kill(pid, SIGKILL)) + PLOGE("kill (%d)", pid); + else if (sigsuspend(&old) && errno != EINTR) + PLOGE("sigsuspend"); + if (sigprocmask(SIG_SETMASK, &old, NULL)) + PLOGE("sigprocmask(SIG_BLOCK)"); + } +} - // before sending the intent, make sure the (uid and euid) and (gid and egid) match, - // otherwise LD_LIBRARY_PATH is wiped in Android 4.0+. - // Also, sanitize all secure environment variables (from linker_environ.c in linker). +static void setup_sigchld_handler(__sighandler_t handler) +{ + struct sigaction act; - /* The same list than GLibc at this point */ - static const char* const unsec_vars[] = { - "GCONV_PATH", - "GETCONF_DIR", - "HOSTALIASES", - "LD_AUDIT", - "LD_DEBUG", - "LD_DEBUG_OUTPUT", - "LD_DYNAMIC_WEAK", - "LD_LIBRARY_PATH", - "LD_ORIGIN_PATH", - "LD_PRELOAD", - "LD_PROFILE", - "LD_SHOW_AUXV", - "LD_USE_LOAD_BIAS", - "LOCALDOMAIN", - "LOCPATH", - "MALLOC_TRACE", - "MALLOC_CHECK_", - "NIS_PATH", - "NLSPATH", - "RESOLV_HOST_CONF", - "RES_OPTIONS", - "TMPDIR", - "TZDIR", - "LD_AOUT_LIBRARY_PATH", - "LD_AOUT_PRELOAD", - // not listed in linker, used due to system() call - "IFS", - }; - const char* const* cp = unsec_vars; - const char* const* endp = cp + sizeof(unsec_vars)/sizeof(unsec_vars[0]); - while (cp < endp) { - unsetenv(*cp); - cp++; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_NOCLDSTOP | SA_RESTART; + if (sigaction(SIGCHLD, &act, NULL)) { + PLOGE("sigaction(SIGCHLD)"); + exit(EXIT_FAILURE); } +} - // sane value so "am" works - setenv("LD_LIBRARY_PATH", "/vendor/lib:/system/lib", 1); - setegid(getgid()); - seteuid(getuid()); - return system(command); +int send_intent(struct su_context *ctx, allow_t allow, const char *action) +{ + const char *socket_path; + unsigned int uid = ctx->from.uid; + __sighandler_t handler; + pid_t pid; + + pid = ctx->child; + if (pid) { + kill_child(pid); + pid = ctx->child; + if (pid) { + LOGE("child %d is still running", pid); + return -1; + } + } + if (allow == INTERACTIVE) { + socket_path = ctx->sock_path; + handler = sigchld_handler; + } else { + socket_path = ""; + handler = SIG_IGN; + } + setup_sigchld_handler(handler); + + pid = fork(); + /* Child */ + if (!pid) { + char command[ARG_MAX]; + + snprintf(command, sizeof(command), + "exec /system/bin/am broadcast --user %d -a %s --es socket '%s' " + "--ei caller_uid %d --ei allow %d --es desired_cmd '%s' " + "--ei all %d --ei version_code %d", + ctx->user.userid, action, socket_path, uid, allow, get_command(&ctx->to), + ctx->to.all, VERSION_CODE); + char *args[] = { "sh", "-c", command, NULL, }; + + /* + * before sending the intent, make sure the effective uid/gid match + * the real uid/gid, otherwise LD_LIBRARY_PATH is wiped + * in Android 4.0+. + */ + set_identity(uid); + int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); + dup2(zero, 0); + int null = open("/dev/null", O_WRONLY | O_CLOEXEC); + dup2(null, 1); + dup2(null, 2); + LOGD("Executing %s\n", command); + execv(_PATH_BSHELL, args); + PLOGE("exec am"); + _exit(EXIT_FAILURE); + } + /* Parent */ + if (pid < 0) { + PLOGE("fork"); + return -1; + } + ctx->child = pid; + return 0; } diff --git a/db.c b/db.c index 52f9673..fad5371 100644 --- a/db.c +++ b/db.c @@ -17,42 +17,68 @@ #include #include #include -#include #include "su.h" -int database_check(const struct su_context *ctx) +int database_check(struct su_context *ctx) { FILE *fp; - char allow = '-'; - char *filename = malloc(snprintf(NULL, 0, "%s/%u-%u", REQUESTOR_STORED_PATH, ctx->from.uid, ctx->to.uid) + 1); - sprintf(filename, "%s/%u-%u", REQUESTOR_STORED_PATH, ctx->from.uid, ctx->to.uid); + char filename[PATH_MAX]; + char allow[7]; + int last = 0; + int from_uid = ctx->from.uid; + + if (ctx->user.owner_mode) { + from_uid = from_uid % 100000; + } + + snprintf(filename, sizeof(filename), + "%s/%u-%u", ctx->user.store_path, from_uid, ctx->to.uid); if ((fp = fopen(filename, "r"))) { - LOGD("Found file"); - char cmd[PATH_MAX]; - fgets(cmd, sizeof(cmd), fp); - int last = strlen(cmd) - 1; - LOGD("this is the last character %u of the string", cmd[5]); - if (cmd[last] == '\n') { - cmd[last] = '\0'; - } - LOGD("Comparing %c %s, %u to %s", cmd[last - 2], cmd, last, get_command(&ctx->to)); - if (strcmp(cmd, get_command(&ctx->to)) == 0) { - allow = fgetc(fp); + LOGD("Found file %s", filename); + + while (fgets(allow, sizeof(allow), fp)) { + last = strlen(allow) - 1; + if (last >= 0) + allow[last] = 0; + + char cmd[ARG_MAX]; + fgets(cmd, sizeof(cmd), fp); + /* skip trailing '\n' */ + last = strlen(cmd) - 1; + if (last >= 0) + cmd[last] = 0; + + LOGD("Comparing '%s' to '%s'", cmd, get_command(&ctx->to)); + if (strcmp(cmd, get_command(&ctx->to)) == 0) + break; + else if (strcmp(cmd, "any") == 0) { + ctx->to.all = 1; + break; + } + else + strcpy(allow, "prompt"); } fclose(fp); - } else if ((fp = fopen(REQUESTOR_STORED_DEFAULT, "r"))) { - LOGD("Using default"); - allow = fgetc(fp); + } else if ((fp = fopen(ctx->user.store_default, "r"))) { + LOGD("Using default file %s", ctx->user.store_default); + fgets(allow, sizeof(allow), fp); + last = strlen(allow) - 1; + if (last >=0) + allow[last] = 0; + fclose(fp); } - free(filename); - if (allow == '1') { - return DB_ALLOW; - } else if (allow == '0') { - return DB_DENY; + if (strcmp(allow, "allow") == 0) { + return ALLOW; + } else if (strcmp(allow, "deny") == 0) { + return DENY; } else { - return DB_INTERACTIVE; + if (ctx->user.userid != 0 && ctx->user.owner_mode) { + return DENY; + } else { + return INTERACTIVE; + } } } diff --git a/su.c b/su.c index d36eaed..06f4238 100644 --- a/su.c +++ b/su.c @@ -24,25 +24,18 @@ #include #include #include -#include -#include #include #include -#include #include #include #include #include #include -#include #include "su.h" #include "utils.h" -/* Still lazt, will fix this */ -static char socket_path[PATH_MAX]; - static int from_init(struct su_initiator *from) { char path[PATH_MAX], exe[PATH_MAX]; @@ -108,6 +101,35 @@ static int from_init(struct su_initiator *from) return 0; } +static void read_options(struct su_context *ctx) +{ + char mode[12]; + FILE *fp; + if ((fp = fopen(REQUESTOR_OPTIONS, "r"))) { + fgets(mode, sizeof(mode), fp); + if (strcmp(mode, "user\n") == 0) { + ctx->user.owner_mode = 0; + } else if (strcmp(mode, "owner\n") == 0) { + ctx->user.owner_mode = 1; + } + } +} + +static void user_init(struct su_context *ctx) +{ + if (ctx->from.uid > 99999) { + ctx->user.userid = ctx->from.uid / 100000; + if (!ctx->user.owner_mode) { + snprintf(ctx->user.data_path, PATH_MAX, "/data/user/%d/%s", + ctx->user.userid, REQUESTOR); + snprintf(ctx->user.store_path, PATH_MAX, "/data/user/%d/%s/files/stored", + ctx->user.userid, REQUESTOR); + snprintf(ctx->user.store_default, PATH_MAX, "/data/user/%d/%s/files/stored/default", + ctx->user.userid, REQUESTOR); + } + } +} + static void populate_environment(const struct su_context *ctx) { struct passwd *pw; @@ -126,22 +148,85 @@ static void populate_environment(const struct su_context *ctx) } } -static void socket_cleanup(void) +void set_identity(unsigned int uid) +{ + /* + * Set effective uid back to root, otherwise setres[ug]id will fail + * if uid isn't root. + */ + if (seteuid(0)) { + PLOGE("seteuid (root)"); + exit(EXIT_FAILURE); + } + if (setresgid(uid, uid, uid)) { + PLOGE("setresgid (%u)", uid); + exit(EXIT_FAILURE); + } + if (setresuid(uid, uid, uid)) { + PLOGE("setresuid (%u)", uid); + exit(EXIT_FAILURE); + } +} + +static void socket_cleanup(struct su_context *ctx) { - unlink(socket_path); + if (ctx && ctx->sock_path[0]) { + if (unlink(ctx->sock_path)) + PLOGE("unlink (%s)", ctx->sock_path); + ctx->sock_path[0] = 0; + } } +static void child_cleanup(struct su_context *ctx) +{ + pid_t pid = ctx->child; + int rc; + + if (!pid) { + LOGE("unexpected child"); + pid = -1; /* pick up any child */ + } + pid = waitpid(pid, &rc, WNOHANG); + if (pid < 0) { + PLOGE("waitpid"); + exit(EXIT_FAILURE); + } + if (WIFEXITED(rc) && WEXITSTATUS(rc)) { + LOGE("child %d terminated with error %d", pid, WEXITSTATUS(rc)); + exit(EXIT_FAILURE); + } + if (WIFSIGNALED(rc) && WTERMSIG(rc) != SIGKILL) { + LOGE("child %d terminated with signal %d", pid, WTERMSIG(rc)); + exit(EXIT_FAILURE); + } + LOGD("child %d terminated, status %d", pid, rc); + ctx->child = 0; +} + +/* + * For use in signal handlers/atexit-function + * NOTE: su_ctx points to main's local variable. + * It's OK due to the program uses exit(3), not return from main() + */ +static struct su_context *su_ctx = NULL; + static void cleanup(void) { - socket_cleanup(); + socket_cleanup(su_ctx); } static void cleanup_signal(int sig) { - socket_cleanup(); + socket_cleanup(su_ctx); exit(128 + sig); } +void sigchld_handler(int sig) +{ + child_cleanup(su_ctx); + (void)sig; +} + static int socket_create_temp(char *path, size_t len) { int fd; @@ -152,6 +237,10 @@ static int socket_create_temp(char *path, size_t len) 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; @@ -185,14 +274,17 @@ static int socket_accept(int serv_fd) { struct timeval tv; fd_set fds; - int fd; + int fd, rc; /* Wait 20 seconds for a connection, then give up. */ tv.tv_sec = 20; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(serv_fd, &fds); - if (select(serv_fd + 1, &fds, NULL, NULL, &tv) < 1) { + do { + rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); + } while (rc < 0 && errno == EINTR); + if (rc < 1) { PLOGE("select"); return -1; } @@ -279,23 +371,23 @@ static void usage(int status) exit(status); } -static void deny(const struct su_context *ctx) +static __attribute__ ((noreturn)) void deny(struct su_context *ctx) { char *cmd = get_command(&ctx->to); - send_intent(ctx, "", 0, ACTION_RESULT); + send_intent(ctx, DENY, ACTION_RESULT); LOGW("request rejected (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd); fprintf(stderr, "%s\n", strerror(EACCES)); exit(EXIT_FAILURE); } -static void allow(const struct su_context *ctx) +static __attribute__ ((noreturn)) void allow(struct su_context *ctx) { char *arg0; int argc, err; umask(ctx->umask); - send_intent(ctx, "", 1, ACTION_RESULT); + send_intent(ctx, ALLOW, ACTION_RESULT); arg0 = strrchr (ctx->to.shell, '/'); arg0 = (arg0) ? arg0 + 1 : ctx->to.shell; @@ -311,25 +403,8 @@ static void allow(const struct su_context *ctx) arg0 = p; } - /* - * Set effective uid back to root, otherwise setres[ug]id will fail - * if ctx->to.uid isn't root. - */ - if (seteuid(0)) { - PLOGE("seteuid (root)"); - exit(EXIT_FAILURE); - } - populate_environment(ctx); - - if (setresgid(ctx->to.uid, ctx->to.uid, ctx->to.uid)) { - PLOGE("setresgid (%u)", ctx->to.uid); - exit(EXIT_FAILURE); - } - if (setresuid(ctx->to.uid, ctx->to.uid, ctx->to.uid)) { - PLOGE("setresuid (%u)", ctx->to.uid); - exit(EXIT_FAILURE); - } + set_identity(ctx->to.uid); #define PARG(arg) \ (ctx->to.optind + (arg) < ctx->to.argc) ? " " : "", \ @@ -354,6 +429,63 @@ static void allow(const struct su_context *ctx) exit(EXIT_FAILURE); } +/* + * CyanogenMod-specific behavior + * + * we can't simply use the property service, since we aren't launched from init + * and can't trust the location of the property workspace. + * Find the properties ourselves. + */ +int access_disabled(const struct su_initiator *from) +{ + char *data; + char build_type[PROPERTY_VALUE_MAX]; + char debuggable[PROPERTY_VALUE_MAX], enabled[PROPERTY_VALUE_MAX]; + size_t len; + + data = read_file("/system/build.prop"); + if (check_property(data, "ro.cm.version")) { + get_property(data, build_type, "ro.build.type", ""); + free(data); + + data = read_file("/default.prop"); + get_property(data, debuggable, "ro.debuggable", "0"); + free(data); + /* only allow su on debuggable builds */ + if (strcmp("1", debuggable) != 0) { + LOGE("Root access is disabled on non-debug builds"); + return 1; + } + + data = read_file("/data/property/persist.sys.root_access"); + if (data != NULL) { + len = strlen(data); + if (len >= PROPERTY_VALUE_MAX) + memcpy(enabled, "1", 2); + else + memcpy(enabled, data, len + 1); + free(data); + } else + memcpy(enabled, "1", 2); + + /* enforce persist.sys.root_access on non-eng builds */ + if (strcmp("eng", build_type) != 0 && (atoi(enabled) & 1) != 1 ) { + LOGE("Root access is disabled by system setting - " + "enable it under settings -> developer options"); + return 1; + } + + /* disallow su in a shell if appropriate */ + if (from->uid == AID_SHELL && (atoi(enabled) == 1)) { + LOGE("Root access is disabled by a system setting - " + "enable it under settings -> developer options"); + return 1; + } + + } + return 0; +} + int main(int argc, char *argv[]) { struct su_context ctx = { @@ -373,13 +505,19 @@ int main(int argc, char *argv[]) .argc = argc, .optind = 0, }, + .user = { + .userid = 0, + .owner_mode = -1, + .data_path = REQUESTOR_DATA_PATH, + .store_path = REQUESTOR_STORED_PATH, + .store_default = REQUESTOR_STORED_DEFAULT, + }, + .child = 0, }; struct stat st; - int socket_serv_fd, fd; - char buf[64], *result, debuggable[PROPERTY_VALUE_MAX]; - char enabled[PROPERTY_VALUE_MAX], build_type[PROPERTY_VALUE_MAX]; - char cm_version[PROPERTY_VALUE_MAX];; - int c, dballow, len; + int c, socket_serv_fd, fd; + char buf[64], *result; + allow_t dballow; struct option long_opts[] = { { "command", required_argument, NULL, 'c' }, { "help", no_argument, NULL, 'h' }, @@ -389,8 +527,6 @@ int main(int argc, char *argv[]) { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 }, }; - char *data; - unsigned sz; while ((c = getopt_long(argc, argv, "+c:hlmps:Vv", long_opts, NULL)) != -1) { switch(c) { @@ -451,60 +587,31 @@ int main(int argc, char *argv[]) } ctx.to.optind = optind; + su_ctx = &ctx; if (from_init(&ctx.from) < 0) { deny(&ctx); } + + read_options(&ctx); + user_init(&ctx); + + if (ctx.user.owner_mode == -1 && ctx.user.userid != 0) + deny(&ctx); - // we can't simply use the property service, since we aren't launched from init and - // can't trust the location of the property workspace. find the properties ourselves. - data = read_file("/default.prop", &sz); - get_property(data, debuggable, "ro.debuggable", "0"); - free(data); - - data = read_file("/system/build.prop", &sz); - get_property(data, cm_version, "ro.cm.version", ""); - get_property(data, build_type, "ro.build.type", ""); - free(data); - - data = read_file("/data/property/persist.sys.root_access", &sz); - if (data != NULL) { - len = strlen(data); - if (len >= PROPERTY_VALUE_MAX) - memcpy(enabled, "1", 2); - else - memcpy(enabled, data, len + 1); - free(data); - } else - memcpy(enabled, "1", 2); + if (access_disabled(&ctx.from)) + deny(&ctx); ctx.umask = umask(027); - // CyanogenMod-specific behavior - if (strlen(cm_version) > 0) { - // only allow su on debuggable builds - if (strcmp("1", debuggable) != 0) { - LOGE("Root access is disabled on non-debug builds"); - deny(&ctx); - } - - // enforce persist.sys.root_access on non-eng builds - if (strcmp("eng", build_type) != 0 && - (atoi(enabled) & 1) != 1 ) { - LOGE("Root access is disabled by system setting - enable it under settings -> developer options"); - deny(&ctx); - } - - // disallow su in a shell if appropriate - if (ctx.from.uid == AID_SHELL && (atoi(enabled) == 1)) { - LOGE("Root access is disabled by a system setting - enable it under settings -> developer options"); - deny(&ctx); - } - } - + /* + * set LD_LIBRARY_PATH if the linker has wiped out it due to we're suid. + * This occurs on Android 4.0+ + */ + setenv("LD_LIBRARY_PATH", "/vendor/lib:/system/lib", 0); if (ctx.from.uid == AID_ROOT || ctx.from.uid == AID_SHELL) allow(&ctx); - if (stat(REQUESTOR_DATA_PATH, &st) < 0) { + if (stat(ctx.user.data_path, &st) < 0) { PLOGE("stat"); deny(&ctx); } @@ -537,13 +644,13 @@ int main(int argc, char *argv[]) dballow = database_check(&ctx); switch (dballow) { - case DB_DENY: deny(&ctx); - case DB_ALLOW: allow(&ctx); - case DB_INTERACTIVE: break; - default: deny(&ctx); + case INTERACTIVE: break; + case ALLOW: allow(&ctx); /* never returns */ + case DENY: + default: deny(&ctx); /* never returns too */ } - socket_serv_fd = socket_create_temp(socket_path, sizeof(socket_path)); + socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path)); if (socket_serv_fd < 0) { deny(&ctx); } @@ -556,7 +663,7 @@ int main(int argc, char *argv[]) signal(SIGABRT, cleanup_signal); atexit(cleanup); - if (send_intent(&ctx, socket_path, -1, ACTION_REQUEST) < 0) { + if (send_intent(&ctx, INTERACTIVE, ACTION_REQUEST) < 0) { deny(&ctx); } @@ -573,7 +680,7 @@ int main(int argc, char *argv[]) close(fd); close(socket_serv_fd); - socket_cleanup(); + socket_cleanup(&ctx); result = buf; @@ -591,7 +698,4 @@ int main(int argc, char *argv[]) LOGE("unknown response from Superuser Requestor: %s", result); deny(&ctx); } - - deny(&ctx); - return -1; } diff --git a/su.h b/su.h index 4cc5485..d0aa786 100644 --- a/su.h +++ b/su.h @@ -25,10 +25,11 @@ #define REQUESTOR "com.noshufou.android.su" #define REQUESTOR_DATA_PATH "/data/data/" REQUESTOR -#define REQUESTOR_CACHE_PATH REQUESTOR_DATA_PATH "/cache" +#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR #define REQUESTOR_STORED_PATH REQUESTOR_DATA_PATH "/files/stored" #define REQUESTOR_STORED_DEFAULT REQUESTOR_STORED_PATH "/default" +#define REQUESTOR_OPTIONS REQUESTOR_STORED_PATH "/options" /* intent actions */ #define ACTION_REQUEST REQUESTOR ".REQUEST" @@ -42,8 +43,8 @@ #define VERSION_EXTRA "" #endif -#define VERSION "3.2" VERSION_EXTRA -#define VERSION_CODE 18 +#define VERSION "3.3" VERSION_EXTRA +#define VERSION_CODE 19 #define DATABASE_VERSION 6 #define PROTO_VERSION 0 @@ -51,6 +52,7 @@ struct su_initiator { pid_t pid; unsigned uid; + unsigned user; char bin[PATH_MAX]; char args[4096]; }; @@ -64,30 +66,55 @@ struct su_request { char **argv; int argc; int optind; + int appId; + int all; +}; + +struct su_user_info { + unsigned userid; + int owner_mode; + char data_path[PATH_MAX]; + char store_path[PATH_MAX]; + char store_default[PATH_MAX]; }; struct su_context { struct su_initiator from; struct su_request to; + struct su_user_info user; mode_t umask; + volatile pid_t child; + char sock_path[PATH_MAX]; }; -enum { - DB_INTERACTIVE, - DB_DENY, - DB_ALLOW -}; - -extern int database_check(const struct su_context *ctx); +typedef enum { + INTERACTIVE = -1, + DENY = 0, + ALLOW = 1, +} allow_t; -extern int send_intent(const struct su_context *ctx, - const char *socket_path, int allow, const char *action); +extern allow_t database_check(struct su_context *ctx); +extern void set_identity(unsigned int uid); +extern int send_intent(struct su_context *ctx, + allow_t allow, const char *action); +extern void sigchld_handler(int sig); static inline char *get_command(const struct su_request *to) { return (to->command) ? to->command : to->shell; } +#include +#ifndef LOGE +#define LOGE(...) ALOGE(__VA_ARGS__) +#endif +#ifndef LOGD +#define LOGD(...) ALOGD(__VA_ARGS__) +#endif +#ifndef LOGW +#define LOGW(...) ALOGW(__VA_ARGS__) +#endif + #if 0 #undef LOGE #define LOGE(fmt,args...) fprintf(stderr, fmt, ##args) @@ -97,6 +124,8 @@ static inline char *get_command(const struct su_request *to) #define LOGW(fmt,args...) fprintf(stderr, fmt, ##args) #endif +#include +#include #define PLOGE(fmt,args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) #define PLOGEV(fmt,err,args...) LOGE(fmt " failed with %d: %s", ##args, err, strerror(err)) diff --git a/utils.c b/utils.c index ec6444b..4f6f9e2 100644 --- a/utils.c +++ b/utils.c @@ -14,48 +14,42 @@ ** limitations under the License. */ +#include +#include #include #include #include #include -#include #include - #include #include #include #include /* reads a file, making sure it is terminated with \n \0 */ -char* read_file(const char *fn, unsigned *_sz) +char* read_file(const char *fn) { - char *data; - int sz; - int fd; - - data = 0; - fd = open(fn, O_RDONLY); - if(fd < 0) return 0; + struct stat st; + char *data = NULL; - sz = lseek(fd, 0, SEEK_END); - if(sz < 0) goto oops; + int fd = open(fn, O_RDONLY); + if (fd < 0) return data; - if(lseek(fd, 0, SEEK_SET) != 0) goto oops; + if (fstat(fd, &st)) goto oops; - data = (char*) malloc(sz + 2); - if(data == 0) goto oops; + data = malloc(st.st_size + 2); + if (!data) goto oops; - if(read(fd, data, sz) != sz) goto oops; + if (read(fd, data, st.st_size) != st.st_size) goto oops; close(fd); - data[sz] = '\n'; - data[sz+1] = 0; - if(_sz) *_sz = sz; + data[st.st_size] = '\n'; + data[st.st_size + 1] = 0; return data; oops: close(fd); - if(data != 0) free(data); - return 0; + if (data) free(data); + return NULL; } int get_property(const char *data, char *found, const char *searchkey, const char *not_found) @@ -100,4 +94,18 @@ int get_property(const char *data, char *found, const char *searchkey, const cha len = strlen(not_found); memcpy(found, not_found, len + 1); return len; -} \ No newline at end of file +} + +/* + * 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/utils.h b/utils.h index 92e1ace..16ec345 100644 --- a/utils.h +++ b/utils.h @@ -18,7 +18,9 @@ #define _UTILS_H_ /* reads a file, making sure it is terminated with \n \0 */ -char* read_file(const char *fn, unsigned *_sz); +extern char* read_file(const char *fn); -int get_property(const char *data, char *found, const char *searchkey, const char *not_found); -#endif \ No newline at end of file +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