mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-10-15 22:33:24 +00:00
Rename module core to native
This commit is contained in:
256
native/jni/utils/cpio.c
Normal file
256
native/jni/utils/cpio.c
Normal file
@@ -0,0 +1,256 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "cpio.h"
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
|
||||
static uint32_t x8u(char *hex) {
|
||||
uint32_t val, inpos = 8, outpos;
|
||||
char pattern[6];
|
||||
|
||||
while (*hex == '0') {
|
||||
hex++;
|
||||
if (!--inpos) return 0;
|
||||
}
|
||||
// Because scanf gratuitously treats %*X differently than printf does.
|
||||
sprintf(pattern, "%%%dx%%n", inpos);
|
||||
sscanf(hex, pattern, &val, &outpos);
|
||||
if (inpos != outpos) LOGE("bad cpio header\n");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
void cpio_free(cpio_entry *e) {
|
||||
if (e) {
|
||||
free(e->filename);
|
||||
free(e->data);
|
||||
free(e);
|
||||
}
|
||||
}
|
||||
|
||||
int cpio_find(struct vector *v, const char *entry) {
|
||||
cpio_entry *e;
|
||||
vec_for_each(v, e) {
|
||||
if (!e) continue;
|
||||
if (strcmp(e->filename, entry) == 0)
|
||||
return _;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int cpio_cmp(const void *a, const void *b) {
|
||||
return strcmp(((cpio_entry *) a)->filename, ((cpio_entry *) b)->filename);
|
||||
}
|
||||
|
||||
void cpio_vec_insert(struct vector *v, cpio_entry *n) {
|
||||
int i = cpio_find(v, n->filename);
|
||||
if (i > 0) {
|
||||
// Replace, then all is done
|
||||
cpio_free(vec_entry(v)[i]);
|
||||
vec_entry(v)[i] = n;
|
||||
return;
|
||||
}
|
||||
vec_push_back(v, n);
|
||||
}
|
||||
|
||||
// Parse cpio file to a vector of cpio_entry
|
||||
void parse_cpio(struct vector *v, const char *filename) {
|
||||
int fd = open(filename, O_RDONLY);
|
||||
if (fd < 0) return;
|
||||
fprintf(stderr, "Loading cpio: [%s]\n", filename);
|
||||
cpio_newc_header header;
|
||||
cpio_entry *f;
|
||||
while(xxread(fd, &header, 110) != -1) {
|
||||
f = xcalloc(sizeof(*f), 1);
|
||||
// f->ino = x8u(header.ino);
|
||||
f->mode = x8u(header.mode);
|
||||
f->uid = x8u(header.uid);
|
||||
f->gid = x8u(header.gid);
|
||||
// f->nlink = x8u(header.nlink);
|
||||
// f->mtime = x8u(header.mtime);
|
||||
f->filesize = x8u(header.filesize);
|
||||
// f->devmajor = x8u(header.devmajor);
|
||||
// f->devminor = x8u(header.devminor);
|
||||
// f->rdevmajor = x8u(header.rdevmajor);
|
||||
// f->rdevminor = x8u(header.rdevminor);
|
||||
uint32_t namesize = x8u(header.namesize);
|
||||
// f->check = x8u(header.check);
|
||||
f->filename = xmalloc(namesize);
|
||||
xxread(fd, f->filename, namesize);
|
||||
file_align(fd, 4, 0);
|
||||
if (strcmp(f->filename, ".") == 0 || strcmp(f->filename, "..") == 0) {
|
||||
cpio_free(f);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(f->filename, "TRAILER!!!") == 0) {
|
||||
cpio_free(f);
|
||||
break;
|
||||
}
|
||||
if (f->filesize) {
|
||||
f->data = xmalloc(f->filesize);
|
||||
xxread(fd, f->data, f->filesize);
|
||||
file_align(fd, 4, 0);
|
||||
}
|
||||
vec_push_back(v, f);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void dump_cpio(struct vector *v, const char *filename) {
|
||||
fprintf(stderr, "Dump cpio: [%s]\n", filename);
|
||||
unsigned inode = 300000;
|
||||
char header[111];
|
||||
// Sort by name
|
||||
vec_sort(v, cpio_cmp);
|
||||
cpio_entry *e;
|
||||
int fd = creat(filename, 0644);
|
||||
vec_for_each(v, e) {
|
||||
sprintf(header, "070701%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x",
|
||||
inode++, // e->ino
|
||||
e->mode,
|
||||
e->uid,
|
||||
e->gid,
|
||||
1, // e->nlink
|
||||
0, // e->mtime
|
||||
e->filesize,
|
||||
0, // e->devmajor
|
||||
0, // e->devminor
|
||||
0, // e->rdevmajor
|
||||
0, // e->rdevminor
|
||||
(uint32_t) strlen(e->filename) + 1,
|
||||
0 // e->check
|
||||
);
|
||||
xwrite(fd, header, 110);
|
||||
xwrite(fd, e->filename, strlen(e->filename) + 1);
|
||||
file_align(fd, 4, 1);
|
||||
if (e->filesize) {
|
||||
xwrite(fd, e->data, e->filesize);
|
||||
file_align(fd, 4, 1);
|
||||
}
|
||||
}
|
||||
// Write trailer
|
||||
sprintf(header, "070701%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x", inode++, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 11, 0);
|
||||
xwrite(fd, header, 110);
|
||||
xwrite(fd, "TRAILER!!!\0", 11);
|
||||
file_align(fd, 4, 1);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void cpio_vec_destroy(struct vector *v) {
|
||||
// Free each cpio_entry
|
||||
cpio_entry *e;
|
||||
vec_for_each(v, e)
|
||||
cpio_free(e);
|
||||
vec_destroy(v);
|
||||
}
|
||||
|
||||
void cpio_rm(struct vector *v, int recursive, const char *entry) {
|
||||
cpio_entry *e;
|
||||
size_t len = strlen(entry);
|
||||
vec_for_each(v, e) {
|
||||
if (!e) continue;
|
||||
if (strncmp(e->filename, entry, len) == 0) {
|
||||
if ((recursive && e->filename[len] == '/') || e->filename[len] == '\0') {
|
||||
fprintf(stderr, "Remove [%s]\n", e->filename);
|
||||
cpio_free(e);
|
||||
vec_cur(v) = NULL;
|
||||
if (!recursive) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cpio_mkdir(struct vector *v, mode_t mode, const char *entry) {
|
||||
cpio_entry *e = xcalloc(sizeof(*e), 1);
|
||||
e->mode = S_IFDIR | mode;
|
||||
e->filename = strdup(entry);
|
||||
cpio_vec_insert(v, e);
|
||||
fprintf(stderr, "Create directory [%s] (%04o)\n",entry, mode);
|
||||
}
|
||||
|
||||
void cpio_ln(struct vector *v, const char *target, const char *entry) {
|
||||
cpio_entry *e = xcalloc(sizeof(*e), 1);
|
||||
e->mode = S_IFLNK;
|
||||
e->filename = strdup(entry);
|
||||
e->filesize = strlen(target);
|
||||
e->data = strdup(target);
|
||||
cpio_vec_insert(v, e);
|
||||
fprintf(stderr, "Create symlink [%s] -> [%s]\n", entry, target);
|
||||
}
|
||||
|
||||
void cpio_add(struct vector *v, mode_t mode, const char *entry, const char *filename) {
|
||||
int fd = xopen(filename, O_RDONLY);
|
||||
cpio_entry *e = xcalloc(sizeof(*e), 1);
|
||||
e->mode = S_IFREG | mode;
|
||||
e->filename = strdup(entry);
|
||||
e->filesize = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
e->data = xmalloc(e->filesize);
|
||||
xxread(fd, e->data, e->filesize);
|
||||
close(fd);
|
||||
cpio_vec_insert(v, e);
|
||||
fprintf(stderr, "Add entry [%s] (%04o)\n", entry, mode);
|
||||
}
|
||||
|
||||
int cpio_mv(struct vector *v, const char *from, const char *to) {
|
||||
struct cpio_entry *e;
|
||||
int f = cpio_find(v, from), t = cpio_find(v, to);
|
||||
if (f > 0) {
|
||||
if (t > 0) {
|
||||
cpio_free(vec_entry(v)[t]);
|
||||
vec_entry(v)[t] = NULL;
|
||||
}
|
||||
e = vec_entry(v)[f];
|
||||
free(e->filename);
|
||||
e->filename = strdup(to);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "Cannot find entry %s\n", from);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cpio_extract(struct vector *v, const char *entry, const char *filename) {
|
||||
int i = cpio_find(v, entry);
|
||||
if (i > 0) {
|
||||
cpio_entry *e = vec_entry(v)[i];
|
||||
fprintf(stderr, "Extracting [%s] to [%s]\n", entry, filename);
|
||||
if (S_ISREG(e->mode)) {
|
||||
int fd = creat(filename, e->mode & 0777);
|
||||
xwrite(fd, e->data, e->filesize);
|
||||
fchown(fd, e->uid, e->gid);
|
||||
close(fd);
|
||||
} else if (S_ISLNK(e->mode)) {
|
||||
char *target = xcalloc(e->filesize + 1, 1);
|
||||
memcpy(target, e->data, e->filesize);
|
||||
unlink(filename);
|
||||
symlink(target, filename);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "Cannot find the file entry [%s]\n", entry);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cpio_extract_all(struct vector *v) {
|
||||
cpio_entry *e;
|
||||
vec_for_each(v, e) {
|
||||
if (!e) continue;
|
||||
fprintf(stderr, "Extracting [%s]\n", e->filename);
|
||||
unlink(e->filename);
|
||||
rmdir(e->filename);
|
||||
if (S_ISDIR(e->mode)) {
|
||||
mkdir(e->filename, e->mode & 0777);
|
||||
} else if (S_ISREG(e->mode)) {
|
||||
int fd = creat(e->filename, e->mode & 0777);
|
||||
xwrite(fd, e->data, e->filesize);
|
||||
fchown(fd, e->uid, e->gid);
|
||||
close(fd);
|
||||
} else if (S_ISLNK(e->mode)) {
|
||||
char *target = xcalloc(e->filesize + 1, 1);
|
||||
memcpy(target, e->data, e->filesize);
|
||||
symlink(target, e->filename);
|
||||
}
|
||||
}
|
||||
}
|
447
native/jni/utils/file.c
Normal file
447
native/jni/utils/file.c
Normal file
@@ -0,0 +1,447 @@
|
||||
/* file.c - Contains all files related utilities
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#ifdef SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#endif
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
char **excl_list = NULL;
|
||||
|
||||
static int is_excl(const char *name) {
|
||||
if (excl_list)
|
||||
for (int i = 0; excl_list[i]; ++i)
|
||||
if (strcmp(name, excl_list[i]) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd_getpath(int fd, char *path, size_t size) {
|
||||
snprintf(path, size, "/proc/self/fd/%d", fd);
|
||||
if (xreadlink(path, path, size) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mkdirs(const char *pathname, mode_t mode) {
|
||||
char *path = strdup(pathname), *p;
|
||||
errno = 0;
|
||||
for (p = path + 1; *p; ++p) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (mkdir(path, mode) == -1) {
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
if (mkdir(path, mode) == -1) {
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
}
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void in_order_walk(int dirfd, void (*callback)(int, struct dirent*)) {
|
||||
struct dirent *entry;
|
||||
int newfd;
|
||||
DIR *dir = fdopendir(dirfd);
|
||||
if (dir == NULL) return;
|
||||
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
continue;
|
||||
if (is_excl(entry->d_name))
|
||||
continue;
|
||||
if (entry->d_type == DT_DIR) {
|
||||
newfd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
in_order_walk(newfd, callback);
|
||||
close(newfd);
|
||||
}
|
||||
callback(dirfd, entry);
|
||||
}
|
||||
}
|
||||
|
||||
static void rm_cb(int dirfd, struct dirent *entry) {
|
||||
switch (entry->d_type) {
|
||||
case DT_DIR:
|
||||
unlinkat(dirfd, entry->d_name, AT_REMOVEDIR);
|
||||
break;
|
||||
default:
|
||||
unlinkat(dirfd, entry->d_name, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void rm_rf(const char *path) {
|
||||
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd >= 0) {
|
||||
frm_rf(fd);
|
||||
close(fd);
|
||||
}
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void frm_rf(int dirfd) {
|
||||
in_order_walk(dirfd, rm_cb);
|
||||
}
|
||||
|
||||
/* This will only on the same file system */
|
||||
void mv_f(const char *source, const char *destination) {
|
||||
struct stat st;
|
||||
xlstat(source, &st);
|
||||
int src, dest;
|
||||
struct file_attr a;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
xmkdirs(destination, st.st_mode & 0777);
|
||||
src = xopen(source, O_RDONLY | O_CLOEXEC);
|
||||
dest = xopen(destination, O_RDONLY | O_CLOEXEC);
|
||||
fclone_attr(src, dest);
|
||||
mv_dir(src, dest);
|
||||
close(src);
|
||||
close(dest);
|
||||
} else{
|
||||
getattr(source, &a);
|
||||
xrename(source, destination);
|
||||
setattr(destination, &a);
|
||||
}
|
||||
rmdir(source);
|
||||
}
|
||||
|
||||
/* This will only on the same file system */
|
||||
void mv_dir(int src, int dest) {
|
||||
struct dirent *entry;
|
||||
DIR *dir;
|
||||
int newsrc, newdest;
|
||||
struct file_attr a;
|
||||
|
||||
dir = xfdopendir(src);
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
continue;
|
||||
if (is_excl(entry->d_name))
|
||||
continue;
|
||||
getattrat(src, entry->d_name, &a);
|
||||
switch (entry->d_type) {
|
||||
case DT_DIR:
|
||||
xmkdirat(dest, entry->d_name, a.st.st_mode & 0777);
|
||||
newsrc = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
newdest = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
fsetattr(newdest, &a);
|
||||
mv_dir(newsrc, newdest);
|
||||
close(newsrc);
|
||||
close(newdest);
|
||||
unlinkat(src, entry->d_name, AT_REMOVEDIR);
|
||||
break;
|
||||
case DT_LNK:
|
||||
case DT_REG:
|
||||
renameat(src, entry->d_name, dest, entry->d_name);
|
||||
setattrat(dest, entry->d_name, &a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cp_afc(const char *source, const char *destination) {
|
||||
int src, dest;
|
||||
struct file_attr a;
|
||||
getattr(source, &a);
|
||||
|
||||
if (S_ISDIR(a.st.st_mode)) {
|
||||
xmkdirs(destination, a.st.st_mode & 0777);
|
||||
src = xopen(source, O_RDONLY | O_CLOEXEC);
|
||||
dest = xopen(destination, O_RDONLY | O_CLOEXEC);
|
||||
fsetattr(dest, &a);
|
||||
clone_dir(src, dest);
|
||||
close(src);
|
||||
close(dest);
|
||||
} else{
|
||||
unlink(destination);
|
||||
if (S_ISREG(a.st.st_mode)) {
|
||||
src = xopen(source, O_RDONLY);
|
||||
dest = xopen(destination, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
xsendfile(dest, src, NULL, a.st.st_size);
|
||||
fsetattr(src, &a);
|
||||
close(src);
|
||||
close(dest);
|
||||
} else if (S_ISLNK(a.st.st_mode)) {
|
||||
char buf[PATH_MAX];
|
||||
xreadlink(source, buf, sizeof(buf));
|
||||
xsymlink(buf, destination);
|
||||
setattr(destination, &a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clone_dir(int src, int dest) {
|
||||
struct dirent *entry;
|
||||
DIR *dir;
|
||||
int srcfd, destfd, newsrc, newdest;
|
||||
char buf[PATH_MAX];
|
||||
struct file_attr a;
|
||||
|
||||
dir = xfdopendir(src);
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
continue;
|
||||
if (is_excl(entry->d_name))
|
||||
continue;
|
||||
getattrat(src, entry->d_name, &a);
|
||||
switch (entry->d_type) {
|
||||
case DT_DIR:
|
||||
xmkdirat(dest, entry->d_name, a.st.st_mode & 0777);
|
||||
setattrat(dest, entry->d_name, &a);
|
||||
newsrc = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
newdest = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
clone_dir(newsrc, newdest);
|
||||
close(newsrc);
|
||||
close(newdest);
|
||||
break;
|
||||
case DT_REG:
|
||||
destfd = xopenat(dest, entry->d_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC);
|
||||
srcfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
xsendfile(destfd, srcfd, 0, a.st.st_size);
|
||||
fsetattr(destfd, &a);
|
||||
close(destfd);
|
||||
close(srcfd);
|
||||
break;
|
||||
case DT_LNK:
|
||||
xreadlinkat(src, entry->d_name, buf, sizeof(buf));
|
||||
symlinkat(buf, dest, entry->d_name);
|
||||
setattrat(dest, entry->d_name, &a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getattr(const char *path, struct file_attr *a) {
|
||||
if (xlstat(path, &a->st) == -1)
|
||||
return -1;
|
||||
#ifdef SELINUX
|
||||
char *con = "";
|
||||
if (lgetfilecon(path, &con) == -1)
|
||||
return -1;
|
||||
strcpy(a->con, con);
|
||||
freecon(con);
|
||||
#else
|
||||
a->con[0] = '\0';
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getattrat(int dirfd, const char *pathname, struct file_attr *a) {
|
||||
int fd = xopenat(dirfd, pathname, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
int ret = fgetattr(fd, a);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fgetattr(int fd, struct file_attr *a) {
|
||||
#ifdef SELINUX
|
||||
char path[PATH_MAX];
|
||||
fd_getpath(fd, path, sizeof(path));
|
||||
return getattr(path, a);
|
||||
#else
|
||||
if (fstat(fd, &a->st) == -1)
|
||||
return -1;
|
||||
a->con[0] = '\0';
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int setattr(const char *path, struct file_attr *a) {
|
||||
if (chmod(path, a->st.st_mode & 0777) < 0)
|
||||
return -1;
|
||||
if (chown(path, a->st.st_uid, a->st.st_gid) < 0)
|
||||
return -1;
|
||||
#ifdef SELINUX
|
||||
if (strlen(a->con) && lsetfilecon(path, a->con) < 0)
|
||||
return -1;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setattrat(int dirfd, const char *pathname, struct file_attr *a) {
|
||||
int fd = xopenat(dirfd, pathname, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
int ret = fsetattr(fd, a);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fsetattr(int fd, struct file_attr *a) {
|
||||
#ifdef SELINUX
|
||||
char path[PATH_MAX];
|
||||
fd_getpath(fd, path, sizeof(path));
|
||||
return setattr(path, a);
|
||||
#else
|
||||
if (fchmod(fd, a->st.st_mode & 0777) < 0)
|
||||
return -1;
|
||||
if (fchown(fd, a->st.st_uid, a->st.st_gid) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void clone_attr(const char *source, const char *target) {
|
||||
struct file_attr a;
|
||||
getattr(source, &a);
|
||||
setattr(target, &a);
|
||||
}
|
||||
|
||||
void fclone_attr(const int sourcefd, const int targetfd) {
|
||||
struct file_attr a;
|
||||
fgetattr(sourcefd, &a);
|
||||
fsetattr(targetfd, &a);
|
||||
}
|
||||
|
||||
#ifdef SELINUX
|
||||
|
||||
#define UNLABEL_CON "u:object_r:unlabeled:s0"
|
||||
#define SYSTEM_CON "u:object_r:system_file:s0"
|
||||
|
||||
void restorecon(int dirfd, int force) {
|
||||
struct dirent *entry;
|
||||
DIR *dir;
|
||||
int fd;
|
||||
char path[PATH_MAX], *con;
|
||||
|
||||
fd_getpath(dirfd, path, sizeof(path));
|
||||
lgetfilecon(path, &con);
|
||||
if (force || strlen(con) == 0 || strcmp(con, UNLABEL_CON) == 0)
|
||||
lsetfilecon(path, SYSTEM_CON);
|
||||
freecon(con);
|
||||
|
||||
dir = xfdopendir(dirfd);
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
continue;
|
||||
if (entry->d_type == DT_DIR) {
|
||||
fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
restorecon(fd, force);
|
||||
} else {
|
||||
fd = xopenat(dirfd, entry->d_name, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
fd_getpath(fd, path, sizeof(path));
|
||||
lgetfilecon(path, &con);
|
||||
if (force || strlen(con) == 0 || strcmp(con, UNLABEL_CON) == 0)
|
||||
lsetfilecon(path, SYSTEM_CON);
|
||||
freecon(con);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SELINUX
|
||||
|
||||
static int _mmap(int rw, const char *filename, void **buf, size_t *size) {
|
||||
struct stat st;
|
||||
int fd = xopen(filename, rw ? O_RDWR : O_RDONLY);
|
||||
fstat(fd, &st);
|
||||
if (S_ISBLK(st.st_mode))
|
||||
ioctl(fd, BLKGETSIZE64, size);
|
||||
else
|
||||
*size = st.st_size;
|
||||
*buf = *size > 0 ? xmmap(NULL, *size, PROT_READ | (rw ? PROT_WRITE : 0), MAP_SHARED, fd, 0) : NULL;
|
||||
close(fd);
|
||||
return S_ISBLK(st.st_mode);
|
||||
}
|
||||
|
||||
int mmap_ro(const char *filename, void **buf, size_t *size) {
|
||||
return _mmap(0, filename, buf, size);
|
||||
}
|
||||
|
||||
int mmap_rw(const char *filename, void **buf, size_t *size) {
|
||||
return _mmap(1, filename, buf, size);
|
||||
}
|
||||
|
||||
void fd_full_read(int fd, void **buf, size_t *size) {
|
||||
*size = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
*buf = xmalloc(*size);
|
||||
xxread(fd, *buf, *size);
|
||||
}
|
||||
|
||||
void full_read(const char *filename, void **buf, size_t *size) {
|
||||
int fd = xopen(filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
*buf = NULL;
|
||||
*size = 0;
|
||||
return;
|
||||
}
|
||||
fd_full_read(fd, buf, size);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void full_read_at(int dirfd, const char *filename, void **buf, size_t *size) {
|
||||
int fd = xopenat(dirfd, filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
*buf = NULL;
|
||||
*size = 0;
|
||||
return;
|
||||
}
|
||||
fd_full_read(fd, buf, size);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void stream_full_read(int fd, void **buf, size_t *size) {
|
||||
size_t cap = 1 << 20;
|
||||
uint8_t tmp[1 << 20];
|
||||
*buf = xmalloc(cap);
|
||||
ssize_t read;
|
||||
*size = 0;
|
||||
while (1) {
|
||||
read = xread(fd, tmp, sizeof(tmp));
|
||||
if (read <= 0)
|
||||
break;
|
||||
if (*size + read > cap) {
|
||||
cap *= 2;
|
||||
*buf = realloc(*buf, cap);
|
||||
}
|
||||
memcpy(*buf + *size, tmp, read);
|
||||
*size += read;
|
||||
}
|
||||
}
|
||||
|
||||
void write_zero(int fd, size_t size) {
|
||||
size_t pos = lseek(fd, 0, SEEK_CUR);
|
||||
ftruncate(fd, pos + size);
|
||||
lseek(fd, pos + size, SEEK_SET);
|
||||
}
|
||||
|
||||
void mem_align(size_t *pos, size_t align) {
|
||||
size_t mask = align - 1;
|
||||
if (*pos & mask) {
|
||||
*pos += align - (*pos & mask);
|
||||
}
|
||||
}
|
||||
|
||||
void file_align(int fd, size_t align, int out) {
|
||||
size_t pos = lseek(fd, 0, SEEK_CUR);
|
||||
size_t mask = align - 1;
|
||||
size_t off;
|
||||
if (pos & mask) {
|
||||
off = align - (pos & mask);
|
||||
if (out) {
|
||||
write_zero(fd, off);
|
||||
} else {
|
||||
lseek(fd, pos + off, SEEK_SET);
|
||||
}
|
||||
}
|
||||
}
|
233
native/jni/utils/img.c
Normal file
233
native/jni/utils/img.c
Normal file
@@ -0,0 +1,233 @@
|
||||
/* img.c - All image related functions
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mount.h>
|
||||
#include <linux/loop.h>
|
||||
|
||||
#include "magisk.h"
|
||||
#include "utils.h"
|
||||
|
||||
static int e2fsck(const char *img) {
|
||||
// Check and repair ext4 image
|
||||
char buffer[128];
|
||||
int pid, fd = -1;
|
||||
pid = exec_command(1, &fd, NULL, "e2fsck", "-yf", img, NULL);
|
||||
if (pid < 0)
|
||||
return 1;
|
||||
while (fdgets(buffer, sizeof(buffer), fd))
|
||||
LOGD("magisk_img: %s", buffer);
|
||||
waitpid(pid, NULL, 0);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *loopsetup(const char *img) {
|
||||
char device[20];
|
||||
struct loop_info64 info;
|
||||
int i, lfd, ffd;
|
||||
memset(&info, 0, sizeof(info));
|
||||
// First get an empty loop device
|
||||
for (i = 0; i <= 7; ++i) {
|
||||
sprintf(device, "/dev/block/loop%d", i);
|
||||
lfd = xopen(device, O_RDWR);
|
||||
if (ioctl(lfd, LOOP_GET_STATUS64, &info) == -1)
|
||||
break;
|
||||
close(lfd);
|
||||
}
|
||||
if (i == 8) return NULL;
|
||||
ffd = xopen(img, O_RDWR);
|
||||
if (ioctl(lfd, LOOP_SET_FD, ffd) == -1)
|
||||
return NULL;
|
||||
strcpy((char *) info.lo_file_name, img);
|
||||
ioctl(lfd, LOOP_SET_STATUS64, &info);
|
||||
close(lfd);
|
||||
close(ffd);
|
||||
return strdup(device);
|
||||
}
|
||||
|
||||
int create_img(const char *img, int size) {
|
||||
if (size == 128) /* WTF...? */
|
||||
size = 132;
|
||||
unlink(img);
|
||||
LOGI("Create %s with size %dM\n", img, size);
|
||||
int ret;
|
||||
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "%dM", size);
|
||||
ret = exec_command_sync("make_ext4fs", "-l", buffer, img, NULL);
|
||||
if (ret < 0)
|
||||
return 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int get_img_size(const char *img, int *used, int *total) {
|
||||
if (access(img, R_OK) == -1)
|
||||
return 1;
|
||||
char buffer[PATH_MAX];
|
||||
int pid, fd = -1, status = 1;
|
||||
pid = exec_command(1, &fd, NULL, "e2fsck", "-n", img, NULL);
|
||||
if (pid < 0)
|
||||
return 1;
|
||||
while (fdgets(buffer, sizeof(buffer), fd)) {
|
||||
if (strstr(buffer, img)) {
|
||||
char *tok = strtok(buffer, ",");
|
||||
while(tok != NULL) {
|
||||
if (strstr(tok, "blocks")) {
|
||||
status = 0;
|
||||
break;
|
||||
}
|
||||
tok = strtok(NULL, ",");
|
||||
}
|
||||
if (status) continue;
|
||||
sscanf(tok, "%d/%d", used, total);
|
||||
*used = *used / 256 + 1;
|
||||
*total /= 256;
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
waitpid(pid, NULL, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int resize_img(const char *img, int size) {
|
||||
LOGI("Resize %s to %dM\n", img, size);
|
||||
if (e2fsck(img))
|
||||
return 1;
|
||||
char buffer[128];
|
||||
int pid, fd = -1, used, total;
|
||||
snprintf(buffer, sizeof(buffer), "%dM", size);
|
||||
pid = exec_command(1, &fd, NULL, "resize2fs", img, buffer, NULL);
|
||||
if (pid < 0)
|
||||
return 1;
|
||||
while (fdgets(buffer, sizeof(buffer), fd))
|
||||
LOGD("magisk_img: %s", buffer);
|
||||
close(fd);
|
||||
waitpid(pid, NULL, 0);
|
||||
|
||||
// Double check our image size
|
||||
get_img_size(img, &used, &total);
|
||||
if (total != size) {
|
||||
// Sammy crap occurs or resize2fs failed, lets create a new image!
|
||||
char *dir = dirname(img);
|
||||
snprintf(buffer, sizeof(buffer), "%s/tmp.img", dir);
|
||||
create_img(buffer, size);
|
||||
char *s_loop, *t_loop;
|
||||
s_loop = mount_image(img, SOURCE_TMP);
|
||||
if (s_loop == NULL) return 1;
|
||||
t_loop = mount_image(buffer, TARGET_TMP);
|
||||
if (t_loop == NULL) return 1;
|
||||
|
||||
cp_afc(SOURCE_TMP, TARGET_TMP);
|
||||
umount_image(SOURCE_TMP, s_loop);
|
||||
umount_image(TARGET_TMP, t_loop);
|
||||
rmdir(SOURCE_TMP);
|
||||
rmdir(TARGET_TMP);
|
||||
free(s_loop);
|
||||
free(t_loop);
|
||||
rename(buffer, img);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *mount_image(const char *img, const char *target) {
|
||||
if (access(img, F_OK) == -1)
|
||||
return NULL;
|
||||
if (access(target, F_OK) == -1) {
|
||||
if (xmkdirs(target, 0755) == -1) {
|
||||
xmount(NULL, "/", NULL, MS_REMOUNT, NULL);
|
||||
xmkdirs(target, 0755);
|
||||
xmount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (e2fsck(img))
|
||||
return NULL;
|
||||
|
||||
char *device = loopsetup(img);
|
||||
if (device)
|
||||
xmount(device, target, "ext4", 0, NULL);
|
||||
return device;
|
||||
}
|
||||
|
||||
void umount_image(const char *target, const char *device) {
|
||||
xumount(target);
|
||||
int fd = xopen(device, O_RDWR);
|
||||
ioctl(fd, LOOP_CLR_FD);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int merge_img(const char *source, const char *target) {
|
||||
if (access(source, F_OK) == -1)
|
||||
return 0;
|
||||
LOGI("* Merging %s -> %s\n", source, target);
|
||||
if (access(target, F_OK) == -1) {
|
||||
xrename(source, target);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char buffer[PATH_MAX];
|
||||
|
||||
// resize target to worst case
|
||||
int s_used, s_total, t_used, t_total, n_total;
|
||||
get_img_size(source, &s_used, &s_total);
|
||||
get_img_size(target, &t_used, &t_total);
|
||||
n_total = round_size(s_used + t_used);
|
||||
if (n_total > t_total)
|
||||
resize_img(target, n_total);
|
||||
|
||||
xmkdir(SOURCE_TMP, 0755);
|
||||
xmkdir(TARGET_TMP, 0755);
|
||||
char *s_loop, *t_loop;
|
||||
s_loop = mount_image(source, SOURCE_TMP);
|
||||
if (s_loop == NULL) return 1;
|
||||
t_loop = mount_image(target, TARGET_TMP);
|
||||
if (t_loop == NULL) return 1;
|
||||
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
if (!(dir = xopendir(SOURCE_TMP)))
|
||||
return 1;
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
if (strcmp(entry->d_name, ".") == 0 ||
|
||||
strcmp(entry->d_name, "..") == 0 ||
|
||||
strcmp(entry->d_name, ".core") == 0 ||
|
||||
strcmp(entry->d_name, "lost+found") == 0)
|
||||
continue;
|
||||
// Cleanup old module if exists
|
||||
snprintf(buffer, sizeof(buffer), "%s/%s", TARGET_TMP, entry->d_name);
|
||||
if (access(buffer, F_OK) == 0) {
|
||||
LOGI("Upgrade module: %s\n", entry->d_name);
|
||||
rm_rf(buffer);
|
||||
} else {
|
||||
LOGI("New module: %s\n", entry->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
cp_afc(SOURCE_TMP, TARGET_TMP);
|
||||
|
||||
// Unmount all loop devices
|
||||
umount_image(SOURCE_TMP, s_loop);
|
||||
umount_image(TARGET_TMP, t_loop);
|
||||
rmdir(SOURCE_TMP);
|
||||
rmdir(TARGET_TMP);
|
||||
free(s_loop);
|
||||
free(t_loop);
|
||||
unlink(source);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void trim_img(const char *img) {
|
||||
int used, total, new_size;
|
||||
get_img_size(img, &used, &total);
|
||||
new_size = round_size(used);
|
||||
if (new_size != total)
|
||||
resize_img(img, new_size);
|
||||
}
|
39
native/jni/utils/list.c
Normal file
39
native/jni/utils/list.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/* list.h - Double link list implementation
|
||||
*/
|
||||
|
||||
#include "list.h"
|
||||
|
||||
void init_list_head(struct list_head *head) {
|
||||
head->next = head;
|
||||
head->prev = head;
|
||||
}
|
||||
|
||||
void list_insert(struct list_head *pos, struct list_head *node) {
|
||||
// First construct our new node
|
||||
node->next = pos->next;
|
||||
node->prev = pos;
|
||||
// Maintain the list
|
||||
pos->next->prev = node;
|
||||
pos->next = node;
|
||||
}
|
||||
|
||||
void list_insert_end(struct list_head *head, struct list_head *node) {
|
||||
list_insert(head->prev, node);
|
||||
}
|
||||
|
||||
struct list_head *list_pop(struct list_head *pos) {
|
||||
struct list_head *ret;
|
||||
ret = pos->prev;
|
||||
// Maintain the list
|
||||
pos->prev->next = pos->next;
|
||||
pos->next->prev = pos->prev;
|
||||
// Remove references
|
||||
pos->next = pos;
|
||||
pos->prev = pos;
|
||||
// Return the previous node in the list
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct list_head *list_pop_end(struct list_head *head) {
|
||||
return list_pop(head->prev);
|
||||
}
|
343
native/jni/utils/misc.c
Normal file
343
native/jni/utils/misc.c
Normal file
@@ -0,0 +1,343 @@
|
||||
/* misc.c - Store all functions that are unable to be catagorized clearly
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/inotify.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
#include "resetprop.h"
|
||||
|
||||
unsigned get_shell_uid() {
|
||||
struct passwd* ppwd = getpwnam("shell");
|
||||
if (NULL == ppwd)
|
||||
return 2000;
|
||||
|
||||
return ppwd->pw_uid;
|
||||
}
|
||||
|
||||
unsigned get_system_uid() {
|
||||
struct passwd* ppwd = getpwnam("system");
|
||||
if (NULL == ppwd)
|
||||
return 1000;
|
||||
|
||||
return ppwd->pw_uid;
|
||||
}
|
||||
|
||||
unsigned get_radio_uid() {
|
||||
struct passwd* ppwd = getpwnam("radio");
|
||||
if (NULL == ppwd)
|
||||
return 1001;
|
||||
|
||||
return ppwd->pw_uid;
|
||||
}
|
||||
|
||||
int check_data() {
|
||||
struct vector v;
|
||||
vec_init(&v);
|
||||
file_to_vector("/proc/mounts", &v);
|
||||
char *line, *crypto;
|
||||
int mnt = 0;
|
||||
vec_for_each(&v, line) {
|
||||
if (strstr(line, " /data ")) {
|
||||
if (strstr(line, "tmpfs") == NULL)
|
||||
mnt = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
vec_deep_destroy(&v);
|
||||
// /data is mounted and not tmpfs and data is unencrypted or vold is started
|
||||
return mnt && (((crypto = getprop("ro.crypto.state")) && strcmp(crypto, "unencrypted") == 0)
|
||||
|| getprop("init.svc.vold"));
|
||||
}
|
||||
|
||||
/* All the string should be freed manually!! */
|
||||
int file_to_vector(const char* filename, struct vector *v) {
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
|
||||
FILE *fp = xfopen(filename, "r");
|
||||
if (fp == NULL)
|
||||
return 1;
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
// Remove end newline
|
||||
if (line[read - 1] == '\n')
|
||||
line[read - 1] = '\0';
|
||||
vec_push_back(v, line);
|
||||
line = NULL;
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vector_to_file(const char *filename, struct vector *v) {
|
||||
FILE *fp = xfopen(filename, "w");
|
||||
if (fp == NULL)
|
||||
return 1;
|
||||
char *line;
|
||||
vec_for_each(v, line) {
|
||||
fprintf(fp, "%s\n", line);
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if the string only contains digits */
|
||||
static int is_num(const char *s) {
|
||||
int len = strlen(s);
|
||||
for (int i = 0; i < len; ++i)
|
||||
if (s[i] < '0' || s[i] > '9')
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Read a whole line from file descriptor */
|
||||
ssize_t fdgets(char *buf, const size_t size, int fd) {
|
||||
ssize_t len = 0;
|
||||
buf[0] = '\0';
|
||||
while (len < size - 1) {
|
||||
int ret = read(fd, buf + len, 1);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
break;
|
||||
if (buf[len] == '\0' || buf[len++] == '\n') {
|
||||
buf[len] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf[size - 1] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Call func for each process */
|
||||
void ps(void (*func)(int)) {
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
|
||||
if (!(dir = xopendir("/proc")))
|
||||
return;
|
||||
|
||||
while ((entry = xreaddir(dir))) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
if (is_num(entry->d_name))
|
||||
func(atoi(entry->d_name));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// Internal usage
|
||||
static void (*ps_filter_cb)(int);
|
||||
static const char *ps_filter_pattern;
|
||||
static void proc_name_filter(int pid) {
|
||||
char buf[64];
|
||||
int fd;
|
||||
snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid);
|
||||
if (access(buf, R_OK) == -1 || (fd = xopen(buf, O_RDONLY)) == -1)
|
||||
return;
|
||||
if (fdgets(buf, sizeof(buf), fd) == 0) {
|
||||
snprintf(buf, sizeof(buf), "/proc/%d/comm", pid);
|
||||
close(fd);
|
||||
if (access(buf, R_OK) == -1 || (fd = xopen(buf, O_RDONLY)) == -1)
|
||||
return;
|
||||
fdgets(buf, sizeof(buf), fd);
|
||||
}
|
||||
if (strcmp(buf, ps_filter_pattern) == 0) {
|
||||
ps_filter_cb(pid);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* Call func with process name filtered with pattern */
|
||||
void ps_filter_proc_name(const char *pattern, void (*func)(int)) {
|
||||
ps_filter_cb = func;
|
||||
ps_filter_pattern = ((pattern == NULL) ? "" : pattern);
|
||||
ps(proc_name_filter);
|
||||
}
|
||||
|
||||
void unlock_blocks() {
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
int fd, dev, OFF = 0;
|
||||
|
||||
if ((dev = xopen("/dev/block", O_RDONLY | O_CLOEXEC)) < 0)
|
||||
return;
|
||||
dir = xfdopendir(dev);
|
||||
|
||||
while((entry = readdir(dir))) {
|
||||
if (entry->d_type == DT_BLK) {
|
||||
if ((fd = openat(dev, entry->d_name, O_RDONLY)) < 0)
|
||||
continue;
|
||||
if (ioctl(fd, BLKROSET, &OFF) == -1)
|
||||
PLOGE("unlock %s", entry->d_name);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
close(dev);
|
||||
}
|
||||
|
||||
void setup_sighandlers(void (*handler)(int)) {
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = handler;
|
||||
for (int i = 0; quit_signals[i]; ++i) {
|
||||
sigaction(quit_signals[i], &act, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fd == NULL -> Ignore output
|
||||
*fd < 0 -> Open pipe and set *fd to the read end
|
||||
*fd >= 0 -> STDOUT (or STDERR) will be redirected to *fd
|
||||
*cb -> A callback function which runs after fork
|
||||
*/
|
||||
static int v_exec_command(int err, int *fd, void (*setupenv)(struct vector*), const char *argv0, va_list argv) {
|
||||
int pipefd[2], writeEnd = -1;
|
||||
|
||||
if (fd) {
|
||||
if (*fd < 0) {
|
||||
if (xpipe2(pipefd, O_CLOEXEC) == -1)
|
||||
return -1;
|
||||
writeEnd = pipefd[1];
|
||||
} else {
|
||||
writeEnd = *fd;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect va_list into vector
|
||||
struct vector args;
|
||||
vec_init(&args);
|
||||
vec_push_back(&args, strdup(argv0));
|
||||
for (void *arg = va_arg(argv, void*); arg; arg = va_arg(argv, void*))
|
||||
vec_push_back(&args, strdup(arg));
|
||||
vec_push_back(&args, NULL);
|
||||
|
||||
// Setup environment
|
||||
char *const *envp;
|
||||
struct vector env;
|
||||
vec_init(&env);
|
||||
if (setupenv) {
|
||||
setupenv(&env);
|
||||
envp = (char **) vec_entry(&env);
|
||||
} else {
|
||||
extern char **environ;
|
||||
envp = environ;
|
||||
}
|
||||
|
||||
int pid = xfork();
|
||||
if (pid != 0) {
|
||||
if (fd && *fd < 0) {
|
||||
// Give the read end and close write end
|
||||
*fd = pipefd[0];
|
||||
close(pipefd[1]);
|
||||
}
|
||||
vec_deep_destroy(&args);
|
||||
vec_deep_destroy(&env);
|
||||
return pid;
|
||||
}
|
||||
|
||||
if (fd) {
|
||||
xdup2(writeEnd, STDOUT_FILENO);
|
||||
if (err) xdup2(writeEnd, STDERR_FILENO);
|
||||
}
|
||||
|
||||
execvpe(argv0, (char **) vec_entry(&args), envp);
|
||||
PLOGE("execvpe");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int exec_command_sync(char *const argv0, ...) {
|
||||
va_list argv;
|
||||
va_start(argv, argv0);
|
||||
int pid, status;
|
||||
pid = v_exec_command(0, NULL, NULL, argv0, argv);
|
||||
va_end(argv);
|
||||
if (pid < 0)
|
||||
return pid;
|
||||
waitpid(pid, &status, 0);
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int exec_command(int err, int *fd, void (*setupenv)(struct vector*), const char *argv0, ...) {
|
||||
va_list argv;
|
||||
va_start(argv, argv0);
|
||||
int pid = v_exec_command(err, fd, setupenv, argv0, argv);
|
||||
va_end(argv);
|
||||
return pid;
|
||||
}
|
||||
|
||||
int bind_mount(const char *from, const char *to) {
|
||||
int ret = xmount(from, to, NULL, MS_BIND, NULL);
|
||||
#ifdef MAGISK_DEBUG
|
||||
LOGI("bind_mount: %s <- %s\n", to, from);
|
||||
#else
|
||||
LOGI("bind_mount: %s\n", to);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
void get_client_cred(int fd, struct ucred *cred) {
|
||||
socklen_t ucred_length = sizeof(*cred);
|
||||
if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &ucred_length))
|
||||
PLOGE("getsockopt");
|
||||
}
|
||||
|
||||
int switch_mnt_ns(int pid) {
|
||||
char mnt[32];
|
||||
snprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid);
|
||||
if(access(mnt, R_OK) == -1) return 1; // Maybe process died..
|
||||
|
||||
int fd, ret;
|
||||
fd = xopen(mnt, O_RDONLY);
|
||||
if (fd < 0) return 1;
|
||||
// Switch to its namespace
|
||||
ret = xsetns(fd, 0);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fork_dont_care() {
|
||||
int pid = xfork();
|
||||
if (pid) {
|
||||
waitpid(pid, NULL, 0);
|
||||
return pid;
|
||||
} else if ((pid = xfork())) {
|
||||
exit(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wait_till_exists(const char *target) {
|
||||
if (access(target, F_OK) == 0)
|
||||
return;
|
||||
int fd = inotify_init();
|
||||
char *dir = dirname(target);
|
||||
char crap[PATH_MAX];
|
||||
inotify_add_watch(fd, dir, IN_CREATE);
|
||||
while (1) {
|
||||
struct inotify_event event;
|
||||
read(fd, &event, sizeof(event));
|
||||
read(fd, crap, event.len);
|
||||
if (access(target, F_OK) == 0)
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
}
|
99
native/jni/utils/pattern.c
Normal file
99
native/jni/utils/pattern.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
static int check_verity_pattern(const char *s) {
|
||||
int skip = 0;
|
||||
if (s[0] == ',') ++skip;
|
||||
if (strncmp(s + skip, "verify", 6) == 0)
|
||||
skip += 6;
|
||||
else if (strncmp(s + skip, "avb", 3) == 0)
|
||||
skip += 3;
|
||||
else
|
||||
return -1;
|
||||
|
||||
if (s[skip] == '=') {
|
||||
while (s[skip] != '\0' && s[skip] != ' ' && s[skip] != '\n' && s[skip] != ',') ++skip;
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
|
||||
static int check_encryption_pattern(const char *s) {
|
||||
const char *encrypt_list[] = { "forceencrypt", "forcefdeorfbe", NULL };
|
||||
for (int i = 0 ; encrypt_list[i]; ++i) {
|
||||
int len = strlen(encrypt_list[i]);
|
||||
if (strncmp(s, encrypt_list[i], len) == 0)
|
||||
return len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void patch_init_rc(void **buf, size_t *size) {
|
||||
int injected = 0;
|
||||
char *new_data = malloc(*size + 23);
|
||||
char *old_data = *buf;
|
||||
size_t pos = 0;
|
||||
|
||||
for (char *tok = strsep(&old_data, "\n"); tok; tok = strsep(&old_data, "\n")) {
|
||||
if (!injected && strncmp(tok, "import", 6) == 0) {
|
||||
if (strstr(tok, "init.magisk.rc")) {
|
||||
injected = 1;
|
||||
} else {
|
||||
strcpy(new_data + pos, "import /init.magisk.rc\n");
|
||||
pos += 23;
|
||||
injected = 1;
|
||||
}
|
||||
} else if (strstr(tok, "selinux.reload_policy")) {
|
||||
continue;
|
||||
}
|
||||
// Copy the line
|
||||
strcpy(new_data + pos, tok);
|
||||
pos += strlen(tok);
|
||||
new_data[pos++] = '\n';
|
||||
}
|
||||
|
||||
free(*buf);
|
||||
*size = pos;
|
||||
*buf = new_data;
|
||||
}
|
||||
|
||||
int patch_verity(void **buf, uint32_t *size, int patch) {
|
||||
int skip, src_size = *size;
|
||||
char *src = *buf, *patched = patch ? xcalloc(src_size, 1) : NULL;
|
||||
for (int read = 0, write = 0; read < src_size; ++read, ++write) {
|
||||
if ((skip = check_verity_pattern(src + read)) > 0) {
|
||||
if (!patch)
|
||||
return 1;
|
||||
fprintf(stderr, "Remove pattern [%.*s]\n", skip, src + read);
|
||||
read += skip;
|
||||
*size -= skip;
|
||||
}
|
||||
if (patch)
|
||||
patched[write] = src[read];
|
||||
}
|
||||
if (patch) {
|
||||
free(*buf);
|
||||
*buf = patched;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void patch_encryption(void **buf, uint32_t *size) {
|
||||
int skip, src_size = *size;
|
||||
char *src = *buf, *patched = xcalloc(src_size, 1);
|
||||
for (int read = 0, write = 0; read < src_size; ++read, ++write) {
|
||||
if ((skip = check_encryption_pattern(src + read)) > 0) {
|
||||
fprintf(stderr, "Replace pattern [%.*s] with [encryptable]\n", skip, src + read);
|
||||
memcpy(patched + read, "encryptable", 11);
|
||||
read += skip;
|
||||
write += 11;
|
||||
*size -= (skip - 11);
|
||||
}
|
||||
patched[write] = src[read];
|
||||
}
|
||||
free(*buf);
|
||||
*buf = patched;
|
||||
}
|
||||
|
||||
|
83
native/jni/utils/vector.c
Normal file
83
native/jni/utils/vector.c
Normal file
@@ -0,0 +1,83 @@
|
||||
/* vector.c - A simple vector implementation in c
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
void vec_init(struct vector *v) {
|
||||
if (v == NULL) return;
|
||||
vec_size(v) = 0;
|
||||
vec_cap(v) = 1;
|
||||
vec_entry(v) = malloc(sizeof(void*));
|
||||
}
|
||||
|
||||
void vec_push_back(struct vector *v, void *p) {
|
||||
if (v == NULL) return;
|
||||
if (vec_size(v) == vec_cap(v)) {
|
||||
vec_cap(v) *= 2;
|
||||
vec_entry(v) = realloc(vec_entry(v), sizeof(void*) * vec_cap(v));
|
||||
}
|
||||
vec_entry(v)[vec_size(v)] = p;
|
||||
++vec_size(v);
|
||||
}
|
||||
|
||||
void *vec_pop_back(struct vector *v) {
|
||||
void *ret = vec_entry(v)[vec_size(v) - 1];
|
||||
--vec_size(v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int (*cmp)(const void *, const void *);
|
||||
|
||||
static int vec_comp(const void *a, const void *b) {
|
||||
void *aa = *((void **)a), *bb = *((void **)b);
|
||||
if (aa == NULL && bb == NULL) return 0;
|
||||
else if (aa == NULL) return 1;
|
||||
else if (bb == NULL) return -1;
|
||||
else return cmp ? cmp(aa, bb) : 0;
|
||||
}
|
||||
|
||||
void vec_sort(struct vector *v, int (*compar)(const void *, const void *)) {
|
||||
if (v == NULL) return;
|
||||
cmp = compar;
|
||||
qsort(vec_entry(v), vec_size(v), sizeof(void*), vec_comp);
|
||||
void *e;
|
||||
vec_for_each_r(v, e) {
|
||||
if (e) break;
|
||||
--vec_size(v);
|
||||
}
|
||||
}
|
||||
|
||||
/* Will cleanup only the vector itself
|
||||
* use in cases when each element requires special cleanup
|
||||
*/
|
||||
void vec_destroy(struct vector *v) {
|
||||
if (v == NULL) return;
|
||||
vec_size(v) = 0;
|
||||
vec_cap(v) = 0;
|
||||
free(vec_entry(v));
|
||||
vec_entry(v) = NULL; // Prevent double destroy segfault
|
||||
}
|
||||
|
||||
/* Will cleanup each element AND the vector itself
|
||||
* Shall be the general case
|
||||
*/
|
||||
void vec_deep_destroy(struct vector *v) {
|
||||
if (v == NULL) return;
|
||||
void *e;
|
||||
vec_for_each(v, e) {
|
||||
free(e);
|
||||
}
|
||||
vec_destroy(v);
|
||||
}
|
||||
|
||||
struct vector *vec_dup(struct vector *v) {
|
||||
struct vector *ret = malloc(sizeof(*ret));
|
||||
vec_size(ret) = vec_size(v);
|
||||
vec_cap(ret) = vec_cap(v);
|
||||
vec_entry(v) = malloc(sizeof(void*) * vec_cap(ret));
|
||||
memcpy(vec_entry(ret), vec_entry(v), sizeof(void*) * vec_cap(ret));
|
||||
return ret;
|
||||
}
|
357
native/jni/utils/xwrap.c
Normal file
357
native/jni/utils/xwrap.c
Normal file
@@ -0,0 +1,357 @@
|
||||
/* xwrap.c - wrappers around existing library functions.
|
||||
*
|
||||
* Functions with the x prefix are wrappers that either succeed or log the
|
||||
* error message. They usually have the same arguments and return value
|
||||
* as the function they wrap.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sendfile.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
|
||||
FILE *xfopen(const char *pathname, const char *mode) {
|
||||
FILE *fp = fopen(pathname, mode);
|
||||
if (fp == NULL) {
|
||||
PLOGE("fopen: %s", pathname);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
FILE *xfdopen(int fd, const char *mode) {
|
||||
FILE *fp = fdopen(fd, mode);
|
||||
if (fp == NULL) {
|
||||
PLOGE("fopen");
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
int xopen2(const char *pathname, int flags) {
|
||||
int fd = open(pathname, flags);
|
||||
if (fd < 0) {
|
||||
PLOGE("open: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xopen3(const char *pathname, int flags, mode_t mode) {
|
||||
int fd = open(pathname, flags, mode);
|
||||
if (fd < 0) {
|
||||
PLOGE("open: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xopenat(int dirfd, const char *pathname, int flags) {
|
||||
int fd = openat(dirfd, pathname, flags);
|
||||
if (fd < 0) {
|
||||
PLOGE("openat: %s", pathname);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
ssize_t xwrite(int fd, const void *buf, size_t count) {
|
||||
int ret = write(fd, buf, count);
|
||||
if (count != ret) {
|
||||
PLOGE("write");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Read error other than EOF
|
||||
ssize_t xread(int fd, void *buf, size_t count) {
|
||||
int ret = read(fd, buf, count);
|
||||
if (ret < 0) {
|
||||
PLOGE("read");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Read exact same size as count
|
||||
ssize_t xxread(int fd, void *buf, size_t count) {
|
||||
int ret = read(fd, buf, count);
|
||||
if (count != ret) {
|
||||
PLOGE("read");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xpipe2(int pipefd[2], int flags) {
|
||||
int ret = pipe2(pipefd, flags);
|
||||
if (ret == -1) {
|
||||
PLOGE("pipe2");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xsetns(int fd, int nstype) {
|
||||
int ret = setns(fd, nstype);
|
||||
if (ret == -1) {
|
||||
PLOGE("setns");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
DIR *xopendir(const char *name) {
|
||||
DIR *d = opendir(name);
|
||||
if (d == NULL) {
|
||||
PLOGE("opendir: %s", name);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
DIR *xfdopendir(int fd) {
|
||||
DIR *d = fdopendir(fd);
|
||||
if (d == NULL) {
|
||||
PLOGE("fdopendir");
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
struct dirent *xreaddir(DIR *dirp) {
|
||||
errno = 0;
|
||||
struct dirent *e = readdir(dirp);
|
||||
if (errno && e == NULL) {
|
||||
PLOGE("readdir");
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
pid_t xsetsid() {
|
||||
pid_t pid = setsid();
|
||||
if (pid == -1) {
|
||||
PLOGE("setsid");
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
int xsocket(int domain, int type, int protocol) {
|
||||
int fd = socket(domain, type, protocol);
|
||||
if (fd == -1) {
|
||||
PLOGE("socket");
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int ret = bind(sockfd, addr, addrlen);
|
||||
if (ret == -1) {
|
||||
PLOGE("bind");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xlisten(int sockfd, int backlog) {
|
||||
int ret = listen(sockfd, backlog);
|
||||
if (ret == -1) {
|
||||
PLOGE("listen");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
|
||||
int fd = accept4(sockfd, addr, addrlen, flags);
|
||||
if (fd == -1) {
|
||||
PLOGE("accept");
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
void *xmalloc(size_t size) {
|
||||
void *p = malloc(size);
|
||||
if (p == NULL) {
|
||||
PLOGE("malloc");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void *xcalloc(size_t nmemb, size_t size) {
|
||||
void *p = calloc(nmemb, size);
|
||||
if (p == NULL) {
|
||||
PLOGE("calloc");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void *xrealloc(void *ptr, size_t size) {
|
||||
void *p = realloc(ptr, size);
|
||||
if (p == NULL) {
|
||||
PLOGE("realloc");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
ssize_t xsendmsg(int sockfd, const struct msghdr *msg, int flags) {
|
||||
int sent = sendmsg(sockfd, msg, flags);
|
||||
if (sent == -1) {
|
||||
PLOGE("sendmsg");
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) {
|
||||
int rec = recvmsg(sockfd, msg, flags);
|
||||
if (rec == -1) {
|
||||
PLOGE("recvmsg");
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
int xpthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine) (void *), void *arg) {
|
||||
errno = pthread_create(thread, attr, start_routine, arg);
|
||||
if (errno) {
|
||||
PLOGE("pthread_create");
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
int xstat(const char *pathname, struct stat *buf) {
|
||||
int ret = stat(pathname, buf);
|
||||
if (ret == -1) {
|
||||
PLOGE("stat %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xlstat(const char *pathname, struct stat *buf) {
|
||||
int ret = lstat(pathname, buf);
|
||||
if (ret == -1) {
|
||||
PLOGE("lstat %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xdup2(int oldfd, int newfd) {
|
||||
int ret = dup2(oldfd, newfd);
|
||||
if (ret == -1) {
|
||||
PLOGE("dup2");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t xreadlink(const char *pathname, char *buf, size_t bufsiz) {
|
||||
ssize_t ret = readlink(pathname, buf, bufsiz);
|
||||
if (ret == -1) {
|
||||
PLOGE("readlink %s", pathname);
|
||||
} else {
|
||||
buf[ret] = '\0';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t xreadlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) {
|
||||
ssize_t ret = readlinkat(dirfd, pathname, buf, bufsiz);
|
||||
if (ret == -1) {
|
||||
PLOGE("readlinkat %s", pathname);
|
||||
} else {
|
||||
buf[ret] = '\0';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xsymlink(const char *target, const char *linkpath) {
|
||||
int ret = symlink(target, linkpath);
|
||||
if (ret == -1) {
|
||||
PLOGE("symlink %s->%s", target, linkpath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmount(const char *source, const char *target,
|
||||
const char *filesystemtype, unsigned long mountflags,
|
||||
const void *data) {
|
||||
int ret = mount(source, target, filesystemtype, MS_SILENT | mountflags, data);
|
||||
if (ret == -1) {
|
||||
PLOGE("mount %s->%s", source, target);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xumount(const char *target) {
|
||||
int ret = umount(target);
|
||||
if (ret == -1) {
|
||||
PLOGE("umount %s", target);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xumount2(const char *target, int flags) {
|
||||
int ret = umount2(target, flags);
|
||||
if (ret == -1) {
|
||||
PLOGE("umount2 %s", target);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xrename(const char *oldpath, const char *newpath) {
|
||||
int ret = rename(oldpath, newpath);
|
||||
if (ret == -1) {
|
||||
PLOGE("rename %s->%s", oldpath, newpath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmkdir(const char *pathname, mode_t mode) {
|
||||
int ret = mkdir(pathname, mode);
|
||||
if (ret == -1 && errno != EEXIST) {
|
||||
PLOGE("mkdir %s %u", pathname, mode);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmkdirs(const char *pathname, mode_t mode) {
|
||||
int ret = mkdirs(pathname, mode);
|
||||
if (ret == -1) {
|
||||
PLOGE("mkdirs %s", pathname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int xmkdirat(int dirfd, const char *pathname, mode_t mode) {
|
||||
int ret = mkdirat(dirfd, pathname, mode);
|
||||
if (ret == -1 && errno != EEXIST) {
|
||||
PLOGE("mkdirat %s %u", pathname, mode);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *xmmap(void *addr, size_t length, int prot, int flags,
|
||||
int fd, off_t offset) {
|
||||
void *ret = mmap(addr, length, prot, flags, fd, offset);
|
||||
if (ret == MAP_FAILED) {
|
||||
PLOGE("mmap");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count) {
|
||||
ssize_t ret = sendfile(out_fd, in_fd, offset, count);
|
||||
if (count != ret) {
|
||||
PLOGE("sendfile");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
pid_t xfork() {
|
||||
int ret = fork();
|
||||
if (ret == -1) {
|
||||
PLOGE("fork");
|
||||
}
|
||||
return ret;
|
||||
}
|
Reference in New Issue
Block a user