From a2554acec652fc65c8ed0c6c1fede9ba8c3693b1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 29 Aug 2018 15:54:56 +0900 Subject: [PATCH] udev-node: replace udev_device by sd_device and modernize code a bit --- src/udev/meson.build | 1 + src/udev/udev-event.c | 7 +- src/udev/udev-node.c | 455 ++++++++++++++++++++++++++---------------- src/udev/udev-node.h | 16 ++ src/udev/udev.h | 7 - 5 files changed, 303 insertions(+), 183 deletions(-) create mode 100644 src/udev/udev-node.h diff --git a/src/udev/meson.build b/src/udev/meson.build index 96df2d3877..0b02d838ca 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -22,6 +22,7 @@ libudev_core_sources = ''' udev-ctrl.c udev-event.c udev-node.c + udev-node.h udev-rules.c udev-watch.c udev-watch.h diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index 5b28e46798..273bddce96 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -24,6 +24,7 @@ #include "process-util.h" #include "signal-util.h" #include "string-util.h" +#include "udev-node.h" #include "udev-watch.h" #include "udev.h" @@ -808,7 +809,7 @@ void udev_event_execute_rules(struct udev_event *event, properties_list); if (major(udev_device_get_devnum(dev)) != 0) - udev_node_remove(dev); + udev_node_remove(dev->device); } else { event->dev_db = udev_device_clone_with_db(dev); if (event->dev_db != NULL) { @@ -849,7 +850,7 @@ void udev_event_execute_rules(struct udev_event *event, /* remove/update possible left-over symlinks from old database entry */ if (event->dev_db != NULL) - udev_node_update_old_links(dev, event->dev_db); + udev_node_update_old_links(dev->device, event->dev_db->device); if (!event->owner_set) event->uid = udev_device_get_devnode_uid(dev); @@ -871,7 +872,7 @@ void udev_event_execute_rules(struct udev_event *event, } apply = streq(udev_device_get_action(dev), "add") || event->owner_set || event->group_set || event->mode_set; - udev_node_add(dev, apply, event->mode, event->uid, event->gid, &event->seclabel_list); + udev_node_add(dev->device, apply, event->mode, event->uid, event->gid, &event->seclabel_list); } /* preserve old, or get new initialization timestamp */ diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index be253ccefe..7a883564b1 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -9,21 +9,30 @@ #include #include +#include "alloc-util.h" #include "device-nodes.h" +#include "device-private.h" +#include "device-util.h" #include "dirent-util.h" +#include "fd-util.h" #include "format-util.h" #include "fs-util.h" +#include "path-util.h" #include "selinux-util.h" #include "smack-util.h" #include "stdio-util.h" #include "string-util.h" -#include "udev.h" +#include "udev-node.h" -static int node_symlink(struct udev_device *dev, const char *node, const char *slink) { +static int node_symlink(sd_device *dev, const char *node, const char *slink) { _cleanup_free_ char *slink_dirname = NULL, *target = NULL; - char slink_tmp[UTIL_PATH_SIZE + 32]; + const char *id_filename, *slink_tmp; struct stat stats; - int r, err = 0; + int r; + + assert(dev); + assert(node); + assert(slink); slink_dirname = dirname_malloc(slink); if (!slink_dirname) @@ -37,233 +46,283 @@ static int node_symlink(struct udev_device *dev, const char *node, const char *s /* preserve link with correct target, do not replace node of other device */ if (lstat(slink, &stats) == 0) { if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) { - log_error("conflicting device node '%s' found, link to '%s' will not be created", slink, node); - goto exit; + log_error("Conflicting device node '%s' found, link to '%s' will not be created.", slink, node); + return -EOPNOTSUPP; } else if (S_ISLNK(stats.st_mode)) { - char buf[UTIL_PATH_SIZE]; - int len; + char buf[PATH_MAX]; + ssize_t len; len = readlink(slink, buf, sizeof(buf)); - if (len > 0 && len < (int)sizeof(buf)) { + if (len > 0 && len < (ssize_t) sizeof(buf)) { buf[len] = '\0'; if (streq(target, buf)) { - log_debug("preserve already existing symlink '%s' to '%s'", slink, target); + log_debug("Preserve already existing symlink '%s' to '%s'", slink, target); (void) label_fix(slink, LABEL_IGNORE_ENOENT); - utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW); - goto exit; + (void) utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW); + return 0; } } } } else { - log_debug("creating symlink '%s' to '%s'", slink, target); + log_debug("Creating symlink '%s' to '%s'", slink, target); do { - err = mkdir_parents_label(slink, 0755); - if (!IN_SET(err, 0, -ENOENT)) + r = mkdir_parents_label(slink, 0755); + if (!IN_SET(r, 0, -ENOENT)) break; mac_selinux_create_file_prepare(slink, S_IFLNK); - err = symlink(target, slink); - if (err != 0) - err = -errno; + if (symlink(target, slink) < 0) + r = -errno; mac_selinux_create_file_clear(); - } while (err == -ENOENT); - if (err == 0) - goto exit; + } while (r == -ENOENT); + if (r == 0) + return 0; } - log_debug("atomically replace '%s'", slink); - strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL); - unlink(slink_tmp); + log_debug("Atomically replace '%s'", slink); + r = device_get_id_filename(dev, &id_filename); + if (r < 0) + return log_error_errno(r, "Failed to get id_filename: %m"); + slink_tmp = strjoina(slink, ".tmp-", id_filename); + (void) unlink(slink_tmp); do { - err = mkdir_parents_label(slink_tmp, 0755); - if (!IN_SET(err, 0, -ENOENT)) + r = mkdir_parents_label(slink_tmp, 0755); + if (!IN_SET(r, 0, -ENOENT)) break; mac_selinux_create_file_prepare(slink_tmp, S_IFLNK); - err = symlink(target, slink_tmp); - if (err != 0) - err = -errno; + if (symlink(target, slink_tmp) < 0) + r = -errno; mac_selinux_create_file_clear(); - } while (err == -ENOENT); - if (err != 0) { - log_error_errno(errno, "symlink '%s' '%s' failed: %m", target, slink_tmp); - goto exit; + } while (r == -ENOENT); + if (r < 0) + return log_error_errno(r, "Failed to create symlink '%s' to '%s': %m", slink_tmp, target); + + if (rename(slink_tmp, slink) < 0) { + r = log_error_errno(errno, "Failed to rename '%s' to '%s' failed: %m", slink_tmp, slink); + (void) unlink(slink_tmp); } - err = rename(slink_tmp, slink); - if (err != 0) { - log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink); - unlink(slink_tmp); - } -exit: - return err; + + return r; } /* find device node of device with highest priority */ -static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) { - DIR *dir; +static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir, char **ret) { + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_free_ char *target = NULL; struct dirent *dent; - int priority = 0; - const char *target = NULL; + int r, priority = 0; + + assert(!add || dev); + assert(stackdir); + assert(ret); if (add) { - priority = udev_device_get_devlink_priority(dev); - strscpy(buf, bufsize, udev_device_get_devnode(dev)); - target = buf; + const char *devnode; + + r = device_get_devlink_priority(dev, &priority); + if (r < 0) + return r; + + r = sd_device_get_devname(dev, &devnode); + if (r < 0) + return r; + + target = strdup(devnode); + if (!target) + return -ENOMEM; } dir = opendir(stackdir); - if (dir == NULL) - return target; + if (!dir) { + if (target) { + *ret = TAKE_PTR(target); + return 0; + } + + return -errno; + } + FOREACH_DIRENT_ALL(dent, dir, break) { - struct udev_device *dev_db; + _cleanup_(sd_device_unrefp) sd_device *dev_db = NULL; + const char *devnode, *id_filename; + int db_prio = 0; if (dent->d_name[0] == '\0') break; if (dent->d_name[0] == '.') continue; - log_debug("found '%s' claiming '%s'", dent->d_name, stackdir); + log_debug("Found '%s' claiming '%s'", dent->d_name, stackdir); - /* did we find ourself? */ - if (streq(dent->d_name, udev_device_get_id_filename(dev))) + if (device_get_id_filename(dev, &id_filename) < 0) continue; - dev_db = udev_device_new_from_device_id(NULL, dent->d_name); - if (dev_db != NULL) { - const char *devnode; + /* did we find ourself? */ + if (streq(dent->d_name, id_filename)) + continue; - devnode = udev_device_get_devnode(dev_db); - if (devnode != NULL) { - if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) { - log_debug("'%s' claims priority %i for '%s'", - udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir); - priority = udev_device_get_devlink_priority(dev_db); - strscpy(buf, bufsize, devnode); - target = buf; - } - } - udev_device_unref(dev_db); + if (sd_device_new_from_device_id(&dev_db, dent->d_name) < 0) + continue; + + if (sd_device_get_devname(dev_db, &devnode) < 0) + continue; + + if (device_get_devlink_priority(dev_db, &db_prio) < 0) + continue; + + if (target && db_prio <= priority) + continue; + + if (DEBUG_LOGGING) { + const char *syspath = NULL; + + (void) sd_device_get_syspath(dev_db, &syspath); + log_debug("Device '%s' claims priority %i for '%s'", strnull(syspath), db_prio, stackdir); } + + r = free_and_strdup(&target, devnode); + if (r < 0) + return r; + priority = db_prio; } - closedir(dir); - return target; + + *ret = TAKE_PTR(target); + return 0; } /* manage "stack of names" with possibly specified device priorities */ -static void link_update(struct udev_device *dev, const char *slink, bool add) { - char name_enc[UTIL_PATH_SIZE]; - char filename[UTIL_PATH_SIZE * 2]; - char dirname[UTIL_PATH_SIZE]; - const char *target; - char buf[UTIL_PATH_SIZE]; +static int link_update(sd_device *dev, const char *slink, bool add) { + _cleanup_free_ char *target = NULL, *filename = NULL, *dirname = NULL; + char name_enc[PATH_MAX]; + const char *id_filename; + int r; + + assert(dev); + assert(slink); + + r = device_get_id_filename(dev, &id_filename); + if (r < 0) + return log_debug_errno(r, "Failed to get id_filename: %m"); util_path_encode(slink + STRLEN("/dev"), name_enc, sizeof(name_enc)); - strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL); - strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL); + dirname = path_join(NULL, "/run/udev/links/", name_enc); + if (!dirname) + return log_oom(); + filename = path_join(NULL, dirname, id_filename); + if (!filename) + return log_oom(); if (!add && unlink(filename) == 0) - rmdir(dirname); + (void) rmdir(dirname); - target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf)); - if (target == NULL) { - log_debug("no reference left, remove '%s'", slink); + r = link_find_prioritized(dev, add, dirname, &target); + if (r < 0) { + log_debug("No reference left, removing '%s'", slink); if (unlink(slink) == 0) - rmdir_parents(slink, "/"); + (void) rmdir_parents(slink, "/"); } else { - log_debug("creating link '%s' to '%s'", slink, target); - node_symlink(dev, target, slink); + log_debug("Creating link '%s' to '%s'", slink, target); + (void) node_symlink(dev, target, slink); } - if (add) { - int err; - + if (add) do { - int fd; + _cleanup_close_ int fd = -1; - err = mkdir_parents(filename, 0755); - if (!IN_SET(err, 0, -ENOENT)) + r = mkdir_parents(filename, 0755); + if (!IN_SET(r, 0, -ENOENT)) break; fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444); - if (fd >= 0) - close(fd); - else - err = -errno; - } while (err == -ENOENT); - } + if (fd < 0) + r = -errno; + } while (r == -ENOENT); + + return r; } -void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) { - struct udev_list_entry *list_entry; +int udev_node_update_old_links(sd_device *dev, sd_device *dev_old) { + const char *name, *devpath; + int r; + + assert(dev); + assert(dev_old); + + r = sd_device_get_devpath(dev, &devpath); + if (r < 0) + return log_debug_errno(r, "Failed to get devpath: %m"); /* update possible left-over symlinks */ - udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) { - const char *name = udev_list_entry_get_name(list_entry); - struct udev_list_entry *list_entry_current; - int found; + FOREACH_DEVICE_DEVLINK(dev_old, name) { + const char *name_current; + bool found = false; /* check if old link name still belongs to this device */ - found = 0; - udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) { - const char *name_current = udev_list_entry_get_name(list_entry_current); - + FOREACH_DEVICE_DEVLINK(dev, name_current) if (streq(name, name_current)) { - found = 1; + found = true; break; } - } + if (found) continue; - log_debug("update old name, '%s' no longer belonging to '%s'", - name, udev_device_get_devpath(dev)); + log_debug("Updating old name, '%s' no longer belonging to '%s'", + name, devpath); link_update(dev, name, false); } + + return 0; } -static int node_permissions_apply(struct udev_device *dev, bool apply, +static int node_permissions_apply(sd_device *dev, bool apply, mode_t mode, uid_t uid, gid_t gid, struct udev_list *seclabel_list) { - const char *devnode = udev_device_get_devnode(dev); - dev_t devnum = udev_device_get_devnum(dev); - struct stat stats; + const char *devnode, *subsystem, *id_filename = NULL; struct udev_list_entry *entry; - int err = 0; + struct stat stats; + dev_t devnum; + int r = 0; - if (streq(udev_device_get_subsystem(dev), "block")) + assert(dev); + + r = sd_device_get_devname(dev, &devnode); + if (r < 0) + return log_debug_errno(r, "Failed to get devname: %m"); + r = sd_device_get_subsystem(dev, &subsystem); + if (r < 0) + return log_debug_errno(r, "Failed to get subsystem: %m"); + r = sd_device_get_devnum(dev, &devnum); + if (r < 0) + return log_debug_errno(r, "Failed to get devnum: %m"); + (void) device_get_id_filename(dev, &id_filename); + + if (streq(subsystem, "block")) mode |= S_IFBLK; else mode |= S_IFCHR; - if (lstat(devnode, &stats) != 0) { - err = log_debug_errno(errno, "cannot stat() node '%s' (%m)", devnode); - goto out; - } + if (lstat(devnode, &stats) < 0) + return log_debug_errno(errno, "cannot stat() node '%s' (%m)", devnode); - if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) { - err = -EEXIST; - log_debug("found node '%s' with non-matching devnum %s, skip handling", - udev_device_get_devnode(dev), udev_device_get_id_filename(dev)); - goto out; - } + if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) + return log_debug_errno(EEXIST, "Found node '%s' with non-matching devnum %s, skip handling", + devnode, id_filename); if (apply) { - bool selinux = false; - bool smack = false; + bool selinux = false, smack = false; if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) { - log_debug("set permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); - err = chmod(devnode, mode); - if (err < 0) - log_warning_errno(errno, "setting mode of %s to %#o failed: %m", devnode, mode); - err = chown(devnode, uid, gid); - if (err < 0) - log_warning_errno(errno, "setting owner of %s to uid=%u, gid=%u failed: %m", devnode, uid, gid); - } else { - log_debug("preserve permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); - } + log_debug("Setting permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); + if (chmod(devnode, mode) < 0) + r = log_warning_errno(errno, "Failed to set mode of %s to %#o: %m", devnode, mode); + if (chown(devnode, uid, gid) < 0) + r = log_warning_errno(errno, "Failed to set owner of %s to uid=%u, gid=%u: %m", devnode, uid, gid); + } else + log_debug("Preserve permissions of %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); /* apply SECLABEL{$module}=$label */ udev_list_entry_foreach(entry, udev_list_get_entry(seclabel_list)) { const char *name, *label; - int r; + int q; name = udev_list_entry_get_name(entry); label = udev_list_entry_get_value(entry); @@ -271,18 +330,18 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, if (streq(name, "selinux")) { selinux = true; - r = mac_selinux_apply(devnode, label); - if (r < 0) - log_error_errno(r, "SECLABEL: failed to set SELinux label '%s': %m", label); + q = mac_selinux_apply(devnode, label); + if (q < 0) + log_error_errno(q, "SECLABEL: failed to set SELinux label '%s': %m", label); else log_debug("SECLABEL: set SELinux label '%s'", label); } else if (streq(name, "smack")) { smack = true; - r = mac_smack_apply(devnode, SMACK_ATTR_ACCESS, label); - if (r < 0) - log_error_errno(r, "SECLABEL: failed to set SMACK label '%s': %m", label); + q = mac_smack_apply(devnode, SMACK_ATTR_ACCESS, label); + if (q < 0) + log_error_errno(q, "SECLABEL: failed to set SMACK label '%s': %m", label); else log_debug("SECLABEL: set SMACK label '%s'", label); @@ -294,49 +353,99 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, if (!selinux) (void) mac_selinux_fix(devnode, LABEL_IGNORE_ENOENT); if (!smack) - mac_smack_apply(devnode, SMACK_ATTR_ACCESS, NULL); + (void) mac_smack_apply(devnode, SMACK_ATTR_ACCESS, NULL); } /* always update timestamp when we re-use the node, like on media change events */ - utimensat(AT_FDCWD, devnode, NULL, 0); -out: - return err; + (void) utimensat(AT_FDCWD, devnode, NULL, 0); + + return r; } -void udev_node_add(struct udev_device *dev, bool apply, - mode_t mode, uid_t uid, gid_t gid, - struct udev_list *seclabel_list) { - char filename[DEV_NUM_PATH_MAX]; - struct udev_list_entry *list_entry; +static int xsprintf_dev_num_path_from_sd_device(sd_device *dev, char **ret) { + char filename[DEV_NUM_PATH_MAX], *s; + const char *subsystem; + dev_t devnum; + int r; - log_debug("handling device node '%s', devnum=%s, mode=%#o, uid="UID_FMT", gid="GID_FMT, - udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid); + assert(ret); - if (node_permissions_apply(dev, apply, mode, uid, gid, seclabel_list) < 0) - return; + r = sd_device_get_subsystem(dev, &subsystem); + if (r < 0) + return r; + + r = sd_device_get_devnum(dev, &devnum); + if (r < 0) + return r; + + xsprintf_dev_num_path(filename, + streq(subsystem, "block") ? "block" : "char", + devnum); + + s = strdup(filename); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int udev_node_add(sd_device *dev, bool apply, + mode_t mode, uid_t uid, gid_t gid, + struct udev_list *seclabel_list) { + const char *devnode, *devlink; + _cleanup_free_ char *filename = NULL; + int r; + + assert(dev); + + r = sd_device_get_devname(dev, &devnode); + if (r < 0) + return log_debug_errno(r, "Failed to get devnode: %m"); + + if (DEBUG_LOGGING) { + const char *id_filename = NULL; + + (void) device_get_id_filename(dev, &id_filename); + log_debug("Handling device node '%s', devnum=%s, mode=%#o, uid="UID_FMT", gid="GID_FMT, + devnode, strnull(id_filename), mode, uid, gid); + } + + r = node_permissions_apply(dev, apply, mode, uid, gid, seclabel_list); + if (r < 0) + return r; + + r = xsprintf_dev_num_path_from_sd_device(dev, &filename); + if (r < 0) + return log_debug_errno(r, "Failed to get device path: %m"); /* always add /dev/{block,char}/$major:$minor */ - xsprintf_dev_num_path(filename, - streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", - udev_device_get_devnum(dev)); - node_symlink(dev, udev_device_get_devnode(dev), filename); + (void) node_symlink(dev, devnode, filename); /* create/update symlinks, add symlinks to name index */ - udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) - link_update(dev, udev_list_entry_get_name(list_entry), true); + FOREACH_DEVICE_DEVLINK(dev, devlink) + (void) link_update(dev, devlink, true); + + return 0; } -void udev_node_remove(struct udev_device *dev) { - struct udev_list_entry *list_entry; - char filename[DEV_NUM_PATH_MAX]; +int udev_node_remove(sd_device *dev) { + _cleanup_free_ char *filename = NULL; + const char *devlink; + int r; + + assert(dev); /* remove/update symlinks, remove symlinks from name index */ - udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) - link_update(dev, udev_list_entry_get_name(list_entry), false); + FOREACH_DEVICE_DEVLINK(dev, devlink) + (void) link_update(dev, devlink, false); + + r = xsprintf_dev_num_path_from_sd_device(dev, &filename); + if (r < 0) + return log_debug_errno(r, "Failed to get device path: %m"); /* remove /dev/{block,char}/$major:$minor */ - xsprintf_dev_num_path(filename, - streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", - udev_device_get_devnum(dev)); - unlink(filename); + (void) unlink(filename); + + return 0; } diff --git a/src/udev/udev-node.h b/src/udev/udev-node.h new file mode 100644 index 0000000000..868549e492 --- /dev/null +++ b/src/udev/udev-node.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#pragma once + +#include +#include + +#include "libudev.h" +#include "sd-device.h" + +#include "libudev-private.h" + +int udev_node_add(sd_device *dev, bool apply, + mode_t mode, uid_t uid, gid_t gid, + struct udev_list *seclabel_list); +int udev_node_remove(sd_device *dev); +int udev_node_update_old_links(sd_device *dev, sd_device *dev_old); diff --git a/src/udev/udev.h b/src/udev/udev.h index 1176b4a44b..2ba407d7a5 100644 --- a/src/udev/udev.h +++ b/src/udev/udev.h @@ -77,13 +77,6 @@ void udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules); void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec); -/* udev-node.c */ -void udev_node_add(struct udev_device *dev, bool apply, - mode_t mode, uid_t uid, gid_t gid, - struct udev_list *seclabel_list); -void udev_node_remove(struct udev_device *dev); -void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old); - /* udev-ctrl.c */ struct udev_ctrl; struct udev_ctrl *udev_ctrl_new(void);