chiark / gitweb /
util: rm_rf_children_dangerous: delete all descendants dangerously
[elogind.git] / src / shared / util.c
index 8caac7b597fb0f3393c77e1ad2fce3fabdb91bb3..eaf2721381fc3c3d2b44ce84a179ad7f67ab7d99 100644 (file)
@@ -909,8 +909,7 @@ int load_env_file(
                         continue;
 
                 if (!(u = normalize_env_assignment(p))) {
-                        log_error("Out of memory");
-                        r = -ENOMEM;
+                        r = log_oom();
                         goto finish;
                 }
 
@@ -918,8 +917,7 @@ int load_env_file(
                 free(u);
 
                 if (!t) {
-                        log_error("Out of memory");
-                        r = -ENOMEM;
+                        r = log_oom();
                         goto finish;
                 }
 
@@ -1083,7 +1081,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                 if (h < 0)
                         return h;
 
-                r = join("[", t, "]", NULL);
+                r = strjoin("[", t, "]", NULL);
                 free(t);
 
                 if (!r)
@@ -1564,19 +1562,25 @@ char *cescape(const char *s) {
         return r;
 }
 
-char *cunescape_length(const char *s, size_t length) {
+char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) {
         char *r, *t;
         const char *f;
+        size_t pl;
 
         assert(s);
 
-        /* Undoes C style string escaping */
+        /* Undoes C style string escaping, and optionally prefixes it. */
 
-        r = new(char, length+1);
+        pl = prefix ? strlen(prefix) : 0;
+
+        r = new(char, pl+length+1);
         if (!r)
                 return r;
 
-        for (f = s, t = r; f < s + length; f++) {
+        if (prefix)
+                memcpy(r, prefix, pl);
+
+        for (f = s, t = r + pl; f < s + length; f++) {
 
                 if (*f != '\\') {
                         *(t++) = *f;
@@ -1687,7 +1691,13 @@ finish:
         return r;
 }
 
+char *cunescape_length(const char *s, size_t length) {
+        return cunescape_length_with_prefix(s, length, NULL);
+}
+
 char *cunescape(const char *s) {
+        assert(s);
+
         return cunescape_length(s, strlen(s));
 }
 
@@ -1790,7 +1800,7 @@ char *ascii_strlower(char *t) {
         return t;
 }
 
-bool ignore_file(const char *filename) {
+static bool ignore_file_allow_backup(const char *filename) {
         assert(filename);
 
         return
@@ -1798,7 +1808,6 @@ bool ignore_file(const char *filename) {
                 streq(filename, "lost+found") ||
                 streq(filename, "aquota.user") ||
                 streq(filename, "aquota.group") ||
-                endswith(filename, "~") ||
                 endswith(filename, ".rpmnew") ||
                 endswith(filename, ".rpmsave") ||
                 endswith(filename, ".rpmorig") ||
@@ -1807,6 +1816,15 @@ bool ignore_file(const char *filename) {
                 endswith(filename, ".swp");
 }
 
+bool ignore_file(const char *filename) {
+        assert(filename);
+
+        if (endswith(filename, "~"))
+                return false;
+
+        return ignore_file_allow_backup(filename);
+}
+
 int fd_nonblock(int fd, bool nonblock) {
         int flags;
 
@@ -2385,8 +2403,9 @@ int acquire_terminal(
                 bool ignore_tiocstty_eperm,
                 usec_t timeout) {
 
-        int fd = -1, notify = -1, r, wd = -1;
+        int fd = -1, notify = -1, r = 0, wd = -1;
         usec_t ts = 0;
+        struct sigaction sa_old, sa_new;
 
         assert(name);
 
@@ -2434,17 +2453,26 @@ int acquire_terminal(
                 if (fd < 0)
                         return fd;
 
+                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+                 * if we already own the tty. */
+                zero(sa_new);
+                sa_new.sa_handler = SIG_IGN;
+                sa_new.sa_flags = SA_RESTART;
+                assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
                 /* First, try to get the tty */
-                r = ioctl(fd, TIOCSCTTY, force);
+                if (ioctl(fd, TIOCSCTTY, force) < 0)
+                        r = -errno;
+
+                assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
 
                 /* Sometimes it makes sense to ignore TIOCSCTTY
                  * returning EPERM, i.e. when very likely we already
                  * are have this controlling terminal. */
-                if (r < 0 && errno == EPERM && ignore_tiocstty_eperm)
+                if (r < 0 && r == -EPERM && ignore_tiocstty_eperm)
                         r = 0;
 
-                if (r < 0 && (force || fail || errno != EPERM)) {
-                        r = -errno;
+                if (r < 0 && (force || fail || r != -EPERM)) {
                         goto fail;
                 }
 
@@ -2940,7 +2968,8 @@ int make_stdio(int fd) {
 int make_null_stdio(void) {
         int null_fd;
 
-        if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0)
+        null_fd = open("/dev/null", O_RDWR|O_NOCTTY);
+        if (null_fd < 0)
                 return -errno;
 
         return make_stdio(null_fd);
@@ -2990,7 +3019,8 @@ unsigned long long random_ull(void) {
         uint64_t ull;
         ssize_t r;
 
-        if ((fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0)
+        fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
                 goto fallback;
 
         r = loop_read(fd, &ull, sizeof(ull), true);
@@ -3067,26 +3097,21 @@ bool hostname_is_set(void) {
         return !isempty(u.nodename) && !streq(u.nodename, "(none)");
 }
 
-char* getlogname_malloc(void) {
-        uid_t uid;
+static char *lookup_uid(uid_t uid) {
         long bufsize;
         char *buf, *name;
         struct passwd pwbuf, *pw = NULL;
-        struct stat st;
-
-        if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
-                uid = st.st_uid;
-        else
-                uid = getuid();
 
         /* Shortcut things to avoid NSS lookups */
         if (uid == 0)
                 return strdup("root");
 
-        if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) <= 0)
+        bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+        if (bufsize <= 0)
                 bufsize = 4096;
 
-        if (!(buf = malloc(bufsize)))
+        buf = malloc(bufsize);
+        if (!buf)
                 return NULL;
 
         if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) {
@@ -3103,6 +3128,28 @@ char* getlogname_malloc(void) {
         return name;
 }
 
+char* getlogname_malloc(void) {
+        uid_t uid;
+        struct stat st;
+
+        if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
+                uid = st.st_uid;
+        else
+                uid = getuid();
+
+        return lookup_uid(uid);
+}
+
+char *getusername_malloc(void) {
+        const char *e;
+
+        e = getenv("USER");
+        if (e)
+                return strdup(e);
+
+        return lookup_uid(getuid());
+}
+
 int getttyname_malloc(int fd, char **r) {
         char path[PATH_MAX], *c;
         int k;
@@ -3243,7 +3290,7 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
         return 0;
 }
 
-int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
+int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
         DIR *d;
         int ret = 0;
 
@@ -3311,7 +3358,7 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root
                                 continue;
                         }
 
-                        r = rm_rf_children(subdir_fd, only_dirs, honour_sticky, root_dev);
+                        r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev);
                         if (r < 0 && ret == 0)
                                 ret = r;
 
@@ -3335,14 +3382,43 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root
         return ret;
 }
 
-int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
-        int fd;
-        int r;
+int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
+        struct statfs s;
+
+        assert(fd >= 0);
+
+        if (fstatfs(fd, &s) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        /* We refuse to clean disk file systems with this call. This
+         * is extra paranoia just to be sure we never ever remove
+         * non-state data */
+
+        if (s.f_type != TMPFS_MAGIC &&
+            s.f_type != RAMFS_MAGIC) {
+                log_error("Attempted to remove disk file system, and we can't allow that.");
+                close_nointr_nofail(fd);
+                return -EPERM;
+        }
+
+        return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev);
+}
+
+static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) {
+        int fd, r;
+        struct statfs s;
 
         assert(path);
 
-        /* Be paranoid */
-        assert(!streq(path, "/"));
+        /* We refuse to clean the root file system with this
+         * call. This is extra paranoia to never cause a really
+         * seriously broken system. */
+        if (path_equal(path, "/")) {
+                log_error("Attempted to remove entire root file system, and we can't allow that.");
+                return -EPERM;
+        }
 
         fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
         if (fd < 0) {
@@ -3350,6 +3426,17 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky
                 if (errno != ENOTDIR)
                         return -errno;
 
+                if (!dangerous) {
+                        if (statfs(path, &s) < 0)
+                                return -errno;
+
+                        if (s.f_type != TMPFS_MAGIC &&
+                            s.f_type != RAMFS_MAGIC) {
+                                log_error("Attempted to remove disk file system, and we can't allow that.");
+                                return -EPERM;
+                        }
+                }
+
                 if (delete_root && !only_dirs)
                         if (unlink(path) < 0 && errno != ENOENT)
                                 return -errno;
@@ -3357,8 +3444,21 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky
                 return 0;
         }
 
-        r = rm_rf_children(fd, only_dirs, honour_sticky, NULL);
+        if (!dangerous) {
+                if (fstatfs(fd, &s) < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                if (s.f_type != TMPFS_MAGIC &&
+                    s.f_type != RAMFS_MAGIC) {
+                        log_error("Attempted to remove disk file system, and we can't allow that.");
+                        close_nointr_nofail(fd);
+                        return -EPERM;
+                }
+        }
 
+        r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL);
         if (delete_root) {
 
                 if (honour_sticky && file_is_priv_sticky(path) > 0)
@@ -3373,6 +3473,14 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky
         return r;
 }
 
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+        return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false);
+}
+
+int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+        return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true);
+}
+
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
         assert(path);
 
@@ -3686,18 +3794,27 @@ int fd_columns(int fd) {
         return ws.ws_col;
 }
 
-unsigned columns(void) {
-        static __thread int parsed_columns = 0;
+static unsigned columns_cached(bool cached) {
+        static __thread int parsed_columns = 0, env_columns = -1;
         const char *e;
 
-        if (_likely_(parsed_columns > 0))
+        if (_likely_(parsed_columns > 0 && cached))
                 return parsed_columns;
 
-        e = getenv("COLUMNS");
-        if (e)
-                parsed_columns = atoi(e);
+        if (_unlikely_(env_columns == -1)) {
+                e = getenv("COLUMNS");
+                if (e)
+                        env_columns = atoi(e);
+                else
+                        env_columns = 0;
+        }
 
-        if (parsed_columns <= 0)
+        if (env_columns > 0) {
+                parsed_columns = env_columns;
+                return parsed_columns;
+        }
+
+        if (parsed_columns <= 0 || !cached)
                 parsed_columns = fd_columns(STDOUT_FILENO);
 
         if (parsed_columns <= 0)
@@ -3706,6 +3823,14 @@ unsigned columns(void) {
         return parsed_columns;
 }
 
+unsigned columns(void) {
+        return columns_cached(true);
+}
+
+unsigned columns_uncached(void) {
+        return columns_cached(false);
+}
+
 int fd_lines(int fd) {
         struct winsize ws;
         zero(ws);
@@ -3886,7 +4011,8 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) {
         assert(name);
         assert(pid > 1);
 
-        if ((r = wait_for_terminate(pid, &status)) < 0) {
+        r = wait_for_terminate(pid, &status);
+        if (r < 0) {
                 log_warning("Failed to wait for %s: %s", name, strerror(-r));
                 return r;
         }
@@ -3909,7 +4035,6 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid) {
 
         log_warning("%s failed due to unknown reason.", name);
         return -EPROTO;
-
 }
 
 _noreturn_ void freeze(void) {
@@ -4001,52 +4126,44 @@ void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
         }
 }
 
-char *fstab_node_to_udev_node(const char *p) {
+static char *tag_to_udev_node(const char *tagvalue, const char *by) {
         char *dn, *t, *u;
         int r;
 
         /* FIXME: to follow udev's logic 100% we need to leave valid
          * UTF8 chars unescaped */
 
-        if (startswith(p, "LABEL=")) {
-
-                if (!(u = unquote(p+6, "\"\'")))
-                        return NULL;
-
-                t = xescape(u, "/ ");
-                free(u);
-
-                if (!t)
-                        return NULL;
-
-                r = asprintf(&dn, "/dev/disk/by-label/%s", t);
-                free(t);
+        u = unquote(tagvalue, "\"\'");
+        if (u == NULL)
+                return NULL;
 
-                if (r < 0)
-                        return NULL;
+        t = xescape(u, "/ ");
+        free(u);
 
-                return dn;
-        }
+        if (t == NULL)
+                return NULL;
 
-        if (startswith(p, "UUID=")) {
+        r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t);
+        free(t);
 
-                if (!(u = unquote(p+5, "\"\'")))
-                        return NULL;
+        if (r < 0)
+                return NULL;
 
-                t = xescape(u, "/ ");
-                free(u);
+        return dn;
+}
 
-                if (!t)
-                        return NULL;
+char *fstab_node_to_udev_node(const char *p) {
+        if (startswith(p, "LABEL="))
+                return tag_to_udev_node(p+6, "label");
 
-                r = asprintf(&dn, "/dev/disk/by-uuid/%s", t);
-                free(t);
+        if (startswith(p, "UUID="))
+                return tag_to_udev_node(p+5, "uuid");
 
-                if (r < 0)
-                        return NULL;
+        if (startswith(p, "PARTUUID="))
+                return tag_to_udev_node(p+9, "partuuid");
 
-                return dn;
-        }
+        if (startswith(p, "PARTLABEL="))
+                return tag_to_udev_node(p+10, "partlabel");
 
         return strdup(p);
 }
@@ -4145,7 +4262,12 @@ bool dirent_is_file(const struct dirent *de) {
 bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
         assert(de);
 
-        if (!dirent_is_file(de))
+        if (de->d_type != DT_REG &&
+            de->d_type != DT_LNK &&
+            de->d_type != DT_UNKNOWN)
+                return false;
+
+        if (ignore_file_allow_backup(de->d_name))
                 return false;
 
         return endswith(de->d_name, suffix);
@@ -4188,7 +4310,7 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) {
                         continue;
 
                 if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
-                        log_error("Out of memory");
+                        log_oom();
                         continue;
                 }
 
@@ -4239,7 +4361,7 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) {
                 }
 
                 if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
-                        if (!is_clean_exit(si.si_code, si.si_status)) {
+                        if (!is_clean_exit(si.si_code, si.si_status, NULL)) {
                                 if (si.si_code == CLD_EXITED)
                                         log_error("%s exited with exit status %i.", path, si.si_status);
                                 else
@@ -4287,134 +4409,6 @@ bool plymouth_running(void) {
         return access("/run/plymouth/pid", F_OK) >= 0;
 }
 
-void parse_syslog_priority(char **p, int *priority) {
-        int a = 0, b = 0, c = 0;
-        int k;
-
-        assert(p);
-        assert(*p);
-        assert(priority);
-
-        if ((*p)[0] != '<')
-                return;
-
-        if (!strchr(*p, '>'))
-                return;
-
-        if ((*p)[2] == '>') {
-                c = undecchar((*p)[1]);
-                k = 3;
-        } else if ((*p)[3] == '>') {
-                b = undecchar((*p)[1]);
-                c = undecchar((*p)[2]);
-                k = 4;
-        } else if ((*p)[4] == '>') {
-                a = undecchar((*p)[1]);
-                b = undecchar((*p)[2]);
-                c = undecchar((*p)[3]);
-                k = 5;
-        } else
-                return;
-
-        if (a < 0 || b < 0 || c < 0)
-                return;
-
-        *priority = a*100+b*10+c;
-        *p += k;
-}
-
-void skip_syslog_pid(char **buf) {
-        char *p;
-
-        assert(buf);
-        assert(*buf);
-
-        p = *buf;
-
-        if (*p != '[')
-                return;
-
-        p++;
-        p += strspn(p, "0123456789");
-
-        if (*p != ']')
-                return;
-
-        p++;
-
-        *buf = p;
-}
-
-void skip_syslog_date(char **buf) {
-        enum {
-                LETTER,
-                SPACE,
-                NUMBER,
-                SPACE_OR_NUMBER,
-                COLON
-        } sequence[] = {
-                LETTER, LETTER, LETTER,
-                SPACE,
-                SPACE_OR_NUMBER, NUMBER,
-                SPACE,
-                SPACE_OR_NUMBER, NUMBER,
-                COLON,
-                SPACE_OR_NUMBER, NUMBER,
-                COLON,
-                SPACE_OR_NUMBER, NUMBER,
-                SPACE
-        };
-
-        char *p;
-        unsigned i;
-
-        assert(buf);
-        assert(*buf);
-
-        p = *buf;
-
-        for (i = 0; i < ELEMENTSOF(sequence); i++, p++) {
-
-                if (!*p)
-                        return;
-
-                switch (sequence[i]) {
-
-                case SPACE:
-                        if (*p != ' ')
-                                return;
-                        break;
-
-                case SPACE_OR_NUMBER:
-                        if (*p == ' ')
-                                break;
-
-                        /* fall through */
-
-                case NUMBER:
-                        if (*p < '0' || *p > '9')
-                                return;
-
-                        break;
-
-                case LETTER:
-                        if (!(*p >= 'A' && *p <= 'Z') &&
-                            !(*p >= 'a' && *p <= 'z'))
-                                return;
-
-                        break;
-
-                case COLON:
-                        if (*p != ':')
-                                return;
-                        break;
-
-                }
-        }
-
-        *buf = p;
-}
-
 char* strshorten(char *s, size_t l) {
         assert(s);
 
@@ -4815,7 +4809,12 @@ int socket_from_display(const char *display, char **path) {
         return 0;
 }
 
-int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) {
+int get_user_creds(
+                const char **username,
+                uid_t *uid, gid_t *gid,
+                const char **home,
+                const char **shell) {
+
         struct passwd *p;
         uid_t u;
 
@@ -4836,6 +4835,10 @@ int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **h
 
                 if (home)
                         *home = "/root";
+
+                if (shell)
+                        *shell = "/bin/sh";
+
                 return 0;
         }
 
@@ -4867,6 +4870,9 @@ int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **h
         if (home)
                 *home = p->pw_dir;
 
+        if (shell)
+                *shell = p->pw_shell;
+
         return 0;
 }
 
@@ -5083,7 +5089,7 @@ finish:
         return r;
 }
 
-char *join(const char *x, ...) {
+char *strjoin(const char *x, ...) {
         va_list ap;
         size_t l;
         char *r, *p;
@@ -5783,3 +5789,117 @@ void warn_melody(void) {
         ioctl(fd, KIOCSOUND, 0);
         close_nointr_nofail(fd);
 }
+
+int make_console_stdio(void) {
+        int fd, r;
+
+        /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
+
+        fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1);
+        if (fd < 0) {
+                log_error("Failed to acquire terminal: %s", strerror(-fd));
+                return fd;
+        }
+
+        r = make_stdio(fd);
+        if (r < 0) {
+                log_error("Failed to duplicate terminal fd: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+int get_home_dir(char **_h) {
+        char *h;
+        const char *e;
+        uid_t u;
+        struct passwd *p;
+
+        assert(_h);
+
+        /* Take the user specified one */
+        e = getenv("HOME");
+        if (e) {
+                h = strdup(e);
+                if (!h)
+                        return -ENOMEM;
+
+                *_h = h;
+                return 0;
+        }
+
+        /* Hardcode home directory for root to avoid NSS */
+        u = getuid();
+        if (u == 0) {
+                h = strdup("/root");
+                if (!h)
+                        return -ENOMEM;
+
+                *_h = h;
+                return 0;
+        }
+
+        /* Check the database... */
+        errno = 0;
+        p = getpwuid(u);
+        if (!p)
+                return errno ? -errno : -ENOENT;
+
+        if (!path_is_absolute(p->pw_dir))
+                return -EINVAL;
+
+        h = strdup(p->pw_dir);
+        if (!h)
+                return -ENOMEM;
+
+        *_h = h;
+        return 0;
+}
+
+int get_shell(char **_sh) {
+        char *sh;
+        const char *e;
+        uid_t u;
+        struct passwd *p;
+
+        assert(_sh);
+
+        /* Take the user specified one */
+        e = getenv("SHELL");
+        if (e) {
+                sh = strdup(e);
+                if (!sh)
+                        return -ENOMEM;
+
+                *_sh = sh;
+                return 0;
+        }
+
+        /* Hardcode home directory for root to avoid NSS */
+        u = getuid();
+        if (u == 0) {
+                sh = strdup("/bin/sh");
+                if (!sh)
+                        return -ENOMEM;
+
+                *_sh = sh;
+                return 0;
+        }
+
+        /* Check the database... */
+        errno = 0;
+        p = getpwuid(u);
+        if (!p)
+                return errno ? -errno : -ESRCH;
+
+        if (!path_is_absolute(p->pw_shell))
+                return -EINVAL;
+
+        sh = strdup(p->pw_shell);
+        if (!sh)
+                return -ENOMEM;
+
+        *_sh = sh;
+        return 0;
+}