X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Flibudev%2Flibudev-device.c;h=37e75d280fa6ed440610036916e0c14b6b1a101d;hp=f218b0278a20559e6966fecba452591762cee7c1;hb=41b848b0ea674dba135d5d5f82b87d126d967fcf;hpb=1edefa4f1d7bae6cc19aa4a97238400c5a04f7a4 diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c index f218b0278..37e75d280 100644 --- a/src/libudev/libudev-device.c +++ b/src/libudev/libudev-device.c @@ -36,13 +36,15 @@ #include "libudev.h" #include "libudev-private.h" +static int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode); + /** * SECTION:libudev-device * @short_description: kernel sys devices * * Representation of kernel sys devices. Devices are uniquely identified * by their syspath, every device has exactly one path in the kernel sys - * filesystem. Devices usually belong to a kernel subsystem, and and have + * filesystem. Devices usually belong to a kernel subsystem, and have * a unique name inside that subsystem. */ @@ -159,7 +161,7 @@ _public_ dev_t udev_device_get_devnum(struct udev_device *udev_device) return udev_device->devnum; } -static int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum) +int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum) { char num[32]; @@ -379,7 +381,7 @@ static struct udev_list_entry *udev_device_add_property_from_string(struct udev_ char name[UTIL_LINE_SIZE]; char *val; - util_strscpy(name, sizeof(name), property); + strscpy(name, sizeof(name), property); val = strchr(name, '='); if (val == NULL) return NULL; @@ -404,7 +406,7 @@ void udev_device_add_property_from_string_parse(struct udev_device *udev_device, if (startswith(property, "DEVPATH=")) { char path[UTIL_PATH_SIZE]; - util_strscpyl(path, sizeof(path), "/sys", &property[8], NULL); + strscpyl(path, sizeof(path), "/sys", &property[8], NULL); udev_device_set_syspath(udev_device, path); } else if (startswith(property, "SUBSYSTEM=")) { udev_device_set_subsystem(udev_device, &property[10]); @@ -417,7 +419,7 @@ void udev_device_add_property_from_string_parse(struct udev_device *udev_device, char *slink; char *next; - util_strscpy(devlinks, sizeof(devlinks), &property[9]); + strscpy(devlinks, sizeof(devlinks), &property[9]); slink = devlinks; next = strchr(slink, ' '); while (next != NULL) { @@ -432,7 +434,7 @@ void udev_device_add_property_from_string_parse(struct udev_device *udev_device, char tags[UTIL_PATH_SIZE]; char *next; - util_strscpy(tags, sizeof(tags), &property[5]); + strscpy(tags, sizeof(tags), &property[5]); next = strchr(tags, ':'); if (next != NULL) { next++; @@ -527,15 +529,17 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile) id = udev_device_get_id_filename(udev_device); if (id == NULL) return -1; - util_strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL); + strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL); dbfile = filename; } f = fopen(dbfile, "re"); if (f == NULL) { - udev_dbg(udev_device->udev, "no db file to read %s: %m\n", dbfile); - return -1; + log_debug("no db file to read %s: %m\n", dbfile); + return -errno; } + + /* devices with a database entry are initialized */ udev_device->is_initialized = true; while (fgets(line, sizeof(line), f)) { @@ -550,7 +554,7 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile) val = &line[2]; switch(line[0]) { case 'S': - util_strscpyl(filename, sizeof(filename), "/dev/", val, NULL); + strscpyl(filename, sizeof(filename), "/dev/", val, NULL); udev_device_add_devlink(udev_device, filename); break; case 'L': @@ -573,7 +577,7 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile) } fclose(f); - udev_dbg(udev_device->udev, "device %p filled with db file data\n", udev_device); + log_debug("device %p filled with db file data\n", udev_device); return 0; } @@ -588,10 +592,10 @@ int udev_device_read_uevent_file(struct udev_device *udev_device) if (udev_device->uevent_loaded) return 0; - util_strscpyl(filename, sizeof(filename), udev_device->syspath, "/uevent", NULL); + strscpyl(filename, sizeof(filename), udev_device->syspath, "/uevent", NULL); f = fopen(filename, "re"); if (f == NULL) - return -1; + return -errno; udev_device->uevent_loaded = true; while (fgets(line, sizeof(line), f)) { @@ -638,14 +642,17 @@ void udev_device_set_info_loaded(struct udev_device *device) struct udev_device *udev_device_new(struct udev *udev) { struct udev_device *udev_device; - struct udev_list_entry *list_entry; - if (udev == NULL) + if (udev == NULL) { + errno = EINVAL; return NULL; + } - udev_device = calloc(1, sizeof(struct udev_device)); - if (udev_device == NULL) + udev_device = new0(struct udev_device, 1); + if (udev_device == NULL) { + errno = ENOMEM; return NULL; + } udev_device->refcount = 1; udev_device->udev = udev; udev_list_init(udev, &udev_device->devlinks_list, true); @@ -654,11 +661,7 @@ struct udev_device *udev_device_new(struct udev *udev) udev_list_init(udev, &udev_device->sysattr_list, false); udev_list_init(udev, &udev_device->tags_list, true); udev_device->watch_handle = -1; - /* copy global properties */ - udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev)) - udev_device_add_property(udev_device, - udev_list_entry_get_name(list_entry), - udev_list_entry_get_value(list_entry)); + return udev_device; } @@ -684,32 +687,40 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con struct stat statbuf; struct udev_device *udev_device; - if (udev == NULL) + if (udev == NULL) { + errno = EINVAL; return NULL; - if (syspath == NULL) + } + + if (syspath == NULL) { + errno = EINVAL; return NULL; + } /* path starts in sys */ if (!startswith(syspath, "/sys")) { - udev_dbg(udev, "not in sys :%s\n", syspath); + log_debug("not in sys :%s\n", syspath); + errno = EINVAL; return NULL; } /* path is not a root directory */ subdir = syspath + strlen("/sys"); pos = strrchr(subdir, '/'); - if (pos == NULL || pos[1] == '\0' || pos < &subdir[2]) + if (pos == NULL || pos[1] == '\0' || pos < &subdir[2]) { + errno = EINVAL; return NULL; + } /* resolve possible symlink to real path */ - util_strscpy(path, sizeof(path), syspath); + strscpy(path, sizeof(path), syspath); util_resolve_sys_link(udev, path, sizeof(path)); if (startswith(path + strlen("/sys"), "/devices/")) { char file[UTIL_PATH_SIZE]; /* all "devices" require a "uevent" file */ - util_strscpyl(file, sizeof(file), path, "/uevent", NULL); + strscpyl(file, sizeof(file), path, "/uevent", NULL); if (stat(file, &statbuf) != 0) return NULL; } else { @@ -723,7 +734,7 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con return NULL; udev_device_set_syspath(udev_device, path); - udev_dbg(udev, "device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device)); + log_debug("device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device)); return udev_device; } @@ -753,8 +764,10 @@ _public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type_str = "block"; else if (type == 'c') type_str = "char"; - else + else { + errno = EINVAL; return NULL; + } /* use /sys/dev/{block,char}/: link */ snprintf(path, sizeof(path), "/sys/dev/%s/%u:%u", @@ -780,7 +793,7 @@ _public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char * * Returns: a new udev device, or #NULL, if it does not exist **/ -_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, char *id) +_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) { char type; int maj, min; @@ -800,13 +813,15 @@ _public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, c int ifindex; ifindex = strtoul(&id[1], NULL, 10); - if (ifindex <= 0) + if (ifindex <= 0) { + errno = EINVAL; return NULL; + } sk = socket(PF_INET, SOCK_DGRAM, 0); if (sk < 0) return NULL; - memset(&ifr, 0x00, sizeof(struct ifreq)); + memzero(&ifr, sizeof(struct ifreq)); ifr.ifr_ifindex = ifindex; if (ioctl(sk, SIOCGIFNAME, &ifr) != 0) { close(sk); @@ -819,18 +834,24 @@ _public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, c return NULL; if (udev_device_get_ifindex(dev) == ifindex) return dev; + + /* this is racy, so we may end up with the wrong device */ udev_device_unref(dev); + errno = ENODEV; return NULL; } case '+': - util_strscpy(subsys, sizeof(subsys), &id[1]); + strscpy(subsys, sizeof(subsys), &id[1]); sysname = strchr(subsys, ':'); - if (sysname == NULL) + if (sysname == NULL) { + errno = EINVAL; return NULL; + } sysname[0] = '\0'; sysname = &sysname[1]; return udev_device_new_from_subsystem_sysname(udev, subsys, sysname); default: + errno = EINVAL; return NULL; } } @@ -856,22 +877,22 @@ _public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev struct stat statbuf; if (streq(subsystem, "subsystem")) { - util_strscpyl(path, sizeof(path), "/sys/subsystem/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/subsystem/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; - util_strscpyl(path, sizeof(path), "/sys/bus/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/bus/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; - util_strscpyl(path, sizeof(path), "/sys/class/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/class/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; goto out; } if (streq(subsystem, "module")) { - util_strscpyl(path, sizeof(path), "/sys/module/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/module/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; goto out; @@ -881,32 +902,34 @@ _public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev char subsys[UTIL_NAME_SIZE]; char *driver; - util_strscpy(subsys, sizeof(subsys), sysname); + strscpy(subsys, sizeof(subsys), sysname); driver = strchr(subsys, ':'); if (driver != NULL) { driver[0] = '\0'; driver = &driver[1]; - util_strscpyl(path, sizeof(path), "/sys/subsystem/", subsys, "/drivers/", driver, NULL); + strscpyl(path, sizeof(path), "/sys/subsystem/", subsys, "/drivers/", driver, NULL); if (stat(path, &statbuf) == 0) goto found; - util_strscpyl(path, sizeof(path), "/sys/bus/", subsys, "/drivers/", driver, NULL); + strscpyl(path, sizeof(path), "/sys/bus/", subsys, "/drivers/", driver, NULL); if (stat(path, &statbuf) == 0) goto found; - } + } else + errno = EINVAL; + goto out; } - util_strscpyl(path, sizeof(path), "/sys/subsystem/", subsystem, "/devices/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/subsystem/", subsystem, "/devices/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; - util_strscpyl(path, sizeof(path), "/sys/bus/", subsystem, "/devices/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/bus/", subsystem, "/devices/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; - util_strscpyl(path, sizeof(path), "/sys/class/", subsystem, "/", sysname, NULL); + strscpyl(path, sizeof(path), "/sys/class/", subsystem, "/", sysname, NULL); if (stat(path, &statbuf) == 0) goto found; out: @@ -943,7 +966,7 @@ _public_ struct udev_device *udev_device_new_from_environment(struct udev *udev) udev_device_add_property_from_string_parse(udev_device, environ[i]); if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) { - udev_dbg(udev, "missing values, invalid device\n"); + log_debug("missing values, invalid device\n"); udev_device_unref(udev_device); udev_device = NULL; } @@ -957,7 +980,7 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic char path[UTIL_PATH_SIZE]; const char *subdir; - util_strscpy(path, sizeof(path), udev_device->syspath); + strscpy(path, sizeof(path), udev_device->syspath); subdir = path + strlen("/sys/"); for (;;) { char *pos; @@ -970,6 +993,8 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic if (udev_device_parent != NULL) return udev_device_parent; } + + errno = ENOENT; return NULL; } @@ -980,9 +1005,8 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic * Find the next parent device, and fill in information from the sys * device and the udev database entry. * - * The returned the device is not referenced. It is attached to the - * child device, and will be cleaned up when the child device - * is cleaned up. + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. * * It is not necessarily just the upper level directory, empty or not * recognized sys directories are ignored. @@ -994,8 +1018,10 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic **/ _public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) { - if (udev_device == NULL) + if (udev_device == NULL) { + errno = EINVAL; return NULL; + } if (!udev_device->parent_set) { udev_device->parent_set = true; udev_device->parent_device = device_new_from_parent(udev_device); @@ -1016,9 +1042,8 @@ _public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_dev * If devtype is #NULL, only subsystem is checked, and any devtype will * match. * - * The returned the device is not referenced. It is attached to the - * child device, and will be cleaned up when the child device - * is cleaned up. + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. * * It can be called as many times as needed, without caring about * references. @@ -1029,8 +1054,10 @@ _public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struc { struct udev_device *parent; - if (subsystem == NULL) + if (subsystem == NULL) { + errno = EINVAL; return NULL; + } parent = udev_device_get_parent(udev_device); while (parent != NULL) { @@ -1047,6 +1074,10 @@ _public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struc } parent = udev_device_get_parent(parent); } + + if (!parent) + errno = ENOENT; + return parent; } @@ -1088,7 +1119,7 @@ _public_ struct udev_device *udev_device_ref(struct udev_device *udev_device) * Drop a reference of a udev device. If the refcount reaches zero, * the resources of the device will be released. * - * Returns: the passed udev device if it has still an active reference, or #NULL otherwise. + * Returns: #NULL **/ _public_ struct udev_device *udev_device_unref(struct udev_device *udev_device) { @@ -1096,7 +1127,7 @@ _public_ struct udev_device *udev_device_unref(struct udev_device *udev_device) return NULL; udev_device->refcount--; if (udev_device->refcount > 0) - return udev_device; + return NULL; if (udev_device->parent_device != NULL) udev_device_unref(udev_device->parent_device); free(udev_device->syspath); @@ -1260,9 +1291,9 @@ _public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct ud size_t l; s = symlinks; - l = util_strpcpyl(&s, sizeof(symlinks), udev_list_entry_get_name(list_entry), NULL); + l = strpcpyl(&s, sizeof(symlinks), udev_list_entry_get_name(list_entry), NULL); udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry)) - l = util_strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry), NULL); + l = strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry), NULL); udev_device_add_property(udev_device, "DEVLINKS", symlinks); } } @@ -1275,9 +1306,9 @@ _public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct ud size_t l; s = tags; - l = util_strpcpyl(&s, sizeof(tags), ":", NULL); + l = strpcpyl(&s, sizeof(tags), ":", NULL); udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device)) - l = util_strpcpyl(&s, l, udev_list_entry_get_name(list_entry), ":", NULL); + l = strpcpyl(&s, l, udev_list_entry_get_name(list_entry), ":", NULL); udev_device_add_property(udev_device, "TAGS", tags); } } @@ -1339,7 +1370,7 @@ void udev_device_set_usec_initialized(struct udev_device *udev_device, usec_t us char num[32]; udev_device->usec_initialized = usec_initialized; - snprintf(num, sizeof(num), "%llu", (unsigned long long)usec_initialized); + snprintf(num, sizeof(num), USEC_FMT, usec_initialized); udev_device_add_property(udev_device, "USEC_INITIALIZED", num); } @@ -1374,15 +1405,13 @@ _public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_devi if (list_entry != NULL) return udev_list_entry_get_value(list_entry); - util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", sysattr, NULL); + strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", sysattr, NULL); if (lstat(path, &statbuf) != 0) { udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, NULL); goto out; } if (S_ISLNK(statbuf.st_mode)) { - struct udev_device *dev; - /* * Some core links return only the last element of the target path, * these are just values, the paths should not be exposed. @@ -1398,17 +1427,6 @@ _public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_devi goto out; } - /* resolve custom link to a device and return its syspath */ - if (!streq(sysattr, "device")) { - util_strscpyl(path, sizeof(path), udev_device->syspath, "/", sysattr, NULL); - dev = udev_device_new_from_syspath(udev_device->udev, path); - if (dev != NULL) { - list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr, - udev_device_get_syspath(dev)); - val = udev_list_entry_get_value(list_entry); - udev_device_unref(dev); - } - } goto out; } @@ -1440,6 +1458,91 @@ out: return val; } +/** + * udev_device_set_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * @value: new value to be set + * + * Update the contents of the sys attribute and the cached value of the device. + * + * Returns: Negative error code on failure or 0 on success. + **/ +_public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value) +{ + struct udev_device *dev; + char path[UTIL_PATH_SIZE]; + struct stat statbuf; + int fd; + ssize_t size, value_len; + int ret = 0; + + if (udev_device == NULL) + return -EINVAL; + dev = udev_device; + if (sysattr == NULL) + return -EINVAL; + if (value == NULL) + value_len = 0; + else + value_len = strlen(value); + + strscpyl(path, sizeof(path), udev_device_get_syspath(dev), "/", sysattr, NULL); + if (lstat(path, &statbuf) != 0) { + udev_list_entry_add(&dev->sysattr_value_list, sysattr, NULL); + ret = -ENXIO; + goto out; + } + + if (S_ISLNK(statbuf.st_mode)) { + ret = -EINVAL; + goto out; + } + + /* skip directories */ + if (S_ISDIR(statbuf.st_mode)) { + ret = -EISDIR; + goto out; + } + + /* skip non-readable files */ + if ((statbuf.st_mode & S_IRUSR) == 0) { + ret = -EACCES; + goto out; + } + + /* Value is limited to 4k */ + if (value_len > 4096) { + ret = -EINVAL; + goto out; + } + util_remove_trailing_chars(value, '\n'); + + /* write attribute value */ + fd = open(path, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + ret = -errno; + goto out; + } + size = write(fd, value, value_len); + close(fd); + if (size < 0) { + ret = -errno; + goto out; + } + if (size < value_len) { + ret = -EIO; + goto out; + } + + /* wrote a valid value, store it in cache and return it */ + udev_list_entry_add(&dev->sysattr_value_list, sysattr, value); +out: + if (dev != udev_device) + udev_device_unref(dev); + return ret; +} + static int udev_device_sysattr_list_read(struct udev_device *udev_device) { struct dirent *dent; @@ -1447,13 +1550,13 @@ static int udev_device_sysattr_list_read(struct udev_device *udev_device) int num = 0; if (udev_device == NULL) - return -1; + return -EINVAL; if (udev_device->sysattr_list_read) return 0; dir = opendir(udev_device_get_syspath(udev_device)); if (!dir) - return -1; + return -errno; for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { char path[UTIL_PATH_SIZE]; @@ -1463,7 +1566,7 @@ static int udev_device_sysattr_list_read(struct udev_device *udev_device) if (dent->d_type != DT_LNK && dent->d_type != DT_REG) continue; - util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", dent->d_name, NULL); + strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", dent->d_name, NULL); if (lstat(path, &statbuf) != 0) continue; if ((statbuf.st_mode & S_IRUSR) == 0) @@ -1539,7 +1642,7 @@ int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath return 0; } -int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode) +static int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode) { free(udev_device->devnode); if (devnode[0] != '/') { @@ -1624,9 +1727,14 @@ void udev_device_set_is_initialized(struct udev_device *udev_device) udev_device->is_initialized = true; } +static bool is_valid_tag(const char *tag) +{ + return !strchr(tag, ':') && !strchr(tag, ' '); +} + int udev_device_add_tag(struct udev_device *udev_device, const char *tag) { - if (strchr(tag, ':') != NULL || strchr(tag, ' ') != NULL) + if (!is_valid_tag(tag)) return -EINVAL; udev_device->tags_uptodate = false; if (udev_list_entry_add(&udev_device->tags_list, tag, NULL) != NULL) @@ -1634,6 +1742,20 @@ int udev_device_add_tag(struct udev_device *udev_device, const char *tag) return -ENOMEM; } +void udev_device_remove_tag(struct udev_device *udev_device, const char *tag) +{ + struct udev_list_entry *e; + + if (!is_valid_tag(tag)) + return; + e = udev_list_get_entry(&udev_device->tags_list); + e = udev_list_entry_get_by_name(e, tag); + if (e) { + udev_device->tags_uptodate = false; + udev_list_entry_delete(e); + } +} + void udev_device_cleanup_tags_list(struct udev_device *udev_device) { udev_device->tags_uptodate = false; @@ -1722,10 +1844,10 @@ static int update_envp_monitor_buf(struct udev_device *udev_device) return -EINVAL; /* add property string to monitor buffer */ - l = util_strpcpyl(&s, l, key, "=", udev_list_entry_get_value(list_entry), NULL); + l = strpcpyl(&s, l, key, "=", udev_list_entry_get_value(list_entry), NULL); if (l == 0) return -EINVAL; - /* advance past the trailing '\0' that util_strpcpyl() guarantees */ + /* advance past the trailing '\0' that strpcpyl() guarantees */ s++; l--; }