Commit 3dfcc6b0 authored by topjohnwu's avatar topjohnwu

Checkout from seSuperuser/Superuser, leaving only native parts

- Checkout from https://github.com/seSuperuser/Superuser (commit: 69f84dd7a035b4a9f18dea69d9e0452bf0f73103)
- Move Superuser/Superuser/jni/su/* to root
- Move Superuser/jni/sqlite3/* to sqlite3
parents
/*
** 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.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <stdarg.h>
#include "su.h"
/* intent actions */
#define ACTION_REQUEST "start", "-n", REQUESTOR "/" REQUESTOR_PREFIX ".RequestActivity"
#define ACTION_NOTIFY "start", "-n", REQUESTOR "/" REQUESTOR_PREFIX ".NotifyActivity"
#define ACTION_RESULT "broadcast", "-n", REQUESTOR "/" REQUESTOR_PREFIX ".SuReceiver"
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
// TODO: leverage this with exec_log?
int silent_run(char* const args[]) {
set_identity(0);
pid_t pid;
pid = fork();
/* Parent */
if (pid < 0) {
PLOGE("fork");
return -1;
}
else if (pid > 0) {
return 0;
}
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);
setenv("CLASSPATH", "/system/framework/am.jar", 1);
execv(args[0], args);
PLOGE("exec am");
_exit(EXIT_FAILURE);
return -1;
}
int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) {
int needs_owner_login_prompt = 0;
if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) {
if (0 != ctx->user.android_user_id) {
needs_owner_login_prompt = 1;
}
snprintf(user, user_len, "0");
}
else if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) {
snprintf(user, user_len, "%d", ctx->user.android_user_id);
}
else if (ctx->user.multiuser_mode == MULTIUSER_MODE_NONE) {
user[0] = '\0';
}
else {
snprintf(user, user_len, "0");
}
return needs_owner_login_prompt;
}
int send_result(struct su_context *ctx, policy_t policy) {
char binary_version[256];
sprintf(binary_version, "%d", VERSION_CODE);
char uid[256];
sprintf(uid, "%d", ctx->from.uid);
char desired_uid[256];
sprintf(desired_uid, "%d", ctx->to.uid);
char user[64];
get_owner_login_user_args(ctx, user, sizeof(user));
if (0 != ctx->user.android_user_id) {
char android_user_id[256];
sprintf(android_user_id, "%d", ctx->user.android_user_id);
char *user_result_command[] = {
AM_PATH,
ACTION_RESULT,
"--ei",
"binary_version",
binary_version,
"--es",
"from_name",
ctx->from.name,
"--es",
"desired_name",
ctx->to.name,
"--ei",
"uid",
uid,
"--ei",
"desired_uid",
desired_uid,
"--es",
"command",
get_command(&ctx->to),
"--es",
"action",
policy == ALLOW ? "allow" : "deny",
user[0] ? "--user" : NULL,
android_user_id,
NULL
};
silent_run(user_result_command);
}
char *result_command[] = {
AM_PATH,
ACTION_RESULT,
"--ei",
"binary_version",
binary_version,
"--es",
"from_name",
ctx->from.name,
"--es",
"desired_name",
ctx->to.name,
"--ei",
"uid",
uid,
"--ei",
"desired_uid",
desired_uid,
"--es",
"command",
get_command(&ctx->to),
"--es",
"action",
policy == ALLOW ? "allow" : "deny",
user[0] ? "--user" : NULL,
user,
NULL
};
return silent_run(result_command);
}
int send_request(struct su_context *ctx) {
// if su is operating in MULTIUSER_MODEL_OWNER,
// and the user requestor is not the owner,
// the owner needs to be notified of the request.
// so there will be two activities shown.
char user[64];
int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user));
int ret;
if (needs_owner_login_prompt) {
char uid[256];
sprintf(uid, "%d", ctx->from.uid);
char android_user_id[256];
sprintf(android_user_id, "%d", ctx->user.android_user_id);
// in multiuser mode, the owner gets the su prompt
char *notify_command[] = {
AM_PATH,
ACTION_NOTIFY,
"--ei",
"caller_uid",
uid,
"--user",
android_user_id,
NULL
};
int ret = silent_run(notify_command);
if (ret) {
return ret;
}
}
char *request_command[] = {
AM_PATH,
ACTION_REQUEST,
"--es",
"socket",
ctx->sock_path,
user[0] ? "--user" : NULL,
user,
NULL
};
return silent_run(request_command);
}
/*
Copyright 2016, Pierre-Hugues Husson <phh@phh.me>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define _GNU_SOURCE
#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 <strings.h>
#include <stdint.h>
#include <pwd.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <sys/types.h>
#include <selinux/selinux.h>
#include <arpa/inet.h>
#include "binds.h"
int bind_foreach(bind_cb cb, void* arg) {
int res = 0;
char *str = NULL;
int fd = open("/data/su/binds", O_RDONLY);
if(fd<0)
return 1;
off_t size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
str = malloc(size);
if(read(fd, str, size) != size)
goto error;
char *base = str;
while(base < str+size) {
char *parse_src, *parse_dst;
int uid;
char *ptr = memchr(base, 0, size-(base-str));
if(ptr == NULL)
goto error;
sscanf(base, "%d", &uid);
parse_src = strchr(base, ':');
if(!parse_src)
goto error;
parse_src++;
parse_dst = strchr(parse_src, ':');
if(!parse_dst)
goto error;
*parse_dst = 0; // Split parse_src string
parse_dst++;
cb(arg, uid, parse_src, parse_dst);
base = ptr+1;
}
res = 1;
error:
if(str) free(str);
close(fd);
return res;
}
int bind_uniq_dst(const char *dst) {
static int _res;
static const char *_dst;
_res = 1;
_dst = dst;
auto void cb(void *arg, int uid, const char *src, const char *dst) {
if(strcmp(dst, _dst) == 0)
_res = 0;
}
if(!bind_foreach(cb, NULL))
return 0;
return _res;
}
void bind_ls(int uid) {
static int _uid;
_uid=uid;
auto void cb(void *arg, int uid, const char *src, const char *dst) {
if(_uid == 0 || _uid == 2000 || _uid == uid) {
fprintf(stderr, "%d %s => %s\n", uid, src, dst);
}
}
bind_foreach(cb, NULL);
}
int bind_remove(const char *path, int uid) {
static int _found = 0;
static const char *_path;
static int _fd;
static int _uid;
_path = path;
_found = 0;
_uid = uid;
unlink("/data/su/bind.new");
_fd = open("/data/su/bind.new", O_WRONLY|O_CREAT, 0600);
if(_fd<0)
return 0;
auto void cb(void *arg, int uid, const char *src, const char *dst) {
//The one we want to drop
if(strcmp(dst, _path) == 0 &&
(_uid == 0 || _uid == 2000 || _uid == uid)) {
_found = 1;
return;
}
char *str = NULL;
int len = asprintf(&str, "%d:%s:%s", uid, src, dst);
write(_fd, str, len+1); //len doesn't include final \0 and we want to write it
free(str);
}
bind_foreach(cb, NULL);
close(_fd);
unlink("/data/su/bind");
rename("/data/su/bind.new", "/data/su/bind");
return _found;
}
int init_foreach(init_cb icb, void* arg) {
int res = 0;
char *str = NULL;
int fd = open("/data/su/init", O_RDONLY);
if(fd<0)
return 1;
off_t size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
str = malloc(size);
if(read(fd, str, size) != size)
goto error;
char *base = str;
while(base < str+size) {
char *parsed;
int uid;
char *ptr = memchr(base, 0, size-(base-str));
if(ptr == NULL)
goto error;
sscanf(base, "%d", &uid);
parsed = strchr(base, ':');
if(!parsed)
goto error;
parsed++;
icb(arg, uid, parsed);
base = ptr+1;
}
res = 1;
error:
if(str) free(str);
close(fd);
return res;
}
int init_uniq(const char *path) {
static int _res;
static const char *_path;
_res = 1;
_path = path;
auto void cb(void *arg, int uid, const char *path) {
if(strcmp(path, _path) == 0)
_res = 0;
}
if(!init_foreach(cb, NULL))
return 0;
return _res;
}
int init_remove(const char *path, int uid) {
static int _found = 0;
static const char *_path;
static int fd;
static int _uid;
_path = path;
_found = 0;
_uid = uid;
unlink("/data/su/init.new");
fd = open("/data/su/init.new", O_WRONLY|O_CREAT, 0600);
if(fd<0)
return 0;
auto void cb(void *arg, int uid, const char *path) {
//The one we want to drop
if(strcmp(path, _path) == 0 &&
(_uid == 0 || _uid == 2000 || uid == _uid)) {
_found = 1;
return;
}
char *str = NULL;
int len = asprintf(&str, "%d:%s", uid, path);
write(fd, str, len+1); //len doesn't include final \0 and we want to write it
free(str);
}
init_foreach(cb, NULL);
close(fd);
unlink("/data/su/init");
rename("/data/su/init.new", "/data/su/init");
return _found;
}
void init_ls(int uid) {
static int _uid;
_uid = uid;
auto void cb(void *arg, int uid, const char *path) {
if(_uid == 2000 || _uid == 0 || _uid == uid)
fprintf(stderr, "%d %s\n", uid, path);
}
init_foreach(cb, NULL);
}
/*
Copyright 2016, Pierre-Hugues Husson <phh@phh.me>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef BINDSH
#define BINDS_H
typedef void (*bind_cb)(void *arg, int uid, const char *src, const char *dst);
extern int bind_foreach(bind_cb cb, void* arg);
extern int bind_uniq_dst(const char *dst);
extern int bind_remove(const char *path, int uid);
extern void bind_ls(int uid);
typedef void (*init_cb)(void *arg, int uid, const char *path);
extern int init_foreach(init_cb cb, void* arg);
extern int init_uniq(const char *dst);
extern int init_remove(const char *path, int uid);
extern void init_ls(int uid);
#endif /* BINDS_H */
/*
** 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 <sys/sendfile.h>
#include <stdarg.h>
#include <sys/types.h>
#include <pthread.h>
#include <sched.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <selinux/selinux.h>
#ifdef SUPERUSER_EMBEDDED
#include <cutils/multiuser.h>
#endif
#include "binds.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:
LOGE("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)) {
LOGE("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) {
LOGE("invalid string length %d", len);
exit(-1);
}
char* val = malloc(sizeof(char) * (len + 1));
if (val == NULL) {
LOGE("unable to malloc string");
exit(-1);
}
val[len] = '\0';
int amount = read(fd, val, len);
if (amount != len) {
LOGE("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);
}
}
#ifdef SUPERUSER_EMBEDDED
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");
}
}
#endif
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_nodaemon(argc, argv);
}
static int daemon_accept(int fd) {
is_daemon = 1;
int pid = read_int(fd);
LOGD("remote pid: %d", pid);
char *pts_slave = read_string(fd);
LOGD("remote pts_slave: %s", pts_slave);
daemon_from_uid = read_int(fd);
LOGD("remote uid: %d", daemon_from_uid);
daemon_from_pid = read_int(fd);
LOGD("remote req pid: %d", daemon_from_pid);
struct ucred credentials;
int ucred_length = sizeof(struct ucred);
/* fill in the user data structure */
if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
LOGE("could obtain credentials from unix domain socket");
exit(-1);
}
// if the credentials on the other side of the wire are NOT root,
// we can't trust anything being sent.
if (credentials.uid != 0) {
daemon_from_uid = credentials.uid;
pid = credentials.pid;
daemon_from_pid = credentials.pid;
}
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) {
LOGE("unable to allocate args: %d", argc);
exit(-1);
}
LOGD("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) {
// 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) {
// In parent, wait for the child to exit, and send the exit code
// across the wire.
int status, code;
free(pts_slave);
LOGD("waiting for child exit");
if (waitpid(child, &status, 0) > 0) {
code = WEXITSTATUS(status);
}
else {
code = -1;
}
// Pass the return code back to the client
LOGD("sending code");
if (write(fd, &code, sizeof(int)) != sizeof(int)) {
PLOGE("unable to write exit code");
}
close(fd);
LOGD("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]) {
//Check pts_slave file is owned by daemon_from_uid
{
struct stat stbuf;
int res = stat(pts_slave, &stbuf);
if(res) {
PLOGE("stat(pts_slave) daemon");
exit(-1);
}
//If caller is not root, ensure the owner of pts_slave is the caller
if(stbuf.st_uid != credentials.uid &&
credentials.uid != 0) {
PLOGE("Wrong permission of pts_slave");
exit(-1);
}
}
// 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);
}
//Check we haven't been fooled
{
struct stat stbuf;
int res = fstat(ptsfd, &stbuf);
if(res) {
//If we have been fooled DO NOT WRITE ANYTHING
_exit(2);
}
if(stbuf.st_uid != credentials.uid &&
credentials.uid != 0) {
_exit(2);
}
}
if (infd < 0) {
LOGD("daemon: stdin using PTY");
infd = ptsfd;
}
if (outfd < 0) {
LOGD("daemon: stdout using PTY");
outfd = ptsfd;
}
if (errfd < 0) {
LOGD("daemon: stderr using PTY");
errfd = ptsfd;
}
} else {
// If a TTY was sent directly, make it the CTTY.
if (isatty(infd)) {
ioctl(infd, TIOCSCTTY, 1);
}
}
free(pts_slave);
#ifdef SUPERUSER_EMBEDDED
if (mount_storage) {
mount_emulated_storage(multiuser_get_user_id(daemon_from_uid));
}
#endif
return run_daemon_child(infd, outfd, errfd, argc, argv);
}
static int copy_file(const char* src, const char* dst, int mode) {
int ifd = open(src, O_RDONLY);
if(ifd<0)
return 1;
if(mode == 0) {
struct stat stbuf;
if(fstat(ifd, &stbuf))
return 1;
mode = stbuf.st_mode & 0777;
LOGE("File %s found mode %o", src, mode);
}
int ofd = open(dst, O_WRONLY|O_CREAT, mode);
if(ofd<0)
return 1;
size_t s = lseek(ifd, 0, SEEK_END);
if(s<0)
return 1;
lseek(ifd, 0, SEEK_SET);
int ret = sendfile(ofd, ifd, NULL, s);
if(ret<0)
return 1;
close(ofd);
close(ifd);
return 0;
}
static void prepare_su_bind() {
int ret = 0;
//Check if there is a use to mount bind
if(access("/system/xbin/su", R_OK) != 0)
return;
ret = copy_file("/sbin/su", "/dev/su/su", 0755);
if(ret) {
PLOGE("Failed to copy su");
return;
}
chmod("/dev/su/su", 0755);
ret = setfilecon("/dev/su/su", "u:object_r:system_file:s0");
if(ret) {
LOGE("Failed to set file context");
return;
}
ret = mount("/dev/su/su", "/system/xbin/su", "", MS_BIND, NULL);
if(ret) {
LOGE("Failed to mount bind");
return;
}
}
static void prepare_binds() {
mkdir("/data/su", 0700);
static int i = 0;
auto void cb(void *arg, int uid, const char *src, const char *dst) {
int ret = 0;
char *tmpfile = NULL;
asprintf(&tmpfile, "/dev/su/bind%d", i++);
struct stat stbuf;
ret = stat(src, &stbuf);
if(ret) {
free(tmpfile);
LOGE("Failed to stat src %s file", src);
return;
}
//Only shell uid is allowed to bind files not his own
if(uid != 2000 && uid != stbuf.st_uid) {
LOGE("File %s has wrong owner: %d vs %d", src, uid, stbuf.st_uid);
return;
}
ret = copy_file(src, tmpfile, 0);
if(ret) {
free(tmpfile);
PLOGE("Failed to copy su");
return;
}
chmod(tmpfile, stbuf.st_mode);
ret = setfilecon(tmpfile, "u:object_r:system_file:s0");
if(ret) {
LOGE("Failed to set file context");
return;
}
ret = mount(tmpfile, dst, "", MS_BIND, NULL);
if(ret) {
LOGE("Failed to mount bind");
return;
}
}
bind_foreach(cb, NULL);
}
static void do_init() {
auto void cb(void *arg, int uid, const char *path) {
int ret = 0;
int p = fork();
if(p)
return;
while(access("/system/bin/sh", R_OK)) sleep(1);
ret = setexeccon("u:r:su:s0");
execl(path, path, NULL);
LOGE("Failed to execute %s. Trying as shell script, ret = %d", path, ret);
ret = setexeccon("u:r:su:s0");
execl("/system/bin/sh", "/system/bin/sh", path, NULL);
LOGE("Failed to execute %s as shell script", path);
_exit(1);
}
init_foreach(cb, NULL);
}
static void prepare() {
setfscreatecon("u:object_r:su_daemon:s0");
mkdir("/dev/su", 0700);
prepare_su_bind();
prepare_binds();
do_init();
setfscreatecon(NULL);
}
int run_daemon() {
if (getuid() != 0 || getgid() != 0) {
PLOGE("daemon requires root. uid/gid not root");
return -1;
}
prepare();
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/server", REQUESTOR_DAEMON_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(REQUESTOR_DAEMON_PATH);
int previous_umask = umask(027);
mkdir(REQUESTOR_DAEMON_PATH, 0777);
memset(sun.sun_path, 0, sizeof(sun.sun_path));
memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1);
if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
PLOGE("daemon bind");
goto err;
}
chmod(REQUESTOR_DAEMON_PATH, 0755);
chmod(sun.sun_path, 0777);
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);
}
}
LOGE("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(int sig) {
(void)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 uid = getuid();
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/server", REQUESTOR_DAEMON_PATH);
memset(sun.sun_path, 0, sizeof(sun.sun_path));
memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1);
if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) {
PLOGE("connect");
exit(-1);
}
LOGD("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;
// Send TTYs directly (instead of proxying with a PTY) if
// the SUPERUSER_SEND_TTY environment variable is set.
if (getenv("SUPERUSER_SEND_TTY") == NULL) {
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);
// User ID
write_int(socketfd, uid);
// 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);
LOGD("client exited %d", code);
return code;
}
/*
** Copyright 2013, Koushik Dutta (@koush)
**
** 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 <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <sqlite3.h>
#include <time.h>
#include "su.h"
struct callback_data_t {
struct su_context *ctx;
policy_t policy;
};
static int database_callback(void *v, int argc, char **argv, char **azColName){
struct callback_data_t *data = (struct callback_data_t *)v;
int command_match = 0;
policy_t policy = DENY;
int i;
time_t until = 0;
for(i = 0; i < argc; i++) {
if (strcmp(azColName[i], "policy") == 0) {
if (argv[i] == NULL) {
policy = DENY;
}
if (strcmp(argv[i], "allow") == 0) {
policy = ALLOW;
}
else if (strcmp(argv[i], "interactive") == 0) {
policy = INTERACTIVE;
}
else {
policy = DENY;
}
}
else if (strcmp(azColName[i], "command") == 0) {
// null or empty command means to match all commands (whitelist all from uid)
command_match = argv[i] == NULL || strlen(argv[i]) == 0 || strcmp(argv[i], get_command(&(data->ctx->to))) == 0;
}
else if (strcmp(azColName[i], "until") == 0) {
if (argv[i] != NULL) {
until = atoi(argv[i]);
}
}
}
// check for command match
if (command_match) {
// also make sure this policy has not expired
if (until == 0 || until > time(NULL)) {
if (policy == DENY) {
data->policy = DENY;
return -1;
}
data->policy = ALLOW;
// even though we allow, continue, so we can see if there's another policy
// that denies...
}
}
return 0;
}
policy_t database_check(struct su_context *ctx) {
sqlite3 *db = NULL;
char query[512];
snprintf(query, sizeof(query), "select policy, until, command from uid_policy where uid=%d", ctx->from.uid);
int ret = sqlite3_open_v2(ctx->user.database_path, &db, SQLITE_OPEN_READONLY, NULL);
if (ret) {
LOGE("sqlite3 open failure: %d", ret);
sqlite3_close(db);
return INTERACTIVE;
}
int result;
char *err = NULL;
struct callback_data_t data;
data.ctx = ctx;
data.policy = INTERACTIVE;
ret = sqlite3_exec(db, query, database_callback, &data, &err);
sqlite3_close(db);
if (err != NULL) {
LOGE("sqlite3_exec: %s", err);
return DENY;
}
return data.policy;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "su.h"
#include "utils.h"
enum {
H_NO_CONTEXT = 0x0001,
};
static struct {
const char *package;
int flags;
int uid;
} apps_list[] = {
{ "com.keramidas.TitaniumBackup", H_NO_CONTEXT, },
};
void hacks_init() {
char oldCwd[512];
int i;
getcwd(oldCwd, sizeof(oldCwd));
chdir("/data/data");
for(i=0; i<(sizeof(apps_list)/sizeof(apps_list[0])); ++i) {
apps_list[i].uid = -1;
struct stat st_buf;
int ret = stat(apps_list[i].package, &st_buf);
LOGW("hacks: Testing (%s:%d:%d)", apps_list[i].package, ret, st_buf.st_uid);
if(ret)
continue;
apps_list[i].uid = st_buf.st_uid;
}
}
void hacks_update_context(struct su_context* ctxt) {
int i;
for(i=0; i<(sizeof(apps_list)/sizeof(apps_list[0])); ++i) {
LOGW("hacks: Testing (%s:%d), %d", apps_list[i].package, ctxt->from.uid);
if(apps_list[i].uid != ctxt->from.uid)
continue;
LOGW("hacks: Found app (%s:%d)", apps_list[i].package, ctxt->from.uid);
if(apps_list[i].flags & H_NO_CONTEXT) {
LOGW("hacks: Disabling context (%s:%d)", apps_list[i].package, ctxt->from.uid);
ctxt->to.context = NULL;
}
}
}
/*
* Copyright 2013, Tan Chee Eng (@tan-ce)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* pts.c
*
* Manages the pseudo-terminal driver on Linux/Android and provides some
* helper functions to handle raw input mode and terminal window resizing
*/
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <termios.h>
#include <errno.h>
#include <pthread.h>
#include "pts.h"
/**
* Helper functions
*/
// Ensures all the data is written out
static int write_blocking(int fd, char *buf, ssize_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 < 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[256];
// Open master ptmx device
fdm = open("/dev/ptmx", O_RDWR);
if (fdm == -1) return -1;
// Get the slave name
if (ptsname_r(fdm, slave_name, slave_name_size-1)) {
close(fdm);
return -2;
}
slave_name[slave_name_size - 1] = '\0';
// Grant, then unlock
if (grantpt(fdm) == -1) {
close(fdm);
return -1;
}
if (unlockpt(fdm) == -1) {
close(fdm);
return -1;
}
return fdm;
}
// Stores the previous termios of stdin
static struct termios old_stdin;
static int stdin_is_raw = 0;
/**
* set_stdin_raw
*
* Changes stdin to raw unbuffered mode, disables echo,
* auto carriage return, etc.
*
* Return Value
* on failure -1, and errno is set
* on success 0
*/
int set_stdin_raw(void) {
struct termios new_termios;
// Save the current stdin termios
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
return -1;
}
// Start from the current settings
new_termios = old_stdin;
// Make the terminal like an SSH or telnet client
new_termios.c_iflag |= IGNPAR;
new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
new_termios.c_oflag &= ~OPOST;
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
return -1;
}
stdin_is_raw = 1;
return 0;
}
/**
* restore_stdin
*
* Restore termios on stdin to the state it was before
* set_stdin_raw() was called. If set_stdin_raw() was
* never called, does nothing and doesn't return an error.
*
* This function is async-safe.
*
* Return Value
* on failure, -1 and errno is set
* on success, 0
*/
int restore_stdin(void) {
if (!stdin_is_raw) return 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
return -1;
}
stdin_is_raw = 0;
return 0;
}
// Flag indicating whether the sigwinch watcher should terminate.
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();
}
/*
* 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
/*
** Copyright 2013, Kevin Cernekee <cernekee@gmail.com>
**
** This was reverse engineered from an HTC "reboot" binary and is an attempt
** to remain bug-compatible with the original.
**
** 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sqlite3.h>
#include <sys/reboot.h>
#include <android/log.h>
#define SETTINGS_DB "/data/data/com.android.providers.settings/databases/settings.db"
#define SCREEN_LOCK_STATUS "/data/misc/screen_lock_status"
int pattern_lock;
int sqlcallback(void *private, int n_columns, char **col_values, char **col_names)
{
pattern_lock = 0;
if (n_columns == 0 || !col_values[0])
return 0;
if (!strcmp(col_values[0], "1"))
pattern_lock = 1;
__android_log_print(ANDROID_LOG_INFO, NULL,
"sqlcallback %s = %s, pattern_locks= %d\n",
col_names[0], col_values[0], pattern_lock);
return 0;
}
int checkPatternLock(void)
{
sqlite3 *pDb = NULL;
char *errmsg = NULL;
if (sqlite3_open(SETTINGS_DB, &pDb) != 0) {
__android_log_print(ANDROID_LOG_ERROR, NULL,
"sqlite3_open error");
/* BUG? probably shouldn't call sqlite3_close() if open failed */
goto out;
}
if (sqlite3_exec(pDb,
"select value from system where name= \"lock_pattern_autolock\"",
sqlcallback, "checkPatternLock", &errmsg) != 0) {
__android_log_print(ANDROID_LOG_ERROR, NULL,
"SQL error: %s\n", errmsg);
sqlite3_free(errmsg);
goto out;
}
out:
sqlite3_close(pDb);
return 0;
}
int main(int argc, char **argv)
{
int no_sync = 0, power_off = 0;
int ret;
opterr = 0;
while ((ret = getopt(argc, argv, "np")) != -1) {
switch (ret) {
case 'n':
no_sync = 1;
break;
case 'p':
power_off = 1;
break;
case '?':
fprintf(stderr, "usage: %s [-n] [-p] [rebootcommand]\n",
argv[0]);
exit(1);
break;
}
}
if (argc > (optind + 1)) {
fprintf(stderr, "%s: too many arguments\n", argv[0]);
exit(1);
}
/* BUG: this should use optind */
if (argc > 1 && !strcmp(argv[1], "oem-78")) {
/* HTC RUU mode: "reboot oem-78" */
FILE *f;
char buf[5];
checkPatternLock();
f = fopen(SCREEN_LOCK_STATUS, "r");
if (!f) {
fputs("5\n", stderr);
exit(0);
}
fgets(buf, 5, f);
if (atoi(buf) == 1) {
if (pattern_lock != 0) {
fputs("1\n", stderr);
exit(0);
}
}
fputs("0\n", stderr);
}
if (!no_sync)
sync();
if (power_off) {
ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_POWER_OFF, 0);
} else if (optind >= argc) {
ret = reboot(LINUX_REBOOT_CMD_RESTART);
} else {
ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, argv[optind]);
}
if (ret < 0) {
perror("reboot");
exit(1);
} else {
fputs("reboot returned\n", stderr);
exit(0);
}
}
/*
** 2001 September 15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains code to implement the "sqlite" command line
** utility for accessing SQLite databases.
*/
#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS)
/* This needs to come before any includes for MSVC compiler */
#define _CRT_SECURE_NO_WARNINGS
#endif
/*
** Enable large-file support for fopen() and friends on unix.
*/
#ifndef SQLITE_DISABLE_LFS
# define _LARGE_FILE 1
# ifndef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 64
# endif
# define _LARGEFILE_SOURCE 1
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "sqlite3.h"
#include <ctype.h>
#include <stdarg.h>
#if !defined(_WIN32) && !defined(WIN32)
# include <signal.h>
# if !defined(__RTP__) && !defined(_WRS_KERNEL)
# include <pwd.h>
# endif
# include <unistd.h>
# include <sys/types.h>
#endif
#ifdef HAVE_EDITLINE
# include <editline/editline.h>
#endif
#if defined(HAVE_READLINE) && HAVE_READLINE==1
# include <readline/readline.h>
# include <readline/history.h>
#endif
#if !defined(HAVE_EDITLINE) && (!defined(HAVE_READLINE) || HAVE_READLINE!=1)
# define readline(p) local_getline(p,stdin,0)
# define add_history(X)
# define read_history(X)
# define write_history(X)
# define stifle_history(X)
#endif
#if defined(_WIN32) || defined(WIN32)
# include <io.h>
#define isatty(h) _isatty(h)
#define access(f,m) _access((f),(m))
#undef popen
#define popen(a,b) _popen((a),(b))
#undef pclose
#define pclose(x) _pclose(x)
#else
/* Make sure isatty() has a prototype.
*/
extern int isatty(int);
#endif
#if defined(_WIN32_WCE)
/* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty()
* thus we always assume that we have a console. That can be
* overridden with the -batch command line option.
*/
#define isatty(x) 1
#endif
/* True if the timer is enabled */
static int enableTimer = 0;
/* ctype macros that work with signed characters */
#define IsSpace(X) isspace((unsigned char)X)
#define IsDigit(X) isdigit((unsigned char)X)
#define ToLower(X) (char)tolower((unsigned char)X)
#if !defined(_WIN32) && !defined(WIN32) && !defined(_WRS_KERNEL)
#include <sys/time.h>
#include <sys/resource.h>
/* Saved resource information for the beginning of an operation */
static struct rusage sBegin;
/*
** Begin timing an operation
*/
static void beginTimer(void){
if( enableTimer ){
getrusage(RUSAGE_SELF, &sBegin);
}
}
/* Return the difference of two time_structs in seconds */
static double timeDiff(struct timeval *pStart, struct timeval *pEnd){
return (pEnd->tv_usec - pStart->tv_usec)*0.000001 +
(double)(pEnd->tv_sec - pStart->tv_sec);
}
/*
** Print the timing results.
*/
static void endTimer(void){
if( enableTimer ){
struct rusage sEnd;
getrusage(RUSAGE_SELF, &sEnd);
printf("CPU Time: user %f sys %f\n",
timeDiff(&sBegin.ru_utime, &sEnd.ru_utime),
timeDiff(&sBegin.ru_stime, &sEnd.ru_stime));
}
}
#define BEGIN_TIMER beginTimer()
#define END_TIMER endTimer()
#define HAS_TIMER 1
#elif (defined(_WIN32) || defined(WIN32))
#include <windows.h>
/* Saved resource information for the beginning of an operation */
static HANDLE hProcess;
static FILETIME ftKernelBegin;
static FILETIME ftUserBegin;
typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME);
static GETPROCTIMES getProcessTimesAddr = NULL;
/*
** Check to see if we have timer support. Return 1 if necessary
** support found (or found previously).
*/
static int hasTimer(void){
if( getProcessTimesAddr ){
return 1;
} else {
/* GetProcessTimes() isn't supported in WIN95 and some other Windows versions.
** See if the version we are running on has it, and if it does, save off
** a pointer to it and the current process handle.
*/
hProcess = GetCurrentProcess();
if( hProcess ){
HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll"));
if( NULL != hinstLib ){
getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes");
if( NULL != getProcessTimesAddr ){
return 1;
}
FreeLibrary(hinstLib);
}
}
}
return 0;
}
/*
** Begin timing an operation
*/
static void beginTimer(void){
if( enableTimer && getProcessTimesAddr ){
FILETIME ftCreation, ftExit;
getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelBegin, &ftUserBegin);
}
}
/* Return the difference of two FILETIME structs in seconds */
static double timeDiff(FILETIME *pStart, FILETIME *pEnd){
sqlite_int64 i64Start = *((sqlite_int64 *) pStart);
sqlite_int64 i64End = *((sqlite_int64 *) pEnd);
return (double) ((i64End - i64Start) / 10000000.0);
}
/*
** Print the timing results.
*/
static void endTimer(void){
if( enableTimer && getProcessTimesAddr){
FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd;
getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelEnd, &ftUserEnd);
printf("CPU Time: user %f sys %f\n",
timeDiff(&ftUserBegin, &ftUserEnd),
timeDiff(&ftKernelBegin, &ftKernelEnd));
}
}
#define BEGIN_TIMER beginTimer()
#define END_TIMER endTimer()
#define HAS_TIMER hasTimer()
#else
#define BEGIN_TIMER
#define END_TIMER
#define HAS_TIMER 0
#endif
/*
** Used to prevent warnings about unused parameters
*/
#define UNUSED_PARAMETER(x) (void)(x)
/*
** If the following flag is set, then command execution stops
** at an error if we are not interactive.
*/
static int bail_on_error = 0;
/*
** Threat stdin as an interactive input if the following variable
** is true. Otherwise, assume stdin is connected to a file or pipe.
*/
static int stdin_is_interactive = 1;
/*
** The following is the open SQLite database. We make a pointer
** to this database a static variable so that it can be accessed
** by the SIGINT handler to interrupt database processing.
*/
static sqlite3 *db = 0;
/*
** True if an interrupt (Control-C) has been received.
*/
static volatile int seenInterrupt = 0;
/*
** This is the name of our program. It is set in main(), used
** in a number of other places, mostly for error messages.
*/
static char *Argv0;
/*
** Prompt strings. Initialized in main. Settable with
** .prompt main continue
*/
static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/
static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */
/*
** Write I/O traces to the following stream.
*/
#ifdef SQLITE_ENABLE_IOTRACE
static FILE *iotrace = 0;
#endif
/*
** This routine works like printf in that its first argument is a
** format string and subsequent arguments are values to be substituted
** in place of % fields. The result of formatting this string
** is written to iotrace.
*/
#ifdef SQLITE_ENABLE_IOTRACE
static void iotracePrintf(const char *zFormat, ...){
va_list ap;
char *z;
if( iotrace==0 ) return;
va_start(ap, zFormat);
z = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
fprintf(iotrace, "%s", z);
sqlite3_free(z);
}
#endif
/*
** Determines if a string is a number of not.
*/
static int isNumber(const char *z, int *realnum){
if( *z=='-' || *z=='+' ) z++;
if( !IsDigit(*z) ){
return 0;
}
z++;
if( realnum ) *realnum = 0;
while( IsDigit(*z) ){ z++; }
if( *z=='.' ){
z++;
if( !IsDigit(*z) ) return 0;
while( IsDigit(*z) ){ z++; }
if( realnum ) *realnum = 1;
}
if( *z=='e' || *z=='E' ){
z++;
if( *z=='+' || *z=='-' ) z++;
if( !IsDigit(*z) ) return 0;
while( IsDigit(*z) ){ z++; }
if( realnum ) *realnum = 1;
}
return *z==0;
}
/*
** A global char* and an SQL function to access its current value
** from within an SQL statement. This program used to use the
** sqlite_exec_printf() API to substitue a string into an SQL statement.
** The correct way to do this with sqlite3 is to use the bind API, but
** since the shell is built around the callback paradigm it would be a lot
** of work. Instead just use this hack, which is quite harmless.
*/
static const char *zShellStatic = 0;
static void shellstaticFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
assert( 0==argc );
assert( zShellStatic );
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
sqlite3_result_text(context, zShellStatic, -1, SQLITE_STATIC);
}
/*
** This routine reads a line of text from FILE in, stores
** the text in memory obtained from malloc() and returns a pointer
** to the text. NULL is returned at end of file, or if malloc()
** fails.
**
** The interface is like "readline" but no command-line editing
** is done.
*/
static char *local_getline(char *zPrompt, FILE *in, int csvFlag){
char *zLine;
int nLine;
int n;
int inQuote = 0;
if( zPrompt && *zPrompt ){
printf("%s",zPrompt);
fflush(stdout);
}
nLine = 100;
zLine = malloc( nLine );
if( zLine==0 ) return 0;
n = 0;
while( 1 ){
if( n+100>nLine ){
nLine = nLine*2 + 100;
zLine = realloc(zLine, nLine);
if( zLine==0 ) return 0;
}
if( fgets(&zLine[n], nLine - n, in)==0 ){
if( n==0 ){
free(zLine);
return 0;
}
zLine[n] = 0;
break;
}
while( zLine[n] ){
if( zLine[n]=='"' ) inQuote = !inQuote;
n++;
}
if( n>0 && zLine[n-1]=='\n' && (!inQuote || !csvFlag) ){
n--;
if( n>0 && zLine[n-1]=='\r' ) n--;
zLine[n] = 0;
break;
}
}
zLine = realloc( zLine, n+1 );
return zLine;
}
/*
** Retrieve a single line of input text.
**
** zPrior is a string of prior text retrieved. If not the empty
** string, then issue a continuation prompt.
*/
static char *one_input_line(const char *zPrior, FILE *in){
char *zPrompt;
char *zResult;
if( in!=0 ){
return local_getline(0, in, 0);
}
if( zPrior && zPrior[0] ){
zPrompt = continuePrompt;
}else{
zPrompt = mainPrompt;
}
zResult = readline(zPrompt);
#if defined(HAVE_READLINE) && HAVE_READLINE==1
if( zResult && *zResult ) add_history(zResult);
#endif
return zResult;
}
struct previous_mode_data {
int valid; /* Is there legit data in here? */
int mode;
int showHeader;
int colWidth[100];
};
/*
** An pointer to an instance of this structure is passed from
** the main program to the callback. This is used to communicate
** state and mode information.
*/
struct callback_data {
sqlite3 *db; /* The database */
int echoOn; /* True to echo input commands */
int statsOn; /* True to display memory stats before each finalize */
int cnt; /* Number of records displayed so far */
FILE *out; /* Write results here */
FILE *traceOut; /* Output for sqlite3_trace() */
int nErr; /* Number of errors seen */
int mode; /* An output mode setting */
int writableSchema; /* True if PRAGMA writable_schema=ON */
int showHeader; /* True to show column names in List or Column mode */
char *zDestTable; /* Name of destination table when MODE_Insert */
char separator[20]; /* Separator character for MODE_List */
int colWidth[100]; /* Requested width of each column when in column mode*/
int actualWidth[100]; /* Actual width of each column */
char nullvalue[20]; /* The text to print when a NULL comes back from
** the database */
struct previous_mode_data explainPrev;
/* Holds the mode information just before
** .explain ON */
char outfile[FILENAME_MAX]; /* Filename for *out */
const char *zDbFilename; /* name of the database file */
const char *zVfs; /* Name of VFS to use */
sqlite3_stmt *pStmt; /* Current statement if any. */
FILE *pLog; /* Write log output here */
};
/*
** These are the allowed modes.
*/
#define MODE_Line 0 /* One column per line. Blank line between records */
#define MODE_Column 1 /* One record per line in neat columns */
#define MODE_List 2 /* One record per line with a separator */
#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
#define MODE_Html 4 /* Generate an XHTML table */
#define MODE_Insert 5 /* Generate SQL "insert" statements */
#define MODE_Tcl 6 /* Generate ANSI-C or TCL quoted elements */
#define MODE_Csv 7 /* Quote strings, numbers are plain */
#define MODE_Explain 8 /* Like MODE_Column, but do not truncate data */
static const char *modeDescr[] = {
"line",
"column",
"list",
"semi",
"html",
"insert",
"tcl",
"csv",
"explain",
};
/*
** Number of elements in an array
*/
#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0]))
/*
** Compute a string length that is limited to what can be stored in
** lower 30 bits of a 32-bit signed integer.
*/
static int strlen30(const char *z){
const char *z2 = z;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
/*
** A callback for the sqlite3_log() interface.
*/
static void shellLog(void *pArg, int iErrCode, const char *zMsg){
struct callback_data *p = (struct callback_data*)pArg;
if( p->pLog==0 ) return;
fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
fflush(p->pLog);
}
/*
** Output the given string as a hex-encoded blob (eg. X'1234' )
*/
static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){
int i;
char *zBlob = (char *)pBlob;
fprintf(out,"X'");
for(i=0; i<nBlob; i++){ fprintf(out,"%02x",zBlob[i]&0xff); }
fprintf(out,"'");
}
/*
** Output the given string as a quoted string using SQL quoting conventions.
*/
static void output_quoted_string(FILE *out, const char *z){
int i;
int nSingle = 0;
for(i=0; z[i]; i++){
if( z[i]=='\'' ) nSingle++;
}
if( nSingle==0 ){
fprintf(out,"'%s'",z);
}else{
fprintf(out,"'");
while( *z ){
for(i=0; z[i] && z[i]!='\''; i++){}
if( i==0 ){
fprintf(out,"''");
z++;
}else if( z[i]=='\'' ){
fprintf(out,"%.*s''",i,z);
z += i+1;
}else{
fprintf(out,"%s",z);
break;
}
}
fprintf(out,"'");
}
}
/*
** Output the given string as a quoted according to C or TCL quoting rules.
*/
static void output_c_string(FILE *out, const char *z){
unsigned int c;
fputc('"', out);
while( (c = *(z++))!=0 ){
if( c=='\\' ){
fputc(c, out);
fputc(c, out);
}else if( c=='"' ){
fputc('\\', out);
fputc('"', out);
}else if( c=='\t' ){
fputc('\\', out);
fputc('t', out);
}else if( c=='\n' ){
fputc('\\', out);
fputc('n', out);
}else if( c=='\r' ){
fputc('\\', out);
fputc('r', out);
}else if( !isprint(c) ){
fprintf(out, "\\%03o", c&0xff);
}else{
fputc(c, out);
}
}
fputc('"', out);
}
/*
** Output the given string with characters that are special to
** HTML escaped.
*/
static void output_html_string(FILE *out, const char *z){
int i;
while( *z ){
for(i=0; z[i]
&& z[i]!='<'
&& z[i]!='&'
&& z[i]!='>'
&& z[i]!='\"'
&& z[i]!='\'';
i++){}
if( i>0 ){
fprintf(out,"%.*s",i,z);
}
if( z[i]=='<' ){
fprintf(out,"&lt;");
}else if( z[i]=='&' ){
fprintf(out,"&amp;");
}else if( z[i]=='>' ){
fprintf(out,"&gt;");
}else if( z[i]=='\"' ){
fprintf(out,"&quot;");
}else if( z[i]=='\'' ){
fprintf(out,"&#39;");
}else{
break;
}
z += i + 1;
}
}
/*
** If a field contains any character identified by a 1 in the following
** array, then the string must be quoted for CSV.
*/
static const char needCsvQuote[] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
/*
** Output a single term of CSV. Actually, p->separator is used for
** the separator, which may or may not be a comma. p->nullvalue is
** the null value. Strings are quoted if necessary.
*/
static void output_csv(struct callback_data *p, const char *z, int bSep){
FILE *out = p->out;
if( z==0 ){
fprintf(out,"%s",p->nullvalue);
}else{
int i;
int nSep = strlen30(p->separator);
for(i=0; z[i]; i++){
if( needCsvQuote[((unsigned char*)z)[i]]
|| (z[i]==p->separator[0] &&
(nSep==1 || memcmp(z, p->separator, nSep)==0)) ){
i = 0;
break;
}
}
if( i==0 ){
putc('"', out);
for(i=0; z[i]; i++){
if( z[i]=='"' ) putc('"', out);
putc(z[i], out);
}
putc('"', out);
}else{
fprintf(out, "%s", z);
}
}
if( bSep ){
fprintf(p->out, "%s", p->separator);
}
}
#ifdef SIGINT
/*
** This routine runs when the user presses Ctrl-C
*/
static void interrupt_handler(int NotUsed){
UNUSED_PARAMETER(NotUsed);
seenInterrupt = 1;
if( db ) sqlite3_interrupt(db);
}
#endif
/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int *aiType){
int i;
struct callback_data *p = (struct callback_data*)pArg;
switch( p->mode ){
case MODE_Line: {
int w = 5;
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
int len = strlen30(azCol[i] ? azCol[i] : "");
if( len>w ) w = len;
}
if( p->cnt++>0 ) fprintf(p->out,"\n");
for(i=0; i<nArg; i++){
fprintf(p->out,"%*s = %s\n", w, azCol[i],
azArg[i] ? azArg[i] : p->nullvalue);
}
break;
}
case MODE_Explain:
case MODE_Column: {
if( p->cnt++==0 ){
for(i=0; i<nArg; i++){
int w, n;
if( i<ArraySize(p->colWidth) ){
w = p->colWidth[i];
}else{
w = 0;
}
if( w==0 ){
w = strlen30(azCol[i] ? azCol[i] : "");
if( w<10 ) w = 10;
n = strlen30(azArg && azArg[i] ? azArg[i] : p->nullvalue);
if( w<n ) w = n;
}
if( i<ArraySize(p->actualWidth) ){
p->actualWidth[i] = w;
}
if( p->showHeader ){
if( w<0 ){
fprintf(p->out,"%*.*s%s",-w,-w,azCol[i], i==nArg-1 ? "\n": " ");
}else{
fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? "\n": " ");
}
}
}
if( p->showHeader ){
for(i=0; i<nArg; i++){
int w;
if( i<ArraySize(p->actualWidth) ){
w = p->actualWidth[i];
if( w<0 ) w = -w;
}else{
w = 10;
}
fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------"
"----------------------------------------------------------",
i==nArg-1 ? "\n": " ");
}
}
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
int w;
if( i<ArraySize(p->actualWidth) ){
w = p->actualWidth[i];
}else{
w = 10;
}
if( p->mode==MODE_Explain && azArg[i] &&
strlen30(azArg[i])>w ){
w = strlen30(azArg[i]);
}
if( w<0 ){
fprintf(p->out,"%*.*s%s",-w,-w,
azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " ");
}else{
fprintf(p->out,"%-*.*s%s",w,w,
azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " ");
}
}
break;
}
case MODE_Semi:
case MODE_List: {
if( p->cnt++==0 && p->showHeader ){
for(i=0; i<nArg; i++){
fprintf(p->out,"%s%s",azCol[i], i==nArg-1 ? "\n" : p->separator);
}
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
char *z = azArg[i];
if( z==0 ) z = p->nullvalue;
fprintf(p->out, "%s", z);
if( i<nArg-1 ){
fprintf(p->out, "%s", p->separator);
}else if( p->mode==MODE_Semi ){
fprintf(p->out, ";\n");
}else{
fprintf(p->out, "\n");
}
}
break;
}
case MODE_Html: {
if( p->cnt++==0 && p->showHeader ){
fprintf(p->out,"<TR>");
for(i=0; i<nArg; i++){
fprintf(p->out,"<TH>");
output_html_string(p->out, azCol[i]);
fprintf(p->out,"</TH>\n");
}
fprintf(p->out,"</TR>\n");
}
if( azArg==0 ) break;
fprintf(p->out,"<TR>");
for(i=0; i<nArg; i++){
fprintf(p->out,"<TD>");
output_html_string(p->out, azArg[i] ? azArg[i] : p->nullvalue);
fprintf(p->out,"</TD>\n");
}
fprintf(p->out,"</TR>\n");
break;
}
case MODE_Tcl: {
if( p->cnt++==0 && p->showHeader ){
for(i=0; i<nArg; i++){
output_c_string(p->out,azCol[i] ? azCol[i] : "");
if(i<nArg-1) fprintf(p->out, "%s", p->separator);
}
fprintf(p->out,"\n");
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
output_c_string(p->out, azArg[i] ? azArg[i] : p->nullvalue);
if(i<nArg-1) fprintf(p->out, "%s", p->separator);
}
fprintf(p->out,"\n");
break;
}
case MODE_Csv: {
if( p->cnt++==0 && p->showHeader ){
for(i=0; i<nArg; i++){
output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
}
fprintf(p->out,"\n");
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
output_csv(p, azArg[i], i<nArg-1);
}
fprintf(p->out,"\n");
break;
}
case MODE_Insert: {
p->cnt++;
if( azArg==0 ) break;
fprintf(p->out,"INSERT INTO %s VALUES(",p->zDestTable);
for(i=0; i<nArg; i++){
char *zSep = i>0 ? ",": "";
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
fprintf(p->out,"%sNULL",zSep);
}else if( aiType && aiType[i]==SQLITE_TEXT ){
if( zSep[0] ) fprintf(p->out,"%s",zSep);
output_quoted_string(p->out, azArg[i]);
}else if( aiType && (aiType[i]==SQLITE_INTEGER || aiType[i]==SQLITE_FLOAT) ){
fprintf(p->out,"%s%s",zSep, azArg[i]);
}else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
const void *pBlob = sqlite3_column_blob(p->pStmt, i);
int nBlob = sqlite3_column_bytes(p->pStmt, i);
if( zSep[0] ) fprintf(p->out,"%s",zSep);
output_hex_blob(p->out, pBlob, nBlob);
}else if( isNumber(azArg[i], 0) ){
fprintf(p->out,"%s%s",zSep, azArg[i]);
}else{
if( zSep[0] ) fprintf(p->out,"%s",zSep);
output_quoted_string(p->out, azArg[i]);
}
}
fprintf(p->out,");\n");
break;
}
}
return 0;
}
/*
** This is the callback routine that the SQLite library
** invokes for each row of a query result.
*/
static int callback(void *pArg, int nArg, char **azArg, char **azCol){
/* since we don't have type info, call the shell_callback with a NULL value */
return shell_callback(pArg, nArg, azArg, azCol, NULL);
}
/*
** Set the destination table field of the callback_data structure to
** the name of the table given. Escape any quote characters in the
** table name.
*/
static void set_table_name(struct callback_data *p, const char *zName){
int i, n;
int needQuote;
char *z;
if( p->zDestTable ){
free(p->zDestTable);
p->zDestTable = 0;
}
if( zName==0 ) return;
needQuote = !isalpha((unsigned char)*zName) && *zName!='_';
for(i=n=0; zName[i]; i++, n++){
if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ){
needQuote = 1;
if( zName[i]=='\'' ) n++;
}
}
if( needQuote ) n += 2;
z = p->zDestTable = malloc( n+1 );
if( z==0 ){
fprintf(stderr,"Error: out of memory\n");
exit(1);
}
n = 0;
if( needQuote ) z[n++] = '\'';
for(i=0; zName[i]; i++){
z[n++] = zName[i];
if( zName[i]=='\'' ) z[n++] = '\'';
}
if( needQuote ) z[n++] = '\'';
z[n] = 0;
}
/* zIn is either a pointer to a NULL-terminated string in memory obtained
** from malloc(), or a NULL pointer. The string pointed to by zAppend is
** added to zIn, and the result returned in memory obtained from malloc().
** zIn, if it was not NULL, is freed.
**
** If the third argument, quote, is not '\0', then it is used as a
** quote character for zAppend.
*/
static char *appendText(char *zIn, char const *zAppend, char quote){
int len;
int i;
int nAppend = strlen30(zAppend);
int nIn = (zIn?strlen30(zIn):0);
len = nAppend+nIn+1;
if( quote ){
len += 2;
for(i=0; i<nAppend; i++){
if( zAppend[i]==quote ) len++;
}
}
zIn = (char *)realloc(zIn, len);
if( !zIn ){
return 0;
}
if( quote ){
char *zCsr = &zIn[nIn];
*zCsr++ = quote;
for(i=0; i<nAppend; i++){
*zCsr++ = zAppend[i];
if( zAppend[i]==quote ) *zCsr++ = quote;
}
*zCsr++ = quote;
*zCsr++ = '\0';
assert( (zCsr-zIn)==len );
}else{
memcpy(&zIn[nIn], zAppend, nAppend);
zIn[len-1] = '\0';
}
return zIn;
}
/*
** Execute a query statement that will generate SQL output. Print
** the result columns, comma-separated, on a line and then add a
** semicolon terminator to the end of that line.
**
** If the number of columns is 1 and that column contains text "--"
** then write the semicolon on a separate line. That way, if a
** "--" comment occurs at the end of the statement, the comment
** won't consume the semicolon terminator.
*/
static int run_table_dump_query(
struct callback_data *p, /* Query context */
const char *zSelect, /* SELECT statement to extract content */
const char *zFirstRow /* Print before first row, if not NULL */
){
sqlite3_stmt *pSelect;
int rc;
int nResult;
int i;
const char *z;
rc = sqlite3_prepare(p->db, zSelect, -1, &pSelect, 0);
if( rc!=SQLITE_OK || !pSelect ){
fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db));
p->nErr++;
return rc;
}
rc = sqlite3_step(pSelect);
nResult = sqlite3_column_count(pSelect);
while( rc==SQLITE_ROW ){
if( zFirstRow ){
fprintf(p->out, "%s", zFirstRow);
zFirstRow = 0;
}
z = (const char*)sqlite3_column_text(pSelect, 0);
fprintf(p->out, "%s", z);
for(i=1; i<nResult; i++){
fprintf(p->out, ",%s", sqlite3_column_text(pSelect, i));
}
if( z==0 ) z = "";
while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
if( z[0] ){
fprintf(p->out, "\n;\n");
}else{
fprintf(p->out, ";\n");
}
rc = sqlite3_step(pSelect);
}
rc = sqlite3_finalize(pSelect);
if( rc!=SQLITE_OK ){
fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db));
p->nErr++;
}
return rc;
}
/*
** Allocate space and save off current error string.
*/
static char *save_err_msg(
sqlite3 *db /* Database to query */
){
int nErrMsg = 1+strlen30(sqlite3_errmsg(db));
char *zErrMsg = sqlite3_malloc(nErrMsg);
if( zErrMsg ){
memcpy(zErrMsg, sqlite3_errmsg(db), nErrMsg);
}
return zErrMsg;
}
/*
** Display memory stats.
*/
static int display_stats(
sqlite3 *db, /* Database to query */
struct callback_data *pArg, /* Pointer to struct callback_data */
int bReset /* True to reset the stats */
){
int iCur;
int iHiwtr;
if( pArg && pArg->out ){
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_MEMORY_USED, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Memory Used: %d (max %d) bytes\n", iCur, iHiwtr);
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Number of Outstanding Allocations: %d (max %d)\n", iCur, iHiwtr);
/*
** Not currently used by the CLI.
** iHiwtr = iCur = -1;
** sqlite3_status(SQLITE_STATUS_PAGECACHE_USED, &iCur, &iHiwtr, bReset);
** fprintf(pArg->out, "Number of Pcache Pages Used: %d (max %d) pages\n", iCur, iHiwtr);
*/
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Number of Pcache Overflow Bytes: %d (max %d) bytes\n", iCur, iHiwtr);
/*
** Not currently used by the CLI.
** iHiwtr = iCur = -1;
** sqlite3_status(SQLITE_STATUS_SCRATCH_USED, &iCur, &iHiwtr, bReset);
** fprintf(pArg->out, "Number of Scratch Allocations Used: %d (max %d)\n", iCur, iHiwtr);
*/
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_SCRATCH_OVERFLOW, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Number of Scratch Overflow Bytes: %d (max %d) bytes\n", iCur, iHiwtr);
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Largest Allocation: %d bytes\n", iHiwtr);
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_PAGECACHE_SIZE, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Largest Pcache Allocation: %d bytes\n", iHiwtr);
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_SCRATCH_SIZE, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Largest Scratch Allocation: %d bytes\n", iHiwtr);
#ifdef YYTRACKMAXSTACKDEPTH
iHiwtr = iCur = -1;
sqlite3_status(SQLITE_STATUS_PARSER_STACK, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Deepest Parser Stack: %d (max %d)\n", iCur, iHiwtr);
#endif
}
if( pArg && pArg->out && db ){
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
fprintf(pArg->out, "Page cache hits: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
fprintf(pArg->out, "Page cache misses: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
fprintf(pArg->out, "Page cache writes: %d\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Schema Heap Usage: %d bytes\n", iCur);
iHiwtr = iCur = -1;
sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
fprintf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", iCur);
}
if( pArg && pArg->out && db && pArg->pStmt ){
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset);
fprintf(pArg->out, "Fullscan Steps: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
fprintf(pArg->out, "Sort Operations: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX, bReset);
fprintf(pArg->out, "Autoindex Inserts: %d\n", iCur);
}
return 0;
}
/*
** Execute a statement or set of statements. Print
** any result rows/columns depending on the current mode
** set via the supplied callback.
**
** This is very similar to SQLite's built-in sqlite3_exec()
** function except it takes a slightly different callback
** and callback data argument.
*/
static int shell_exec(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
int (*xCallback)(void*,int,char**,char**,int*), /* Callback function */
/* (not the same as sqlite3_exec) */
struct callback_data *pArg, /* Pointer to struct callback_data */
char **pzErrMsg /* Error msg written here */
){
sqlite3_stmt *pStmt = NULL; /* Statement to execute. */
int rc = SQLITE_OK; /* Return Code */
int rc2;
const char *zLeftover; /* Tail of unprocessed SQL */
if( pzErrMsg ){
*pzErrMsg = NULL;
}
while( zSql[0] && (SQLITE_OK == rc) ){
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
if( SQLITE_OK != rc ){
if( pzErrMsg ){
*pzErrMsg = save_err_msg(db);
}
}else{
if( !pStmt ){
/* this happens for a comment or white-space */
zSql = zLeftover;
while( IsSpace(zSql[0]) ) zSql++;
continue;
}
/* save off the prepared statment handle and reset row count */
if( pArg ){
pArg->pStmt = pStmt;
pArg->cnt = 0;
}
/* echo the sql statement if echo on */
if( pArg && pArg->echoOn ){
const char *zStmtSql = sqlite3_sql(pStmt);
fprintf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql);
}
/* Output TESTCTRL_EXPLAIN text of requested */
if( pArg && pArg->mode==MODE_Explain ){
const char *zExplain = 0;
sqlite3_test_control(SQLITE_TESTCTRL_EXPLAIN_STMT, pStmt, &zExplain);
if( zExplain && zExplain[0] ){
fprintf(pArg->out, "%s", zExplain);
}
}
/* perform the first step. this will tell us if we
** have a result set or not and how wide it is.
*/
rc = sqlite3_step(pStmt);
/* if we have a result set... */
if( SQLITE_ROW == rc ){
/* if we have a callback... */
if( xCallback ){
/* allocate space for col name ptr, value ptr, and type */
int nCol = sqlite3_column_count(pStmt);
void *pData = sqlite3_malloc(3*nCol*sizeof(const char*) + 1);
if( !pData ){
rc = SQLITE_NOMEM;
}else{
char **azCols = (char **)pData; /* Names of result columns */
char **azVals = &azCols[nCol]; /* Results */
int *aiTypes = (int *)&azVals[nCol]; /* Result types */
int i;
assert(sizeof(int) <= sizeof(char *));
/* save off ptrs to column names */
for(i=0; i<nCol; i++){
azCols[i] = (char *)sqlite3_column_name(pStmt, i);
}
do{
/* extract the data and data types */
for(i=0; i<nCol; i++){
azVals[i] = (char *)sqlite3_column_text(pStmt, i);
aiTypes[i] = sqlite3_column_type(pStmt, i);
if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){
rc = SQLITE_NOMEM;
break; /* from for */
}
} /* end for */
/* if data and types extracted successfully... */
if( SQLITE_ROW == rc ){
/* call the supplied callback with the result row data */
if( xCallback(pArg, nCol, azVals, azCols, aiTypes) ){
rc = SQLITE_ABORT;
}else{
rc = sqlite3_step(pStmt);
}
}
} while( SQLITE_ROW == rc );
sqlite3_free(pData);
}
}else{
do{
rc = sqlite3_step(pStmt);
} while( rc == SQLITE_ROW );
}
}
/* print usage stats if stats on */
if( pArg && pArg->statsOn ){
display_stats(db, pArg, 0);
}
/* Finalize the statement just executed. If this fails, save a
** copy of the error message. Otherwise, set zSql to point to the
** next statement to execute. */
rc2 = sqlite3_finalize(pStmt);
if( rc!=SQLITE_NOMEM ) rc = rc2;
if( rc==SQLITE_OK ){
zSql = zLeftover;
while( IsSpace(zSql[0]) ) zSql++;
}else if( pzErrMsg ){
*pzErrMsg = save_err_msg(db);
}
/* clear saved stmt handle */
if( pArg ){
pArg->pStmt = NULL;
}
}
} /* end while */
return rc;
}
/*
** This is a different callback routine used for dumping the database.
** Each row received by this callback consists of a table name,
** the table type ("index" or "table") and SQL to create the table.
** This routine should print text sufficient to recreate the table.
*/
static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
int rc;
const char *zTable;
const char *zType;
const char *zSql;
const char *zPrepStmt = 0;
struct callback_data *p = (struct callback_data *)pArg;
UNUSED_PARAMETER(azCol);
if( nArg!=3 ) return 1;
zTable = azArg[0];
zType = azArg[1];
zSql = azArg[2];
if( strcmp(zTable, "sqlite_sequence")==0 ){
zPrepStmt = "DELETE FROM sqlite_sequence;\n";
}else if( strcmp(zTable, "sqlite_stat1")==0 ){
fprintf(p->out, "ANALYZE sqlite_master;\n");
}else if( strncmp(zTable, "sqlite_", 7)==0 ){
return 0;
}else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
char *zIns;
if( !p->writableSchema ){
fprintf(p->out, "PRAGMA writable_schema=ON;\n");
p->writableSchema = 1;
}
zIns = sqlite3_mprintf(
"INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
"VALUES('table','%q','%q',0,'%q');",
zTable, zTable, zSql);
fprintf(p->out, "%s\n", zIns);
sqlite3_free(zIns);
return 0;
}else{
fprintf(p->out, "%s;\n", zSql);
}
if( strcmp(zType, "table")==0 ){
sqlite3_stmt *pTableInfo = 0;
char *zSelect = 0;
char *zTableInfo = 0;
char *zTmp = 0;
int nRow = 0;
zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0);
zTableInfo = appendText(zTableInfo, zTable, '"');
zTableInfo = appendText(zTableInfo, ");", 0);
rc = sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0);
free(zTableInfo);
if( rc!=SQLITE_OK || !pTableInfo ){
return 1;
}
zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0);
/* Always quote the table name, even if it appears to be pure ascii,
** in case it is a keyword. Ex: INSERT INTO "table" ... */
zTmp = appendText(zTmp, zTable, '"');
if( zTmp ){
zSelect = appendText(zSelect, zTmp, '\'');
free(zTmp);
}
zSelect = appendText(zSelect, " || ' VALUES(' || ", 0);
rc = sqlite3_step(pTableInfo);
while( rc==SQLITE_ROW ){
const char *zText = (const char *)sqlite3_column_text(pTableInfo, 1);
zSelect = appendText(zSelect, "quote(", 0);
zSelect = appendText(zSelect, zText, '"');
rc = sqlite3_step(pTableInfo);
if( rc==SQLITE_ROW ){
zSelect = appendText(zSelect, "), ", 0);
}else{
zSelect = appendText(zSelect, ") ", 0);
}
nRow++;
}
rc = sqlite3_finalize(pTableInfo);
if( rc!=SQLITE_OK || nRow==0 ){
free(zSelect);
return 1;
}
zSelect = appendText(zSelect, "|| ')' FROM ", 0);
zSelect = appendText(zSelect, zTable, '"');
rc = run_table_dump_query(p, zSelect, zPrepStmt);
if( rc==SQLITE_CORRUPT ){
zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0);
run_table_dump_query(p, zSelect, 0);
}
free(zSelect);
}
return 0;
}
/*
** Run zQuery. Use dump_callback() as the callback routine so that
** the contents of the query are output as SQL statements.
**
** If we get a SQLITE_CORRUPT error, rerun the query after appending
** "ORDER BY rowid DESC" to the end.
*/
static int run_schema_dump_query(
struct callback_data *p,
const char *zQuery
){
int rc;
char *zErr = 0;
rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
if( rc==SQLITE_CORRUPT ){
char *zQ2;
int len = strlen30(zQuery);
fprintf(p->out, "/****** CORRUPTION ERROR *******/\n");
if( zErr ){
fprintf(p->out, "/****** %s ******/\n", zErr);
sqlite3_free(zErr);
zErr = 0;
}
zQ2 = malloc( len+100 );
if( zQ2==0 ) return rc;
sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
if( rc ){
fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
}else{
rc = SQLITE_CORRUPT;
}
sqlite3_free(zErr);
free(zQ2);
}
return rc;
}
/*
** Text of a help message
*/
static char zHelp[] =
".backup ?DB? FILE Backup DB (default \"main\") to FILE\n"
".bail ON|OFF Stop after hitting an error. Default OFF\n"
".databases List names and files of attached databases\n"
".dump ?TABLE? ... Dump the database in an SQL text format\n"
" If TABLE specified, only dump tables matching\n"
" LIKE pattern TABLE.\n"
".echo ON|OFF Turn command echo on or off\n"
".exit Exit this program\n"
".explain ?ON|OFF? Turn output mode suitable for EXPLAIN on or off.\n"
" With no args, it turns EXPLAIN on.\n"
".header(s) ON|OFF Turn display of headers on or off\n"
".help Show this message\n"
".import FILE TABLE Import data from FILE into TABLE\n"
".indices ?TABLE? Show names of all indices\n"
" If TABLE specified, only show indices for tables\n"
" matching LIKE pattern TABLE.\n"
#ifdef SQLITE_ENABLE_IOTRACE
".iotrace FILE Enable I/O diagnostic logging to FILE\n"
#endif
#ifndef SQLITE_OMIT_LOAD_EXTENSION
".load FILE ?ENTRY? Load an extension library\n"
#endif
".log FILE|off Turn logging on or off. FILE can be stderr/stdout\n"
".mode MODE ?TABLE? Set output mode where MODE is one of:\n"
" csv Comma-separated values\n"
" column Left-aligned columns. (See .width)\n"
" html HTML <table> code\n"
" insert SQL insert statements for TABLE\n"
" line One value per line\n"
" list Values delimited by .separator string\n"
" tabs Tab-separated values\n"
" tcl TCL list elements\n"
".nullvalue STRING Use STRING in place of NULL values\n"
".output FILENAME Send output to FILENAME\n"
".output stdout Send output to the screen\n"
".print STRING... Print literal STRING\n"
".prompt MAIN CONTINUE Replace the standard prompts\n"
".quit Exit this program\n"
".read FILENAME Execute SQL in FILENAME\n"
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE\n"
".schema ?TABLE? Show the CREATE statements\n"
" If TABLE specified, only show tables matching\n"
" LIKE pattern TABLE.\n"
".separator STRING Change separator used by output mode and .import\n"
".show Show the current values for various settings\n"
".stats ON|OFF Turn stats on or off\n"
".tables ?TABLE? List names of tables\n"
" If TABLE specified, only list tables matching\n"
" LIKE pattern TABLE.\n"
".timeout MS Try opening locked tables for MS milliseconds\n"
".trace FILE|off Output each SQL statement as it is run\n"
".vfsname ?AUX? Print the name of the VFS stack\n"
".width NUM1 NUM2 ... Set column widths for \"column\" mode\n"
;
static char zTimerHelp[] =
".timer ON|OFF Turn the CPU timer measurement on or off\n"
;
/* Forward reference */
static int process_input(struct callback_data *p, FILE *in);
/*
** Make sure the database is open. If it is not, then open it. If
** the database fails to open, print an error message and exit.
*/
static void open_db(struct callback_data *p){
if( p->db==0 ){
sqlite3_initialize();
sqlite3_open(p->zDbFilename, &p->db);
db = p->db;
if( db && sqlite3_errcode(db)==SQLITE_OK ){
sqlite3_create_function(db, "shellstatic", 0, SQLITE_UTF8, 0,
shellstaticFunc, 0, 0);
}
if( db==0 || SQLITE_OK!=sqlite3_errcode(db) ){
fprintf(stderr,"Error: unable to open database \"%s\": %s\n",
p->zDbFilename, sqlite3_errmsg(db));
exit(1);
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
sqlite3_enable_load_extension(p->db, 1);
#endif
}
}
/*
** Do C-language style dequoting.
**
** \t -> tab
** \n -> newline
** \r -> carriage return
** \NNN -> ascii character NNN in octal
** \\ -> backslash
*/
static void resolve_backslashes(char *z){
int i, j;
char c;
for(i=j=0; (c = z[i])!=0; i++, j++){
if( c=='\\' ){
c = z[++i];
if( c=='n' ){
c = '\n';
}else if( c=='t' ){
c = '\t';
}else if( c=='r' ){
c = '\r';
}else if( c>='0' && c<='7' ){
c -= '0';
if( z[i+1]>='0' && z[i+1]<='7' ){
i++;
c = (c<<3) + z[i] - '0';
if( z[i+1]>='0' && z[i+1]<='7' ){
i++;
c = (c<<3) + z[i] - '0';
}
}
}
}
z[j] = c;
}
z[j] = 0;
}
/*
** Interpret zArg as a boolean value. Return either 0 or 1.
*/
static int booleanValue(char *zArg){
int val = atoi(zArg);
int j;
for(j=0; zArg[j]; j++){
zArg[j] = ToLower(zArg[j]);
}
if( strcmp(zArg,"on")==0 ){
val = 1;
}else if( strcmp(zArg,"yes")==0 ){
val = 1;
}
return val;
}
/*
** Close an output file, assuming it is not stderr or stdout
*/
static void output_file_close(FILE *f){
if( f && f!=stdout && f!=stderr ) fclose(f);
}
/*
** Try to open an output file. The names "stdout" and "stderr" are
** recognized and do the right thing. NULL is returned if the output
** filename is "off".
*/
static FILE *output_file_open(const char *zFile){
FILE *f;
if( strcmp(zFile,"stdout")==0 ){
f = stdout;
}else if( strcmp(zFile, "stderr")==0 ){
f = stderr;
}else if( strcmp(zFile, "off")==0 ){
f = 0;
}else{
f = fopen(zFile, "wb");
if( f==0 ){
fprintf(stderr, "Error: cannot open \"%s\"\n", zFile);
}
}
return f;
}
/*
** A routine for handling output from sqlite3_trace().
*/
static void sql_trace_callback(void *pArg, const char *z){
FILE *f = (FILE*)pArg;
if( f ) fprintf(f, "%s\n", z);
}
/*
** A no-op routine that runs with the ".breakpoint" doc-command. This is
** a useful spot to set a debugger breakpoint.
*/
static void test_breakpoint(void){
static int nCall = 0;
nCall++;
}
/*
** If an input line begins with "." then invoke this routine to
** process that line.
**
** Return 1 on error, 2 to exit, and 0 otherwise.
*/
static int do_meta_command(char *zLine, struct callback_data *p){
int i = 1;
int nArg = 0;
int n, c;
int rc = 0;
char *azArg[50];
/* Parse the input line into tokens.
*/
while( zLine[i] && nArg<ArraySize(azArg) ){
while( IsSpace(zLine[i]) ){ i++; }
if( zLine[i]==0 ) break;
if( zLine[i]=='\'' || zLine[i]=='"' ){
int delim = zLine[i++];
azArg[nArg++] = &zLine[i];
while( zLine[i] && zLine[i]!=delim ){ i++; }
if( zLine[i]==delim ){
zLine[i++] = 0;
}
if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
}else{
azArg[nArg++] = &zLine[i];
while( zLine[i] && !IsSpace(zLine[i]) ){ i++; }
if( zLine[i] ) zLine[i++] = 0;
resolve_backslashes(azArg[nArg-1]);
}
}
/* Process the input line.
*/
if( nArg==0 ) return 0; /* no tokens, no error */
n = strlen30(azArg[0]);
c = azArg[0][0];
if( c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0 && nArg>1 && nArg<4){
const char *zDestFile;
const char *zDb;
sqlite3 *pDest;
sqlite3_backup *pBackup;
if( nArg==2 ){
zDestFile = azArg[1];
zDb = "main";
}else{
zDestFile = azArg[2];
zDb = azArg[1];
}
rc = sqlite3_open(zDestFile, &pDest);
if( rc!=SQLITE_OK ){
fprintf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
sqlite3_close(pDest);
return 1;
}
open_db(p);
pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
if( pBackup==0 ){
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
sqlite3_close(pDest);
return 1;
}
while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
sqlite3_backup_finish(pBackup);
if( rc==SQLITE_DONE ){
rc = 0;
}else{
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
rc = 1;
}
sqlite3_close(pDest);
}else
if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 && nArg>1 && nArg<3 ){
bail_on_error = booleanValue(azArg[1]);
}else
/* The undocumented ".breakpoint" command causes a call to the no-op
** routine named test_breakpoint().
*/
if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){
test_breakpoint();
}else
if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 && nArg==1 ){
struct callback_data data;
char *zErrMsg = 0;
open_db(p);
memcpy(&data, p, sizeof(data));
data.showHeader = 1;
data.mode = MODE_Column;
data.colWidth[0] = 3;
data.colWidth[1] = 15;
data.colWidth[2] = 58;
data.cnt = 0;
sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg);
if( zErrMsg ){
fprintf(stderr,"Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}
}else
if( c=='d' && strncmp(azArg[0], "dump", n)==0 && nArg<3 ){
open_db(p);
/* When playing back a "dump", the content might appear in an order
** which causes immediate foreign key constraints to be violated.
** So disable foreign-key constraint enforcement to prevent problems. */
fprintf(p->out, "PRAGMA foreign_keys=OFF;\n");
fprintf(p->out, "BEGIN TRANSACTION;\n");
p->writableSchema = 0;
sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
p->nErr = 0;
if( nArg==1 ){
run_schema_dump_query(p,
"SELECT name, type, sql FROM sqlite_master "
"WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'"
);
run_schema_dump_query(p,
"SELECT name, type, sql FROM sqlite_master "
"WHERE name=='sqlite_sequence'"
);
run_table_dump_query(p,
"SELECT sql FROM sqlite_master "
"WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0
);
}else{
int i;
for(i=1; i<nArg; i++){
zShellStatic = azArg[i];
run_schema_dump_query(p,
"SELECT name, type, sql FROM sqlite_master "
"WHERE tbl_name LIKE shellstatic() AND type=='table'"
" AND sql NOT NULL");
run_table_dump_query(p,
"SELECT sql FROM sqlite_master "
"WHERE sql NOT NULL"
" AND type IN ('index','trigger','view')"
" AND tbl_name LIKE shellstatic()", 0
);
zShellStatic = 0;
}
}
if( p->writableSchema ){
fprintf(p->out, "PRAGMA writable_schema=OFF;\n");
p->writableSchema = 0;
}
sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n");
}else
if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 && nArg<3 ){
p->echoOn = booleanValue(azArg[1]);
}else
if( c=='e' && strncmp(azArg[0], "exit", n)==0 && nArg==1 ){
rc = 2;
}else
if( c=='e' && strncmp(azArg[0], "explain", n)==0 && nArg<3 ){
int val = nArg>=2 ? booleanValue(azArg[1]) : 1;
if(val == 1) {
if(!p->explainPrev.valid) {
p->explainPrev.valid = 1;
p->explainPrev.mode = p->mode;
p->explainPrev.showHeader = p->showHeader;
memcpy(p->explainPrev.colWidth,p->colWidth,sizeof(p->colWidth));
}
/* We could put this code under the !p->explainValid
** condition so that it does not execute if we are already in
** explain mode. However, always executing it allows us an easy
** was to reset to explain mode in case the user previously
** did an .explain followed by a .width, .mode or .header
** command.
*/
p->mode = MODE_Explain;
p->showHeader = 1;
memset(p->colWidth,0,ArraySize(p->colWidth));
p->colWidth[0] = 4; /* addr */
p->colWidth[1] = 13; /* opcode */
p->colWidth[2] = 4; /* P1 */
p->colWidth[3] = 4; /* P2 */
p->colWidth[4] = 4; /* P3 */
p->colWidth[5] = 13; /* P4 */
p->colWidth[6] = 2; /* P5 */
p->colWidth[7] = 13; /* Comment */
}else if (p->explainPrev.valid) {
p->explainPrev.valid = 0;
p->mode = p->explainPrev.mode;
p->showHeader = p->explainPrev.showHeader;
memcpy(p->colWidth,p->explainPrev.colWidth,sizeof(p->colWidth));
}
}else
if( c=='h' && (strncmp(azArg[0], "header", n)==0 ||
strncmp(azArg[0], "headers", n)==0) && nArg>1 && nArg<3 ){
p->showHeader = booleanValue(azArg[1]);
}else
if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
fprintf(stderr,"%s",zHelp);
if( HAS_TIMER ){
fprintf(stderr,"%s",zTimerHelp);
}
}else
if( c=='i' && strncmp(azArg[0], "import", n)==0 && nArg==3 ){
char *zTable = azArg[2]; /* Insert data into this table */
char *zFile = azArg[1]; /* The file from which to extract data */
sqlite3_stmt *pStmt = NULL; /* A statement */
int nCol; /* Number of columns in the table */
int nByte; /* Number of bytes in an SQL string */
int i, j; /* Loop counters */
int nSep; /* Number of bytes in p->separator[] */
char *zSql; /* An SQL statement */
char *zLine; /* A single line of input from the file */
char **azCol; /* zLine[] broken up into columns */
char *zCommit; /* How to commit changes */
FILE *in; /* The input file */
int lineno = 0; /* Line number of input file */
open_db(p);
nSep = strlen30(p->separator);
if( nSep==0 ){
fprintf(stderr, "Error: non-null separator required for import\n");
return 1;
}
zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
if( zSql==0 ){
fprintf(stderr, "Error: out of memory\n");
return 1;
}
nByte = strlen30(zSql);
rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ){
if (pStmt) sqlite3_finalize(pStmt);
fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db));
return 1;
}
nCol = sqlite3_column_count(pStmt);
sqlite3_finalize(pStmt);
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
zSql = malloc( nByte + 20 + nCol*2 );
if( zSql==0 ){
fprintf(stderr, "Error: out of memory\n");
return 1;
}
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zTable);
j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
zSql[j++] = '?';
}
zSql[j++] = ')';
zSql[j] = 0;
rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
free(zSql);
if( rc ){
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
if (pStmt) sqlite3_finalize(pStmt);
return 1;
}
in = fopen(zFile, "rb");
if( in==0 ){
fprintf(stderr, "Error: cannot open \"%s\"\n", zFile);
sqlite3_finalize(pStmt);
return 1;
}
azCol = malloc( sizeof(azCol[0])*(nCol+1) );
if( azCol==0 ){
fprintf(stderr, "Error: out of memory\n");
fclose(in);
sqlite3_finalize(pStmt);
return 1;
}
sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
zCommit = "COMMIT";
while( (zLine = local_getline(0, in, 1))!=0 ){
char *z, c;
int inQuote = 0;
lineno++;
azCol[0] = zLine;
for(i=0, z=zLine; (c = *z)!=0; z++){
if( c=='"' ) inQuote = !inQuote;
if( c=='\n' ) lineno++;
if( !inQuote && c==p->separator[0] && strncmp(z,p->separator,nSep)==0 ){
*z = 0;
i++;
if( i<nCol ){
azCol[i] = &z[nSep];
z += nSep-1;
}
}
} /* end for */
*z = 0;
if( i+1!=nCol ){
fprintf(stderr,
"Error: %s line %d: expected %d columns of data but found %d\n",
zFile, lineno, nCol, i+1);
zCommit = "ROLLBACK";
free(zLine);
rc = 1;
break; /* from while */
}
for(i=0; i<nCol; i++){
if( azCol[i][0]=='"' ){
int k;
for(z=azCol[i], j=1, k=0; z[j]; j++){
if( z[j]=='"' ){ j++; if( z[j]==0 ) break; }
z[k++] = z[j];
}
z[k] = 0;
}
sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
}
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
free(zLine);
if( rc!=SQLITE_OK ){
fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db));
zCommit = "ROLLBACK";
rc = 1;
break; /* from while */
}
} /* end while */
free(azCol);
fclose(in);
sqlite3_finalize(pStmt);
sqlite3_exec(p->db, zCommit, 0, 0, 0);
}else
if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg<3 ){
struct callback_data data;
char *zErrMsg = 0;
open_db(p);
memcpy(&data, p, sizeof(data));
data.showHeader = 0;
data.mode = MODE_List;
if( nArg==1 ){
rc = sqlite3_exec(p->db,
"SELECT name FROM sqlite_master "
"WHERE type='index' AND name NOT LIKE 'sqlite_%' "
"UNION ALL "
"SELECT name FROM sqlite_temp_master "
"WHERE type='index' "
"ORDER BY 1",
callback, &data, &zErrMsg
);
}else{
zShellStatic = azArg[1];
rc = sqlite3_exec(p->db,
"SELECT name FROM sqlite_master "
"WHERE type='index' AND tbl_name LIKE shellstatic() "
"UNION ALL "
"SELECT name FROM sqlite_temp_master "
"WHERE type='index' AND tbl_name LIKE shellstatic() "
"ORDER BY 1",
callback, &data, &zErrMsg
);
zShellStatic = 0;
}
if( zErrMsg ){
fprintf(stderr,"Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}else if( rc != SQLITE_OK ){
fprintf(stderr,"Error: querying sqlite_master and sqlite_temp_master\n");
rc = 1;
}
}else
#ifdef SQLITE_ENABLE_IOTRACE
if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){
extern void (*sqlite3IoTrace)(const char*, ...);
if( iotrace && iotrace!=stdout ) fclose(iotrace);
iotrace = 0;
if( nArg<2 ){
sqlite3IoTrace = 0;
}else if( strcmp(azArg[1], "-")==0 ){
sqlite3IoTrace = iotracePrintf;
iotrace = stdout;
}else{
iotrace = fopen(azArg[1], "w");
if( iotrace==0 ){
fprintf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
sqlite3IoTrace = 0;
rc = 1;
}else{
sqlite3IoTrace = iotracePrintf;
}
}
}else
#endif
#ifndef SQLITE_OMIT_LOAD_EXTENSION
if( c=='l' && strncmp(azArg[0], "load", n)==0 && nArg>=2 ){
const char *zFile, *zProc;
char *zErrMsg = 0;
zFile = azArg[1];
zProc = nArg>=3 ? azArg[2] : 0;
open_db(p);
rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
if( rc!=SQLITE_OK ){
fprintf(stderr, "Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}
}else
#endif
if( c=='l' && strncmp(azArg[0], "log", n)==0 && nArg>=2 ){
const char *zFile = azArg[1];
output_file_close(p->pLog);
p->pLog = output_file_open(zFile);
}else
if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg==2 ){
int n2 = strlen30(azArg[1]);
if( (n2==4 && strncmp(azArg[1],"line",n2)==0)
||
(n2==5 && strncmp(azArg[1],"lines",n2)==0) ){
p->mode = MODE_Line;
}else if( (n2==6 && strncmp(azArg[1],"column",n2)==0)
||
(n2==7 && strncmp(azArg[1],"columns",n2)==0) ){
p->mode = MODE_Column;
}else if( n2==4 && strncmp(azArg[1],"list",n2)==0 ){
p->mode = MODE_List;
}else if( n2==4 && strncmp(azArg[1],"html",n2)==0 ){
p->mode = MODE_Html;
}else if( n2==3 && strncmp(azArg[1],"tcl",n2)==0 ){
p->mode = MODE_Tcl;
sqlite3_snprintf(sizeof(p->separator), p->separator, " ");
}else if( n2==3 && strncmp(azArg[1],"csv",n2)==0 ){
p->mode = MODE_Csv;
sqlite3_snprintf(sizeof(p->separator), p->separator, ",");
}else if( n2==4 && strncmp(azArg[1],"tabs",n2)==0 ){
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->separator), p->separator, "\t");
}else if( n2==6 && strncmp(azArg[1],"insert",n2)==0 ){
p->mode = MODE_Insert;
set_table_name(p, "table");
}else {
fprintf(stderr,"Error: mode should be one of: "
"column csv html insert line list tabs tcl\n");
rc = 1;
}
}else
if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg==3 ){
int n2 = strlen30(azArg[1]);
if( n2==6 && strncmp(azArg[1],"insert",n2)==0 ){
p->mode = MODE_Insert;
set_table_name(p, azArg[2]);
}else {
fprintf(stderr, "Error: invalid arguments: "
" \"%s\". Enter \".help\" for help\n", azArg[2]);
rc = 1;
}
}else
if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 && nArg==2 ) {
sqlite3_snprintf(sizeof(p->nullvalue), p->nullvalue,
"%.*s", (int)ArraySize(p->nullvalue)-1, azArg[1]);
}else
if( c=='o' && strncmp(azArg[0], "output", n)==0 && nArg==2 ){
if( p->outfile[0]=='|' ){
pclose(p->out);
}else{
output_file_close(p->out);
}
p->outfile[0] = 0;
if( azArg[1][0]=='|' ){
p->out = popen(&azArg[1][1], "w");
if( p->out==0 ){
fprintf(stderr,"Error: cannot open pipe \"%s\"\n", &azArg[1][1]);
p->out = stdout;
rc = 1;
}else{
sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]);
}
}else{
p->out = output_file_open(azArg[1]);
if( p->out==0 ){
if( strcmp(azArg[1],"off")!=0 ){
fprintf(stderr,"Error: cannot write to \"%s\"\n", azArg[1]);
}
p->out = stdout;
rc = 1;
} else {
sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]);
}
}
}else
if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){
int i;
for(i=1; i<nArg; i++){
if( i>1 ) fprintf(p->out, " ");
fprintf(p->out, "%s", azArg[i]);
}
fprintf(p->out, "\n");
}else
if( c=='p' && strncmp(azArg[0], "prompt", n)==0 && (nArg==2 || nArg==3)){
if( nArg >= 2) {
strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
}
if( nArg >= 3) {
strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
}
}else
if( c=='q' && strncmp(azArg[0], "quit", n)==0 && nArg==1 ){
rc = 2;
}else
if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 && nArg==2 ){
FILE *alt = fopen(azArg[1], "rb");
if( alt==0 ){
fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
rc = 1;
}else{
rc = process_input(p, alt);
fclose(alt);
}
}else
if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 && nArg>1 && nArg<4){
const char *zSrcFile;
const char *zDb;
sqlite3 *pSrc;
sqlite3_backup *pBackup;
int nTimeout = 0;
if( nArg==2 ){
zSrcFile = azArg[1];
zDb = "main";
}else{
zSrcFile = azArg[2];
zDb = azArg[1];
}
rc = sqlite3_open(zSrcFile, &pSrc);
if( rc!=SQLITE_OK ){
fprintf(stderr, "Error: cannot open \"%s\"\n", zSrcFile);
sqlite3_close(pSrc);
return 1;
}
open_db(p);
pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
if( pBackup==0 ){
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
sqlite3_close(pSrc);
return 1;
}
while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
|| rc==SQLITE_BUSY ){
if( rc==SQLITE_BUSY ){
if( nTimeout++ >= 3 ) break;
sqlite3_sleep(100);
}
}
sqlite3_backup_finish(pBackup);
if( rc==SQLITE_DONE ){
rc = 0;
}else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
fprintf(stderr, "Error: source database is busy\n");
rc = 1;
}else{
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
rc = 1;
}
sqlite3_close(pSrc);
}else
if( c=='s' && strncmp(azArg[0], "schema", n)==0 && nArg<3 ){
struct callback_data data;
char *zErrMsg = 0;
open_db(p);
memcpy(&data, p, sizeof(data));
data.showHeader = 0;
data.mode = MODE_Semi;
if( nArg>1 ){
int i;
for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]);
if( strcmp(azArg[1],"sqlite_master")==0 ){
char *new_argv[2], *new_colv[2];
new_argv[0] = "CREATE TABLE sqlite_master (\n"
" type text,\n"
" name text,\n"
" tbl_name text,\n"
" rootpage integer,\n"
" sql text\n"
")";
new_argv[1] = 0;
new_colv[0] = "sql";
new_colv[1] = 0;
callback(&data, 1, new_argv, new_colv);
rc = SQLITE_OK;
}else if( strcmp(azArg[1],"sqlite_temp_master")==0 ){
char *new_argv[2], *new_colv[2];
new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n"
" type text,\n"
" name text,\n"
" tbl_name text,\n"
" rootpage integer,\n"
" sql text\n"
")";
new_argv[1] = 0;
new_colv[0] = "sql";
new_colv[1] = 0;
callback(&data, 1, new_argv, new_colv);
rc = SQLITE_OK;
}else{
zShellStatic = azArg[1];
rc = sqlite3_exec(p->db,
"SELECT sql FROM "
" (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
" FROM sqlite_master UNION ALL"
" SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) "
"WHERE lower(tbl_name) LIKE shellstatic()"
" AND type!='meta' AND sql NOTNULL "
"ORDER BY substr(type,2,1), "
" CASE type WHEN 'view' THEN rowid ELSE name END",
callback, &data, &zErrMsg);
zShellStatic = 0;
}
}else{
rc = sqlite3_exec(p->db,
"SELECT sql FROM "
" (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
" FROM sqlite_master UNION ALL"
" SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) "
"WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%'"
"ORDER BY substr(type,2,1),"
" CASE type WHEN 'view' THEN rowid ELSE name END",
callback, &data, &zErrMsg
);
}
if( zErrMsg ){
fprintf(stderr,"Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}else if( rc != SQLITE_OK ){
fprintf(stderr,"Error: querying schema information\n");
rc = 1;
}else{
rc = 0;
}
}else
if( c=='s' && strncmp(azArg[0], "separator", n)==0 && nArg==2 ){
sqlite3_snprintf(sizeof(p->separator), p->separator,
"%.*s", (int)sizeof(p->separator)-1, azArg[1]);
}else
if( c=='s' && strncmp(azArg[0], "show", n)==0 && nArg==1 ){
int i;
fprintf(p->out,"%9.9s: %s\n","echo", p->echoOn ? "on" : "off");
fprintf(p->out,"%9.9s: %s\n","explain", p->explainPrev.valid ? "on" :"off");
fprintf(p->out,"%9.9s: %s\n","headers", p->showHeader ? "on" : "off");
fprintf(p->out,"%9.9s: %s\n","mode", modeDescr[p->mode]);
fprintf(p->out,"%9.9s: ", "nullvalue");
output_c_string(p->out, p->nullvalue);
fprintf(p->out, "\n");
fprintf(p->out,"%9.9s: %s\n","output",
strlen30(p->outfile) ? p->outfile : "stdout");
fprintf(p->out,"%9.9s: ", "separator");
output_c_string(p->out, p->separator);
fprintf(p->out, "\n");
fprintf(p->out,"%9.9s: %s\n","stats", p->statsOn ? "on" : "off");
fprintf(p->out,"%9.9s: ","width");
for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) {
fprintf(p->out,"%d ",p->colWidth[i]);
}
fprintf(p->out,"\n");
}else
if( c=='s' && strncmp(azArg[0], "stats", n)==0 && nArg>1 && nArg<3 ){
p->statsOn = booleanValue(azArg[1]);
}else
if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 && nArg<3 ){
sqlite3_stmt *pStmt;
char **azResult;
int nRow, nAlloc;
char *zSql = 0;
int ii;
open_db(p);
rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
if( rc ) return rc;
zSql = sqlite3_mprintf(
"SELECT name FROM sqlite_master"
" WHERE type IN ('table','view')"
" AND name NOT LIKE 'sqlite_%%'"
" AND name LIKE ?1");
while( sqlite3_step(pStmt)==SQLITE_ROW ){
const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
if( zDbName==0 || strcmp(zDbName,"main")==0 ) continue;
if( strcmp(zDbName,"temp")==0 ){
zSql = sqlite3_mprintf(
"%z UNION ALL "
"SELECT 'temp.' || name FROM sqlite_temp_master"
" WHERE type IN ('table','view')"
" AND name NOT LIKE 'sqlite_%%'"
" AND name LIKE ?1", zSql);
}else{
zSql = sqlite3_mprintf(
"%z UNION ALL "
"SELECT '%q.' || name FROM \"%w\".sqlite_master"
" WHERE type IN ('table','view')"
" AND name NOT LIKE 'sqlite_%%'"
" AND name LIKE ?1", zSql, zDbName, zDbName);
}
}
sqlite3_finalize(pStmt);
zSql = sqlite3_mprintf("%z ORDER BY 1", zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ) return rc;
nRow = nAlloc = 0;
azResult = 0;
if( nArg>1 ){
sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
}else{
sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
}
while( sqlite3_step(pStmt)==SQLITE_ROW ){
if( nRow>=nAlloc ){
char **azNew;
int n = nAlloc*2 + 10;
azNew = sqlite3_realloc(azResult, sizeof(azResult[0])*n);
if( azNew==0 ){
fprintf(stderr, "Error: out of memory\n");
break;
}
nAlloc = n;
azResult = azNew;
}
azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
if( azResult[nRow] ) nRow++;
}
sqlite3_finalize(pStmt);
if( nRow>0 ){
int len, maxlen = 0;
int i, j;
int nPrintCol, nPrintRow;
for(i=0; i<nRow; i++){
len = strlen30(azResult[i]);
if( len>maxlen ) maxlen = len;
}
nPrintCol = 80/(maxlen+2);
if( nPrintCol<1 ) nPrintCol = 1;
nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
for(i=0; i<nPrintRow; i++){
for(j=i; j<nRow; j+=nPrintRow){
char *zSp = j<nPrintRow ? "" : " ";
printf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : "");
}
printf("\n");
}
}
for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
sqlite3_free(azResult);
}else
if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){
static const struct {
const char *zCtrlName; /* Name of a test-control option */
int ctrlCode; /* Integer code for that option */
} aCtrl[] = {
{ "prng_save", SQLITE_TESTCTRL_PRNG_SAVE },
{ "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE },
{ "prng_reset", SQLITE_TESTCTRL_PRNG_RESET },
{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST },
{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL },
{ "benign_malloc_hooks", SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS },
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE },
{ "assert", SQLITE_TESTCTRL_ASSERT },
{ "always", SQLITE_TESTCTRL_ALWAYS },
{ "reserve", SQLITE_TESTCTRL_RESERVE },
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS },
{ "iskeyword", SQLITE_TESTCTRL_ISKEYWORD },
{ "scratchmalloc", SQLITE_TESTCTRL_SCRATCHMALLOC },
};
int testctrl = -1;
int rc = 0;
int i, n;
open_db(p);
/* convert testctrl text option to value. allow any unique prefix
** of the option name, or a numerical value. */
n = strlen30(azArg[1]);
for(i=0; i<(int)(sizeof(aCtrl)/sizeof(aCtrl[0])); i++){
if( strncmp(azArg[1], aCtrl[i].zCtrlName, n)==0 ){
if( testctrl<0 ){
testctrl = aCtrl[i].ctrlCode;
}else{
fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[1]);
testctrl = -1;
break;
}
}
}
if( testctrl<0 ) testctrl = atoi(azArg[1]);
if( (testctrl<SQLITE_TESTCTRL_FIRST) || (testctrl>SQLITE_TESTCTRL_LAST) ){
fprintf(stderr,"Error: invalid testctrl option: %s\n", azArg[1]);
}else{
switch(testctrl){
/* sqlite3_test_control(int, db, int) */
case SQLITE_TESTCTRL_OPTIMIZATIONS:
case SQLITE_TESTCTRL_RESERVE:
if( nArg==3 ){
int opt = (int)strtol(azArg[2], 0, 0);
rc = sqlite3_test_control(testctrl, p->db, opt);
printf("%d (0x%08x)\n", rc, rc);
} else {
fprintf(stderr,"Error: testctrl %s takes a single int option\n",
azArg[1]);
}
break;
/* sqlite3_test_control(int) */
case SQLITE_TESTCTRL_PRNG_SAVE:
case SQLITE_TESTCTRL_PRNG_RESTORE:
case SQLITE_TESTCTRL_PRNG_RESET:
if( nArg==2 ){
rc = sqlite3_test_control(testctrl);
printf("%d (0x%08x)\n", rc, rc);
} else {
fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]);
}
break;
/* sqlite3_test_control(int, uint) */
case SQLITE_TESTCTRL_PENDING_BYTE:
if( nArg==3 ){
unsigned int opt = (unsigned int)atoi(azArg[2]);
rc = sqlite3_test_control(testctrl, opt);
printf("%d (0x%08x)\n", rc, rc);
} else {
fprintf(stderr,"Error: testctrl %s takes a single unsigned"
" int option\n", azArg[1]);
}
break;
/* sqlite3_test_control(int, int) */
case SQLITE_TESTCTRL_ASSERT:
case SQLITE_TESTCTRL_ALWAYS:
if( nArg==3 ){
int opt = atoi(azArg[2]);
rc = sqlite3_test_control(testctrl, opt);
printf("%d (0x%08x)\n", rc, rc);
} else {
fprintf(stderr,"Error: testctrl %s takes a single int option\n",
azArg[1]);
}
break;
/* sqlite3_test_control(int, char *) */
#ifdef SQLITE_N_KEYWORD
case SQLITE_TESTCTRL_ISKEYWORD:
if( nArg==3 ){
const char *opt = azArg[2];
rc = sqlite3_test_control(testctrl, opt);
printf("%d (0x%08x)\n", rc, rc);
} else {
fprintf(stderr,"Error: testctrl %s takes a single char * option\n",
azArg[1]);
}
break;
#endif
case SQLITE_TESTCTRL_BITVEC_TEST:
case SQLITE_TESTCTRL_FAULT_INSTALL:
case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS:
case SQLITE_TESTCTRL_SCRATCHMALLOC:
default:
fprintf(stderr,"Error: CLI support for testctrl %s not implemented\n",
azArg[1]);
break;
}
}
}else
if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 && nArg==2 ){
open_db(p);
sqlite3_busy_timeout(p->db, atoi(azArg[1]));
}else
if( HAS_TIMER && c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0
&& nArg==2
){
enableTimer = booleanValue(azArg[1]);
}else
if( c=='t' && strncmp(azArg[0], "trace", n)==0 && nArg>1 ){
open_db(p);
output_file_close(p->traceOut);
p->traceOut = output_file_open(azArg[1]);
#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT)
if( p->traceOut==0 ){
sqlite3_trace(p->db, 0, 0);
}else{
sqlite3_trace(p->db, sql_trace_callback, p->traceOut);
}
#endif
}else
if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
printf("SQLite %s %s\n" /*extra-version-info*/,
sqlite3_libversion(), sqlite3_sourceid());
}else
if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){
const char *zDbName = nArg==2 ? azArg[1] : "main";
char *zVfsName = 0;
if( p->db ){
sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
if( zVfsName ){
printf("%s\n", zVfsName);
sqlite3_free(zVfsName);
}
}
}else
#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE)
if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
extern int sqlite3WhereTrace;
sqlite3WhereTrace = atoi(azArg[1]);
}else
#endif
if( c=='w' && strncmp(azArg[0], "width", n)==0 && nArg>1 ){
int j;
assert( nArg<=ArraySize(azArg) );
for(j=1; j<nArg && j<ArraySize(p->colWidth); j++){
p->colWidth[j-1] = atoi(azArg[j]);
}
}else
{
fprintf(stderr, "Error: unknown command or invalid arguments: "
" \"%s\". Enter \".help\" for help\n", azArg[0]);
rc = 1;
}
return rc;
}
/*
** Return TRUE if a semicolon occurs anywhere in the first N characters
** of string z[].
*/
static int _contains_semicolon(const char *z, int N){
int i;
for(i=0; i<N; i++){ if( z[i]==';' ) return 1; }
return 0;
}
/*
** Test to see if a line consists entirely of whitespace.
*/
static int _all_whitespace(const char *z){
for(; *z; z++){
if( IsSpace(z[0]) ) continue;
if( *z=='/' && z[1]=='*' ){
z += 2;
while( *z && (*z!='*' || z[1]!='/') ){ z++; }
if( *z==0 ) return 0;
z++;
continue;
}
if( *z=='-' && z[1]=='-' ){
z += 2;
while( *z && *z!='\n' ){ z++; }
if( *z==0 ) return 1;
continue;
}
return 0;
}
return 1;
}
/*
** Return TRUE if the line typed in is an SQL command terminator other
** than a semi-colon. The SQL Server style "go" command is understood
** as is the Oracle "/".
*/
static int _is_command_terminator(const char *zLine){
while( IsSpace(zLine[0]) ){ zLine++; };
if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){
return 1; /* Oracle */
}
if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o'
&& _all_whitespace(&zLine[2]) ){
return 1; /* SQL Server */
}
return 0;
}
/*
** Return true if zSql is a complete SQL statement. Return false if it
** ends in the middle of a string literal or C-style comment.
*/
static int _is_complete(char *zSql, int nSql){
int rc;
if( zSql==0 ) return 1;
zSql[nSql] = ';';
zSql[nSql+1] = 0;
rc = sqlite3_complete(zSql);
zSql[nSql] = 0;
return rc;
}
/*
** Read input from *in and process it. If *in==0 then input
** is interactive - the user is typing it it. Otherwise, input
** is coming from a file or device. A prompt is issued and history
** is saved only if input is interactive. An interrupt signal will
** cause this routine to exit immediately, unless input is interactive.
**
** Return the number of errors.
*/
static int process_input(struct callback_data *p, FILE *in){
char *zLine = 0;
char *zSql = 0;
int nSql = 0;
int nSqlPrior = 0;
char *zErrMsg;
int rc;
int errCnt = 0;
int lineno = 0;
int startline = 0;
while( errCnt==0 || !bail_on_error || (in==0 && stdin_is_interactive) ){
fflush(p->out);
free(zLine);
zLine = one_input_line(zSql, in);
if( zLine==0 ){
/* End of input */
if( stdin_is_interactive ) printf("\n");
break;
}
if( seenInterrupt ){
if( in!=0 ) break;
seenInterrupt = 0;
}
lineno++;
if( (zSql==0 || zSql[0]==0) && _all_whitespace(zLine) ) continue;
if( zLine && zLine[0]=='.' && nSql==0 ){
if( p->echoOn ) printf("%s\n", zLine);
rc = do_meta_command(zLine, p);
if( rc==2 ){ /* exit requested */
break;
}else if( rc ){
errCnt++;
}
continue;
}
if( _is_command_terminator(zLine) && _is_complete(zSql, nSql) ){
memcpy(zLine,";",2);
}
nSqlPrior = nSql;
if( zSql==0 ){
int i;
for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
if( zLine[i]!=0 ){
nSql = strlen30(zLine);
zSql = malloc( nSql+3 );
if( zSql==0 ){
fprintf(stderr, "Error: out of memory\n");
exit(1);
}
memcpy(zSql, zLine, nSql+1);
startline = lineno;
}
}else{
int len = strlen30(zLine);
zSql = realloc( zSql, nSql + len + 4 );
if( zSql==0 ){
fprintf(stderr,"Error: out of memory\n");
exit(1);
}
zSql[nSql++] = '\n';
memcpy(&zSql[nSql], zLine, len+1);
nSql += len;
}
if( zSql && _contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior)
&& sqlite3_complete(zSql) ){
p->cnt = 0;
open_db(p);
BEGIN_TIMER;
rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg);
END_TIMER;
if( rc || zErrMsg ){
char zPrefix[100];
if( in!=0 || !stdin_is_interactive ){
sqlite3_snprintf(sizeof(zPrefix), zPrefix,
"Error: near line %d:", startline);
}else{
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:");
}
if( zErrMsg!=0 ){
fprintf(stderr, "%s %s\n", zPrefix, zErrMsg);
sqlite3_free(zErrMsg);
zErrMsg = 0;
}else{
fprintf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db));
}
errCnt++;
}
free(zSql);
zSql = 0;
nSql = 0;
}
}
if( zSql ){
if( !_all_whitespace(zSql) ){
fprintf(stderr, "Error: incomplete SQL: %s\n", zSql);
}
free(zSql);
}
free(zLine);
return errCnt>0;
}
/*
** Return a pathname which is the user's home directory. A
** 0 return indicates an error of some kind.
*/
static char *find_home_dir(void){
static char *home_dir = NULL;
if( home_dir ) return home_dir;
#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) && !defined(__RTP__) && !defined(_WRS_KERNEL)
{
struct passwd *pwent;
uid_t uid = getuid();
if( (pwent=getpwuid(uid)) != NULL) {
home_dir = pwent->pw_dir;
}
}
#endif
#if defined(_WIN32_WCE)
/* Windows CE (arm-wince-mingw32ce-gcc) does not provide getenv()
*/
home_dir = "/";
#else
#if defined(_WIN32) || defined(WIN32)
if (!home_dir) {
home_dir = getenv("USERPROFILE");
}
#endif
if (!home_dir) {
home_dir = getenv("HOME");
}
#if defined(_WIN32) || defined(WIN32)
if (!home_dir) {
char *zDrive, *zPath;
int n;
zDrive = getenv("HOMEDRIVE");
zPath = getenv("HOMEPATH");
if( zDrive && zPath ){
n = strlen30(zDrive) + strlen30(zPath) + 1;
home_dir = malloc( n );
if( home_dir==0 ) return 0;
sqlite3_snprintf(n, home_dir, "%s%s", zDrive, zPath);
return home_dir;
}
home_dir = "c:\\";
}
#endif
#endif /* !_WIN32_WCE */
if( home_dir ){
int n = strlen30(home_dir) + 1;
char *z = malloc( n );
if( z ) memcpy(z, home_dir, n);
home_dir = z;
}
return home_dir;
}
/*
** Read input from the file given by sqliterc_override. Or if that
** parameter is NULL, take input from ~/.sqliterc
**
** Returns the number of errors.
*/
static int process_sqliterc(
struct callback_data *p, /* Configuration data */
const char *sqliterc_override /* Name of config file. NULL to use default */
){
char *home_dir = NULL;
const char *sqliterc = sqliterc_override;
char *zBuf = 0;
FILE *in = NULL;
int rc = 0;
if (sqliterc == NULL) {
home_dir = find_home_dir();
if( home_dir==0 ){
#if !defined(__RTP__) && !defined(_WRS_KERNEL)
fprintf(stderr,"%s: Error: cannot locate your home directory\n", Argv0);
#endif
return 1;
}
sqlite3_initialize();
zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir);
sqliterc = zBuf;
}
in = fopen(sqliterc,"rb");
if( in ){
if( stdin_is_interactive ){
fprintf(stderr,"-- Loading resources from %s\n",sqliterc);
}
rc = process_input(p,in);
fclose(in);
}
sqlite3_free(zBuf);
return rc;
}
/*
** Show available command line options
*/
static const char zOptions[] =
" -bail stop after hitting an error\n"
" -batch force batch I/O\n"
" -column set output mode to 'column'\n"
" -cmd COMMAND run \"COMMAND\" before reading stdin\n"
" -csv set output mode to 'csv'\n"
" -echo print commands before execution\n"
" -init FILENAME read/process named file\n"
" -[no]header turn headers on or off\n"
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
" -heap SIZE Size of heap for memsys3 or memsys5\n"
#endif
" -help show this message\n"
" -html set output mode to HTML\n"
" -interactive force interactive I/O\n"
" -line set output mode to 'line'\n"
" -list set output mode to 'list'\n"
#ifdef SQLITE_ENABLE_MULTIPLEX
" -multiplex enable the multiplexor VFS\n"
#endif
" -nullvalue TEXT set text string for NULL values. Default ''\n"
" -separator SEP set output field separator. Default: '|'\n"
" -stats print memory stats before each finalize\n"
" -version show SQLite version\n"
" -vfs NAME use NAME as the default VFS\n"
#ifdef SQLITE_ENABLE_VFSTRACE
" -vfstrace enable tracing of all VFS calls\n"
#endif
;
static void usage(int showDetail){
fprintf(stderr,
"Usage: %s [OPTIONS] FILENAME [SQL]\n"
"FILENAME is the name of an SQLite database. A new database is created\n"
"if the file does not previously exist.\n", Argv0);
if( showDetail ){
fprintf(stderr, "OPTIONS include:\n%s", zOptions);
}else{
fprintf(stderr, "Use the -help option for additional information\n");
}
exit(1);
}
/*
** Initialize the state information in data
*/
static void main_init(struct callback_data *data) {
memset(data, 0, sizeof(*data));
data->mode = MODE_List;
memcpy(data->separator,"|", 2);
data->showHeader = 0;
sqlite3_config(SQLITE_CONFIG_URI, 1);
sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
}
/*
** Get the argument to an --option. Throw an error and die if no argument
** is available.
*/
static char *cmdline_option_value(int argc, char **argv, int i){
if( i==argc ){
fprintf(stderr, "%s: Error: missing argument to %s\n",
argv[0], argv[argc-1]);
exit(1);
}
return argv[i];
}
int main(int argc, char **argv){
char *zErrMsg = 0;
struct callback_data data;
const char *zInitFile = 0;
char *zFirstCmd = 0;
int i;
int rc = 0;
if( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)!=0 ){
fprintf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
sqlite3_sourceid(), SQLITE_SOURCE_ID);
exit(1);
}
Argv0 = argv[0];
main_init(&data);
stdin_is_interactive = isatty(0);
/* Make sure we have a valid signal handler early, before anything
** else is done.
*/
#ifdef SIGINT
signal(SIGINT, interrupt_handler);
#endif
/* Do an initial pass through the command-line argument to locate
** the name of the database file, the name of the initialization file,
** the size of the alternative malloc heap,
** and the first command to execute.
*/
for(i=1; i<argc; i++){
char *z;
z = argv[i];
if( z[0]!='-' ){
if( data.zDbFilename==0 ){
data.zDbFilename = z;
continue;
}
if( zFirstCmd==0 ){
zFirstCmd = z;
continue;
}
fprintf(stderr,"%s: Error: too many options: \"%s\"\n", Argv0, argv[i]);
fprintf(stderr,"Use -help for a list of options.\n");
return 1;
}
if( z[1]=='-' ) z++;
if( strcmp(z,"-separator")==0
|| strcmp(z,"-nullvalue")==0
|| strcmp(z,"-cmd")==0
){
(void)cmdline_option_value(argc, argv, ++i);
}else if( strcmp(z,"-init")==0 ){
zInitFile = cmdline_option_value(argc, argv, ++i);
}else if( strcmp(z,"-batch")==0 ){
/* Need to check for batch mode here to so we can avoid printing
** informational messages (like from process_sqliterc) before
** we do the actual processing of arguments later in a second pass.
*/
stdin_is_interactive = 0;
}else if( strcmp(z,"-heap")==0 ){
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
int j, c;
const char *zSize;
sqlite3_int64 szHeap;
zSize = cmdline_option_value(argc, argv, ++i);
szHeap = atoi(zSize);
for(j=0; (c = zSize[j])!=0; j++){
if( c=='M' ){ szHeap *= 1000000; break; }
if( c=='K' ){ szHeap *= 1000; break; }
if( c=='G' ){ szHeap *= 1000000000; break; }
}
if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
#endif
#ifdef SQLITE_ENABLE_VFSTRACE
}else if( strcmp(z,"-vfstrace")==0 ){
extern int vfstrace_register(
const char *zTraceName,
const char *zOldVfsName,
int (*xOut)(const char*,void*),
void *pOutArg,
int makeDefault
);
vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
#endif
#ifdef SQLITE_ENABLE_MULTIPLEX
}else if( strcmp(z,"-multiplex")==0 ){
extern int sqlite3_multiple_initialize(const char*,int);
sqlite3_multiplex_initialize(0, 1);
#endif
}else if( strcmp(z,"-vfs")==0 ){
sqlite3_vfs *pVfs = sqlite3_vfs_find(cmdline_option_value(argc,argv,++i));
if( pVfs ){
sqlite3_vfs_register(pVfs, 1);
}else{
fprintf(stderr, "no such VFS: \"%s\"\n", argv[i]);
exit(1);
}
}
}
if( data.zDbFilename==0 ){
#ifndef SQLITE_OMIT_MEMORYDB
data.zDbFilename = ":memory:";
#else
fprintf(stderr,"%s: Error: no database filename specified\n", Argv0);
return 1;
#endif
}
data.out = stdout;
/* Go ahead and open the database file if it already exists. If the
** file does not exist, delay opening it. This prevents empty database
** files from being created if a user mistypes the database name argument
** to the sqlite command-line tool.
*/
if( access(data.zDbFilename, 0)==0 ){
open_db(&data);
}
/* Process the initialization file if there is one. If no -init option
** is given on the command line, look for a file named ~/.sqliterc and
** try to process it.
*/
rc = process_sqliterc(&data,zInitFile);
if( rc>0 ){
return rc;
}
/* Make a second pass through the command-line argument and set
** options. This second pass is delayed until after the initialization
** file is processed so that the command-line arguments will override
** settings in the initialization file.
*/
for(i=1; i<argc; i++){
char *z = argv[i];
if( z[0]!='-' ) continue;
if( z[1]=='-' ){ z++; }
if( strcmp(z,"-init")==0 ){
i++;
}else if( strcmp(z,"-html")==0 ){
data.mode = MODE_Html;
}else if( strcmp(z,"-list")==0 ){
data.mode = MODE_List;
}else if( strcmp(z,"-line")==0 ){
data.mode = MODE_Line;
}else if( strcmp(z,"-column")==0 ){
data.mode = MODE_Column;
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.separator,",",2);
}else if( strcmp(z,"-separator")==0 ){
sqlite3_snprintf(sizeof(data.separator), data.separator,
"%s",cmdline_option_value(argc,argv,++i));
}else if( strcmp(z,"-nullvalue")==0 ){
sqlite3_snprintf(sizeof(data.nullvalue), data.nullvalue,
"%s",cmdline_option_value(argc,argv,++i));
}else if( strcmp(z,"-header")==0 ){
data.showHeader = 1;
}else if( strcmp(z,"-noheader")==0 ){
data.showHeader = 0;
}else if( strcmp(z,"-echo")==0 ){
data.echoOn = 1;
}else if( strcmp(z,"-stats")==0 ){
data.statsOn = 1;
}else if( strcmp(z,"-bail")==0 ){
bail_on_error = 1;
}else if( strcmp(z,"-version")==0 ){
printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
return 0;
}else if( strcmp(z,"-interactive")==0 ){
stdin_is_interactive = 1;
}else if( strcmp(z,"-batch")==0 ){
stdin_is_interactive = 0;
}else if( strcmp(z,"-heap")==0 ){
i++;
}else if( strcmp(z,"-vfs")==0 ){
i++;
#ifdef SQLITE_ENABLE_VFSTRACE
}else if( strcmp(z,"-vfstrace")==0 ){
i++;
#endif
#ifdef SQLITE_ENABLE_MULTIPLEX
}else if( strcmp(z,"-multiplex")==0 ){
i++;
#endif
}else if( strcmp(z,"-help")==0 ){
usage(1);
}else if( strcmp(z,"-cmd")==0 ){
if( i==argc-1 ) break;
z = cmdline_option_value(argc,argv,++i);
if( z[0]=='.' ){
rc = do_meta_command(z, &data);
if( rc && bail_on_error ) return rc;
}else{
open_db(&data);
rc = shell_exec(data.db, z, shell_callback, &data, &zErrMsg);
if( zErrMsg!=0 ){
fprintf(stderr,"Error: %s\n", zErrMsg);
if( bail_on_error ) return rc!=0 ? rc : 1;
}else if( rc!=0 ){
fprintf(stderr,"Error: unable to process SQL \"%s\"\n", z);
if( bail_on_error ) return rc;
}
}
}else{
fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
fprintf(stderr,"Use -help for a list of options.\n");
return 1;
}
}
if( zFirstCmd ){
/* Run just the command that follows the database name
*/
if( zFirstCmd[0]=='.' ){
rc = do_meta_command(zFirstCmd, &data);
}else{
open_db(&data);
rc = shell_exec(data.db, zFirstCmd, shell_callback, &data, &zErrMsg);
if( zErrMsg!=0 ){
fprintf(stderr,"Error: %s\n", zErrMsg);
return rc!=0 ? rc : 1;
}else if( rc!=0 ){
fprintf(stderr,"Error: unable to process SQL \"%s\"\n", zFirstCmd);
return rc;
}
}
}else{
/* Run commands received from standard input
*/
if( stdin_is_interactive ){
char *zHome;
char *zHistory = 0;
int nHistory;
printf(
"SQLite version %s %.19s\n" /*extra-version-info*/
"Enter \".help\" for instructions\n"
"Enter SQL statements terminated with a \";\"\n",
sqlite3_libversion(), sqlite3_sourceid()
);
zHome = find_home_dir();
if( zHome ){
nHistory = strlen30(zHome) + 20;
if( (zHistory = malloc(nHistory))!=0 ){
sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
}
}
#if defined(HAVE_READLINE) && HAVE_READLINE==1
if( zHistory ) read_history(zHistory);
#endif
rc = process_input(&data, 0);
if( zHistory ){
stifle_history(100);
write_history(zHistory);
free(zHistory);
}
}else{
rc = process_input(&data, stdin);
}
}
set_table_name(&data, 0);
if( data.db ){
sqlite3_close(data.db);
}
return rc;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
** 2006 June 7
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance. Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of
** sqlite3.h.
*/
#ifndef _SQLITE3EXT_H_
#define _SQLITE3EXT_H_
#include "sqlite3.h"
typedef struct sqlite3_api_routines sqlite3_api_routines;
/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING: In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only. If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each others' shared
** libraries!
*/
struct sqlite3_api_routines {
void * (*aggregate_context)(sqlite3_context*,int nBytes);
int (*aggregate_count)(sqlite3_context*);
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
int (*bind_double)(sqlite3_stmt*,int,double);
int (*bind_int)(sqlite3_stmt*,int,int);
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
int (*bind_null)(sqlite3_stmt*,int);
int (*bind_parameter_count)(sqlite3_stmt*);
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
int (*busy_timeout)(sqlite3*,int ms);
int (*changes)(sqlite3*);
int (*close)(sqlite3*);
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const char*));
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const void*));
const void * (*column_blob)(sqlite3_stmt*,int iCol);
int (*column_bytes)(sqlite3_stmt*,int iCol);
int (*column_bytes16)(sqlite3_stmt*,int iCol);
int (*column_count)(sqlite3_stmt*pStmt);
const char * (*column_database_name)(sqlite3_stmt*,int);
const void * (*column_database_name16)(sqlite3_stmt*,int);
const char * (*column_decltype)(sqlite3_stmt*,int i);
const void * (*column_decltype16)(sqlite3_stmt*,int);
double (*column_double)(sqlite3_stmt*,int iCol);
int (*column_int)(sqlite3_stmt*,int iCol);
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
const char * (*column_name)(sqlite3_stmt*,int);
const void * (*column_name16)(sqlite3_stmt*,int);
const char * (*column_origin_name)(sqlite3_stmt*,int);
const void * (*column_origin_name16)(sqlite3_stmt*,int);
const char * (*column_table_name)(sqlite3_stmt*,int);
const void * (*column_table_name16)(sqlite3_stmt*,int);
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
const void * (*column_text16)(sqlite3_stmt*,int iCol);
int (*column_type)(sqlite3_stmt*,int iCol);
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
int (*complete)(const char*sql);
int (*complete16)(const void*sql);
int (*create_collation)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_collation16)(sqlite3*,const void*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_function)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_function16)(sqlite3*,const void*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
int (*data_count)(sqlite3_stmt*pStmt);
sqlite3 * (*db_handle)(sqlite3_stmt*);
int (*declare_vtab)(sqlite3*,const char*);
int (*enable_shared_cache)(int);
int (*errcode)(sqlite3*db);
const char * (*errmsg)(sqlite3*);
const void * (*errmsg16)(sqlite3*);
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
int (*expired)(sqlite3_stmt*);
int (*finalize)(sqlite3_stmt*pStmt);
void (*free)(void*);
void (*free_table)(char**result);
int (*get_autocommit)(sqlite3*);
void * (*get_auxdata)(sqlite3_context*,int);
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
int (*global_recover)(void);
void (*interruptx)(sqlite3*);
sqlite_int64 (*last_insert_rowid)(sqlite3*);
const char * (*libversion)(void);
int (*libversion_number)(void);
void *(*malloc)(int);
char * (*mprintf)(const char*,...);
int (*open)(const char*,sqlite3**);
int (*open16)(const void*,sqlite3**);
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
void *(*realloc)(void*,int);
int (*reset)(sqlite3_stmt*pStmt);
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_double)(sqlite3_context*,double);
void (*result_error)(sqlite3_context*,const char*,int);
void (*result_error16)(sqlite3_context*,const void*,int);
void (*result_int)(sqlite3_context*,int);
void (*result_int64)(sqlite3_context*,sqlite_int64);
void (*result_null)(sqlite3_context*);
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_value)(sqlite3_context*,sqlite3_value*);
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
char * (*snprintf)(int,char*,const char*,...);
int (*step)(sqlite3_stmt*);
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
void (*thread_cleanup)(void);
int (*total_changes)(sqlite3*);
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
sqlite_int64),void*);
void * (*user_data)(sqlite3_context*);
const void * (*value_blob)(sqlite3_value*);
int (*value_bytes)(sqlite3_value*);
int (*value_bytes16)(sqlite3_value*);
double (*value_double)(sqlite3_value*);
int (*value_int)(sqlite3_value*);
sqlite_int64 (*value_int64)(sqlite3_value*);
int (*value_numeric_type)(sqlite3_value*);
const unsigned char * (*value_text)(sqlite3_value*);
const void * (*value_text16)(sqlite3_value*);
const void * (*value_text16be)(sqlite3_value*);
const void * (*value_text16le)(sqlite3_value*);
int (*value_type)(sqlite3_value*);
char *(*vmprintf)(const char*,va_list);
/* Added ??? */
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
/* Added by 3.3.13 */
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
int (*clear_bindings)(sqlite3_stmt*);
/* Added by 3.4.1 */
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
void (*xDestroy)(void *));
/* Added by 3.5.0 */
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
int (*blob_bytes)(sqlite3_blob*);
int (*blob_close)(sqlite3_blob*);
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
int,sqlite3_blob**);
int (*blob_read)(sqlite3_blob*,void*,int,int);
int (*blob_write)(sqlite3_blob*,const void*,int,int);
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*),
void(*)(void*));
int (*file_control)(sqlite3*,const char*,int,void*);
sqlite3_int64 (*memory_highwater)(int);
sqlite3_int64 (*memory_used)(void);
sqlite3_mutex *(*mutex_alloc)(int);
void (*mutex_enter)(sqlite3_mutex*);
void (*mutex_free)(sqlite3_mutex*);
void (*mutex_leave)(sqlite3_mutex*);
int (*mutex_try)(sqlite3_mutex*);
int (*open_v2)(const char*,sqlite3**,int,const char*);
int (*release_memory)(int);
void (*result_error_nomem)(sqlite3_context*);
void (*result_error_toobig)(sqlite3_context*);
int (*sleep)(int);
void (*soft_heap_limit)(int);
sqlite3_vfs *(*vfs_find)(const char*);
int (*vfs_register)(sqlite3_vfs*,int);
int (*vfs_unregister)(sqlite3_vfs*);
int (*xthreadsafe)(void);
void (*result_zeroblob)(sqlite3_context*,int);
void (*result_error_code)(sqlite3_context*,int);
int (*test_control)(int, ...);
void (*randomness)(int,void*);
sqlite3 *(*context_db_handle)(sqlite3_context*);
int (*extended_result_codes)(sqlite3*,int);
int (*limit)(sqlite3*,int,int);
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
const char *(*sql)(sqlite3_stmt*);
int (*status)(int,int*,int*,int);
int (*backup_finish)(sqlite3_backup*);
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
int (*backup_pagecount)(sqlite3_backup*);
int (*backup_remaining)(sqlite3_backup*);
int (*backup_step)(sqlite3_backup*,int);
const char *(*compileoption_get)(int);
int (*compileoption_used)(const char*);
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void(*xDestroy)(void*));
int (*db_config)(sqlite3*,int,...);
sqlite3_mutex *(*db_mutex)(sqlite3*);
int (*db_status)(sqlite3*,int,int*,int*,int);
int (*extended_errcode)(sqlite3*);
void (*log)(int,const char*,...);
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
const char *(*sourceid)(void);
int (*stmt_status)(sqlite3_stmt*,int,int);
int (*strnicmp)(const char*,const char*,int);
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
int (*wal_autocheckpoint)(sqlite3*,int);
int (*wal_checkpoint)(sqlite3*,const char*);
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
int (*vtab_config)(sqlite3*,int op,...);
int (*vtab_on_conflict)(sqlite3*);
};
/*
** The following macros redefine the API routines so that they are
** redirected throught the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition. But the main library does not want to redefine
** the API. So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#ifndef SQLITE_CORE
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob sqlite3_api->bind_blob
#define sqlite3_bind_double sqlite3_api->bind_double
#define sqlite3_bind_int sqlite3_api->bind_int
#define sqlite3_bind_int64 sqlite3_api->bind_int64
#define sqlite3_bind_null sqlite3_api->bind_null
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
#define sqlite3_bind_text sqlite3_api->bind_text
#define sqlite3_bind_text16 sqlite3_api->bind_text16
#define sqlite3_bind_value sqlite3_api->bind_value
#define sqlite3_busy_handler sqlite3_api->busy_handler
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
#define sqlite3_changes sqlite3_api->changes
#define sqlite3_close sqlite3_api->close
#define sqlite3_collation_needed sqlite3_api->collation_needed
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
#define sqlite3_column_blob sqlite3_api->column_blob
#define sqlite3_column_bytes sqlite3_api->column_bytes
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
#define sqlite3_column_count sqlite3_api->column_count
#define sqlite3_column_database_name sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype sqlite3_api->column_decltype
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
#define sqlite3_column_double sqlite3_api->column_double
#define sqlite3_column_int sqlite3_api->column_int
#define sqlite3_column_int64 sqlite3_api->column_int64
#define sqlite3_column_name sqlite3_api->column_name
#define sqlite3_column_name16 sqlite3_api->column_name16
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
#define sqlite3_column_table_name sqlite3_api->column_table_name
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
#define sqlite3_column_text sqlite3_api->column_text
#define sqlite3_column_text16 sqlite3_api->column_text16
#define sqlite3_column_type sqlite3_api->column_type
#define sqlite3_column_value sqlite3_api->column_value
#define sqlite3_commit_hook sqlite3_api->commit_hook
#define sqlite3_complete sqlite3_api->complete
#define sqlite3_complete16 sqlite3_api->complete16
#define sqlite3_create_collation sqlite3_api->create_collation
#define sqlite3_create_collation16 sqlite3_api->create_collation16
#define sqlite3_create_function sqlite3_api->create_function
#define sqlite3_create_function16 sqlite3_api->create_function16
#define sqlite3_create_module sqlite3_api->create_module
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
#define sqlite3_data_count sqlite3_api->data_count
#define sqlite3_db_handle sqlite3_api->db_handle
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
#define sqlite3_errcode sqlite3_api->errcode
#define sqlite3_errmsg sqlite3_api->errmsg
#define sqlite3_errmsg16 sqlite3_api->errmsg16
#define sqlite3_exec sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired sqlite3_api->expired
#endif
#define sqlite3_finalize sqlite3_api->finalize
#define sqlite3_free sqlite3_api->free
#define sqlite3_free_table sqlite3_api->free_table
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
#define sqlite3_get_table sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover sqlite3_api->global_recover
#endif
#define sqlite3_interrupt sqlite3_api->interruptx
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
#define sqlite3_libversion sqlite3_api->libversion
#define sqlite3_libversion_number sqlite3_api->libversion_number
#define sqlite3_malloc sqlite3_api->malloc
#define sqlite3_mprintf sqlite3_api->mprintf
#define sqlite3_open sqlite3_api->open
#define sqlite3_open16 sqlite3_api->open16
#define sqlite3_prepare sqlite3_api->prepare
#define sqlite3_prepare16 sqlite3_api->prepare16
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_profile sqlite3_api->profile
#define sqlite3_progress_handler sqlite3_api->progress_handler
#define sqlite3_realloc sqlite3_api->realloc
#define sqlite3_reset sqlite3_api->reset
#define sqlite3_result_blob sqlite3_api->result_blob
#define sqlite3_result_double sqlite3_api->result_double
#define sqlite3_result_error sqlite3_api->result_error
#define sqlite3_result_error16 sqlite3_api->result_error16
#define sqlite3_result_int sqlite3_api->result_int
#define sqlite3_result_int64 sqlite3_api->result_int64
#define sqlite3_result_null sqlite3_api->result_null
#define sqlite3_result_text sqlite3_api->result_text
#define sqlite3_result_text16 sqlite3_api->result_text16
#define sqlite3_result_text16be sqlite3_api->result_text16be
#define sqlite3_result_text16le sqlite3_api->result_text16le
#define sqlite3_result_value sqlite3_api->result_value
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
#define sqlite3_snprintf sqlite3_api->snprintf
#define sqlite3_step sqlite3_api->step
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
#define sqlite3_total_changes sqlite3_api->total_changes
#define sqlite3_trace sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook sqlite3_api->update_hook
#define sqlite3_user_data sqlite3_api->user_data
#define sqlite3_value_blob sqlite3_api->value_blob
#define sqlite3_value_bytes sqlite3_api->value_bytes
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
#define sqlite3_value_double sqlite3_api->value_double
#define sqlite3_value_int sqlite3_api->value_int
#define sqlite3_value_int64 sqlite3_api->value_int64
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
#define sqlite3_value_text sqlite3_api->value_text
#define sqlite3_value_text16 sqlite3_api->value_text16
#define sqlite3_value_text16be sqlite3_api->value_text16be
#define sqlite3_value_text16le sqlite3_api->value_text16le
#define sqlite3_value_type sqlite3_api->value_type
#define sqlite3_vmprintf sqlite3_api->vmprintf
#define sqlite3_overload_function sqlite3_api->overload_function
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
#define sqlite3_blob_close sqlite3_api->blob_close
#define sqlite3_blob_open sqlite3_api->blob_open
#define sqlite3_blob_read sqlite3_api->blob_read
#define sqlite3_blob_write sqlite3_api->blob_write
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
#define sqlite3_file_control sqlite3_api->file_control
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
#define sqlite3_memory_used sqlite3_api->memory_used
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
#define sqlite3_mutex_free sqlite3_api->mutex_free
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
#define sqlite3_mutex_try sqlite3_api->mutex_try
#define sqlite3_open_v2 sqlite3_api->open_v2
#define sqlite3_release_memory sqlite3_api->release_memory
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
#define sqlite3_sleep sqlite3_api->sleep
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find sqlite3_api->vfs_find
#define sqlite3_vfs_register sqlite3_api->vfs_register
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
#define sqlite3_result_error_code sqlite3_api->result_error_code
#define sqlite3_test_control sqlite3_api->test_control
#define sqlite3_randomness sqlite3_api->randomness
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
#define sqlite3_limit sqlite3_api->limit
#define sqlite3_next_stmt sqlite3_api->next_stmt
#define sqlite3_sql sqlite3_api->sql
#define sqlite3_status sqlite3_api->status
#define sqlite3_backup_finish sqlite3_api->backup_finish
#define sqlite3_backup_init sqlite3_api->backup_init
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
#define sqlite3_backup_step sqlite3_api->backup_step
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
#define sqlite3_db_config sqlite3_api->db_config
#define sqlite3_db_mutex sqlite3_api->db_mutex
#define sqlite3_db_status sqlite3_api->db_status
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
#define sqlite3_log sqlite3_api->log
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid sqlite3_api->sourceid
#define sqlite3_stmt_status sqlite3_api->stmt_status
#define sqlite3_strnicmp sqlite3_api->strnicmp
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook sqlite3_api->wal_hook
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
#define sqlite3_vtab_config sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
#endif /* SQLITE_CORE */
#define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api = 0;
#define SQLITE_EXTENSION_INIT2(v) sqlite3_api = v;
#endif /* _SQLITE3EXT_H_ */
// vim: set ts=4 expandtab sw=4 :
/*
** Copyright 2015, Pierre-Hugues Husson <phh@phh.me>
** 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
#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 <strings.h>
#include <stdint.h>
#include <pwd.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <sys/types.h>
#include <selinux/selinux.h>
#include <arpa/inet.h>
#include <sys/auxv.h>
#include "su.h"
#include "utils.h"
#include "binds.h"
extern int is_daemon;
extern int daemon_from_uid;
extern int daemon_from_pid;
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 fork_zero_fucks() {
int pid = fork();
if (pid) {
int status;
waitpid(pid, &status, 0);
return pid;
}
else {
if ( (pid = fork()) != 0)
exit(0);
return 0;
}
}
void exec_log(char *priority, char* logline) {
int pid;
if ((pid = fork()) == 0) {
int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO);
execl("/system/bin/log", "/system/bin/log", "-p", priority, "-t", LOG_TAG, logline, NULL);
_exit(0);
}
int status;
waitpid(pid, &status, 0);
}
void exec_loge(const char* fmt, ...) {
va_list args;
char logline[PATH_MAX];
va_start(args, fmt);
vsnprintf(logline, PATH_MAX, fmt, args);
va_end(args);
exec_log("e", logline);
}
void exec_logw(const char* fmt, ...) {
va_list args;
char logline[PATH_MAX];
va_start(args, fmt);
vsnprintf(logline, PATH_MAX, fmt, args);
va_end(args);
exec_log("w", logline);
}
void exec_logd(const char* fmt, ...) {
va_list args;
char logline[PATH_MAX];
va_start(args, fmt);
vsnprintf(logline, PATH_MAX, fmt, args);
va_end(args);
exec_log("d", logline);
}
static int from_init(struct su_initiator *from) {
char path[PATH_MAX], exe[PATH_MAX];
char args[4096], *argv0, *argv_rest;
int fd;
ssize_t len;
int i;
int err;
from->uid = getuid();
from->pid = getppid();
if (is_daemon) {
from->uid = daemon_from_uid;
from->pid = daemon_from_pid;
}
/* Get the command line */
snprintf(path, sizeof(path), "/proc/%u/cmdline", from->pid);
fd = open(path, O_RDONLY);
if (fd < 0) {
PLOGE("Opening command line");
return -1;
}
len = read(fd, args, sizeof(args));
err = errno;
close(fd);
if (len < 0 || len == sizeof(args)) {
PLOGEV("Reading command line", err);
return -1;
}
argv0 = args;
argv_rest = NULL;
for (i = 0; i < len; i++) {
if (args[i] == '\0') {
if (!argv_rest) {
argv_rest = &args[i+1];
} else {
args[i] = ' ';
}
}
}
args[len] = '\0';
if (argv_rest) {
strncpy(from->args, argv_rest, sizeof(from->args));
from->args[sizeof(from->args)-1] = '\0';
} else {
from->args[0] = '\0';
}
/* If this isn't app_process, use the real path instead of argv[0] */
snprintf(path, sizeof(path), "/proc/%u/exe", from->pid);
len = readlink(path, exe, sizeof(exe));
if (len < 0) {
PLOGE("Getting exe path");
return -1;
}
exe[len] = '\0';
if (strcmp(exe, "/system/bin/app_process")) {
argv0 = exe;
}
strncpy(from->bin, argv0, sizeof(from->bin));
from->bin[sizeof(from->bin)-1] = '\0';
struct passwd *pw;
pw = getpwuid(from->uid);
if (pw && pw->pw_name) {
strncpy(from->name, pw->pw_name, sizeof(from->name));
}
return 0;
}
static int get_multiuser_mode() {
char *data;
char sdk_ver[PROPERTY_VALUE_MAX];
data = read_file("/system/build.prop");
get_property(data, sdk_ver, "ro.build.version.sdk", "0");
free(data);
int sdk = atoi(sdk_ver);
if (sdk < 17)
return MULTIUSER_MODE_NONE;
int ret = MULTIUSER_MODE_OWNER_ONLY;
char mode[12];
FILE *fp;
if ((fp = fopen(REQUESTOR_MULTIUSER_MODE, "r"))) {
fgets(mode, sizeof(mode), fp);
int last = strlen(mode) - 1;
if (mode[last] == '\n')
mode[last] = '\0';
if (strcmp(mode, MULTIUSER_VALUE_USER) == 0) {
ret = MULTIUSER_MODE_USER;
} else if (strcmp(mode, MULTIUSER_VALUE_OWNER_MANAGED) == 0) {
ret = MULTIUSER_MODE_OWNER_MANAGED;
}
else {
ret = MULTIUSER_MODE_OWNER_ONLY;
}
fclose(fp);
}
return ret;
}
static void read_options(struct su_context *ctx) {
ctx->user.multiuser_mode = get_multiuser_mode();
}
static void user_init(struct su_context *ctx) {
if (ctx->from.uid > 99999) {
ctx->user.android_user_id = ctx->from.uid / 100000;
if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) {
snprintf(ctx->user.database_path, PATH_MAX, "%s/%d/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR_DATABASE_PATH);
snprintf(ctx->user.base_path, PATH_MAX, "%s/%d/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR);
}
}
}
static void populate_environment(const struct su_context *ctx) {
struct passwd *pw;
if (ctx->to.keepenv)
return;
pw = getpwuid(ctx->to.uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
if (ctx->to.shell)
setenv("SHELL", ctx->to.shell, 1);
else
setenv("SHELL", DEFAULT_SHELL, 1);
if (ctx->to.login || ctx->to.uid) {
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
}
}
}
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) {
if (ctx && ctx->sock_path[0]) {
if (unlink(ctx->sock_path))
PLOGE("unlink (%s)", ctx->sock_path);
ctx->sock_path[0] = 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(su_ctx);
}
static void cleanup_signal(int sig) {
socket_cleanup(su_ctx);
exit(128 + sig);
}
static int socket_create_temp(char *path, size_t len) {
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;
snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid());
memset(sun.sun_path, 0, sizeof(sun.sun_path));
snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", 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);
if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
PLOGE("bind");
goto err;
}
if (listen(fd, 1) < 0) {
PLOGE("listen");
goto err;
}
return fd;
err:
close(fd);
return -1;
}
static int socket_accept(int serv_fd) {
struct timeval tv;
fd_set fds;
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);
do {
rc = select(serv_fd + 1, &fds, NULL, NULL, &tv);
} while (rc < 0 && errno == EINTR);
if (rc < 1) {
PLOGE("select");
return -1;
}
fd = accept(serv_fd, NULL, NULL);
if (fd < 0) {
PLOGE("accept");
return -1;
}
return fd;
}
#define write_data(fd, data, data_len) \
do { \
uint32_t __len = htonl(data_len); \
__len = write((fd), &__len, sizeof(__len)); \
if (__len != sizeof(__len)) { \
PLOGE("write(" #data ")"); \
return -1; \
} \
__len = write((fd), data, data_len); \
if (__len != data_len) { \
PLOGE("write(" #data ")"); \
return -1; \
} \
} while (0)
#define write_string_data(fd, name, data) \
do { \
write_data(fd, name, strlen(name)); \
write_data(fd, data, strlen(data)); \
} while (0)
// stringify everything.
#define write_token(fd, name, data) \
do { \
char buf[16]; \
snprintf(buf, sizeof(buf), "%d", data); \
write_string_data(fd, name, buf); \
} while (0)
static int socket_send_request(int fd, const struct su_context *ctx) {
write_token(fd, "version", PROTO_VERSION);
write_token(fd, "binary.version", VERSION_CODE);
write_token(fd, "pid", ctx->from.pid);
write_string_data(fd, "from.name", ctx->from.name);
write_string_data(fd, "to.name", ctx->to.name);
write_token(fd, "from.uid", ctx->from.uid);
write_token(fd, "to.uid", ctx->to.uid);
write_string_data(fd, "from.bin", ctx->from.bin);
write_string_data(fd, "bind.from", ctx->bind.from);
write_string_data(fd, "bind.to", ctx->bind.to);
write_string_data(fd, "init", ctx->init);
// TODO: Fix issue where not using -c does not result a in a command
write_string_data(fd, "command", get_command(&ctx->to));
write_token(fd, "eof", PROTO_VERSION);
return 0;
}
static int socket_receive_result(int fd, char *result, ssize_t result_len) {
ssize_t len;
LOGD("waiting for user");
len = read(fd, result, result_len-1);
if (len < 0) {
PLOGE("read(result)");
return -1;
}
result[len] = '\0';
return 0;
}
static void usage(int status) {
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
fprintf(stream,
"Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n"
"Options:\n"
" --daemon start the su daemon agent\n"
" -c, --command COMMAND pass COMMAND to the invoked shell\n"
" --context context Change SELinux context\n"
" -h, --help display this help message and exit\n"
" -, -l, --login pretend the shell to be a login shell\n"
" -m, -p,\n"
" --preserve-environment do not change environment variables\n"
" -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n"
" -u display the multiuser mode and exit\n"
" -v, --version display version number and exit\n"
" -V display version code and exit,\n"
" this is used almost exclusively by Superuser.apk\n");
exit(status);
}
static __attribute__ ((noreturn)) void allow_bind(struct su_context *ctx) {
if(ctx->from.uid == 0)
exit(1);
if(ctx->bind.from[0] == '!') {
int ret = bind_remove(ctx->bind.to, ctx->from.uid);
if(!ret) {
fprintf(stderr, "The mentioned bind destination path didn't exist\n");
exit(1);
}
exit(0);
}
if(strcmp("--ls", ctx->bind.from)==0) {
bind_ls(ctx->from.uid);
exit(0);
}
if(!bind_uniq_dst(ctx->bind.to)) {
fprintf(stderr, "BIND: Distant file NOT unique. I refuse.\n");
exit(1);
}
int fd = open("/data/su/binds", O_WRONLY|O_APPEND|O_CREAT, 0600);
if(fd<0) {
fprintf(stderr, "Failed to open binds file\n");
exit(1);
}
char *str = NULL;
int len = asprintf(&str, "%d:%s:%s", ctx->from.uid, ctx->bind.from, ctx->bind.to);
write(fd, str, len+1); //len doesn't include final \0 and we want to write it
free(str);
close(fd);
exit(0);
}
static __attribute__ ((noreturn)) void allow_init(struct su_context *ctx) {
if(ctx->init[0]=='!') {
int ret = init_remove(ctx->init+1, ctx->from.uid);
if(!ret) {
fprintf(stderr, "The mentioned init path didn't exist\n");
exit(1);
}
exit(0);
}
if(strcmp("--ls", ctx->init) == 0) {
init_ls(ctx->from.uid);
exit(0);
}
if(!init_uniq(ctx->init))
//This script is already in init list
exit(1);
int fd = open("/data/su/init", O_WRONLY|O_APPEND|O_CREAT, 0600);
if(fd<0) {
fprintf(stderr, "Failed to open init file\n");
exit(1);
}
char *str = NULL;
int len = asprintf(&str, "%d:%s", ctx->from.uid, ctx->init);
write(fd, str, len+1); //len doesn't include final \0 and we want to write it
free(str);
close(fd);
exit(0);
}
static __attribute__ ((noreturn)) void deny(struct su_context *ctx) {
char *cmd = get_command(&ctx->to);
int send_to_app = 1;
// no need to log if called by root
if (ctx->from.uid == AID_ROOT)
send_to_app = 0;
// dumpstate (which logs to logcat/shell) will spam the crap out of the system with su calls
if (strcmp("/system/bin/dumpstate", ctx->from.bin) == 0)
send_to_app = 0;
if (send_to_app)
send_result(ctx, DENY);
LOGW("request rejected (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd);
fprintf(stderr, "%s\n", strerror(EACCES));
exit(EXIT_FAILURE);
}
static __attribute__ ((noreturn)) void allow(struct su_context *ctx) {
char *arg0;
int argc, err;
hacks_update_context(ctx);
umask(ctx->umask);
int send_to_app = 1;
// no need to log if called by root
if (ctx->from.uid == AID_ROOT)
send_to_app = 0;
// dumpstate (which logs to logcat/shell) will spam the crap out of the system with su calls
if (strcmp("/system/bin/dumpstate", ctx->from.bin) == 0)
send_to_app = 0;
if (send_to_app)
send_result(ctx, ALLOW);
if(ctx->bind.from[0] && ctx->bind.to[0])
allow_bind(ctx);
if(ctx->init[0])
allow_init(ctx);
char *binary;
argc = ctx->to.optind;
if (ctx->to.command) {
binary = ctx->to.shell;
ctx->to.argv[--argc] = ctx->to.command;
ctx->to.argv[--argc] = "-c";
}
else if (ctx->to.shell) {
binary = ctx->to.shell;
}
else {
if (ctx->to.argv[argc]) {
binary = ctx->to.argv[argc++];
}
else {
binary = DEFAULT_SHELL;
}
}
arg0 = strrchr (binary, '/');
arg0 = (arg0) ? arg0 + 1 : binary;
if (ctx->to.login) {
int s = strlen(arg0) + 2;
char *p = malloc(s);
if (!p)
exit(EXIT_FAILURE);
*p = '-';
strcpy(p + 1, arg0);
arg0 = p;
}
populate_environment(ctx);
set_identity(ctx->to.uid);
#define PARG(arg) \
(argc + (arg) < ctx->to.argc) ? " " : "", \
(argc + (arg) < ctx->to.argc) ? ctx->to.argv[argc + (arg)] : ""
LOGD("%u %s executing %u %s using binary %s : %s%s%s%s%s%s%s%s%s%s%s%s%s%s",
ctx->from.uid, ctx->from.bin,
ctx->to.uid, get_command(&ctx->to), binary,
arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5),
(ctx->to.optind + 6 < ctx->to.argc) ? " ..." : "");
if(ctx->to.context && strcmp(ctx->to.context, "u:r:su_light:s0") == 0) {
setexeccon(ctx->to.context);
} else {
setexeccon("u:r:su:s0");
}
ctx->to.argv[--argc] = arg0;
execvp(binary, ctx->to.argv + argc);
err = errno;
PLOGE("exec");
fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(err));
exit(EXIT_FAILURE);
}
static int get_api_version() {
char sdk_ver[PROPERTY_VALUE_MAX];
char *data = read_file("/system/build.prop");
get_property(data, sdk_ver, "ro.build.version.sdk", "0");
int ver = atoi(sdk_ver);
free(data);
return ver;
}
static void fork_for_samsung(void)
{
// Samsung CONFIG_SEC_RESTRICT_SETUID wants the parent process to have
// EUID 0, or else our setresuid() calls will be denied. So make sure
// all such syscalls are executed by a child process.
int rv;
switch (fork()) {
case 0:
return;
case -1:
PLOGE("fork");
exit(1);
default:
if (wait(&rv) < 0) {
exit(1);
} else {
exit(WEXITSTATUS(rv));
}
}
}
int main(int argc, char *argv[]) {
if (argc == 2 && strcmp(argv[1], "--daemon") == 0) {
//Everything we'll exec will be in su, not su_daemon
setexeccon("u:r:su:s0");
return run_daemon();
}
return su_main(argc, argv);
}
int su_main(int argc, char *argv[]) {
int ppid = getppid();
if ((geteuid() != AID_ROOT && getuid() != AID_ROOT) ||
(get_api_version() >= 18 && getuid() == AID_SHELL) ||
get_api_version() >= 19) {
// attempt to connect to daemon...
LOGD("starting daemon client %d %d", getuid(), geteuid());
return connect_daemon(argc, argv, ppid);
} else {
return su_main_nodaemon(argc, argv);
}
}
int su_main_nodaemon(int argc, char **argv) {
int ppid = getppid();
fork_for_samsung();
// Sanitize all secure environment variables (from linker_environ.c in AOSP linker).
/* 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",
};
if(getauxval(AT_SECURE)) {
const char* const* cp = unsec_vars;
const char* const* endp = cp + sizeof(unsec_vars)/sizeof(unsec_vars[0]);
while (cp < endp) {
unsetenv(*cp);
cp++;
}
}
LOGD("su invoked.");
//Chainfire compatibility
if(argc >= 3 && (
strcmp(argv[1], "-cn") == 0 ||
strcmp(argv[1], "--context") == 0
)) {
argc-=2;
argv+=2;
}
struct su_context ctx = {
.from = {
.pid = -1,
.uid = 0,
.bin = "",
.args = "",
.name = "",
},
.to = {
.uid = AID_ROOT,
.login = 0,
.keepenv = 0,
.shell = NULL,
.command = NULL,
.context = NULL,
.argv = argv,
.argc = argc,
.optind = 0,
.name = "",
},
.user = {
.android_user_id = 0,
.multiuser_mode = MULTIUSER_MODE_OWNER_ONLY,
.database_path = REQUESTOR_DATA_PATH REQUESTOR_DATABASE_PATH,
.base_path = REQUESTOR_DATA_PATH REQUESTOR
},
.bind = {
.from = "",
.to = "",
},
.init = "",
};
struct stat st;
int c, socket_serv_fd, fd;
char buf[64], *result;
policy_t dballow;
struct option long_opts[] = {
{ "bind", required_argument, NULL, 'b' },
{ "command", required_argument, NULL, 'c' },
{ "help", no_argument, NULL, 'h' },
{ "init", required_argument, NULL, 'i' },
{ "login", no_argument, NULL, 'l' },
{ "preserve-environment", no_argument, NULL, 'p' },
{ "shell", required_argument, NULL, 's' },
{ "version", no_argument, NULL, 'v' },
{ "context", required_argument, NULL, 'z' },
{ NULL, 0, NULL, 0 },
};
while ((c = getopt_long(argc, argv, "+b:c:hlmps:Vvuz:", long_opts, NULL)) != -1) {
switch(c) {
case 'b': {
char *s = strdup(optarg);
char *pos = strchr(s, ':');
if(pos) {
pos[0] = 0;
ctx.bind.to = pos + 1;
ctx.bind.from = s;
} else {
ctx.bind.from = "--ls";
ctx.bind.to = "--ls";
}
}
break;
case 'c':
ctx.to.shell = DEFAULT_SHELL;
ctx.to.command = optarg;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
case 'i':
ctx.init = optarg;
break;
case 'l':
ctx.to.login = 1;
break;
case 'm':
case 'p':
ctx.to.keepenv = 1;
break;
case 's':
ctx.to.shell = optarg;
break;
case 'V':
printf("%d\n", VERSION_CODE);
exit(EXIT_SUCCESS);
case 'v':
printf("%s cm-su subind suinit\n", VERSION);
exit(EXIT_SUCCESS);
case 'u':
switch (get_multiuser_mode()) {
case MULTIUSER_MODE_USER:
printf("%s\n", MULTIUSER_VALUE_USER);
break;
case MULTIUSER_MODE_OWNER_MANAGED:
printf("%s\n", MULTIUSER_VALUE_OWNER_MANAGED);
break;
case MULTIUSER_MODE_OWNER_ONLY:
printf("%s\n", MULTIUSER_VALUE_OWNER_ONLY);
break;
case MULTIUSER_MODE_NONE:
printf("%s\n", MULTIUSER_VALUE_NONE);
break;
}
exit(EXIT_SUCCESS);
case 'z':
ctx.to.context = optarg;
break;
default:
/* Bionic getopt_long doesn't terminate its error output by newline */
fprintf(stderr, "\n");
usage(2);
}
}
hacks_init();
if (optind < argc && !strcmp(argv[optind], "-")) {
ctx.to.login = 1;
optind++;
}
/* username or uid */
if (optind < argc && strcmp(argv[optind], "--")) {
struct passwd *pw;
pw = getpwnam(argv[optind]);
if (!pw) {
char *endptr;
/* It seems we shouldn't do this at all */
errno = 0;
ctx.to.uid = strtoul(argv[optind], &endptr, 10);
if (errno || *endptr) {
LOGE("Unknown id: %s\n", argv[optind]);
fprintf(stderr, "Unknown id: %s\n", argv[optind]);
exit(EXIT_FAILURE);
}
} else {
ctx.to.uid = pw->pw_uid;
if (pw->pw_name)
strncpy(ctx.to.name, pw->pw_name, sizeof(ctx.to.name));
}
optind++;
}
if (optind < argc && !strcmp(argv[optind], "--")) {
optind++;
}
ctx.to.optind = optind;
su_ctx = &ctx;
if (from_init(&ctx.from) < 0) {
deny(&ctx);
}
read_options(&ctx);
user_init(&ctx);
// the latter two are necessary for stock ROMs like note 2 which do dumb things with su, or crash otherwise
if (ctx.from.uid == AID_ROOT) {
LOGD("Allowing root/system/radio.");
allow(&ctx);
}
// verify superuser is installed
if (stat(ctx.user.base_path, &st) < 0) {
// send to market (disabled, because people are and think this is hijacking their su)
// if (0 == strcmp(JAVA_PACKAGE_NAME, REQUESTOR))
// silent_run("am start -d http://www.clockworkmod.com/superuser/install.html -a android.intent.action.VIEW");
PLOGE("stat %s", ctx.user.base_path);
deny(&ctx);
}
// odd perms on superuser data dir
if (st.st_gid != st.st_uid) {
LOGE("Bad uid/gid %d/%d for Superuser Requestor application",
(int)st.st_uid, (int)st.st_gid);
deny(&ctx);
}
// always allow if this is the superuser uid
// superuser needs to be able to reenable itself when disabled...
if (ctx.from.uid == st.st_uid) {
allow(&ctx);
}
// autogrant shell at this point
if (ctx.from.uid == AID_SHELL) {
LOGD("Allowing shell.");
allow(&ctx);
}
// deny if this is a non owner request and owner mode only
if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0) {
deny(&ctx);
}
ctx.umask = umask(027);
mkdir(REQUESTOR_CACHE_PATH, 0770);
if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
PLOGE("chown (%s, %ld, %ld)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);
deny(&ctx);
}
if (setgroups(0, NULL)) {
PLOGE("setgroups");
deny(&ctx);
}
if (setegid(st.st_gid)) {
PLOGE("setegid (%lu)", st.st_gid);
deny(&ctx);
}
if (seteuid(st.st_uid)) {
PLOGE("seteuid (%lu)", st.st_uid);
deny(&ctx);
}
//TODO: Ignore database check for init and bind?
dballow = database_check(&ctx);
switch (dballow) {
case INTERACTIVE:
break;
case ALLOW:
LOGD("db allowed");
allow(&ctx); /* never returns */
case DENY:
default:
LOGD("db denied");
deny(&ctx); /* never returns too */
}
socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));
LOGD(ctx.sock_path);
if (socket_serv_fd < 0) {
deny(&ctx);
}
signal(SIGHUP, cleanup_signal);
signal(SIGPIPE, cleanup_signal);
signal(SIGTERM, cleanup_signal);
signal(SIGQUIT, cleanup_signal);
signal(SIGINT, cleanup_signal);
signal(SIGABRT, cleanup_signal);
if (send_request(&ctx) < 0) {
deny(&ctx);
}
atexit(cleanup);
fd = socket_accept(socket_serv_fd);
if (fd < 0) {
deny(&ctx);
}
if (socket_send_request(fd, &ctx)) {
deny(&ctx);
}
if (socket_receive_result(fd, buf, sizeof(buf))) {
deny(&ctx);
}
close(fd);
close(socket_serv_fd);
socket_cleanup(&ctx);
result = buf;
#define SOCKET_RESPONSE "socket:"
if (strncmp(result, SOCKET_RESPONSE, sizeof(SOCKET_RESPONSE) - 1))
LOGW("SECURITY RISK: Requestor still receives credentials in intent");
else
result += sizeof(SOCKET_RESPONSE) - 1;
if (!strcmp(result, "DENY")) {
deny(&ctx);
} else if (!strcmp(result, "ALLOW")) {
allow(&ctx);
} else {
LOGE("unknown response from Superuser Requestor: %s", result);
deny(&ctx);
}
}
/*
** 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"
#ifndef AID_SHELL
#define AID_SHELL (get_shell_uid())
#endif
#ifndef AID_ROOT
#define AID_ROOT 0
#endif
#ifndef AID_SYSTEM
#define AID_SYSTEM (get_system_uid())
#endif
#ifndef AID_RADIO
#define AID_RADIO (get_radio_uid())
#endif
// 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
// DO NOT CHANGE LINE BELOW, java package name will always be the same
#define JAVA_PACKAGE_NAME "com.koushikdutta.superuser"
// If --rename-manifest-package is used in AAPT, this
// must be changed to correspond to the new APK package name
// See the two Android.mk files for more details.
#ifndef REQUESTOR
#define REQUESTOR JAVA_PACKAGE_NAME
#endif
// This is used if wrapping the fragment classes and activities
// with classes in another package. CM requirement.
#ifndef REQUESTOR_PREFIX
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME
#endif
#define REQUESTOR_DATA_PATH "/data/data/"
#define REQUESTOR_FILES_PATH REQUESTOR_DATA_PATH REQUESTOR "/files"
#define REQUESTOR_USER_PATH "/data/user/"
#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR
#define REQUESTOR_DAEMON_PATH REQUESTOR_CACHE_PATH ".daemon"
// there's no guarantee that the db or files are actually created named as such by
// SQLiteOpenHelper, etc. Though that is the behavior as of current.
// it is up to the Android application to symlink as appropriate.
#define REQUESTOR_DATABASE_PATH REQUESTOR "/databases/su.sqlite"
#define REQUESTOR_MULTIUSER_MODE REQUESTOR_FILES_PATH "/multiuser_mode"
#define DEFAULT_SHELL "/system/bin/sh"
#define xstr(a) str(a)
#define str(a) #a
#ifndef VERSION_CODE
#define VERSION_CODE 17
#endif
#define VERSION xstr(VERSION_CODE) " " REQUESTOR
#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 *context;
char *command;
char **argv;
int argc;
int optind;
};
struct su_user_info {
// the user in android userspace (multiuser)
// that invoked this action.
unsigned android_user_id;
// how su behaves with multiuser. see enum below.
int multiuser_mode;
// path to superuser directory. this is populated according
// to the multiuser mode.
// this is used to check uid/gid for protecting socket.
// this is used instead of database, as it is more likely
// to exist. db will not exist if su has never launched.
char base_path[PATH_MAX];
// path to su database. this is populated according
// to the multiuser mode.
char database_path[PATH_MAX];
};
struct su_bind {
const char *from;
const char *to;
};
struct su_context {
struct su_initiator from;
struct su_request to;
struct su_user_info user;
struct su_bind bind;
const char *init;
mode_t umask;
char sock_path[PATH_MAX];
};
// multiuser su behavior
typedef enum {
// only owner can su
MULTIUSER_MODE_OWNER_ONLY = 0,
// owner gets a su prompt
MULTIUSER_MODE_OWNER_MANAGED = 1,
// user gets a su prompt
MULTIUSER_MODE_USER = 2,
MULTIUSER_MODE_NONE = 3,
} multiuser_mode_t;
#define MULTIUSER_VALUE_OWNER_ONLY "owner"
#define MULTIUSER_VALUE_OWNER_MANAGED "managed"
#define MULTIUSER_VALUE_USER "user"
#define MULTIUSER_VALUE_NONE "none"
typedef enum {
INTERACTIVE = 0,
DENY = 1,
ALLOW = 2,
} policy_t;
extern policy_t database_check(struct su_context *ctx);
extern void set_identity(unsigned int uid);
extern int send_request(struct su_context *ctx);
extern int send_result(struct su_context *ctx, policy_t policy);
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;
}
void exec_loge(const char* fmt, ...);
void exec_logw(const char* fmt, ...);
void exec_logd(const char* fmt, ...);
int run_daemon();
int connect_daemon(int argc, char *argv[], int ppid);
int su_main(int argc, char *argv[]);
int su_main_nodaemon(int argc, char *argv[]);
// 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();
void hacks_init();
void hacks_update_context(struct su_context* ctxt);
// fallback to using /system/bin/log.
// can't use liblog.so because this is a static binary.
#ifndef LOGE
#define LOGE exec_loge
#endif
#ifndef LOGD
#define LOGD exec_logd
#endif
#ifndef LOGW
#define LOGW exec_logw
#endif
#if 0
#undef LOGE
#define LOGE(fmt,args...) fprintf(stderr, fmt, ##args)
#undef LOGD
#define LOGD(fmt,args...) fprintf(stderr, fmt, ##args)
#undef LOGW
#define LOGW(fmt,args...) fprintf(stderr, fmt, ##args)
#endif
#include <errno.h>
#include <string.h>
#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))
#endif
/*
** 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;
sol = strdup(data);
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;
}
}
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;
}
/*
** 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment