log_debug_elogind("Migrating \"%s\"/\"%s\" to \"%s\"/\"%s\" (%s)",
cfrom, pfrom, cto, pto,
- ignore_self ? "ignoring self" : "watching self");
+ (flags & CGROUP_IGNORE_SELF)
+ ? "ignoring self" : "watching self");
do {
_cleanup_fclose_ FILE *f = NULL;
pid_t pid = 0;
return write_string_file(p, value, 0);
}
-#if 0 /// UNNEEDED by elogind
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) {
_cleanup_free_ char *p = NULL;
int r;
return read_one_line_file(p, ret);
}
+#if 0 /// UNNEEDED by elogind
int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) {
CGroupController c;
int r, unified;
int cg_create_and_attach(const char *controller, const char *path, pid_t pid);
int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value);
-#if 0 /// UNNEEDED by elogind
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret);
+#if 0 /// UNNEEDED by elogind
int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid);
int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid);
#endif // 0
return 0;
}
#endif // 0
+
+int read_nul_string(FILE *f, char **ret) {
+ _cleanup_free_ char *x = NULL;
+ size_t allocated = 0, n = 0;
+
+ assert(f);
+ assert(ret);
+
+ /* Reads a NUL-terminated string from the specified file. */
+
+ for (;;) {
+ int c;
+
+ if (!GREEDY_REALLOC(x, allocated, n+2))
+ return -ENOMEM;
+
+ c = fgetc(f);
+ if (c == 0) /* Terminate at NUL byte */
+ break;
+ if (c == EOF) {
+ if (ferror(f))
+ return -errno;
+ break; /* Terminate at EOF */
+ }
+
+ x[n++] = (char) c;
+ }
+
+ if (x)
+ x[n] = 0;
+ else {
+ x = new0(char, 1);
+ if (!x)
+ return -ENOMEM;
+ }
+
+ *ret = x;
+ x = NULL;
+
+ return 0;
+}
int link_tmpfile(int fd, const char *path, const char *target);
#endif // 0
+
+int read_nul_string(FILE *f, char **ret);
}
#if 0 /// UNNEEDED by elogind
+int var_tmp(char **ret) {
+ const char *tmp_dir = NULL;
+ const char *env_tmp_dir = NULL;
+ char *c = NULL;
+ int r;
+
+ assert(ret);
+
+ env_tmp_dir = getenv("TMPDIR");
+ if (env_tmp_dir != NULL) {
+ r = is_dir(env_tmp_dir, true);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ if (r > 0)
+ tmp_dir = env_tmp_dir;
+ }
+
+ if (!tmp_dir)
+ tmp_dir = "/var/tmp";
+
+ c = strdup(tmp_dir);
+ if (!c)
+ return -ENOMEM;
+ *ret = c;
+
+ return 0;
+}
+
int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
int r;
int get_files_in_directory(const char *path, char ***list);
#if 0 /// UNNEEDED by elogind
+int var_tmp(char **ret);
+
#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \
int set_consume(Set *s, void *value) {
int r;
+ assert(s);
+ assert(value);
+
r = set_put(s, value);
if (r <= 0)
free(value);
int n = 0, r;
char **i;
+ assert(s);
+
STRV_FOREACH(i, l) {
r = set_put_strdup(s, *i);
if (r < 0)
return n;
}
+
+int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags) {
+ const char *p = v;
+ int r;
+
+ assert(s);
+ assert(v);
+
+ for (;;) {
+ char *word;
+
+ r = extract_first_word(&p, &word, separators, flags);
+ if (r <= 0)
+ return r;
+
+ r = set_consume(s, word);
+ if (r < 0)
+ return r;
+ }
+}
#endif // 0
#include <uchar.h>
#include <unistd.h>
+/// Additional includes needed by elogind
#include "musl_missing.h"
#ifdef HAVE_AUDIT
#ifndef INPUT_PROP_ACCELEROMETER
#define INPUT_PROP_ACCELEROMETER 0x06
#endif
+#endif // 0
#ifndef HAVE_KEY_SERIAL_T
typedef int32_t key_serial_t;
#endif
+#if 0 /// UNNEEDED by elogind
#ifndef KEYCTL_READ
#define KEYCTL_READ 11
#endif
/* ======================================================================= */
#if 0 /// UNNEEDED by elogind
-static inline int raw_clone(unsigned long flags, void *child_stack) {
-#if defined(__s390__) || defined(__CRIS__)
- /* On s390 and cris the order of the first and second arguments
- * of the raw clone() system call is reversed. */
- return (int) syscall(__NR_clone, child_stack, flags);
-#else
- return (int) syscall(__NR_clone, flags, child_stack);
-#endif
-}
-
-/* ======================================================================= */
-
static inline pid_t raw_getpid(void) {
#if defined(__alpha__)
return (pid_t) syscall(__NR_getxpid);
#include "fs-util.h"
#include "macro.h"
-#include "missing.h"
#include "mkdir.h"
#include "path-util.h"
#include "stat-util.h"
#include "user-util.h"
+/// Additional includes needed by elogind
+#include "missing.h"
+
int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir) {
struct stat st;
}
}
#endif // 0
+
+const char* mode_to_inaccessible_node(mode_t mode) {
+ /* This function maps a node type to the correspondent inaccessible node type.
+ * Character and block inaccessible devices may not be created (because major=0 and minor=0),
+ * in such case we map character and block devices to the inaccessible node type socket. */
+ switch(mode & S_IFMT) {
+ case S_IFREG:
+ return "/run/systemd/inaccessible/reg";
+ case S_IFDIR:
+ return "/run/systemd/inaccessible/dir";
+ case S_IFCHR:
+ if (access("/run/systemd/inaccessible/chr", F_OK) == 0)
+ return "/run/systemd/inaccessible/chr";
+ return "/run/systemd/inaccessible/sock";
+ case S_IFBLK:
+ if (access("/run/systemd/inaccessible/blk", F_OK) == 0)
+ return "/run/systemd/inaccessible/blk";
+ return "/run/systemd/inaccessible/sock";
+ case S_IFIFO:
+ return "/run/systemd/inaccessible/fifo";
+ case S_IFSOCK:
+ return "/run/systemd/inaccessible/sock";
+ }
+ return NULL;
+}
#ifdef HAVE_VALGRIND_VALGRIND_H
if (getpid() == 1 && RUNNING_ON_VALGRIND) {
pid_t pid;
- pid = raw_clone(SIGCHLD, NULL);
+ pid = raw_clone(SIGCHLD);
if (pid < 0)
log_emergency_errno(errno, "Failed to fork off valgrind helper: %m");
else if (pid == 0)
#pragma once
/***
- This file is part of elogind.
+ This file is part of systemd.
Copyright 2010 Lennart Poettering
Copyright 2016 Michael Karcher
- elogind is free software; you can redistribute it and/or modify it
+ systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
- elogind is distributed in the hope that it will be useful, but
+ systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
- along with elogind; If not, see <http://www.gnu.org/licenses/>.
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sched.h>
int set_put_strdup(Set *s, const char *p);
#if 0 /// UNNEEDED by elogind
int set_put_strdupv(Set *s, char **l);
+int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags);
#endif // 0
#define SET_FOREACH(e, s, i) \
}
char **strv_parse_nulstr(const char *s, size_t l) {
+ /* l is the length of the input data, which will be split at NULs into
+ * elements of the resulting strv. Hence, the number of items in the resulting strv
+ * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
+ * unless s[l-1] is NUL, in which case the final empty string is not stored in
+ * the resulting strv, and length is equal to the number of NUL bytes.
+ *
+ * Note that contrary to a normal nulstr which cannot contain empty strings, because
+ * the input data is terminated by any two consequent NUL bytes, this parser accepts
+ * empty strings in s.
+ */
+
const char *p;
unsigned c = 0, i = 0;
char **v;
#if 0 /// UNNEEDED by elogind
int strv_make_nulstr(char **l, char **p, size_t *q) {
+ /* A valid nulstr with two NULs at the end will be created, but
+ * q will be the length without the two trailing NULs. Thus the output
+ * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
+ * and can also be parsed by strv_parse_nulstr as long as the length
+ * is provided separately.
+ */
+
size_t n_allocated = 0, n = 0;
_cleanup_free_ char *m = NULL;
char **i;
z = strlen(*i);
- if (!GREEDY_REALLOC(m, n_allocated, n + z + 1))
+ if (!GREEDY_REALLOC(m, n_allocated, n + z + 2))
return -ENOMEM;
memcpy(m + n, *i, z + 1);
m = new0(char, 1);
if (!m)
return -ENOMEM;
- n = 0;
- }
+ n = 1;
+ } else
+ /* make sure there is a second extra NUL at the end of resulting nulstr */
+ m[n] = '\0';
+ assert(n > 0);
*p = m;
- *q = n;
+ *q = n - 1;
m = NULL;
}
#endif // 0
+bool terminal_is_dumb(void) {
+ const char *e;
+
+ if (!on_tty())
+ return true;
+
+ e = getenv("TERM");
+ if (!e)
+ return true;
+
+ return streq(e, "dumb");
+}
+
bool colors_enabled(void) {
static int enabled = -1;
colors = getenv("SYSTEMD_COLORS");
if (colors)
enabled = parse_boolean(colors) != 0;
- else if (streq_ptr(getenv("TERM"), "dumb"))
- enabled = false;
else
- enabled = on_tty();
+ enabled = !terminal_is_dumb();
}
return enabled;
char *resolve_dev_console(char **active);
#endif // 0
+int get_kernel_consoles(char ***consoles);
bool tty_is_vc(const char *tty);
#if 0 /// UNNEEDED by elogind
bool tty_is_vc_resolve(const char *tty);
#endif // 0
bool on_tty(void);
+bool terminal_is_dumb(void);
bool colors_enabled(void);
static inline const char *ansi_underline(void) {
return ts;
}
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
+ assert(ts);
+
+ ts->realtime = now(CLOCK_REALTIME);
+ ts->monotonic = now(CLOCK_MONOTONIC);
+ ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;
+
+ return ts;
+}
+
dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
}
#if 0 /// UNNEEDED by elogind
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
+ int64_t delta;
+
+ assert(ts);
+
+ if (u == USEC_INFINITY || u <= 0) {
+ ts->realtime = ts->monotonic = ts->boottime = u;
+ return ts;
+ }
+
+ ts->realtime = u;
+ delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+ ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
+ ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
+
+ return ts;
+}
+
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
}
#endif // 0
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+
+ switch (clock) {
+
+ case CLOCK_REALTIME:
+ case CLOCK_REALTIME_ALARM:
+ return ts->realtime;
+
+ case CLOCK_MONOTONIC:
+ return ts->monotonic;
+
+ case CLOCK_BOOTTIME:
+ case CLOCK_BOOTTIME_ALARM:
+ return ts->boottime;
+
+ default:
+ return USEC_INFINITY;
+ }
+}
+
usec_t timespec_load(const struct timespec *ts) {
assert(ts);
else
return CLOCK_MONOTONIC;
}
+#endif // 0
+
+bool clock_supported(clockid_t clock) {
+ struct timespec ts;
+ switch (clock) {
+
+ case CLOCK_MONOTONIC:
+ case CLOCK_REALTIME:
+ return true;
+
+ case CLOCK_BOOTTIME:
+ return clock_boottime_supported();
+
+ case CLOCK_BOOTTIME_ALARM:
+ if (!clock_boottime_supported())
+ return false;
+
+ /* fall through, after checking the cached value for CLOCK_BOOTTIME. */
+
+ default:
+ /* For everything else, check properly */
+ return clock_gettime(clock, &ts) >= 0;
+ }
+}
+
+#if 0 /// UNNEEDED by elogind
int get_timezone(char **tz) {
_cleanup_free_ char *t = NULL;
const char *e;
usec_t monotonic;
} dual_timestamp;
+typedef struct triple_timestamp {
+ usec_t realtime;
+ usec_t monotonic;
+ usec_t boottime;
+} triple_timestamp;
+
#define USEC_INFINITY ((usec_t) -1)
#define NSEC_INFINITY ((nsec_t) -1)
#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
-#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL })
+#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {})
+#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {})
usec_t now(clockid_t clock);
#if 0 /// UNNEEDED by elogind
dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u);
#endif // 0
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts);
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u);
+
+#define DUAL_TIMESTAMP_HAS_CLOCK(clock) \
+ IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC)
+
+#define TRIPLE_TIMESTAMP_HAS_CLOCK(clock) \
+ IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM)
+
static inline bool dual_timestamp_is_set(dual_timestamp *ts) {
return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
(ts->monotonic > 0 && ts->monotonic != USEC_INFINITY));
}
+static inline bool triple_timestamp_is_set(triple_timestamp *ts) {
+ return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
+ (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) ||
+ (ts->boottime > 0 && ts->boottime != USEC_INFINITY));
+}
+
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
+
usec_t timespec_load(const struct timespec *ts) _pure_;
struct timespec *timespec_store(struct timespec *ts, usec_t u);
#endif // 0
bool clock_boottime_supported(void);
+bool clock_supported(clockid_t clock);
#if 0 /// UNNEEDED by elogind
clockid_t clock_boottime_or_monotonic(void);
#endif // 0
#define _cleanup_umask_ _cleanup_(umaskp)
-#if 0 /// UNNEEDED by elogind
struct _umask_struct_ {
mode_t mask;
bool quit;
for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \
!_saved_umask_.quit ; \
_saved_umask_.quit = true)
-#endif // 0
#include "alloc-util.h"
#include "build.h"
+#include "cgroup-util.h"
//#include "def.h"
#include "dirent-util.h"
#include "fd-util.h"
int saved_argc = 0;
char **saved_argv = NULL;
+static int saved_in_initrd = -1;
size_t page_size(void) {
static thread_local size_t pgsz = 0;
/* Detach from stdout/stderr. and reopen
* /dev/tty for them. This is important to
- * ensure that when loginctl is started via
+ * ensure that when systemctl is started via
* popen() or a similar call that expects to
* read EOF we actually do generate EOF and
- * not delay this indefinitely because we
+ * not delay this indefinitely by because we
* keep an unused copy of stdin around. */
fd = open("/dev/tty", O_WRONLY);
if (fd < 0) {
}
bool in_initrd(void) {
- static int saved = -1;
struct statfs s;
- if (saved >= 0)
- return saved;
+ if (saved_in_initrd >= 0)
+ return saved_in_initrd;
/* We make two checks here:
*
* emptying when transititioning to the main systemd.
*/
- saved = access("/etc/initrd-release", F_OK) >= 0 &&
- statfs("/", &s) >= 0 &&
- is_temporary_fs(&s);
+ saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
+ statfs("/", &s) >= 0 &&
+ is_temporary_fs(&s);
- return saved;
+ return saved_in_initrd;
+}
+
+void in_initrd_force(bool value) {
+ saved_in_initrd = value;
}
#if 0 /// UNNEEDED by elogind
return found_online || !found_offline;
}
-bool id128_is_valid(const char *s) {
- size_t i, l;
-
- l = strlen(s);
- if (l == 32) {
-
- /* Simple formatted 128bit hex string */
-
- for (i = 0; i < l; i++) {
- char c = s[i];
-
- if (!(c >= '0' && c <= '9') &&
- !(c >= 'a' && c <= 'z') &&
- !(c >= 'A' && c <= 'Z'))
- return false;
- }
-
- } else if (l == 36) {
-
- /* Formatted UUID */
-
- for (i = 0; i < l; i++) {
- char c = s[i];
-
- if ((i == 8 || i == 13 || i == 18 || i == 23)) {
- if (c != '-')
- return false;
- } else {
- if (!(c >= '0' && c <= '9') &&
- !(c >= 'a' && c <= 'z') &&
- !(c >= 'A' && c <= 'Z'))
- return false;
- }
- }
-
- } else
- return false;
-
- return true;
-}
#endif // 0
-
int container_get_leader(const char *machine, pid_t *pid) {
_cleanup_free_ char *s = NULL, *class = NULL;
const char *p;
}
uint64_t physical_memory(void) {
- long mem;
+ _cleanup_free_ char *root = NULL, *value = NULL;
+ uint64_t mem, lim;
+ size_t ps;
+ long sc;
+
+ /* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
+ * memory.
+ *
+ * In order to support containers nicely that have a configured memory limit we'll take the minimum of the
+ * physically reported amount of memory and the limit configured for the root cgroup, if there is any. */
+
+ sc = sysconf(_SC_PHYS_PAGES);
+ assert(sc > 0);
+
+ ps = page_size();
+ mem = (uint64_t) sc * (uint64_t) ps;
+
+ if (cg_get_root_path(&root) < 0)
+ return mem;
+
+ if (cg_get_attribute("memory", root, "memory.limit_in_bytes", &value))
+ return mem;
+
+ if (safe_atou64(value, &lim) < 0)
+ return mem;
+
+ /* Make sure the limit is a multiple of our own page size */
+ lim /= ps;
+ lim *= ps;
+
+ return MIN(mem, lim);
+}
+
+uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
+ uint64_t p, m, ps, r;
+
+ assert(max > 0);
+
+ /* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success
+ * the result is a multiple of the page size (rounds down). */
+
+ ps = page_size();
+ assert(ps > 0);
+
+ p = physical_memory() / ps;
+ assert(p > 0);
+
+ m = p * v;
+ if (m / p != v)
+ return UINT64_MAX;
+
+ m /= max;
+
+ r = m * ps;
+ if (r / ps != m)
+ return UINT64_MAX;
+
+ return r;
+}
+
+uint64_t system_tasks_max(void) {
+
+#if SIZEOF_PID_T == 4
+#define TASKS_MAX ((uint64_t) (INT32_MAX-1))
+#elif SIZEOF_PID_T == 2
+#define TASKS_MAX ((uint64_t) (INT16_MAX-1))
+#else
+#error "Unknown pid_t size"
+#endif
+
+ _cleanup_free_ char *value = NULL, *root = NULL;
+ uint64_t a = TASKS_MAX, b = TASKS_MAX;
+
+ /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
+ * limit:
+ *
+ * a) the maximum value for the pid_t type
+ * b) the cgroups pids_max attribute for the system
+ * c) the kernel's configure maximum PID value
+ *
+ * And then pick the smallest of the three */
+
+ if (read_one_line_file("/proc/sys/kernel/pid_max", &value) >= 0)
+ (void) safe_atou64(value, &a);
+
+ if (cg_get_root_path(&root) >= 0) {
+ value = mfree(value);
+
+ if (cg_get_attribute("pids", root, "pids.max", &value) >= 0)
+ (void) safe_atou64(value, &b);
+ }
+
+ return MIN3(TASKS_MAX,
+ a <= 0 ? TASKS_MAX : a,
+ b <= 0 ? TASKS_MAX : b);
+}
+
+uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
+ uint64_t t, m;
+
+ assert(max > 0);
+
+ /* Multiply the system's task value by the fraction v/max. Hence, if max==100 this calculates percentages
+ * relative to the system's maximum number of tasks. Returns UINT64_MAX on overflow. */
- /* We return this as uint64_t in case we are running as 32bit
- * process on a 64bit kernel with huge amounts of memory */
+ t = system_tasks_max();
+ assert(t > 0);
- mem = sysconf(_SC_PHYS_PAGES);
- assert(mem > 0);
+ m = t * v;
+ if (m / t != v) /* overflow? */
+ return UINT64_MAX;
- return (uint64_t) mem * (uint64_t) page_size();
+ return m / max;
}
#if 0 /// UNNEEDED by elogind
int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...);
bool in_initrd(void);
+void in_initrd_force(bool value);
#if 0 /// UNNEEDED by elogind
void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
}
#if 0 /// UNNEEDED by elogind
-bool id128_is_valid(const char *s) _pure_;
#endif // 0
-
int container_get_leader(const char *machine, pid_t *pid);
int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
uint64_t physical_memory(void);
+uint64_t physical_memory_scale(uint64_t v, uint64_t max);
+
+uint64_t system_tasks_max(void);
+uint64_t system_tasks_max_scale(uint64_t v, uint64_t max);
#if 0 /// UNNEEDED by elogind
#endif // 0