Remove bind, init, and GNU compiler dependancy
This commit is contained in:
parent
42a66ad49e
commit
7955ddceb2
246
binds.c
246
binds.c
@ -1,246 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
33
binds.h
33
binds.h
@ -1,33 +0,0 @@
|
||||
/*
|
||||
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 */
|
104
daemon.c
104
daemon.c
@ -47,7 +47,6 @@
|
||||
#include <cutils/multiuser.h>
|
||||
#endif
|
||||
|
||||
#include "binds.h"
|
||||
#include "su.h"
|
||||
#include "utils.h"
|
||||
#include "pts.h"
|
||||
@ -499,79 +498,68 @@ static void prepare_su_bind() {
|
||||
}
|
||||
}
|
||||
|
||||
static void prepare_binds() {
|
||||
mkdir("/data/su", 0700);
|
||||
static int i = 0;
|
||||
static void bind_cb_func(void *arg, int uid, const char *src, const char *dst) {
|
||||
int ret = 0, 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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 = 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 = 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);
|
||||
ret = mount(tmpfile, dst, "", MS_BIND, NULL);
|
||||
if(ret) {
|
||||
LOGE("Failed to mount bind");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void do_init() {
|
||||
auto void cb(void *arg, int uid, const char *path) {
|
||||
int ret = 0;
|
||||
static void init_cb_func(void *arg, int uid, const char *path) {
|
||||
int ret = 0;
|
||||
|
||||
int p = fork();
|
||||
if(p)
|
||||
return;
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
static void prepare() {
|
||||
setfscreatecon("u:object_r:su_daemon:s0");
|
||||
mkdir("/dev/su", 0700);
|
||||
prepare_su_bind();
|
||||
prepare_binds();
|
||||
do_init();
|
||||
setfscreatecon(NULL);
|
||||
}
|
||||
|
||||
|
187
su.c
187
su.c
@ -42,7 +42,6 @@
|
||||
|
||||
#include "su.h"
|
||||
#include "utils.h"
|
||||
#include "binds.h"
|
||||
|
||||
extern int is_daemon;
|
||||
extern int daemon_from_uid;
|
||||
@ -470,70 +469,6 @@ static void usage(int status) {
|
||||
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);
|
||||
|
||||
@ -575,11 +510,11 @@ static __attribute__ ((noreturn)) void allow(struct su_context *ctx) {
|
||||
if (send_to_app)
|
||||
send_result(ctx, ALLOW);
|
||||
|
||||
if(ctx->bind.from[0] && ctx->bind.to[0])
|
||||
allow_bind(ctx);
|
||||
// if(ctx->bind.from[0] && ctx->bind.to[0])
|
||||
// allow_bind(ctx);
|
||||
|
||||
if(ctx->init[0])
|
||||
allow_init(ctx);
|
||||
// if(ctx->init[0])
|
||||
// allow_init(ctx);
|
||||
|
||||
char *binary;
|
||||
argc = ctx->to.optind;
|
||||
@ -777,21 +712,14 @@ int su_main_nodaemon(int argc, char **argv) {
|
||||
.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' },
|
||||
@ -800,71 +728,54 @@ int su_main_nodaemon(int argc, char **argv) {
|
||||
{ NULL, 0, NULL, 0 },
|
||||
};
|
||||
|
||||
while ((c = getopt_long(argc, argv, "+b:c:hlmps:Vvuz:", long_opts, NULL)) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "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";
|
||||
}
|
||||
case 'c':
|
||||
ctx.to.shell = DEFAULT_SHELL;
|
||||
ctx.to.command = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
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 (topjohnwu v1)\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;
|
||||
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);
|
||||
default:
|
||||
/* Bionic getopt_long doesn't terminate its error output by newline */
|
||||
fprintf(stderr, "\n");
|
||||
usage(2);
|
||||
}
|
||||
}
|
||||
hacks_init();
|
||||
|
Loading…
Reference in New Issue
Block a user