* limitations under the License.
*/
-#include <stdlib.h>
#include <stdio.h>
+#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "sysdeps.h"
#define TRACE_TAG TRACE_SERVICES
+#include "log.h"
+
#include "sdb.h"
#include "file_sync_service.h"
# include <sys/inotify.h>
# include "sdktools.h"
#endif
+#include <sys/socket.h>
+#include <sys/un.h>
#include "strutils.h"
#include "utils.h"
#include <system_info.h>
#include <tzplatform_config.h>
+#include <sys/smack.h>
#include <vconf.h>
#include <limits.h>
#include <termios.h>
#include <sys/ioctl.h>
+#include "sdbd_plugin.h"
+#include "plugin.h"
+
+#define ENV_BUF_MAX 4096
+
typedef struct stinfo stinfo;
struct stinfo {
void *cookie;
};
-
void *service_bootstrap_func(void *x)
{
stinfo *sti = x;
static int is_support_interactive_shell()
{
- return (!strncmp(g_capabilities.intershell_support, SDBD_CAP_RET_ENABLED, strlen(SDBD_CAP_RET_ENABLED)));
+ return (!strncmp(g_capabilities.intershell_support, PLUGIN_RET_ENABLED, strlen(PLUGIN_RET_ENABLED)));
}
#if 0
sdb_close(fd);
}
-void restart_root_service(int fd, void *cookie)
-{
- char buf[100];
- char value[PROPERTY_VALUE_MAX];
-
- if (getuid() == 0) {
- snprintf(buf, sizeof(buf), "sdbd is already running as root\n");
- writex(fd, buf, strlen(buf));
- sdb_close(fd);
- } else {
- property_get("ro.debuggable", value, "");
- if (strcmp(value, "1") != 0) {
- snprintf(buf, sizeof(buf), "sdbd cannot run as root in production builds\n");
- writex(fd, buf, strlen(buf));
- sdb_close(fd);
- return;
- }
-
- property_set("service.sdb.root", "1");
- snprintf(buf, sizeof(buf), "restarting sdbd as root\n");
- writex(fd, buf, strlen(buf));
- sdb_close(fd);
- }
-}
#endif
-void restart_tcp_service(int fd, void *cookie)
-{
- char buf[100];
- char value[PROPERTY_VALUE_MAX];
- int port = (int)cookie;
-
- if (port <= 0) {
- snprintf(buf, sizeof(buf), "invalid port\n");
- writex(fd, buf, strlen(buf));
- sdb_close(fd);
- return;
- }
-
- snprintf(value, sizeof(value), "%d", port);
- property_set("service.sdb.tcp.port", value);
- snprintf(buf, sizeof(buf), "restarting in TCP mode port: %d\n", port);
- writex(fd, buf, strlen(buf));
- sdb_close(fd);
-}
-
static int is_support_rootonoff()
{
- return (!strncmp(g_capabilities.rootonoff_support, SDBD_CAP_RET_ENABLED, strlen(SDBD_CAP_RET_ENABLED)));
+ return (!strncmp(g_capabilities.rootonoff_support, PLUGIN_RET_ENABLED, strlen(PLUGIN_RET_ENABLED)));
}
void rootshell_service(int fd, void *cookie)
char *mode = (char*) cookie;
if (!strcmp(mode, "on")) {
- if (getuid() == 0) {
- if (rootshell_mode == 1) {
- //snprintf(buf, sizeof(buf), "Already changed to sdk user mode\n");
- // do not show message
+ if (rootshell_mode == 1) {
+ //snprintf(buf, sizeof(buf), "Already changed to sdk user mode\n");
+ // do not show message
+ } else {
+ if (is_support_rootonoff()) {
+ rootshell_mode = 1;
+ //allows a permitted user to execute a command as the superuser
+ snprintf(buf, sizeof(buf), "Switched to 'root' account mode\n");
} else {
- if (is_support_rootonoff()) {
- rootshell_mode = 1;
- //allows a permitted user to execute a command as the superuser
- snprintf(buf, sizeof(buf), "Switched to 'root' account mode\n");
- } else {
- snprintf(buf, sizeof(buf), "Permission denied\n");
- }
- writex(fd, buf, strlen(buf));
+ snprintf(buf, sizeof(buf), "Permission denied\n");
}
- } else {
- D("need root permission for root shell: %d\n", getuid());
- rootshell_mode = 0;
- snprintf(buf, sizeof(buf), "Permission denied\n");
writex(fd, buf, strlen(buf));
}
} else if (!strcmp(mode, "off")) {
sdb_close(fd);
}
-void restart_usb_service(int fd, void *cookie)
-{
- char buf[100];
+enum tzplatform_get_env_error_status {
+ NO_ERROR_TZPLATFORM_ENV = 0,
+ ERROR_TZPLATFORM_ENV_GENERAL = 1,
+ ERROR_TZPLATFORM_ENV_INVALID_VARIABLES = 2,
+};
- property_set("service.sdb.tcp.port", "0");
- snprintf(buf, sizeof(buf), "restarting in USB mode\n");
+void get_tzplatform_env(int fd, void *cookie) {
+ char buf[PATH_MAX] = { 0, };
+ char *env_name = (char*) cookie;
+ D("environment variable name: %s\n", env_name);
+ enum tzplatform_variable env_id = tzplatform_getid(env_name);
+ if (env_id != _TZPLATFORM_VARIABLES_INVALID_) {
+ const char *env_value = tzplatform_getenv(env_id);
+ if (env_value) {
+ D("environment value : %s\n", env_value);
+ snprintf(buf, sizeof(buf), "%d%s", NO_ERROR_TZPLATFORM_ENV, env_value);
+ } else {
+ D("failed to get environment value using tzplatform_getenv");
+ snprintf(buf, sizeof(buf), "%d", ERROR_TZPLATFORM_ENV_GENERAL);
+ }
+ } else {
+ D("environment name (%s) is invalid\n", env_name);
+ snprintf(buf, sizeof(buf), "%d", ERROR_TZPLATFORM_ENV_INVALID_VARIABLES);
+ }
writex(fd, buf, strlen(buf));
+ free(env_name);
sdb_close(fd);
}
if ( ifd < 0 ) {
D( "inotify_init failed\n");
+ sdb_close(fd);
return;
}
if (event->mask & IN_CREATE) {
if (!(event->mask & IN_ISDIR)) {
char *cspath = NULL;
- int len = asprintf(&cspath, "%s/%s", CS_PATH,
- event->name);
- D( "The file %s was created.\n", cspath);
- writex(fd, cspath, len);
- if (cspath != NULL) {
+ int len = asprintf(&cspath, "%s/%s", CS_PATH, event->name);
+ if (len >= 0) {
+ D( "The file %s was created.\n", cspath);
+ writex(fd, cspath, len);
free(cspath);
+ } else {
+ D( "asprintf was failed\n" );
}
}
}
}
#if !SDB_HOST
-
-static void redirect_and_exec(int pts, const char *cmd, char * const argv[], char * const envp[])
+/* receive the ptm from child, sdbd-user */
+static ssize_t recv_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
- dup2(pts, 0);
- dup2(pts, 1);
- dup2(pts, 2);
+ struct msghdr msg;
+ struct iovec iov[1];
+ struct cmsghdr *pheader;
+ union {
+ struct cmsghdr cmhdr;
+ char control[CMSG_SPACE(sizeof(int))];
+ } control_un;
+ ssize_t ret;
+
+ msg.msg_control = control_un.control;
+ msg.msg_controllen = sizeof(control_un.control);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+
+ iov[0].iov_base = ptr;
+ iov[0].iov_len = nbytes;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ if ((ret = recvmsg(fd, &msg, 0)) <= 0) {
+ return ret;
+ }
- sdb_close(pts);
+ if ((pheader = CMSG_FIRSTHDR(&msg)) != NULL &&
+ pheader->cmsg_len == CMSG_LEN(sizeof(int))) {
+ if (pheader->cmsg_level != SOL_SOCKET) {
+ D("sdb: control level != SOL_SOCKET");
+ exit(-1);
+ }
+ if (pheader->cmsg_type != SCM_RIGHTS) {
+ D("sdb: control type != SCM_RIGHTS");
+ exit(-1);
+ }
+ memcpy(recvfd, CMSG_DATA(pheader), sizeof(int));
+ } else {
+ *recvfd = -1;
+ }
- execve(cmd, argv, envp);
+ return ret;
}
-static int create_subprocess(const char *cmd, pid_t *pid, char * const argv[], char * const envp[])
+int create_subprocess(const char *cmd, pid_t *pid, char * const argv[], char * const envp[])
{
- char devname[64];
- int ptm;
-
- ptm = unix_open("/dev/ptmx", O_RDWR); // | O_NOCTTY);
- if(ptm < 0){
- D("[ cannot open /dev/ptmx - errno:%d ]\n",errno);
- return -1;
- }
- if (fcntl(ptm, F_SETFD, FD_CLOEXEC) < 0) {
- D("[ cannot set cloexec to /dev/ptmx - errno:%d ]\n",errno);
- }
-
- if(grantpt(ptm) || unlockpt(ptm) ||
- ptsname_r(ptm, devname, sizeof(devname)) != 0 ){
- D("[ trouble with /dev/ptmx - errno:%d ]\n", errno);
- sdb_close(ptm);
- return -1;
- }
-
*pid = fork();
if(*pid < 0) {
D("- fork failed: errno:%d -\n", errno);
- sdb_close(ptm);
return -1;
}
- if(*pid == 0){
- int pts;
-
- setsid();
-
- pts = unix_open(devname, O_RDWR);
- if(pts < 0) {
- fprintf(stderr, "child failed to open pseudo-term slave: %s\n", devname);
- exit(-1);
- }
-
- sdb_close(ptm);
-
- // set OOM adjustment to zero
- {
- char text[64];
- //snprintf(text, sizeof text, "/proc/%d/oom_score_adj", getpid());
- snprintf(text, sizeof text, "/proc/%d/oom_adj", getpid());
- int fd = sdb_open(text, O_WRONLY);
- if (fd >= 0) {
- sdb_write(fd, "0", 1);
- sdb_close(fd);
- } else {
- // FIXME: not supposed to be here
- D("sdb: unable to open %s due to errno:%d\n", text, errno);
- }
- }
-
+ if (*pid == 0) {
if (should_drop_privileges()) {
- if (argv[2] != NULL && getuid() == 0 && request_plugin_verification(SDBD_CMD_VERIFY_ROOTCMD, argv[2])) {
+ if (argv[2] != NULL && request_validity_to_plugin(PLUGIN_SYNC_CMD_VERIFY_ROOTCMD, argv[2])) {
// do nothing
D("sdb: executes root commands!!:%s\n", argv[2]);
} else {
- set_sdk_user_privileges();
+ if (getuid() != g_sdk_user_id && set_sdk_user_privileges(RESERVE_CAPABILITIES_AFTER_FORK) < 0) {
+ D("failed to set SDK user privileges\n");
+ exit(-1);
+ }
}
+ } else {
+ set_root_privileges();
}
- redirect_and_exec(pts, cmd, argv, envp);
- fprintf(stderr, "- exec '%s' failed: (errno:%d) -\n",
- cmd, errno);
+ /* exec sdbduser */
+ execve(cmd, argv, envp);
+ D("- exec '%s' failed: (errno:%d) -\n", cmd, errno);
exit(-1);
} else {
// Don't set child's OOM adjustment to zero.
// Let the child do it itself, as sometimes the parent starts
// running before the child has a /proc/pid/oom_adj.
// """sdb: unable to open /proc/644/oom_adj""" seen in some logs.
+ char tmptext[32];
+ int ptm = -1;
+ struct sockaddr_un addr;
+ int sock;
+ int trycnt = 1;
+
+ /* The child process will open .sdbduser_pid.sock socket.
+ This socket transfers the ptm fd that was opened by child process.
+ You can see related code on subprocess.c file. */
+ snprintf(tmptext, sizeof tmptext, "/tmp/.sdbduser_%d.sock", (int)(*pid));
+ char *sockpath = strdup(tmptext);
+ if (sockpath == NULL) {
+ D("failed to get socket path, %s\n", strerror(errno));
+ return -1;
+ }
+ D("read fd socket is %s\n", sockpath);
+
+ sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (sock == -1) {
+ D("socket error, %s\n", strerror(errno));
+ free(sockpath);
+ return -1;
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_LOCAL;
+ strcpy(addr.sun_path, sockpath);
+ int slen = offsetof(struct sockaddr_un, sun_path) + strlen(sockpath);
+ while (connect(sock, (struct sockaddr *)&addr, slen) == -1
+ && trycnt < 100) {
+ D("try to connect socket %s, %d times.\n", sockpath, trycnt++);
+ /* sleep maximum 100 times */
+ usleep(10000);
+ }
+ if (trycnt == 100) {
+ D("failed to connect, err: %s\n", strerror(errno));
+ free(sockpath);
+ return -1;
+ }
+
+ char c;
+ if (recv_fd(sock, &c, 1, &ptm) == -1) {
+ D("recv_fd error, %s\n", strerror(errno));
+ free(sockpath);
+ return -1;
+ } else {
+ D("got ptm fd from child, fd: %d\n", ptm);
+ }
+
+ if (sdb_close(sock) == -1) {
+ D("close sock error, %s\n", strerror(errno));
+ }
+ free(sockpath);
+
+ D("getting child's ptm successed.\n");
return ptm;
}
}
#endif /* !SDB_HOST */
-#define SHELL_COMMAND "/bin/sh"
+#define SHELL_COMMAND "/usr/sbin/sdbd-user"
#define LOGIN_COMMAND "/bin/login"
#define SUPER_USER "root"
#define LOGIN_CONFIG "/etc/login.defs"
for (;;) {
int status;
pid_t p = waitpid(pid, &status, 0);
+ D("entered fd=%d, post waitpid(pid=%d)\n", fd, p);
if (p == pid) {
D("fd=%d, post waitpid(pid=%d) status=%04x\n", fd, p, status);
if (WIFEXITED(status)) {
}
}
-static void get_env(char *key, char **env)
+void get_env(char *key, char **env)
{
FILE *fp;
char buf[1024];
e = buf + (strlen(buf) - 1);
// trim string
- while(*e == ' ' || *e == '\n' || *e == '\t') {
+ while( (e > s) && (*e == ' ' || *e == '\n' || *e == '\t')) {
e--;
}
*(e+1) ='\0';
char *value = NULL;
char *trim_value = NULL;
char path[PATH_MAX];
+ char *envp[MAX_TOKENS];
+ int envp_cnt = 0;
+
memset(path, 0, sizeof(path));
+ memset(envp, 0, sizeof(envp));
- char *envp[] = {
- "TERM=linux", /* without this, some programs based on screen can't work, e.g. top */
- "DISPLAY=:0", /* without this, some programs based on without launchpad can't work */
- NULL,
- NULL,
- NULL,
- NULL,
- NULL
- };
+ envp[envp_cnt++] = strdup("TERM=linux");
+ envp[envp_cnt++] = strdup("DISPLAY=:0");
if (should_drop_privileges()) {
if (g_sdk_home_dir_env) {
- envp[2] = g_sdk_home_dir_env;
+ envp[envp_cnt++] = strdup(g_sdk_home_dir_env);
} else {
- envp[2] = "HOME=/home/owner";
+ envp[envp_cnt++] = strdup("HOME=/home/owner");
}
get_env("ENV_PATH", &value);
} else {
if(value == NULL) {
get_env("ENV_ROOTPATH", &value);
}
- envp[2] = "HOME=/root";
+ envp[envp_cnt++] = strdup("HOME=/root");
}
if (value != NULL) {
trim_value = str_trim(value);
} else {
snprintf(path, sizeof(path), "%s", trim_value);
}
- envp[3] = path;
+ envp[envp_cnt++] = strdup(path);
} else {
snprintf(path, sizeof(path), "%s", value);
- envp[3] = path;
+ envp[envp_cnt++] = strdup(path);
}
free(value);
}
- D("path env:%s,%s,%s,%s\n", envp[0], envp[1], envp[2], envp[3]);
+ /* get environment variables from plugin */
+ char *envp_plugin = NULL;
+ envp_plugin = malloc(ENV_BUF_MAX);
+ if (envp_plugin == NULL) {
+ D("Cannot allocate the shell commnad buffer.");
+ return -1;
+ }
+ memset(envp_plugin, 0, ENV_BUF_MAX);
+ if (!request_conversion_to_plugin(PLUGIN_SYNC_CMD_GET_SHELL_ENV, NULL,
+ envp_plugin, ENV_BUF_MAX)) {
+ D("Failed to convert the shell command. (%s)\n", name);
+ free(envp_plugin);
+ return -1;
+ } else {
+ if(envp_plugin[0] != '\0') {
+ envp_cnt = tokenize_append(envp_plugin, "\n", envp, MAX_TOKENS, envp_cnt);
+ }
+ }
+ free(envp_plugin);
+
+ /* Last element of envp must be the NULL-terminator to prevent execvp fail */
+ envp[envp_cnt] = NULL;
if(name) { // in case of shell execution directly
// Check the shell command validation.
- if (!request_plugin_verification(SDBD_CMD_VERIFY_SHELLCMD, name)) {
+ if (!request_validity_to_plugin(PLUGIN_SYNC_CMD_VERIFY_SHELLCMD, name)) {
D("This shell command is invalid. (%s)\n", name);
return -1;
}
}
memset(new_cmd, 0, SDBD_SHELL_CMD_MAX);
- if(!request_plugin_cmd(SDBD_CMD_CONV_SHELLCMD, name, new_cmd, SDBD_SHELL_CMD_MAX)) {
+ if(!request_conversion_to_plugin(PLUGIN_SYNC_CMD_CONVERT_SHELLCMD, name, new_cmd, SDBD_SHELL_CMD_MAX)) {
D("Failed to convert the shell command. (%s)\n", name);
free(new_cmd);
return -1;
}
#endif
}
+
+ /* free environment variables */
+ int i = 0;
+ if(envp_cnt > 0) {
+ for(i = 0; i < envp_cnt; i++) {
+ if(envp[i]) {
+ D("envp[%d] = %s\n", i, envp[i]);
+ free(envp[i]);
+ }
+ }
+ }
+
D("create_subprocess() ret_fd=%d pid=%d\n", ret_fd, pid);
if (ret_fd < 0) {
if(sdb_thread_create( &t, service_bootstrap_func, sti)){
free(sti);
sdb_close(s[0]);
- sdb_close(s[1]);
printf("cannot create service monitor thread\n");
return -1;
}
return ret_fd;
}
-
#endif
static void get_platforminfo(int fd, void *cookie) {
// Secure protocol support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "secure_protocol", g_capabilities.secure_protocol);
+ "secure_protocol", g_capabilities.secure_protocol);
// Interactive shell support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "intershell_support", g_capabilities.intershell_support);
+ "intershell_support", g_capabilities.intershell_support);
// File push/pull support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "filesync_support", g_capabilities.filesync_support);
+ "filesync_support", g_capabilities.filesync_support);
// USB protocol support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "usbproto_support", g_capabilities.usbproto_support);
+ "usbproto_support", g_capabilities.usbproto_support);
// Socket protocol support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "sockproto_support", g_capabilities.sockproto_support);
+ "sockproto_support", g_capabilities.sockproto_support);
+
+ // Window size synchronization support
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "syncwinsz_support", g_capabilities.syncwinsz_support);
+
+ // sdbd root permission
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "sdbd_rootperm", g_capabilities.root_permission);
// Root command support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "rootonoff_support", g_capabilities.rootonoff_support);
+ "rootonoff_support", g_capabilities.rootonoff_support);
+
+ // Encryption support
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "encryption_support", g_capabilities.encryption_support);
// Zone support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "zone_support", g_capabilities.zone_support);
+ "zone_support", g_capabilities.zone_support);
// Multi-User support
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "multiuser_support", g_capabilities.multiuser_support);
+ "multiuser_support", g_capabilities.multiuser_support);
// CPU Architecture of model
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "cpu_arch", g_capabilities.cpu_arch);
+ "cpu_arch", g_capabilities.cpu_arch);
// SDK Tool path
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "sdk_toolpath", g_capabilities.sdk_toolpath);
+ "sdk_toolpath", g_capabilities.sdk_toolpath);
// Profile name
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "profile_name", g_capabilities.profile_name);
+ "profile_name", g_capabilities.profile_name);
// Vendor name
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "vendor_name", g_capabilities.vendor_name);
+ "vendor_name", g_capabilities.vendor_name);
+
+ // Target name of the launch possible
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "can_launch", g_capabilities.can_launch);
// Platform version
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "platform_version", g_capabilities.platform_version);
+ "platform_version", g_capabilities.platform_version);
// Product version
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "product_version", g_capabilities.product_version);
+ "product_version", g_capabilities.product_version);
// Sdbd version
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "sdbd_version", g_capabilities.sdbd_version);
+ "sdbd_version", g_capabilities.sdbd_version);
// Sdbd plugin version
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "sdbd_plugin_version", g_capabilities.sdbd_plugin_version);
+ "sdbd_plugin_version", g_capabilities.sdbd_plugin_version);
- // Window size synchronization support
+ // Capability version
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "sdbd_cap_version", g_capabilities.sdbd_cap_version);
+
+ // Sdbd log enable
offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
- "syncwinsz_support", g_capabilities.syncwinsz_support);
+ "log_enable", g_capabilities.log_enable);
+ // Sdbd log path
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "log_path", g_capabilities.log_path);
+
+ // Application command support
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "appcmd_support", g_capabilities.appcmd_support);
+
+ // appid2pid support
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "appid2pid_support", g_capabilities.appid2pid_support);
+
+ // pkgcmd debug mode support
+ offset += put_key_value_string(cap_buffer, offset, CAPBUF_SIZE,
+ "pkgcmd_debugmode", g_capabilities.pkgcmd_debugmode);
offset++; // for '\0' character
if(!strncmp(name+10, "syncwinsz:", 10)){
ret = create_service_thread(sync_windowsize, (void *)name+20);
}
+ } else if(!strncmp(name, "tzplatformenv:", 14)) {
+ char* env_variable = NULL;
+ env_variable = strdup(name+14);
+ ret = create_service_thread(get_tzplatform_env, (void *)(env_variable));
+ } else if(!strncmp(name, "appcmd:", 7)){
+ ret = request_appcmd_to_plugin(name+7);
}
if (ret >= 0) {