diff options
Diffstat (limited to 'src')
71 files changed, 3782 insertions, 1346 deletions
diff --git a/src/accelerometer/accelerometer.c b/src/accelerometer/accelerometer.c index 71c5b0c199..862224b7aa 100644 --- a/src/accelerometer/accelerometer.c +++ b/src/accelerometer/accelerometer.c @@ -73,14 +73,6 @@ #define LONG(x) ((x)/BITS_PER_LONG) #define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) -_printf_(6,0) -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - log_metav(priority, file, line, fn, format, args); -} - typedef enum { ORIENTATION_UNDEFINED, ORIENTATION_NORMAL, @@ -207,11 +199,13 @@ static void test_orientation(struct udev *udev, puts(text); } -static void help(void) -{ - printf("Usage: accelerometer [options] <device path>\n" - " --debug debug to stderr\n" - " --help print this help text\n\n"); +static void help(void) { + + printf("%s [options] <device path>\n\n" + "Accelerometer device identification.\n\n" + " -h --help Print this message\n" + " -d --debug Debug to stderr\n" + , program_invocation_short_name); } int main (int argc, char** argv) @@ -236,13 +230,11 @@ int main (int argc, char** argv) if (udev == NULL) return 1; - udev_set_log_fn(udev, log_fn); - /* CLI argument parsing */ while (1) { int option; - option = getopt_long(argc, argv, "dxh", options, NULL); + option = getopt_long(argc, argv, "dh", options, NULL); if (option == -1) break; @@ -250,7 +242,6 @@ int main (int argc, char** argv) case 'd': log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); - udev_set_log_priority(udev, LOG_DEBUG); log_open(); break; case 'h': diff --git a/src/ata_id/ata_id.c b/src/ata_id/ata_id.c index 8ead84cf27..175e619a70 100644 --- a/src/ata_id/ata_id.c +++ b/src/ata_id/ata_id.c @@ -47,6 +47,7 @@ #include "libudev.h" #include "libudev-private.h" +#include "udev-util.h" #include "log.h" #define COMMAND_TIMEOUT_MSEC (30 * 1000) @@ -409,29 +410,23 @@ out: return ret; } -_printf_(6,0) -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - log_metav(priority, file, line, fn, format, args); -} - int main(int argc, char *argv[]) { - struct udev *udev; + _cleanup_udev_unref_ struct udev *udev = NULL; struct hd_driveid id; - uint8_t identify[512]; - uint16_t *identify_words; + union { + uint8_t byte[512]; + uint16_t wyde[256]; + uint64_t octa[64]; + } identify; char model[41]; char model_enc[256]; char serial[21]; char revision[9]; const char *node = NULL; int export = 0; - int fd; + _cleanup_close_ int fd = -1; uint16_t word; - int rc = 0; int is_packet_device = 0; static const struct option options[] = { { "export", no_argument, NULL, 'x' }, @@ -443,9 +438,7 @@ int main(int argc, char *argv[]) udev = udev_new(); if (udev == NULL) - goto exit; - - udev_set_log_fn(udev, log_fn); + return 0; while (1) { int option; @@ -460,59 +453,55 @@ int main(int argc, char *argv[]) break; case 'h': printf("Usage: ata_id [--export] [--help] <device>\n" - " --export print values as environment keys\n" - " --help print this help text\n\n"); - goto exit; + " -x,--export print values as environment keys\n" + " -h,--help print this help text\n\n"); + return 0; } } node = argv[optind]; if (node == NULL) { log_error("no node specified"); - rc = 1; - goto exit; + return 1; } fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC); if (fd < 0) { log_error("unable to open '%s'", node); - rc = 1; - goto exit; + return 1; } - if (disk_identify(udev, fd, identify, &is_packet_device) == 0) { + if (disk_identify(udev, fd, identify.byte, &is_packet_device) == 0) { /* * fix up only the fields from the IDENTIFY data that we are going to * use and copy it into the hd_driveid struct for convenience */ - disk_identify_fixup_string(identify, 10, 20); /* serial */ - disk_identify_fixup_string(identify, 23, 8); /* fwrev */ - disk_identify_fixup_string(identify, 27, 40); /* model */ - disk_identify_fixup_uint16(identify, 0); /* configuration */ - disk_identify_fixup_uint16(identify, 75); /* queue depth */ - disk_identify_fixup_uint16(identify, 75); /* SATA capabilities */ - disk_identify_fixup_uint16(identify, 82); /* command set supported */ - disk_identify_fixup_uint16(identify, 83); /* command set supported */ - disk_identify_fixup_uint16(identify, 84); /* command set supported */ - disk_identify_fixup_uint16(identify, 85); /* command set supported */ - disk_identify_fixup_uint16(identify, 86); /* command set supported */ - disk_identify_fixup_uint16(identify, 87); /* command set supported */ - disk_identify_fixup_uint16(identify, 89); /* time required for SECURITY ERASE UNIT */ - disk_identify_fixup_uint16(identify, 90); /* time required for enhanced SECURITY ERASE UNIT */ - disk_identify_fixup_uint16(identify, 91); /* current APM values */ - disk_identify_fixup_uint16(identify, 94); /* current AAM value */ - disk_identify_fixup_uint16(identify, 128); /* device lock function */ - disk_identify_fixup_uint16(identify, 217); /* nominal media rotation rate */ - memcpy(&id, identify, sizeof id); + disk_identify_fixup_string(identify.byte, 10, 20); /* serial */ + disk_identify_fixup_string(identify.byte, 23, 8); /* fwrev */ + disk_identify_fixup_string(identify.byte, 27, 40); /* model */ + disk_identify_fixup_uint16(identify.byte, 0); /* configuration */ + disk_identify_fixup_uint16(identify.byte, 75); /* queue depth */ + disk_identify_fixup_uint16(identify.byte, 75); /* SATA capabilities */ + disk_identify_fixup_uint16(identify.byte, 82); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 83); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 84); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 85); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 86); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 87); /* command set supported */ + disk_identify_fixup_uint16(identify.byte, 89); /* time required for SECURITY ERASE UNIT */ + disk_identify_fixup_uint16(identify.byte, 90); /* time required for enhanced SECURITY ERASE UNIT */ + disk_identify_fixup_uint16(identify.byte, 91); /* current APM values */ + disk_identify_fixup_uint16(identify.byte, 94); /* current AAM value */ + disk_identify_fixup_uint16(identify.byte, 128); /* device lock function */ + disk_identify_fixup_uint16(identify.byte, 217); /* nominal media rotation rate */ + memcpy(&id, identify.byte, sizeof id); } else { /* If this fails, then try HDIO_GET_IDENTITY */ if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) { - log_debug("HDIO_GET_IDENTITY failed for '%s': %m", node); - rc = 2; - goto close; + log_debug_errno(errno, "HDIO_GET_IDENTITY failed for '%s': %m", node); + return 2; } } - identify_words = (uint16_t *) identify; memcpy (model, id.model, 40); model[40] = '\0'; @@ -562,8 +551,8 @@ int main(int argc, char *argv[]) } if (id.command_set_1 & (1<<5)) { - printf ("ID_ATA_WRITE_CACHE=1\n"); - printf ("ID_ATA_WRITE_CACHE_ENABLED=%d\n", (id.cfs_enable_1 & (1<<5)) ? 1 : 0); + printf("ID_ATA_WRITE_CACHE=1\n"); + printf("ID_ATA_WRITE_CACHE_ENABLED=%d\n", (id.cfs_enable_1 & (1<<5)) ? 1 : 0); } if (id.command_set_1 & (1<<10)) { printf("ID_ATA_FEATURE_SET_HPA=1\n"); @@ -626,7 +615,8 @@ int main(int argc, char *argv[]) * the device does not claim compliance with the Serial ATA specification and words * 76 through 79 are not valid and shall be ignored. */ - word = *((uint16_t *) identify + 76); + + word = identify.wyde[76]; if (word != 0x0000 && word != 0xffff) { printf("ID_ATA_SATA=1\n"); /* @@ -643,54 +633,34 @@ int main(int argc, char *argv[]) } /* Word 217 indicates the nominal media rotation rate of the device */ - word = *((uint16_t *) identify + 217); - if (word != 0x0000) { - if (word == 0x0001) { - printf ("ID_ATA_ROTATION_RATE_RPM=0\n"); /* non-rotating e.g. SSD */ - } else if (word >= 0x0401 && word <= 0xfffe) { - printf ("ID_ATA_ROTATION_RATE_RPM=%d\n", word); - } - } + word = identify.wyde[217]; + if (word == 0x0001) + printf ("ID_ATA_ROTATION_RATE_RPM=0\n"); /* non-rotating e.g. SSD */ + else if (word >= 0x0401 && word <= 0xfffe) + printf ("ID_ATA_ROTATION_RATE_RPM=%d\n", word); /* * Words 108-111 contain a mandatory World Wide Name (WWN) in the NAA IEEE Registered identifier * format. Word 108 bits (15:12) shall contain 5h, indicating that the naming authority is IEEE. * All other values are reserved. */ - word = *((uint16_t *) identify + 108); - if ((word & 0xf000) == 0x5000) { - uint64_t wwwn; - - wwwn = *((uint16_t *) identify + 108); - wwwn <<= 16; - wwwn |= *((uint16_t *) identify + 109); - wwwn <<= 16; - wwwn |= *((uint16_t *) identify + 110); - wwwn <<= 16; - wwwn |= *((uint16_t *) identify + 111); - printf("ID_WWN=0x%llx\n", (unsigned long long int) wwwn); - /* ATA devices have no vendor extension */ - printf("ID_WWN_WITH_EXTENSION=0x%llx\n", (unsigned long long int) wwwn); - } + word = identify.wyde[108]; + if ((word & 0xf000) == 0x5000) + printf("ID_WWN=0x%1$"PRIu64"x\n" + "ID_WWN_WITH_EXTENSION=0x%1$"PRIu64"x\n", + identify.octa[108/4]); /* from Linux's include/linux/ata.h */ - if (identify_words[0] == 0x848a || identify_words[0] == 0x844a) { + if (identify.wyde[0] == 0x848a || + identify.wyde[0] == 0x844a || + (identify.wyde[83] & 0xc004) == 0x4004) printf("ID_ATA_CFA=1\n"); - } else { - if ((identify_words[83] & 0xc004) == 0x4004) { - printf("ID_ATA_CFA=1\n"); - } - } } else { if (serial[0] != '\0') printf("%s_%s\n", model, serial); else printf("%s\n", model); } -close: - close(fd); -exit: - udev_unref(udev); - log_close(); - return rc; + + return 0; } diff --git a/src/cdrom_id/cdrom_id.c b/src/cdrom_id/cdrom_id.c index 9b3ab7021e..7bafd62809 100644 --- a/src/cdrom_id/cdrom_id.c +++ b/src/cdrom_id/cdrom_id.c @@ -41,14 +41,6 @@ #include "libudev.h" #include "libudev-private.h" -_printf_(6,0) -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - log_metav(priority, file, line, fn, format, args); -} - /* device info */ static unsigned int cd_cd_rom; static unsigned int cd_cd_r; @@ -878,8 +870,6 @@ int main(int argc, char *argv[]) if (udev == NULL) goto exit; - udev_set_log_fn(udev, log_fn); - while (1) { int option; @@ -900,16 +890,15 @@ int main(int argc, char *argv[]) case 'd': log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); - udev_set_log_priority(udev, LOG_DEBUG); log_open(); break; case 'h': printf("Usage: cdrom_id [options] <device>\n" - " --lock-media lock the media (to enable eject request events)\n" - " --unlock-media unlock the media\n" - " --eject-media eject the media\n" - " --debug debug to stderr\n" - " --help print this help text\n\n"); + " -l,--lock-media lock the media (to enable eject request events)\n" + " -u,--unlock-media unlock the media\n" + " -e,--eject-media eject the media\n" + " -d,--debug debug to stderr\n" + " -h,--help print this help text\n\n"); goto exit; default: rc = 1; @@ -925,7 +914,7 @@ int main(int argc, char *argv[]) goto exit; } - srand((unsigned int)getpid()); + initialize_srand(); for (cnt = 20; cnt > 0; cnt--) { struct timespec duration; @@ -1077,17 +1066,17 @@ work: if (cd_media_state != NULL) printf("ID_CDROM_MEDIA_STATE=%s\n", cd_media_state); if (cd_media_session_next > 0) - printf("ID_CDROM_MEDIA_SESSION_NEXT=%d\n", cd_media_session_next); + printf("ID_CDROM_MEDIA_SESSION_NEXT=%u\n", cd_media_session_next); if (cd_media_session_count > 0) - printf("ID_CDROM_MEDIA_SESSION_COUNT=%d\n", cd_media_session_count); + printf("ID_CDROM_MEDIA_SESSION_COUNT=%u\n", cd_media_session_count); if (cd_media_session_count > 1 && cd_media_session_last_offset > 0) printf("ID_CDROM_MEDIA_SESSION_LAST_OFFSET=%llu\n", cd_media_session_last_offset); if (cd_media_track_count > 0) - printf("ID_CDROM_MEDIA_TRACK_COUNT=%d\n", cd_media_track_count); + printf("ID_CDROM_MEDIA_TRACK_COUNT=%u\n", cd_media_track_count); if (cd_media_track_count_audio > 0) - printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%d\n", cd_media_track_count_audio); + printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%u\n", cd_media_track_count_audio); if (cd_media_track_count_data > 0) - printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%d\n", cd_media_track_count_data); + printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%u\n", cd_media_track_count_data); exit: if (fd >= 0) close(fd); diff --git a/src/collect/collect.c b/src/collect/collect.c index 323d56a82d..f309f3b98e 100644 --- a/src/collect/collect.c +++ b/src/collect/collect.c @@ -72,15 +72,19 @@ noreturn static void sig_alrm(int signo) static void usage(void) { - printf("usage: collect [--add|--remove] [--debug] <checkpoint> <id> <idlist>\n" - "\n" + printf("%s [options] <checkpoint> <id> <idlist>\n\n" + "Collect variables across events.\n\n" + " -h --help Print this message\n" + " -a --add Add ID <id> to the list <idlist>\n" + " -r --remove Remove ID <id> from the list <idlist>\n" + " -d --debug Debug to stderr\n\n" " Adds ID <id> to the list governed by <checkpoint>.\n" " <id> must be part of the list <idlist>.\n" " If all IDs given by <idlist> are listed (ie collect has been\n" " invoked for each ID in <idlist>) collect returns 0, the\n" " number of missing IDs otherwise.\n" - " On error a negative number is returned.\n" - "\n"); + " On error a negative number is returned.\n\n" + , program_invocation_short_name); } /* @@ -90,12 +94,12 @@ static void usage(void) */ static int prepare(char *dir, char *filename) { - struct stat statbuf; char buf[512]; - int fd; + int r, fd; - if (stat(dir, &statbuf) < 0) - mkdir(dir, 0700); + r = mkdir(dir, 0700); + if (r < 0 && errno != EEXIST) + return -errno; snprintf(buf, sizeof(buf), "%s/%s", dir, filename); @@ -157,7 +161,7 @@ static int checkout(int fd) if (!ptr && word < (buf + len)) { bufsize = bufsize << 1; if (debug) - fprintf(stderr, "ID overflow, restarting with size %zi\n", bufsize); + fprintf(stderr, "ID overflow, restarting with size %zu\n", bufsize); free(buf); lseek(fd, 0, SEEK_SET); goto restart; @@ -258,7 +262,7 @@ static void reject(char *us) * kickout * * Remove all IDs in the internal list which are not part - * of the list passed via the commandline. + * of the list passed via the command line. */ static void kickout(void) { diff --git a/src/libudev/libudev-device-private.c b/src/libudev/libudev-device-private.c index 637d064819..fb4c6e2940 100644 --- a/src/libudev/libudev-device-private.c +++ b/src/libudev/libudev-device-private.c @@ -102,7 +102,6 @@ static bool device_has_info(struct udev_device *udev_device) int udev_device_update_db(struct udev_device *udev_device) { - struct udev *udev = udev_device_get_udev(udev_device); bool has_info; const char *id; char filename[UTIL_PATH_SIZE]; @@ -129,10 +128,8 @@ int udev_device_update_db(struct udev_device *udev_device) strscpyl(filename_tmp, sizeof(filename_tmp), filename, ".tmp", NULL); mkdir_parents(filename_tmp, 0755); f = fopen(filename_tmp, "we"); - if (f == NULL) { - udev_err(udev, "unable to create temporary db file '%s': %m\n", filename_tmp); - return -1; - } + if (f == NULL) + return log_debug_errno(errno, "unable to create temporary db file '%s': %m", filename_tmp); /* * set 'sticky' bit to indicate that we should not clean the @@ -172,7 +169,7 @@ int udev_device_update_db(struct udev_device *udev_device) r = rename(filename_tmp, filename); if (r < 0) return -1; - udev_dbg(udev, "created %s file '%s' for '%s'\n", has_info ? "db" : "empty", + log_debug("created %s file '%s' for '%s'", has_info ? "db" : "empty", filename, udev_device_get_devpath(udev_device)); return 0; } diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c index 2699374072..825a0e10f4 100644 --- a/src/libudev/libudev-device.c +++ b/src/libudev/libudev-device.c @@ -139,7 +139,7 @@ static int udev_device_set_ifindex(struct udev_device *udev_device, int ifindex) char num[32]; udev_device->ifindex = ifindex; - snprintf(num, sizeof(num), "%u", ifindex); + snprintf(num, sizeof(num), "%d", ifindex); udev_device_add_property(udev_device, "IFINDEX", num); return 0; } @@ -534,10 +534,8 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile) } f = fopen(dbfile, "re"); - if (f == NULL) { - udev_dbg(udev_device->udev, "no db file to read %s: %m\n", dbfile); - return -errno; - } + if (f == NULL) + return log_debug_errno(errno, "no db file to read %s: %m", dbfile); /* devices with a database entry are initialized */ udev_device->is_initialized = true; @@ -577,7 +575,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", udev_device); return 0; } @@ -642,7 +640,6 @@ 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) { errno = EINVAL; @@ -662,11 +659,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; } @@ -704,7 +697,7 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con /* path starts in sys */ if (!startswith(syspath, "/sys")) { - udev_dbg(udev, "not in sys :%s\n", syspath); + log_debug("not in sys :%s", syspath); errno = EINVAL; return NULL; } @@ -730,8 +723,13 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con return NULL; } else { /* everything else just needs to be a directory */ - if (stat(path, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) + if (stat(path, &statbuf) != 0) + return NULL; + + if (!S_ISDIR(statbuf.st_mode)) { + errno = EISDIR; return NULL; + } } udev_device = udev_device_new(udev); @@ -739,7 +737,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'", udev_device, udev_device_get_devpath(udev_device)); return udev_device; } @@ -971,7 +969,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"); udev_device_unref(udev_device); udev_device = NULL; } diff --git a/src/libudev/libudev-hwdb.c b/src/libudev/libudev-hwdb.c index 5582f71c68..d457604a07 100644 --- a/src/libudev/libudev-hwdb.c +++ b/src/libudev/libudev-hwdb.c @@ -277,38 +277,38 @@ _public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { hwdb->f = fopen(UDEV_HWDB_BIN, "re"); if (!hwdb->f) { - udev_dbg(udev, "error reading " UDEV_HWDB_BIN ": %m"); + log_debug(UDEV_HWDB_BIN "does not exist, please run udevadm hwdb --update"); udev_hwdb_unref(hwdb); return NULL; } if (fstat(fileno(hwdb->f), &hwdb->st) < 0 || (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) { - udev_dbg(udev, "error reading " UDEV_HWDB_BIN ": %m"); + log_debug_errno(errno, "error reading " UDEV_HWDB_BIN ": %m"); udev_hwdb_unref(hwdb); return NULL; } hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); if (hwdb->map == MAP_FAILED) { - udev_dbg(udev, "error mapping " UDEV_HWDB_BIN ": %m"); + log_debug_errno(errno, "error mapping " UDEV_HWDB_BIN ": %m"); udev_hwdb_unref(hwdb); return NULL; } if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) { - udev_dbg(udev, "error recognizing the format of " UDEV_HWDB_BIN); + log_debug("error recognizing the format of " UDEV_HWDB_BIN); udev_hwdb_unref(hwdb); return NULL; } - udev_dbg(udev, "=== trie on-disk ===\n"); - udev_dbg(udev, "tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); - udev_dbg(udev, "file size: %8"PRIu64" bytes\n", hwdb->st.st_size); - udev_dbg(udev, "header size %8"PRIu64" bytes\n", le64toh(hwdb->head->header_size)); - udev_dbg(udev, "strings %8"PRIu64" bytes\n", le64toh(hwdb->head->strings_len)); - udev_dbg(udev, "nodes %8"PRIu64" bytes\n", le64toh(hwdb->head->nodes_len)); + log_debug("=== trie on-disk ==="); + log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); + log_debug("file size: %8"PRIu64" bytes", hwdb->st.st_size); + log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size)); + log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len)); + log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len)); return hwdb; } @@ -360,6 +360,7 @@ bool udev_hwdb_validate(struct udev_hwdb *hwdb) { return false; if (stat("/etc/udev/hwdb.bin", &st) < 0) return true; + if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim)) return true; return false; diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c index 137ea148d7..ae1b95b0b3 100644 --- a/src/libudev/libudev-monitor.c +++ b/src/libudev/libudev-monitor.c @@ -121,7 +121,7 @@ static bool udev_has_devtmpfs(struct udev *udev) { r = name_to_handle_at(AT_FDCWD, "/dev", &h.handle, &mount_id, 0); if (r < 0) { if (errno != EOPNOTSUPP) - udev_err(udev, "name_to_handle_at on /dev: %m\n"); + log_debug_errno(errno, "name_to_handle_at on /dev: %m"); return false; } @@ -174,7 +174,7 @@ struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const c * will not receive any messages. */ if (access("/run/udev/control", F_OK) < 0 && !udev_has_devtmpfs(udev)) { - udev_dbg(udev, "the udev service seems not to be active, disable the monitor\n"); + log_debug("the udev service seems not to be active, disable the monitor"); group = UDEV_MONITOR_NONE; } else group = UDEV_MONITOR_UDEV; @@ -190,7 +190,7 @@ struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const c if (fd < 0) { udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); if (udev_monitor->sock == -1) { - udev_err(udev, "error getting socket: %m\n"); + log_debug_errno(errno, "error getting socket: %m"); free(udev_monitor); return NULL; } @@ -407,14 +407,14 @@ _public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) if (err == 0) udev_monitor->snl.nl.nl_pid = snl.nl.nl_pid; } else { - udev_err(udev_monitor->udev, "bind failed: %m\n"); + log_debug_errno(errno, "bind failed: %m"); return -errno; } /* enable receiving of sender credentials */ err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); if (err < 0) - udev_err(udev_monitor->udev, "setting SO_PASSCRED failed: %m\n"); + log_debug_errno(errno, "setting SO_PASSCRED failed: %m"); return 0; } @@ -582,7 +582,10 @@ _public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *ud struct cmsghdr *cmsg; union sockaddr_union snl; struct ucred *cred; - char buf[8192]; + union { + struct udev_monitor_netlink_header nlh; + char raw[8192]; + } buf; ssize_t buflen; ssize_t bufpos; @@ -602,12 +605,12 @@ retry: buflen = recvmsg(udev_monitor->sock, &smsg, 0); if (buflen < 0) { if (errno != EINTR) - udev_dbg(udev_monitor->udev, "unable to receive message\n"); + log_debug("unable to receive message"); return NULL; } if (buflen < 32 || (size_t)buflen >= sizeof(buf)) { - udev_dbg(udev_monitor->udev, "invalid message length\n"); + log_debug("invalid message length"); return NULL; } @@ -615,12 +618,12 @@ retry: /* unicast message, check if we trust the sender */ if (udev_monitor->snl_trusted_sender.nl.nl_pid == 0 || snl.nl.nl_pid != udev_monitor->snl_trusted_sender.nl.nl_pid) { - udev_dbg(udev_monitor->udev, "unicast netlink message ignored\n"); + log_debug("unicast netlink message ignored"); return NULL; } } else if (snl.nl.nl_groups == UDEV_MONITOR_KERNEL) { if (snl.nl.nl_pid > 0) { - udev_dbg(udev_monitor->udev, "multicast kernel netlink message from pid %d ignored\n", + log_debug("multicast kernel netlink message from pid %d ignored", snl.nl.nl_pid); return NULL; } @@ -628,13 +631,13 @@ retry: cmsg = CMSG_FIRSTHDR(&smsg); if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { - udev_dbg(udev_monitor->udev, "no sender credentials received, message ignored\n"); + log_debug("no sender credentials received, message ignored"); return NULL; } cred = (struct ucred *)CMSG_DATA(cmsg); if (cred->uid != 0) { - udev_dbg(udev_monitor->udev, "sender uid=%d, message ignored\n", cred->uid); + log_debug("sender uid=%d, message ignored", cred->uid); return NULL; } @@ -642,38 +645,35 @@ retry: if (udev_device == NULL) return NULL; - if (memcmp(buf, "libudev", 8) == 0) { - struct udev_monitor_netlink_header *nlh; - + if (memcmp(buf.raw, "libudev", 8) == 0) { /* udev message needs proper version magic */ - nlh = (struct udev_monitor_netlink_header *) buf; - if (nlh->magic != htonl(UDEV_MONITOR_MAGIC)) { - udev_err(udev_monitor->udev, "unrecognized message signature (%x != %x)\n", - nlh->magic, htonl(UDEV_MONITOR_MAGIC)); + if (buf.nlh.magic != htonl(UDEV_MONITOR_MAGIC)) { + log_debug("unrecognized message signature (%x != %x)", + buf.nlh.magic, htonl(UDEV_MONITOR_MAGIC)); udev_device_unref(udev_device); return NULL; } - if (nlh->properties_off+32 > (size_t)buflen) { + if (buf.nlh.properties_off+32 > (size_t)buflen) { udev_device_unref(udev_device); return NULL; } - bufpos = nlh->properties_off; + bufpos = buf.nlh.properties_off; /* devices received from udev are always initialized */ udev_device_set_is_initialized(udev_device); } else { /* kernel message with header */ - bufpos = strlen(buf) + 1; + bufpos = strlen(buf.raw) + 1; if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) { - udev_dbg(udev_monitor->udev, "invalid message length\n"); + log_debug("invalid message length"); udev_device_unref(udev_device); return NULL; } /* check message header */ - if (strstr(buf, "@/") == NULL) { - udev_dbg(udev_monitor->udev, "unrecognized message header\n"); + if (strstr(buf.raw, "@/") == NULL) { + log_debug("unrecognized message header"); udev_device_unref(udev_device); return NULL; } @@ -685,7 +685,7 @@ retry: char *key; size_t keylen; - key = &buf[bufpos]; + key = &buf.raw[bufpos]; keylen = strlen(key); if (keylen == 0) break; @@ -694,7 +694,7 @@ retry: } if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) { - udev_dbg(udev_monitor->udev, "missing values, invalid device\n"); + log_debug("missing values, invalid device"); udev_device_unref(udev_device); return NULL; } @@ -778,7 +778,7 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, smsg.msg_name = &udev_monitor->snl_destination; smsg.msg_namelen = sizeof(struct sockaddr_nl); count = sendmsg(udev_monitor->sock, &smsg, 0); - udev_dbg(udev_monitor->udev, "passed %zi bytes to netlink monitor %p\n", count, udev_monitor); + log_debug("passed %zi bytes to netlink monitor %p", count, udev_monitor); return count; } diff --git a/src/libudev/libudev-private.h b/src/libudev/libudev-private.h index 09e786c320..64f132f91d 100644 --- a/src/libudev/libudev-private.h +++ b/src/libudev/libudev-private.h @@ -26,7 +26,6 @@ #include "libudev.h" #include "macro.h" -#include "time-util.h" #include "util.h" #include "mkdir.h" #include "strxcpyx.h" @@ -34,24 +33,8 @@ #define READ_END 0 #define WRITE_END 1 -/* avoid (sometimes expensive) calculations of parameters for debug output */ -#define udev_log_cond(udev, prio, arg...) \ - do { \ - if (udev_get_log_priority(udev) >= prio) \ - udev_log(udev, prio, __FILE__, __LINE__, __FUNCTION__, ## arg); \ - } while (0) - -#define udev_dbg(udev, arg...) udev_log_cond(udev, LOG_DEBUG, ## arg) -#define udev_info(udev, arg...) udev_log_cond(udev, LOG_INFO, ## arg) -#define udev_err(udev, arg...) udev_log_cond(udev, LOG_ERR, ## arg) - /* libudev.c */ -void udev_log(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, ...) _printf_(6, 7); int udev_get_rules_path(struct udev *udev, char **path[], usec_t *ts_usec[]); -struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value); -struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev); /* libudev-device.c */ struct udev_device *udev_device_new(struct udev *udev); diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c index 00dc6e157e..e626aad53e 100644 --- a/src/libudev/libudev-util.c +++ b/src/libudev/libudev-util.c @@ -96,7 +96,7 @@ int util_resolve_subsys_kernel(struct udev *udev, const char *string, strscpy(result, maxsize, val); else result[0] = '\0'; - udev_dbg(udev, "value '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result); + log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); } else { size_t l; char *s; @@ -105,7 +105,7 @@ int util_resolve_subsys_kernel(struct udev *udev, const char *string, l = strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL); if (attr != NULL) strpcpyl(&s, l, "/", attr, NULL); - udev_dbg(udev, "path '[%s/%s]%s' is '%s'\n", subsys, sysname, attr, result); + log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); } udev_device_unref(dev); return 0; @@ -162,9 +162,13 @@ int util_log_priority(const char *priority) char *endptr; int prio; - prio = strtol(priority, &endptr, 10); - if (endptr[0] == '\0' || isspace(endptr[0])) - return prio; + prio = strtoul(priority, &endptr, 10); + if (endptr[0] == '\0' || isspace(endptr[0])) { + if (prio >= 0 && prio <= 7) + return prio; + else + return -ERANGE; + } return log_level_from_string(priority); } diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c index 4373ab4d54..e656b352c6 100644 --- a/src/libudev/libudev.c +++ b/src/libudev/libudev.c @@ -1,7 +1,7 @@ /*** This file is part of systemd. - Copyright 2008-2012 Kay Sievers <kay@vrfy.org> + Copyright 2008-2014 Kay Sievers <kay@vrfy.org> 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 @@ -50,41 +50,18 @@ struct udev { int priority, const char *file, int line, const char *fn, const char *format, va_list args); void *userdata; - struct udev_list properties_list; - int log_priority; }; -void udev_log(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, ...) -{ - va_list args; - - va_start(args, format); - udev->log_fn(udev, priority, file, line, fn, format, args); - va_end(args); -} - -_printf_(6,0) -static void log_stderr(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args) -{ - fprintf(stderr, "libudev: %s: ", fn); - vfprintf(stderr, format, args); -} - /** * udev_get_userdata: * @udev: udev library context * * Retrieve stored data pointer from library context. This might be useful - * to access from callbacks like a custom logging function. + * to access from callbacks. * * Returns: stored userdata **/ -_public_ void *udev_get_userdata(struct udev *udev) -{ +_public_ void *udev_get_userdata(struct udev *udev) { if (udev == NULL) return NULL; return udev->userdata; @@ -97,8 +74,7 @@ _public_ void *udev_get_userdata(struct udev *udev) * * Store custom @userdata in the library context. **/ -_public_ void udev_set_userdata(struct udev *udev, void *userdata) -{ +_public_ void udev_set_userdata(struct udev *udev, void *userdata) { if (udev == NULL) return; udev->userdata = userdata; @@ -115,23 +91,19 @@ _public_ void udev_set_userdata(struct udev *udev, void *userdata) * * Returns: a new udev library context **/ -_public_ struct udev *udev_new(void) -{ +_public_ struct udev *udev_new(void) { struct udev *udev; - FILE *f; + _cleanup_fclose_ FILE *f = NULL; udev = new0(struct udev, 1); if (udev == NULL) return NULL; udev->refcount = 1; - udev->log_fn = log_stderr; - udev->log_priority = LOG_INFO; - udev_list_init(udev, &udev->properties_list, true); f = fopen( UDEV_CONF_FILE, "re"); if (f != NULL) { char line[UTIL_LINE_SIZE]; - int line_nr = 0; + unsigned line_nr = 0; while (fgets(line, sizeof(line), f)) { size_t len; @@ -152,7 +124,7 @@ _public_ struct udev *udev_new(void) /* split key/value */ val = strchr(key, '='); if (val == NULL) { - udev_err(udev, "missing <key>=<value> in " UDEV_CONF_FILE "[%i]; skip line\n", line_nr); + log_debug(UDEV_CONF_FILE ":%u: missing assignment, skipping line.", line_nr); continue; } val[0] = '\0'; @@ -184,7 +156,7 @@ _public_ struct udev *udev_new(void) /* unquote */ if (val[0] == '"' || val[0] == '\'') { if (val[len-1] != val[0]) { - udev_err(udev, "inconsistent quoting in " UDEV_CONF_FILE"[%i]; skip line\n", line_nr); + log_debug(UDEV_CONF_FILE ":%u: inconsistent quoting, skipping line.", line_nr); continue; } val[len-1] = '\0'; @@ -192,11 +164,16 @@ _public_ struct udev *udev_new(void) } if (streq(key, "udev_log")) { - udev_set_log_priority(udev, util_log_priority(val)); + int prio; + + prio = util_log_priority(val); + if (prio < 0) + log_debug("/etc/udev/udev.conf:%u: invalid log level '%s', ignoring.", line_nr, val); + else + log_set_max_level(prio); continue; } } - fclose(f); } return udev; @@ -210,8 +187,7 @@ _public_ struct udev *udev_new(void) * * Returns: the passed udev library context **/ -_public_ struct udev *udev_ref(struct udev *udev) -{ +_public_ struct udev *udev_ref(struct udev *udev) { if (udev == NULL) return NULL; udev->refcount++; @@ -227,14 +203,12 @@ _public_ struct udev *udev_ref(struct udev *udev) * * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise. **/ -_public_ struct udev *udev_unref(struct udev *udev) -{ +_public_ struct udev *udev_unref(struct udev *udev) { if (udev == NULL) return NULL; udev->refcount--; if (udev->refcount > 0) return udev; - udev_list_cleanup(&udev->properties_list); free(udev); return NULL; } @@ -242,68 +216,37 @@ _public_ struct udev *udev_unref(struct udev *udev) /** * udev_set_log_fn: * @udev: udev library context - * @log_fn: function to be called for logging messages + * @log_fn: function to be called for log messages * - * The built-in logging writes to stderr. It can be - * overridden by a custom function, to plug log messages - * into the users' logging functionality. + * This function is deprecated. * **/ _public_ void udev_set_log_fn(struct udev *udev, void (*log_fn)(struct udev *udev, int priority, const char *file, int line, const char *fn, - const char *format, va_list args)) -{ - udev->log_fn = log_fn; - udev_dbg(udev, "custom logging function %p registered\n", log_fn); + const char *format, va_list args)) { + return; } /** * udev_get_log_priority: * @udev: udev library context * - * The initial logging priority is read from the udev config file - * at startup. + * This function is deprecated. * - * Returns: the current logging priority **/ -_public_ int udev_get_log_priority(struct udev *udev) -{ - return udev->log_priority; +_public_ int udev_get_log_priority(struct udev *udev) { + return log_get_max_level(); } /** * udev_set_log_priority: * @udev: udev library context - * @priority: the new logging priority + * @priority: the new log priority + * + * This function is deprecated. * - * Set the current logging priority. The value controls which messages - * are logged. **/ -_public_ void udev_set_log_priority(struct udev *udev, int priority) -{ - char num[32]; - - udev->log_priority = priority; - snprintf(num, sizeof(num), "%u", udev->log_priority); - udev_add_property(udev, "UDEV_LOG", num); -} - -struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value) -{ - if (value == NULL) { - struct udev_list_entry *list_entry; - - list_entry = udev_get_properties_list_entry(udev); - list_entry = udev_list_entry_get_by_name(list_entry, key); - if (list_entry != NULL) - udev_list_entry_delete(list_entry); - return NULL; - } - return udev_list_entry_add(&udev->properties_list, key, value); -} - -struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev) -{ - return udev_list_get_entry(&udev->properties_list); +_public_ void udev_set_log_priority(struct udev *udev, int priority) { + log_set_max_level(priority); } diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h index 4f2f11502c..a94505c09e 100644 --- a/src/libudev/libudev.h +++ b/src/libudev/libudev.h @@ -41,9 +41,9 @@ struct udev *udev_new(void); void udev_set_log_fn(struct udev *udev, void (*log_fn)(struct udev *udev, int priority, const char *file, int line, const char *fn, - const char *format, va_list args)); -int udev_get_log_priority(struct udev *udev); -void udev_set_log_priority(struct udev *udev, int priority); + const char *format, va_list args)) __attribute__ ((deprecated)); +int udev_get_log_priority(struct udev *udev) __attribute__ ((deprecated)); +void udev_set_log_priority(struct udev *udev, int priority) __attribute__ ((deprecated)); void *udev_get_userdata(struct udev *udev); void udev_set_userdata(struct udev *udev, void *userdata); diff --git a/src/scsi_id/scsi_id.c b/src/scsi_id/scsi_id.c index ad190552f7..8b76d87fbe 100644 --- a/src/scsi_id/scsi_id.c +++ b/src/scsi_id/scsi_id.c @@ -67,14 +67,6 @@ static char model_enc_str[256]; static char revision_str[16]; static char type_str[16]; -_printf_(6,0) -static void log_fn(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) -{ - log_metav(priority, file, line, fn, format, args); -} - static void set_type(const char *from, char *to, size_t len) { int type_num; @@ -186,7 +178,7 @@ static int get_file_options(struct udev *udev, if (errno == ENOENT) return 1; else { - log_error("can't open %s: %m", config_file); + log_error_errno(errno, "can't open %s: %m", config_file); return -1; } } @@ -316,18 +308,20 @@ static int get_file_options(struct udev *udev, } static void help(void) { - printf("Usage: scsi_id [OPTION...] DEVICE\n" - " -d,--device= device node for SG_IO commands\n" - " -f,--config= location of config file\n" - " -p,--page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n" - " -s,--sg-version=3|4 use SGv3 or SGv4\n" - " -b,--blacklisted threat device as blacklisted\n" - " -g,--whitelisted threat device as whitelisted\n" - " -u,--replace-whitespace replace all whitespace by underscores\n" - " -v,--verbose verbose logging\n" - " --version print version\n" - " -x,--export print values as environment keys\n" - " -h,--help print this help text\n\n"); + printf("Usage: %s [OPTION...] DEVICE\n\n" + "SCSI device identification.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n\n" + " -d --device= Device node for SG_IO commands\n" + " -f --config= Location of config file\n" + " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n" + " -s --sg-version=3|4 Use SGv3 or SGv4\n" + " -b --blacklisted Treat device as blacklisted\n" + " -g --whitelisted Treat device as whitelisted\n" + " -u --replace-whitespace Replace all whitespace by underscores\n" + " -v --verbose Verbose logging\n" + " -x --export Print values as environment keys\n" + , program_invocation_short_name); } @@ -394,7 +388,6 @@ static int set_options(struct udev *udev, case 'v': log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); - udev_set_log_priority(udev, LOG_DEBUG); log_open(); break; @@ -594,8 +587,6 @@ int main(int argc, char **argv) if (udev == NULL) goto exit; - udev_set_log_fn(udev, log_fn); - /* * Get config file options. */ @@ -620,7 +611,7 @@ int main(int argc, char **argv) exit(1); if (!dev_specified) { - log_error("no device specified"); + log_error("No device specified."); retval = 1; goto exit; } diff --git a/src/scsi_id/scsi_serial.c b/src/scsi_id/scsi_serial.c index 1806498503..37081adc1f 100644 --- a/src/scsi_id/scsi_serial.c +++ b/src/scsi_id/scsi_serial.c @@ -365,7 +365,7 @@ resend: dev_scsi->use_sg = 3; goto resend; } - log_debug("%s: ioctl failed: %m", dev_scsi->kernel); + log_debug_errno(errno, "%s: ioctl failed: %m", dev_scsi->kernel); goto error; } @@ -823,12 +823,12 @@ int scsi_std_inquiry(struct udev *udev, fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { - log_debug("scsi_id: cannot open %s: %m", devname); + log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname); return 1; } if (fstat(fd, &statbuf) < 0) { - log_debug("scsi_id: cannot stat %s: %m", devname); + log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname); err = 2; goto out; } @@ -865,7 +865,7 @@ int scsi_get_serial(struct udev *udev, int retval; memzero(dev_scsi->serial, len); - srand((unsigned int)getpid()); + initialize_srand(); for (cnt = 20; cnt > 0; cnt--) { struct timespec duration; diff --git a/src/shared/Makefile.am b/src/shared/Makefile.am index 3862d31910..bb695f8720 100644 --- a/src/shared/Makefile.am +++ b/src/shared/Makefile.am @@ -19,7 +19,6 @@ libudev_shared_la_SOURCES=\ MurmurHash2.c \ path-util.c \ selinux-util.c \ - set.c \ siphash24.c \ smack-util.c \ strbuf.c \ @@ -41,6 +40,7 @@ noinst_HEADERS = \ hashmap.h \ ioprio.h \ label.h \ + list.h \ log.h \ macro.h \ mempool.h \ diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c index 3f9018cf1f..f1dad13656 100644 --- a/src/shared/cgroup-util.c +++ b/src/shared/cgroup-util.c @@ -118,7 +118,7 @@ int cg_kill(const char *controller, const char *path, int sig, bool sigcont, boo if (ret >= 0 && errno != ESRCH) ret = -errno; } else { - if (sigcont) + if (sigcont && sig != SIGKILL) kill(pid, SIGCONT); if (ret == 0) diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c index fa4edf11c6..9cfdc60930 100644 --- a/src/shared/hashmap.c +++ b/src/shared/hashmap.c @@ -1,7 +1,10 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + /*** - This file is part of eudev, forked from systemd. + This file is part of systemd. Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt 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 @@ -24,38 +27,250 @@ #include "util.h" #include "hashmap.h" +#include "set.h" #include "macro.h" #include "siphash24.h" +#include "strv.h" +#include "list.h" #include "mempool.h" -#define INITIAL_N_BUCKETS 31 - -struct hashmap_entry { +/* + * Implementation of hashmaps. + * Addressing: open + * - uses less RAM compared to closed addressing (chaining), because + * our entries are small (especially in Sets, which tend to contain + * the majority of entries in systemd). + * Collision resolution: Robin Hood + * - tends to equalize displacement of entries from their optimal buckets. + * Probe sequence: linear + * - though theoretically worse than random probing/uniform hashing/double + * hashing, it is good for cache locality. + * + * References: + * Celis, P. 1986. Robin Hood Hashing. + * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada. + * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf + * - The results are derived for random probing. Suggests deletion with + * tombstones and two mean-centered search methods. None of that works + * well for linear probing. + * + * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies. + * ACM Trans. Algorithms 1, 2 (October 2005), 177-213. + * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964 + * http://www.math.uu.se/~svante/papers/sj157.pdf + * - Applies to Robin Hood with linear probing. Contains remarks on + * the unsuitability of mean-centered search with linear probing. + * + * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing. + * ACM Trans. Algorithms 1, 2 (October 2005), 214-242. + * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965 + * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes + * in a successful search), and Janson writes about displacement. C = d + 1. + * + * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion. + * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ + * - Explanation of backward shift deletion with pictures. + * + * Khuong, P. 2013. The Other Robin Hood Hashing. + * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/ + * - Short summary of random vs. linear probing, and tombstones vs. backward shift. + */ + +/* + * XXX Ideas for improvement: + * For unordered hashmaps, randomize iteration order, similarly to Perl: + * http://blog.booking.com/hardening-perls-hash-function.html + */ + +/* INV_KEEP_FREE = 1 / (1 - max_load_factor) + * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */ +#define INV_KEEP_FREE 5U + +/* Fields common to entries of all hashmap/set types */ +struct hashmap_base_entry { const void *key; +}; + +/* Entry types for specific hashmap/set types + * hashmap_base_entry must be at the beginning of each entry struct. */ + +struct plain_hashmap_entry { + struct hashmap_base_entry b; void *value; - struct hashmap_entry *bucket_next, *bucket_previous; - struct hashmap_entry *iterate_next, *iterate_previous; }; -struct Hashmap { - const struct hash_ops *hash_ops; +struct ordered_hashmap_entry { + struct plain_hashmap_entry p; + unsigned iterate_next, iterate_previous; +}; + +struct set_entry { + struct hashmap_base_entry b; +}; + +/* In several functions it is advantageous to have the hash table extended + * virtually by a couple of additional buckets. We reserve special index values + * for these "swap" buckets. */ +#define _IDX_SWAP_BEGIN (UINT_MAX - 3) +#define IDX_PUT (_IDX_SWAP_BEGIN + 0) +#define IDX_TMP (_IDX_SWAP_BEGIN + 1) +#define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2) + +#define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */ +#define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */ + +assert_cc(IDX_FIRST == _IDX_SWAP_END); +assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); + +/* Storage space for the "swap" buckets. + * All entry types can fit into a ordered_hashmap_entry. */ +struct swap_entries { + struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; +}; + +/* Distance from Initial Bucket */ +typedef uint8_t dib_raw_t; +#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */ +#define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */ +#define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */ +#define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */ + +#define DIB_FREE UINT_MAX + +#ifdef ENABLE_DEBUG_HASHMAP +struct hashmap_debug_info { + LIST_FIELDS(struct hashmap_debug_info, debug_list); + unsigned max_entries; /* high watermark of n_entries */ + + /* who allocated this hashmap */ + int line; + const char *file; + const char *func; + + /* fields to detect modification while iterating */ + unsigned put_count; /* counts puts into the hashmap */ + unsigned rem_count; /* counts removals from hashmap */ + unsigned last_rem_idx; /* remembers last removal index */ +}; - struct hashmap_entry *iterate_list_head, *iterate_list_tail; +/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */ +static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list); + +#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug; + +#else /* !ENABLE_DEBUG_HASHMAP */ +#define HASHMAP_DEBUG_FIELDS +#endif /* ENABLE_DEBUG_HASHMAP */ + +enum HashmapType { + HASHMAP_TYPE_PLAIN, + HASHMAP_TYPE_ORDERED, + HASHMAP_TYPE_SET, + _HASHMAP_TYPE_MAX +}; + +struct _packed_ indirect_storage { + char *storage; /* where buckets and DIBs are stored */ + uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */ + + unsigned n_entries; /* number of stored entries */ + unsigned n_buckets; /* number of buckets */ + + unsigned idx_lowest_entry; /* Index below which all buckets are free. + Makes "while(hashmap_steal_first())" loops + O(n) instead of O(n^2) for unordered hashmaps. */ + uint8_t _pad[3]; /* padding for the whole HashmapBase */ + /* The bitfields in HashmapBase complete the alignment of the whole thing. */ +}; + +struct direct_storage { + /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. + * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, + * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ + char storage[sizeof(struct indirect_storage)]; +}; + +#define DIRECT_BUCKETS(entry_t) \ + (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t))) + +/* We should be able to store at least one entry directly. */ +assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1); + +/* We have 3 bits for n_direct_entries. */ +assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3)); + +/* Hashmaps with directly stored entries all use this shared hash key. + * It's no big deal if the key is guessed, because there can be only + * a handful of directly stored entries in a hashmap. When a hashmap + * outgrows direct storage, it gets its own key for indirect storage. */ +static uint8_t shared_hash_key[HASH_KEY_SIZE]; +static bool shared_hash_key_initialized; + +/* Fields that all hashmap/set types must have */ +struct HashmapBase { + const struct hash_ops *hash_ops; /* hash and compare ops to use */ + + union _packed_ { + struct indirect_storage indirect; /* if has_indirect */ + struct direct_storage direct; /* if !has_indirect */ + }; + + enum HashmapType type:2; /* HASHMAP_TYPE_* */ + bool has_indirect:1; /* whether indirect storage is used */ + unsigned n_direct_entries:3; /* Number of entries in direct storage. + * Only valid if !has_indirect. */ + bool from_pool:1; /* whether was allocated from mempool */ + HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */ +}; + +/* Specific hash types + * HashmapBase must be at the beginning of each hashmap struct. */ + +struct Hashmap { + struct HashmapBase b; +}; - struct hashmap_entry ** buckets; - unsigned n_buckets, n_entries; +struct OrderedHashmap { + struct HashmapBase b; + unsigned iterate_list_head, iterate_list_tail; +}; - uint8_t hash_key[HASH_KEY_SIZE]; - bool from_pool:1; +struct Set { + struct HashmapBase b; }; -struct hashmap_tile { - Hashmap h; - struct hashmap_entry *initial_buckets[INITIAL_N_BUCKETS]; +DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8); +DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8); +/* No need for a separate Set pool */ +assert_cc(sizeof(Hashmap) == sizeof(Set)); + +struct hashmap_type_info { + size_t head_size; + size_t entry_size; + struct mempool *mempool; + unsigned n_direct_buckets; }; -static DEFINE_MEMPOOL(hashmap_pool, struct hashmap_tile, 8); -static DEFINE_MEMPOOL(hashmap_entry_pool, struct hashmap_entry, 64); +static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { + [HASHMAP_TYPE_PLAIN] = { + .head_size = sizeof(Hashmap), + .entry_size = sizeof(struct plain_hashmap_entry), + .mempool = &hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry), + }, + [HASHMAP_TYPE_ORDERED] = { + .head_size = sizeof(OrderedHashmap), + .entry_size = sizeof(struct ordered_hashmap_entry), + .mempool = &ordered_hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry), + }, + [HASHMAP_TYPE_SET] = { + .head_size = sizeof(Set), + .entry_size = sizeof(struct set_entry), + .mempool = &hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct set_entry), + }, +}; unsigned long string_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) { uint64_t u; @@ -87,9 +302,82 @@ const struct hash_ops trivial_hash_ops = { .compare = trivial_compare_func }; -static unsigned bucket_hash(Hashmap *h, const void *p) { - return (unsigned) (h->hash_ops->hash(p, h->hash_key) % h->n_buckets); +unsigned long uint64_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) { + uint64_t u; + siphash24((uint8_t*) &u, p, sizeof(uint64_t), hash_key); + return (unsigned long) u; +} + +int uint64_compare_func(const void *_a, const void *_b) { + uint64_t a, b; + a = *(const uint64_t*) _a; + b = *(const uint64_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops uint64_hash_ops = { + .hash = uint64_hash_func, + .compare = uint64_compare_func +}; + +#if SIZEOF_DEV_T != 8 +unsigned long devt_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) { + uint64_t u; + siphash24((uint8_t*) &u, p, sizeof(dev_t), hash_key); + return (unsigned long) u; +} + +int devt_compare_func(const void *_a, const void *_b) { + dev_t a, b; + a = *(const dev_t*) _a; + b = *(const dev_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#endif + +static unsigned n_buckets(HashmapBase *h) { + return h->has_indirect ? h->indirect.n_buckets + : hashmap_type_info[h->type].n_direct_buckets; +} + +static unsigned n_entries(HashmapBase *h) { + return h->has_indirect ? h->indirect.n_entries + : h->n_direct_entries; +} + +static void n_entries_inc(HashmapBase *h) { + if (h->has_indirect) + h->indirect.n_entries++; + else + h->n_direct_entries++; +} + +static void n_entries_dec(HashmapBase *h) { + if (h->has_indirect) + h->indirect.n_entries--; + else + h->n_direct_entries--; +} + +static char *storage_ptr(HashmapBase *h) { + return h->has_indirect ? h->indirect.storage + : h->direct.storage; +} + +static uint8_t *hash_key(HashmapBase *h) { + return h->has_indirect ? h->indirect.hash_key + : shared_hash_key; +} + +static unsigned base_bucket_hash(HashmapBase *h, const void *p) { + return (unsigned) (h->hash_ops->hash(p, hash_key(h)) % n_buckets(h)); } +#define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { static uint8_t current[HASH_KEY_SIZE]; @@ -110,131 +398,484 @@ static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { memcpy(hash_key, current, sizeof(current)); } -Hashmap *hashmap_new(const struct hash_ops *hash_ops) { - bool b; - struct hashmap_tile *ht; - Hashmap *h; +static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) { + return (struct hashmap_base_entry*) + (storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); +} + +static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) { + return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) { + return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct set_entry *set_bucket_at(Set *h, unsigned idx) { + return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) { + return &swap->e[idx - _IDX_SWAP_BEGIN]; +} - b = is_main_thread(); +/* Returns a pointer to the bucket at index idx. + * Understands real indexes and swap indexes, hence "_virtual". */ +static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap, + unsigned idx) { + if (idx < _IDX_SWAP_BEGIN) + return bucket_at(h, idx); - if (b) { - ht = mempool_alloc_tile(&hashmap_pool); - if (!ht) - return NULL; + if (idx < _IDX_SWAP_END) + return &bucket_at_swap(swap, idx)->p.b; - memzero(ht, sizeof(struct hashmap_tile)); - } else { - ht = malloc0(sizeof(struct hashmap_tile)); + assert_not_reached("Invalid index"); +} + +static dib_raw_t *dib_raw_ptr(HashmapBase *h) { + return (dib_raw_t*) + (storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); +} + +static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) { + return idx >= from ? idx - from + : n_buckets(h) + idx - from; +} + +static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) { + unsigned initial_bucket; + + if (raw_dib == DIB_RAW_FREE) + return DIB_FREE; + + if (_likely_(raw_dib < DIB_RAW_OVERFLOW)) + return raw_dib; + + /* + * Having an overflow DIB value is very unlikely. The hash function + * would have to be bad. For example, in a table of size 2^24 filled + * to load factor 0.9 the maximum observed DIB is only about 60. + * In theory (assuming I used Maxima correctly), for an infinite size + * hash table with load factor 0.8 the probability of a given entry + * having DIB > 40 is 1.9e-8. + * This returns the correct DIB value by recomputing the hash value in + * the unlikely case. XXX Hitting this case could be a hint to rehash. + */ + initial_bucket = bucket_hash(h, bucket_at(h, idx)->key); + return bucket_distance(h, idx, initial_bucket); +} + +static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) { + dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE; +} + +static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { + dib_raw_t *dibs; + + dibs = dib_raw_ptr(h); + + for ( ; idx < n_buckets(h); idx++) + if (dibs[idx] != DIB_RAW_FREE) + return idx; + + return IDX_NIL; +} + +static void bucket_mark_free(HashmapBase *h, unsigned idx) { + memset(bucket_at(h, idx), 0, hashmap_type_info[h->type].entry_size); + bucket_set_dib(h, idx, DIB_FREE); +} - if (!ht) - return NULL; +static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, + unsigned from, unsigned to) { + struct hashmap_base_entry *e_from, *e_to; + + assert(from != to); + + e_from = bucket_at_virtual(h, swap, from); + e_to = bucket_at_virtual(h, swap, to); + + memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + struct ordered_hashmap_entry *le, *le_to; + + le_to = (struct ordered_hashmap_entry*) e_to; + + if (le_to->iterate_next != IDX_NIL) { + le = (struct ordered_hashmap_entry*) + bucket_at_virtual(h, swap, le_to->iterate_next); + le->iterate_previous = to; + } + + if (le_to->iterate_previous != IDX_NIL) { + le = (struct ordered_hashmap_entry*) + bucket_at_virtual(h, swap, le_to->iterate_previous); + le->iterate_next = to; + } + + if (lh->iterate_list_head == from) + lh->iterate_list_head = to; + if (lh->iterate_list_tail == from) + lh->iterate_list_tail = to; } +} - h = &ht->h; - h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; +static unsigned next_idx(HashmapBase *h, unsigned idx) { + return (idx + 1U) % n_buckets(h); +} - h->n_buckets = INITIAL_N_BUCKETS; - h->n_entries = 0; - h->iterate_list_head = h->iterate_list_tail = NULL; +static unsigned prev_idx(HashmapBase *h, unsigned idx) { + return (n_buckets(h) + idx - 1U) % n_buckets(h); +} - h->buckets = ht->initial_buckets; +static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) { + switch (h->type) { - h->from_pool = b; + case HASHMAP_TYPE_PLAIN: + case HASHMAP_TYPE_ORDERED: + return ((struct plain_hashmap_entry*)e)->value; - get_hash_key(h->hash_key, true); + case HASHMAP_TYPE_SET: + return (void*) e->key; - return h; + default: + assert_not_reached("Unknown hashmap type"); + } } -static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { +static void base_remove_entry(HashmapBase *h, unsigned idx) { + unsigned left, right, prev, dib; + dib_raw_t raw_dib, *dibs; + + dibs = dib_raw_ptr(h); + assert(dibs[idx] != DIB_RAW_FREE); + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.rem_count++; + h->debug.last_rem_idx = idx; +#endif + + left = idx; + /* Find the stop bucket ("right"). It is either free or has DIB == 0. */ + for (right = next_idx(h, left); ; right = next_idx(h, right)) { + raw_dib = dibs[right]; + if (raw_dib == 0 || raw_dib == DIB_RAW_FREE) + break; + + /* The buckets are not supposed to be all occupied and with DIB > 0. + * That would mean we could make everyone better off by shifting them + * backward. This scenario is impossible. */ + assert(left != right); + } + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx); + + if (le->iterate_next != IDX_NIL) + ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous; + else + lh->iterate_list_tail = le->iterate_previous; + + if (le->iterate_previous != IDX_NIL) + ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next; + else + lh->iterate_list_head = le->iterate_next; + } + + /* Now shift all buckets in the interval (left, right) one step backwards */ + for (prev = left, left = next_idx(h, left); left != right; + prev = left, left = next_idx(h, left)) { + dib = bucket_calculate_dib(h, left, dibs[left]); + assert(dib != 0); + bucket_move_entry(h, NULL, left, prev); + bucket_set_dib(h, prev, dib - 1); + } + + bucket_mark_free(h, prev); + n_entries_dec(h); +} +#define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx) + +static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) { + struct ordered_hashmap_entry *e; + unsigned idx; + assert(h); - assert(e); - - /* Insert into hash table */ - e->bucket_next = h->buckets[hash]; - e->bucket_previous = NULL; - if (h->buckets[hash]) - h->buckets[hash]->bucket_previous = e; - h->buckets[hash] = e; - - /* Insert into iteration list */ - e->iterate_previous = h->iterate_list_tail; - e->iterate_next = NULL; - if (h->iterate_list_tail) { - assert(h->iterate_list_head); - h->iterate_list_tail->iterate_next = e; + assert(i); + + if (i->idx == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST) { + idx = h->iterate_list_head; + e = ordered_bucket_at(h, idx); } else { - assert(!h->iterate_list_head); - h->iterate_list_head = e; + idx = i->idx; + e = ordered_bucket_at(h, idx); + /* + * We allow removing the current entry while iterating, but removal may cause + * a backward shift. The next entry may thus move one bucket to the left. + * To detect when it happens, we remember the key pointer of the entry we were + * going to iterate next. If it does not match, there was a backward shift. + */ + if (e->p.b.key != i->next_key) { + idx = prev_idx(HASHMAP_BASE(h), idx); + e = ordered_bucket_at(h, idx); + } + assert(e->p.b.key == i->next_key); } - h->iterate_list_tail = e; - h->n_entries++; - assert(h->n_entries >= 1); +#ifdef ENABLE_DEBUG_HASHMAP + i->prev_idx = idx; +#endif + + if (e->iterate_next != IDX_NIL) { + struct ordered_hashmap_entry *n; + i->idx = e->iterate_next; + n = ordered_bucket_at(h, i->idx); + i->next_key = n->p.b.key; + } else + i->idx = IDX_NIL; + + return idx; + +at_end: + i->idx = IDX_NIL; + return IDX_NIL; } -static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { +static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) { + unsigned idx; + assert(h); - assert(e); + assert(i); - /* Remove from iteration list */ - if (e->iterate_next) - e->iterate_next->iterate_previous = e->iterate_previous; - else - h->iterate_list_tail = e->iterate_previous; + if (i->idx == IDX_NIL) + goto at_end; - if (e->iterate_previous) - e->iterate_previous->iterate_next = e->iterate_next; - else - h->iterate_list_head = e->iterate_next; + if (i->idx == IDX_FIRST) { + /* fast forward to the first occupied bucket */ + if (h->has_indirect) { + i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry); + h->indirect.idx_lowest_entry = i->idx; + } else + i->idx = skip_free_buckets(h, 0); + + if (i->idx == IDX_NIL) + goto at_end; + } else { + struct hashmap_base_entry *e; + + assert(i->idx > 0); - /* Remove from hash table bucket list */ - if (e->bucket_next) - e->bucket_next->bucket_previous = e->bucket_previous; + e = bucket_at(h, i->idx); + /* + * We allow removing the current entry while iterating, but removal may cause + * a backward shift. The next entry may thus move one bucket to the left. + * To detect when it happens, we remember the key pointer of the entry we were + * going to iterate next. If it does not match, there was a backward shift. + */ + if (e->key != i->next_key) + e = bucket_at(h, --i->idx); + + assert(e->key == i->next_key); + } - if (e->bucket_previous) - e->bucket_previous->bucket_next = e->bucket_next; + idx = i->idx; +#ifdef ENABLE_DEBUG_HASHMAP + i->prev_idx = idx; +#endif + + i->idx = skip_free_buckets(h, i->idx + 1); + if (i->idx != IDX_NIL) + i->next_key = bucket_at(h, i->idx)->key; else - h->buckets[hash] = e->bucket_next; + i->idx = IDX_NIL; + + return idx; - assert(h->n_entries >= 1); - h->n_entries--; +at_end: + i->idx = IDX_NIL; + return IDX_NIL; } -static void remove_entry(Hashmap *h, struct hashmap_entry *e) { - unsigned hash; +static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) { + if (!h) { + i->idx = IDX_NIL; + return IDX_NIL; + } - assert(h); - assert(e); +#ifdef ENABLE_DEBUG_HASHMAP + if (i->idx == IDX_FIRST) { + i->put_count = h->debug.put_count; + i->rem_count = h->debug.rem_count; + } else { + /* While iterating, must not add any new entries */ + assert(i->put_count == h->debug.put_count); + /* ... or remove entries other than the current one */ + assert(i->rem_count == h->debug.rem_count || + (i->rem_count == h->debug.rem_count - 1 && + i->prev_idx == h->debug.last_rem_idx)); + /* Reset our removals counter */ + i->rem_count = h->debug.rem_count; + } +#endif - hash = bucket_hash(h, e->key); - unlink_entry(h, e, hash); + return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i) + : hashmap_iterate_in_internal_order(h, i); +} - if (h->from_pool) - mempool_free_tile(&hashmap_entry_pool, e); - else - free(e); +void *internal_hashmap_iterate(HashmapBase *h, Iterator *i, const void **key) { + struct hashmap_base_entry *e; + void *data; + unsigned idx; + + idx = hashmap_iterate_entry(h, i); + if (idx == IDX_NIL) { + if (key) + *key = NULL; + + return NULL; + } + + e = bucket_at(h, idx); + data = entry_value(h, e); + if (key) + *key = e->key; + + return data; } -void hashmap_free(Hashmap*h) { +void *set_iterate(Set *s, Iterator *i) { + return internal_hashmap_iterate(HASHMAP_BASE(s), i, NULL); +} - /* Free the hashmap, but nothing in it */ +#define HASHMAP_FOREACH_IDX(idx, h, i) \ + for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ + (idx != IDX_NIL); \ + (idx) = hashmap_iterate_entry((h), &(i))) + +static void reset_direct_storage(HashmapBase *h) { + const struct hashmap_type_info *hi = &hashmap_type_info[h->type]; + void *p; + + assert(!h->has_indirect); + + p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets); + memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets); +} + +static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { + HashmapBase *h; + const struct hashmap_type_info *hi = &hashmap_type_info[type]; + bool use_pool; + + use_pool = is_main_thread(); + + h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); if (!h) - return; + return NULL; + + h->type = type; + h->from_pool = use_pool; + h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; - hashmap_clear(h); + if (type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*)h; + lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; + } + + reset_direct_storage(h); + + if (!shared_hash_key_initialized) { + random_bytes(shared_hash_key, sizeof(shared_hash_key)); + shared_hash_key_initialized= true; + } - if (h->buckets != (struct hashmap_entry**) ((uint8_t*) h + ALIGN(sizeof(Hashmap)))) - free(h->buckets); +#ifdef ENABLE_DEBUG_HASHMAP + LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug); + h->debug.func = func; + h->debug.file = file; + h->debug.line = line; +#endif + + return h; +} + +Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); +} + +OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); +} + +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); +} + +static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops, + enum HashmapType type HASHMAP_DEBUG_PARAMS) { + HashmapBase *q; + + assert(h); + + if (*h) + return 0; + + q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS); + if (!q) + return -ENOMEM; + + *h = q; + return 0; +} + +int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); +} + +int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); +} + +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); +} + +static void hashmap_free_no_clear(HashmapBase *h) { + assert(!h->has_indirect); + assert(!h->n_direct_entries); + +#ifdef ENABLE_DEBUG_HASHMAP + LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug); +#endif if (h->from_pool) - mempool_free_tile(&hashmap_pool, container_of(h, struct hashmap_tile, h)); + mempool_free_tile(hashmap_type_info[h->type].mempool, h); else free(h); } -void hashmap_free_free(Hashmap *h) { +void internal_hashmap_free(HashmapBase *h) { + + /* Free the hashmap, but nothing in it */ + + if (!h) + return; + + internal_hashmap_clear(h); + hashmap_free_no_clear(h); +} + +void internal_hashmap_free_free(HashmapBase *h) { /* Free the hashmap and all data objects in it, but not the * keys */ @@ -242,249 +883,763 @@ void hashmap_free_free(Hashmap *h) { if (!h) return; - hashmap_clear_free(h); - hashmap_free(h); + internal_hashmap_clear_free(h); + hashmap_free_no_clear(h); } -void hashmap_clear(Hashmap *h) { +void hashmap_free_free_free(Hashmap *h) { + + /* Free the hashmap and all data and key objects in it */ + if (!h) return; - while (h->iterate_list_head) - remove_entry(h, h->iterate_list_head); + hashmap_clear_free_free(h); + hashmap_free_no_clear(HASHMAP_BASE(h)); } -void hashmap_clear_free(Hashmap *h) { - void *p; +void internal_hashmap_clear(HashmapBase *h) { + if (!h) + return; + + if (h->has_indirect) { + free(h->indirect.storage); + h->has_indirect = false; + } + + h->n_direct_entries = 0; + reset_direct_storage(h); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; + } +} + +void internal_hashmap_clear_free(HashmapBase *h) { + unsigned idx; if (!h) return; - while ((p = hashmap_steal_first(h))) - free(p); + for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; + idx = skip_free_buckets(h, idx + 1)) + free(entry_value(h, bucket_at(h, idx))); + + internal_hashmap_clear(h); } -static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) { - struct hashmap_entry *e; - assert(h); - assert(hash < h->n_buckets); +void hashmap_clear_free_free(Hashmap *h) { + unsigned idx; - for (e = h->buckets[hash]; e; e = e->bucket_next) - if (h->hash_ops->compare(e->key, key) == 0) - return e; + if (!h) + return; + + for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL; + idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) { + struct plain_hashmap_entry *e = plain_bucket_at(h, idx); + free((void*)e->b.key); + free(e->value); + } - return NULL; + internal_hashmap_clear(HASHMAP_BASE(h)); } -static int resize_buckets(Hashmap *h, unsigned entries_add) { - struct hashmap_entry **n, *i; - unsigned m, new_n_entries, new_n_buckets; - uint8_t nkey[HASH_KEY_SIZE]; +static int resize_buckets(HashmapBase *h, unsigned entries_add); - assert(h); +/* + * Finds an empty bucket to put an entry into, starting the scan at 'idx'. + * Performs Robin Hood swaps as it goes. The entry to put must be placed + * by the caller into swap slot IDX_PUT. + * If used for in-place resizing, may leave a displaced entry in swap slot + * IDX_PUT. Caller must rehash it next. + * Returns: true if it left a displaced entry to rehash next in IDX_PUT, + * false otherwise. + */ +static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, + struct swap_entries *swap) { + dib_raw_t raw_dib, *dibs; + unsigned dib, distance; - new_n_entries = h->n_entries + entries_add; +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.put_count++; +#endif - /* overflow? */ - if (_unlikely_(new_n_entries < entries_add || new_n_entries > UINT_MAX / 4)) - return -ENOMEM; + dibs = dib_raw_ptr(h); - new_n_buckets = new_n_entries * 4 / 3; + for (distance = 0; ; distance++) { + raw_dib = dibs[idx]; + if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) { + if (raw_dib == DIB_RAW_REHASH) + bucket_move_entry(h, swap, idx, IDX_TMP); - if (_likely_(new_n_buckets <= h->n_buckets)) - return 0; + if (h->has_indirect && h->indirect.idx_lowest_entry > idx) + h->indirect.idx_lowest_entry = idx; - /* Increase by four at least */ - m = MAX((h->n_entries+1)*4-1, new_n_buckets); + bucket_set_dib(h, idx, distance); + bucket_move_entry(h, swap, IDX_PUT, idx); + if (raw_dib == DIB_RAW_REHASH) { + bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); + return true; + } - /* If we hit OOM we simply risk packed hashmaps... */ - n = new0(struct hashmap_entry*, m); - if (!n) - return -ENOMEM; + return false; + } - /* Let's use a different randomized hash key for the - * extension, so that people cannot guess what we are using - * here forever */ - get_hash_key(nkey, false); + dib = bucket_calculate_dib(h, idx, raw_dib); - for (i = h->iterate_list_head; i; i = i->iterate_next) { - unsigned long old_bucket, new_bucket; + if (dib < distance) { + /* Found a wealthier entry. Go Robin Hood! */ - old_bucket = h->hash_ops->hash(i->key, h->hash_key) % h->n_buckets; + bucket_set_dib(h, idx, distance); - /* First, drop from old bucket table */ - if (i->bucket_next) - i->bucket_next->bucket_previous = i->bucket_previous; + /* swap the entries */ + bucket_move_entry(h, swap, idx, IDX_TMP); + bucket_move_entry(h, swap, IDX_PUT, idx); + bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); - if (i->bucket_previous) - i->bucket_previous->bucket_next = i->bucket_next; - else - h->buckets[old_bucket] = i->bucket_next; + distance = dib; + } - /* Then, add to new backet table */ - new_bucket = h->hash_ops->hash(i->key, nkey) % m; + idx = next_idx(h, idx); + } +} - i->bucket_next = n[new_bucket]; - i->bucket_previous = NULL; - if (n[new_bucket]) - n[new_bucket]->bucket_previous = i; - n[new_bucket] = i; +/* + * Puts an entry into a hashmap, boldly - no check whether key already exists. + * The caller must place the entry (only its key and value, not link indexes) + * in swap slot IDX_PUT. + * Caller must ensure: the key does not exist yet in the hashmap. + * that resize is not needed if !may_resize. + * Returns: 1 if entry was put successfully. + * -ENOMEM if may_resize==true and resize failed with -ENOMEM. + * Cannot return -ENOMEM if !may_resize. + */ +static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx, + struct swap_entries *swap, bool may_resize) { + struct ordered_hashmap_entry *new_entry; + int r; + + assert(idx < n_buckets(h)); + + new_entry = bucket_at_swap(swap, IDX_PUT); + + if (may_resize) { + r = resize_buckets(h, 1); + if (r < 0) + return r; + if (r > 0) + idx = bucket_hash(h, new_entry->p.b.key); } + assert(n_entries(h) < n_buckets(h)); - if (h->buckets != (struct hashmap_entry**) ((uint8_t*) h + ALIGN(sizeof(Hashmap)))) - free(h->buckets); + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; - h->buckets = n; - h->n_buckets = m; + new_entry->iterate_next = IDX_NIL; + new_entry->iterate_previous = lh->iterate_list_tail; - memcpy(h->hash_key, nkey, HASH_KEY_SIZE); + if (lh->iterate_list_tail != IDX_NIL) { + struct ordered_hashmap_entry *old_tail; + + old_tail = ordered_bucket_at(lh, lh->iterate_list_tail); + assert(old_tail->iterate_next == IDX_NIL); + old_tail->iterate_next = IDX_PUT; + } + + lh->iterate_list_tail = IDX_PUT; + if (lh->iterate_list_head == IDX_NIL) + lh->iterate_list_head = IDX_PUT; + } + + assert_se(hashmap_put_robin_hood(h, idx, swap) == false); + + n_entries_inc(h); +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h)); +#endif return 1; } +#define hashmap_put_boldly(h, idx, swap, may_resize) \ + hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize) + +/* + * Returns 0 if resize is not needed. + * 1 if successfully resized. + * -ENOMEM on allocation failure. + */ +static int resize_buckets(HashmapBase *h, unsigned entries_add) { + struct swap_entries swap; + char *new_storage; + dib_raw_t *old_dibs, *new_dibs; + const struct hashmap_type_info *hi; + unsigned idx, optimal_idx; + unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries; + uint8_t new_shift; + bool rehash_next; -static int __hashmap_put(Hashmap *h, const void *key, void *value, unsigned hash) { - /* For when we know no such entry exists yet */ + assert(h); - struct hashmap_entry *e; + hi = &hashmap_type_info[h->type]; + new_n_entries = n_entries(h) + entries_add; - if (resize_buckets(h, 1) > 0) - hash = bucket_hash(h, key); + /* overflow? */ + if (_unlikely_(new_n_entries < entries_add)) + return -ENOMEM; - if (h->from_pool) - e = mempool_alloc_tile(&hashmap_entry_pool); - else - e = new(struct hashmap_entry, 1); + /* For direct storage we allow 100% load, because it's tiny. */ + if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets) + return 0; - if (!e) + /* + * Load factor = n/m = 1 - (1/INV_KEEP_FREE). + * From it follows: m = n + n/(INV_KEEP_FREE - 1) + */ + new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1); + /* overflow? */ + if (_unlikely_(new_n_buckets < new_n_entries)) return -ENOMEM; - e->key = key; - e->value = value; + if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t)))) + return -ENOMEM; - link_entry(h, e, hash); + old_n_buckets = n_buckets(h); + + if (_likely_(new_n_buckets <= old_n_buckets)) + return 0; + + new_shift = log2u_round_up(MAX( + new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)), + 2 * sizeof(struct direct_storage))); + + /* Realloc storage (buckets and DIB array). */ + new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL, + 1U << new_shift); + if (!new_storage) + return -ENOMEM; + + /* Must upgrade direct to indirect storage. */ + if (!h->has_indirect) { + memcpy(new_storage, h->direct.storage, + old_n_buckets * (hi->entry_size + sizeof(dib_raw_t))); + h->indirect.n_entries = h->n_direct_entries; + h->indirect.idx_lowest_entry = 0; + h->n_direct_entries = 0; + } + + /* Get a new hash key. If we've just upgraded to indirect storage, + * allow reusing a previously generated key. It's still a different key + * from the shared one that we used for direct storage. */ + get_hash_key(h->indirect.hash_key, !h->has_indirect); + + h->has_indirect = true; + h->indirect.storage = new_storage; + h->indirect.n_buckets = (1U << new_shift) / + (hi->entry_size + sizeof(dib_raw_t)); + + old_dibs = (dib_raw_t*)(new_storage + hi->entry_size * old_n_buckets); + new_dibs = dib_raw_ptr(h); + + /* + * Move the DIB array to the new place, replacing valid DIB values with + * DIB_RAW_REHASH to indicate all of the used buckets need rehashing. + * Note: Overlap is not possible, because we have at least doubled the + * number of buckets and dib_raw_t is smaller than any entry type. + */ + for (idx = 0; idx < old_n_buckets; idx++) { + assert(old_dibs[idx] != DIB_RAW_REHASH); + new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE + : DIB_RAW_REHASH; + } + + /* Zero the area of newly added entries (including the old DIB area) */ + memset(bucket_at(h, old_n_buckets), 0, + (n_buckets(h) - old_n_buckets) * hi->entry_size); + + /* The upper half of the new DIB array needs initialization */ + memset(&new_dibs[old_n_buckets], DIB_RAW_INIT, + (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t)); + + /* Rehash entries that need it */ + n_rehashed = 0; + for (idx = 0; idx < old_n_buckets; idx++) { + if (new_dibs[idx] != DIB_RAW_REHASH) + continue; + + optimal_idx = bucket_hash(h, bucket_at(h, idx)->key); + + /* + * Not much to do if by luck the entry hashes to its current + * location. Just set its DIB. + */ + if (optimal_idx == idx) { + new_dibs[idx] = 0; + n_rehashed++; + continue; + } + + new_dibs[idx] = DIB_RAW_FREE; + bucket_move_entry(h, &swap, idx, IDX_PUT); + /* bucket_move_entry does not clear the source */ + memset(bucket_at(h, idx), 0, hi->entry_size); + + do { + /* + * Find the new bucket for the current entry. This may make + * another entry homeless and load it into IDX_PUT. + */ + rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap); + n_rehashed++; + + /* Did the current entry displace another one? */ + if (rehash_next) + optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key); + } while (rehash_next); + } + + assert(n_rehashed == n_entries(h)); return 1; } +/* + * Finds an entry with a matching key + * Returns: index of the found entry, or IDX_NIL if not found. + */ +static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) { + struct hashmap_base_entry *e; + unsigned dib, distance; + dib_raw_t *dibs = dib_raw_ptr(h); + + assert(idx < n_buckets(h)); + + for (distance = 0; ; distance++) { + if (dibs[idx] == DIB_RAW_FREE) + return IDX_NIL; + + dib = bucket_calculate_dib(h, idx, dibs[idx]); + + if (dib < distance) + return IDX_NIL; + if (dib == distance) { + e = bucket_at(h, idx); + if (h->hash_ops->compare(e->key, key) == 0) + return idx; + } + + idx = next_idx(h, idx); + } +} +#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key) + int hashmap_put(Hashmap *h, const void *key, void *value) { - struct hashmap_entry *e; - unsigned hash; + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned hash, idx; assert(h); hash = bucket_hash(h, key); - e = hash_scan(h, hash, key); - if (e) { + idx = bucket_scan(h, hash, key); + if (idx != IDX_NIL) { + e = plain_bucket_at(h, idx); if (e->value == value) return 0; return -EEXIST; } - return __hashmap_put(h, key, value, hash); + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = key; + e->value = value; + return hashmap_put_boldly(h, hash, &swap, true); } -void* hashmap_get(Hashmap *h, const void *key) { - unsigned hash; - struct hashmap_entry *e; +int set_put(Set *s, const void *key) { + struct swap_entries swap; + struct hashmap_base_entry *e; + unsigned hash, idx; + + assert(s); + + hash = bucket_hash(s, key); + idx = bucket_scan(s, hash, key); + if (idx != IDX_NIL) + return 0; + + e = &bucket_at_swap(&swap, IDX_PUT)->p.b; + e->key = key; + return hashmap_put_boldly(s, hash, &swap, true); +} + +int hashmap_replace(Hashmap *h, const void *key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx != IDX_NIL) { + e = plain_bucket_at(h, idx); +#ifdef ENABLE_DEBUG_HASHMAP + /* Although the key is equal, the key pointer may have changed, + * and this would break our assumption for iterating. So count + * this operation as incompatible with iteration. */ + if (e->b.key != key) { + h->b.debug.put_count++; + h->b.debug.rem_count++; + h->b.debug.last_rem_idx = idx; + } +#endif + e->b.key = key; + e->value = value; + return 0; + } + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = key; + e->value = value; + return hashmap_put_boldly(h, hash, &swap, true); +} + +int hashmap_update(Hashmap *h, const void *key, void *value) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return -ENOENT; + + e = plain_bucket_at(h, idx); + e->value = value; + return 0; +} + +void *internal_hashmap_get(HashmapBase *h, const void *key) { + struct hashmap_base_entry *e; + unsigned hash, idx; if (!h) return NULL; hash = bucket_hash(h, key); - e = hash_scan(h, hash, key); - if (!e) + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) return NULL; - return e->value; + e = bucket_at(h, idx); + return entry_value(h, e); } -void* hashmap_get2(Hashmap *h, const void *key, void **key2) { - unsigned hash; - struct hashmap_entry *e; +void *hashmap_get2(Hashmap *h, const void *key, void **key2) { + struct plain_hashmap_entry *e; + unsigned hash, idx; if (!h) return NULL; hash = bucket_hash(h, key); - e = hash_scan(h, hash, key); - if (!e) + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) return NULL; + e = plain_bucket_at(h, idx); if (key2) - *key2 = (void*) e->key; + *key2 = (void*) e->b.key; return e->value; } -bool hashmap_contains(Hashmap *h, const void *key) { +bool internal_hashmap_contains(HashmapBase *h, const void *key) { unsigned hash; if (!h) return false; hash = bucket_hash(h, key); - return !!hash_scan(h, hash, key); + return bucket_scan(h, hash, key) != IDX_NIL; } -void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) { - struct hashmap_entry *e; +void *internal_hashmap_remove(HashmapBase *h, const void *key) { + struct hashmap_base_entry *e; + unsigned hash, idx; + void *data; - assert(i); + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + data = entry_value(h, e); + remove_entry(h, idx); + + return data; +} + +void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + void *data; + + if (!h) { + if (rkey) + *rkey = NULL; + return NULL; + } + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) { + if (rkey) + *rkey = NULL; + return NULL; + } + + e = plain_bucket_at(h, idx); + data = e->value; + if (rkey) + *rkey = (void*) e->b.key; + + remove_entry(h, idx); + + return data; +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned old_hash, new_hash, idx; if (!h) - goto at_end; + return -ENOENT; - if (*i == ITERATOR_LAST) - goto at_end; + old_hash = bucket_hash(h, old_key); + idx = bucket_scan(h, old_hash, old_key); + if (idx == IDX_NIL) + return -ENOENT; - if (*i == ITERATOR_FIRST && !h->iterate_list_head) - goto at_end; + new_hash = bucket_hash(h, new_key); + if (bucket_scan(h, new_hash, new_key) != IDX_NIL) + return -EEXIST; - e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i; + remove_entry(h, idx); - if (e->iterate_next) - *i = (Iterator) e->iterate_next; - else - *i = ITERATOR_LAST; + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = new_key; + e->value = value; + assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); - if (key) - *key = e->key; + return 0; +} - return e->value; +int set_remove_and_put(Set *s, const void *old_key, const void *new_key) { + struct swap_entries swap; + struct hashmap_base_entry *e; + unsigned old_hash, new_hash, idx; -at_end: - *i = ITERATOR_LAST; + if (!s) + return -ENOENT; - if (key) - *key = NULL; + old_hash = bucket_hash(s, old_key); + idx = bucket_scan(s, old_hash, old_key); + if (idx == IDX_NIL) + return -ENOENT; + + new_hash = bucket_hash(s, new_key); + if (bucket_scan(s, new_hash, new_key) != IDX_NIL) + return -EEXIST; + + remove_entry(s, idx); - return NULL; + e = &bucket_at_swap(&swap, IDX_PUT)->p.b; + e->key = new_key; + assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1); + + return 0; } -void* hashmap_steal_first(Hashmap *h) { - void *data; +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned old_hash, new_hash, idx_old, idx_new; if (!h) + return -ENOENT; + + old_hash = bucket_hash(h, old_key); + idx_old = bucket_scan(h, old_hash, old_key); + if (idx_old == IDX_NIL) + return -ENOENT; + + old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key; + + new_hash = bucket_hash(h, new_key); + idx_new = bucket_scan(h, new_hash, new_key); + if (idx_new != IDX_NIL) + if (idx_old != idx_new) { + remove_entry(h, idx_new); + /* Compensate for a possible backward shift. */ + if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key) + idx_old = prev_idx(HASHMAP_BASE(h), idx_old); + assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key); + } + + remove_entry(h, idx_old); + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = new_key; + e->value = value; + assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); + + return 0; +} + +void *hashmap_remove_value(Hashmap *h, const void *key, void *value) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = plain_bucket_at(h, idx); + if (e->value != value) + return NULL; + + remove_entry(h, idx); + + return value; +} + +static unsigned find_first_entry(HashmapBase *h) { + Iterator i = ITERATOR_FIRST; + + if (!h || !n_entries(h)) + return IDX_NIL; + + return hashmap_iterate_entry(h, &i); +} + +void *internal_hashmap_first(HashmapBase *h) { + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + return entry_value(h, bucket_at(h, idx)); +} + +void *internal_hashmap_first_key(HashmapBase *h) { + struct hashmap_base_entry *e; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) return NULL; - if (!h->iterate_list_head) + e = bucket_at(h, idx); + return (void*) e->key; +} + +void *internal_hashmap_steal_first(HashmapBase *h) { + struct hashmap_base_entry *e; + void *data; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) return NULL; - data = h->iterate_list_head->value; - remove_entry(h, h->iterate_list_head); + e = bucket_at(h, idx); + data = entry_value(h, e); + remove_entry(h, idx); return data; } -unsigned hashmap_size(Hashmap *h) { +void *internal_hashmap_steal_first_key(HashmapBase *h) { + struct hashmap_base_entry *e; + void *key; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + key = (void*) e->key; + remove_entry(h, idx); + + return key; +} + +unsigned internal_hashmap_size(HashmapBase *h) { if (!h) return 0; - return h->n_entries; + return n_entries(h); +} + +unsigned internal_hashmap_buckets(HashmapBase *h) { + + if (!h) + return 0; + + return n_buckets(h); +} + +int internal_hashmap_merge(Hashmap *h, Hashmap *other) { + Iterator i; + unsigned idx; + + assert(h); + + HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { + struct plain_hashmap_entry *pe = plain_bucket_at(other, idx); + int r; + + r = hashmap_put(h, pe->b.key, pe->value); + if (r < 0 && r != -EEXIST) + return r; + } + + return 0; +} + +int set_merge(Set *s, Set *other) { + Iterator i; + unsigned idx; + + assert(s); + + HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { + struct set_entry *se = set_bucket_at(other, idx); + int r; + + r = set_put(s, se->b.key); + if (r < 0) + return r; + } + + return 0; } -int hashmap_reserve(Hashmap *h, unsigned entries_add) { +int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) { int r; assert(h); @@ -496,20 +1651,200 @@ int hashmap_reserve(Hashmap *h, unsigned entries_add) { return 0; } -char **hashmap_get_strv(Hashmap *h) { +/* + * The same as hashmap_merge(), but every new item from other is moved to h. + * Keys already in h are skipped and stay in other. + * Returns: 0 on success. + * -ENOMEM on alloc failure, in which case no move has been done. + */ +int internal_hashmap_move(HashmapBase *h, HashmapBase *other) { + struct swap_entries swap; + struct hashmap_base_entry *e, *n; + Iterator i; + unsigned idx; + int r; + + assert(h); + + if (!other) + return 0; + + assert(other->type == h->type); + + /* + * This reserves buckets for the worst case, where none of other's + * entries are yet present in h. This is preferable to risking + * an allocation failure in the middle of the moving and having to + * rollback or return a partial result. + */ + r = resize_buckets(h, n_entries(other)); + if (r < 0) + return r; + + HASHMAP_FOREACH_IDX(idx, other, i) { + unsigned h_hash; + + e = bucket_at(other, idx); + h_hash = bucket_hash(h, e->key); + if (bucket_scan(h, h_hash, e->key) != IDX_NIL) + continue; + + n = &bucket_at_swap(&swap, IDX_PUT)->p.b; + n->key = e->key; + if (h->type != HASHMAP_TYPE_SET) + ((struct plain_hashmap_entry*) n)->value = + ((struct plain_hashmap_entry*) e)->value; + assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1); + + remove_entry(other, idx); + } + + return 0; +} + +int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) { + struct swap_entries swap; + unsigned h_hash, other_hash, idx; + struct hashmap_base_entry *e, *n; + int r; + + assert(h); + + h_hash = bucket_hash(h, key); + if (bucket_scan(h, h_hash, key) != IDX_NIL) + return -EEXIST; + + if (!other) + return -ENOENT; + + assert(other->type == h->type); + + other_hash = bucket_hash(other, key); + idx = bucket_scan(other, other_hash, key); + if (idx == IDX_NIL) + return -ENOENT; + + e = bucket_at(other, idx); + + n = &bucket_at_swap(&swap, IDX_PUT)->p.b; + n->key = e->key; + if (h->type != HASHMAP_TYPE_SET) + ((struct plain_hashmap_entry*) n)->value = + ((struct plain_hashmap_entry*) e)->value; + r = hashmap_put_boldly(h, h_hash, &swap, true); + if (r < 0) + return r; + + remove_entry(other, idx); + return 0; +} + +HashmapBase *internal_hashmap_copy(HashmapBase *h) { + HashmapBase *copy; + int r; + + assert(h); + + copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS); + if (!copy) + return NULL; + + switch (h->type) { + case HASHMAP_TYPE_PLAIN: + case HASHMAP_TYPE_ORDERED: + r = hashmap_merge((Hashmap*)copy, (Hashmap*)h); + break; + case HASHMAP_TYPE_SET: + r = set_merge((Set*)copy, (Set*)h); + break; + default: + assert_not_reached("Unknown hashmap type"); + } + + if (r < 0) { + internal_hashmap_free(copy); + return NULL; + } + + return copy; +} + +char **internal_hashmap_get_strv(HashmapBase *h) { char **sv; - Iterator it; - char *item; - int n; + Iterator i; + unsigned idx, n; - sv = new(char*, h->n_entries+1); + sv = new(char*, n_entries(h)+1); if (!sv) return NULL; n = 0; - HASHMAP_FOREACH(item, h, it) - sv[n++] = item; + HASHMAP_FOREACH_IDX(idx, h, i) + sv[n++] = entry_value(h, bucket_at(h, idx)); sv[n] = NULL; return sv; } + +void *ordered_hashmap_next(OrderedHashmap *h, const void *key) { + struct ordered_hashmap_entry *e; + unsigned hash, idx; + + assert(key); + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = ordered_bucket_at(h, idx); + if (e->iterate_next == IDX_NIL) + return NULL; + return ordered_bucket_at(h, e->iterate_next)->p.value; +} + +int set_consume(Set *s, void *value) { + int r; + + r = set_put(s, value); + if (r <= 0) + free(value); + + return r; +} + +int set_put_strdup(Set *s, const char *p) { + char *c; + int r; + + assert(s); + assert(p); + + c = strdup(p); + if (!c) + return -ENOMEM; + + r = set_consume(s, c); + if (r == -EEXIST) + return 0; + + return r; +} + +int set_put_strdupv(Set *s, char **l) { + int n = 0, r; + char **i; + + STRV_FOREACH(i, l) { + r = set_put_strdup(s, *i); + if (r < 0) + return r; + + n += r; + } + + return n; +} diff --git a/src/shared/hashmap.h b/src/shared/hashmap.h index 22bda37437..894f67939e 100644 --- a/src/shared/hashmap.h +++ b/src/shared/hashmap.h @@ -1,7 +1,12 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + /*** - This file is part of eudev, forked from systemd. + This file is part of systemd. Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt 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 @@ -17,26 +22,50 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#pragma once - #include <stdbool.h> #include "macro.h" #include "util.h" -/* Pretty straightforward hash table implementation. As a minor - * optimization a NULL hashmap object will be treated as empty hashmap - * for all read operations. That way it is not necessary to - * instantiate an object for each Hashmap use. */ +/* + * A hash table implementation. As a minor optimization a NULL hashmap object + * will be treated as empty hashmap for all read operations. That way it is not + * necessary to instantiate an object for each Hashmap use. + * + * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap), + * the implemention will: + * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py) + * - perform extra checks for invalid use of iterators + */ #define HASH_KEY_SIZE 16 -typedef struct Hashmap Hashmap; -typedef struct _IteratorStruct _IteratorStruct; -typedef _IteratorStruct* Iterator; +/* The base type for all hashmap and set types. Many functions in the + * implementation take (HashmapBase*) parameters and are run-time polymorphic, + * though the API is not meant to be polymorphic (do not call functions + * internal_*() directly). */ +typedef struct HashmapBase HashmapBase; + +/* Specific hashmap/set types */ +typedef struct Hashmap Hashmap; /* Maps keys to values */ +typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */ +typedef struct Set Set; /* Stores just keys */ -#define ITERATOR_FIRST ((Iterator) 0) -#define ITERATOR_LAST ((Iterator) -1) +/* Ideally the Iterator would be an opaque struct, but it is instantiated + * by hashmap users, so the definition has to be here. Do not use its fields + * directly. */ +typedef struct { + unsigned idx; /* index of an entry to be iterated next */ + const void *next_key; /* expected value of that entry's key pointer */ +#ifdef ENABLE_DEBUG_HASHMAP + unsigned put_count; /* hashmap's put_count recorded at start of iteration */ + unsigned rem_count; /* hashmap's rem_count in previous iteration */ + unsigned prev_idx; /* idx in previous iteration */ +#endif +} Iterator; + +#define _IDX_ITERATOR_FIRST (UINT_MAX - 1) +#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) typedef unsigned long (*hash_func_t)(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]); typedef int (*compare_func_t)(const void *a, const void *b); @@ -57,29 +86,335 @@ unsigned long trivial_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_S int trivial_compare_func(const void *a, const void *b) _const_; extern const struct hash_ops trivial_hash_ops; -Hashmap *hashmap_new(const struct hash_ops *hash_ops); -void hashmap_free(Hashmap *h); -void hashmap_free_free(Hashmap *h); +/* 32bit values we can always just embedd in the pointer itself, but + * in order to support 32bit archs we need store 64bit values + * indirectly, since they don't fit in a pointer. */ +unsigned long uint64_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) _pure_; +int uint64_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops uint64_hash_ops; + +/* On some archs dev_t is 32bit, and on others 64bit. And sometimes + * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ +#if SIZEOF_DEV_T != 8 +unsigned long devt_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) _pure_; +int devt_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#else +#define devt_hash_func uint64_hash_func +#define devt_compare_func uint64_compare_func +#define devt_hash_ops uint64_hash_ops +#endif + +/* Macros for type checking */ +#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ + (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ + __builtin_types_compatible_p(typeof(h), Hashmap*) || \ + __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \ + __builtin_types_compatible_p(typeof(h), Set*)) + +#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \ + (__builtin_types_compatible_p(typeof(h), Hashmap*) || \ + __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \ + +#define HASHMAP_BASE(h) \ + __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \ + (HashmapBase*)(h), \ + (void)0) + +#define PLAIN_HASHMAP(h) \ + __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \ + (Hashmap*)(h), \ + (void)0) + +#ifdef ENABLE_DEBUG_HASHMAP +# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line +# define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ +# define HASHMAP_DEBUG_PASS_ARGS , func, file, line +#else +# define HASHMAP_DEBUG_PARAMS +# define HASHMAP_DEBUG_SRC_ARGS +# define HASHMAP_DEBUG_PASS_ARGS +#endif + +Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) +#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) + +void internal_hashmap_free(HashmapBase *h); +static inline void hashmap_free(Hashmap *h) { + internal_hashmap_free(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_free(OrderedHashmap *h) { + internal_hashmap_free(HASHMAP_BASE(h)); +} + +void internal_hashmap_free_free(HashmapBase *h); +static inline void hashmap_free_free(Hashmap *h) { + internal_hashmap_free_free(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_free_free(OrderedHashmap *h) { + internal_hashmap_free_free(HASHMAP_BASE(h)); +} + +void hashmap_free_free_free(Hashmap *h); +static inline void ordered_hashmap_free_free_free(OrderedHashmap *h) { + hashmap_free_free_free(PLAIN_HASHMAP(h)); +} + +HashmapBase *internal_hashmap_copy(HashmapBase *h); +static inline Hashmap *hashmap_copy(Hashmap *h) { + return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) { + return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); +} + +int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) +#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) int hashmap_put(Hashmap *h, const void *key, void *value); -void *hashmap_get(Hashmap *h, const void *key); +static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) { + return hashmap_put(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_update(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { + return hashmap_update(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_replace(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) { + return hashmap_replace(PLAIN_HASHMAP(h), key, value); +} + +void *internal_hashmap_get(HashmapBase *h, const void *key); +static inline void *hashmap_get(Hashmap *h, const void *key) { + return internal_hashmap_get(HASHMAP_BASE(h), key); +} +static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) { + return internal_hashmap_get(HASHMAP_BASE(h), key); +} + void *hashmap_get2(Hashmap *h, const void *key, void **rkey); -bool hashmap_contains(Hashmap *h, const void *key); -int hashmap_reserve(Hashmap *h, unsigned entries_add); +static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) { + return hashmap_get2(PLAIN_HASHMAP(h), key, rkey); +} -unsigned hashmap_size(Hashmap *h) _pure_; +bool internal_hashmap_contains(HashmapBase *h, const void *key); +static inline bool hashmap_contains(Hashmap *h, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(h), key); +} +static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(h), key); +} -void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key); +void *internal_hashmap_remove(HashmapBase *h, const void *key); +static inline void *hashmap_remove(Hashmap *h, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(h), key); +} +static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(h), key); +} -void hashmap_clear(Hashmap *h); -void hashmap_clear_free(Hashmap *h); +void *hashmap_remove2(Hashmap *h, const void *key, void **rkey); +static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) { + return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey); +} -void *hashmap_steal_first(Hashmap *h); +void *hashmap_remove_value(Hashmap *h, const void *key, void *value); +static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) { + return hashmap_remove_value(PLAIN_HASHMAP(h), key, value); +} -char **hashmap_get_strv(Hashmap *h); +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); +static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { + return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value); +} +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); +static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { + return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); +} + +/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa + * should just work, allow this by having looser type-checking here. */ +int internal_hashmap_merge(Hashmap *h, Hashmap *other); +#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) +#define ordered_hashmap_merge(h, other) hashmap_merge(h, other) + +int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add); +static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} +static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} + +int internal_hashmap_move(HashmapBase *h, HashmapBase *other); +/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */ +static inline int hashmap_move(Hashmap *h, Hashmap *other) { + return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); +} +static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) { + return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); +} + +int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key); +static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); +} +static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); +} + +unsigned internal_hashmap_size(HashmapBase *h) _pure_; +static inline unsigned hashmap_size(Hashmap *h) { + return internal_hashmap_size(HASHMAP_BASE(h)); +} +static inline unsigned ordered_hashmap_size(OrderedHashmap *h) { + return internal_hashmap_size(HASHMAP_BASE(h)); +} + +static inline bool hashmap_isempty(Hashmap *h) { + return hashmap_size(h) == 0; +} +static inline bool ordered_hashmap_isempty(OrderedHashmap *h) { + return ordered_hashmap_size(h) == 0; +} + +unsigned internal_hashmap_buckets(HashmapBase *h) _pure_; +static inline unsigned hashmap_buckets(Hashmap *h) { + return internal_hashmap_buckets(HASHMAP_BASE(h)); +} +static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) { + return internal_hashmap_buckets(HASHMAP_BASE(h)); +} + +void *internal_hashmap_iterate(HashmapBase *h, Iterator *i, const void **key); +static inline void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) { + return internal_hashmap_iterate(HASHMAP_BASE(h), i, key); +} +static inline void *ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, const void **key) { + return internal_hashmap_iterate(HASHMAP_BASE(h), i, key); +} + +void internal_hashmap_clear(HashmapBase *h); +static inline void hashmap_clear(Hashmap *h) { + internal_hashmap_clear(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_clear(OrderedHashmap *h) { + internal_hashmap_clear(HASHMAP_BASE(h)); +} + +void internal_hashmap_clear_free(HashmapBase *h); +static inline void hashmap_clear_free(Hashmap *h) { + internal_hashmap_clear_free(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { + internal_hashmap_clear_free(HASHMAP_BASE(h)); +} + +void hashmap_clear_free_free(Hashmap *h); +static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { + hashmap_clear_free_free(PLAIN_HASHMAP(h)); +} + +/* + * Note about all *_first*() functions + * + * For plain Hashmaps and Sets the order of entries is undefined. + * The functions find whatever entry is first in the implementation + * internal order. + * + * Only for OrderedHashmaps the order is well defined and finding + * the first entry is O(1). + */ + +void *internal_hashmap_steal_first(HashmapBase *h); +static inline void *hashmap_steal_first(Hashmap *h) { + return internal_hashmap_steal_first(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) { + return internal_hashmap_steal_first(HASHMAP_BASE(h)); +} + +void *internal_hashmap_steal_first_key(HashmapBase *h); +static inline void *hashmap_steal_first_key(Hashmap *h) { + return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) { + return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); +} + +void *internal_hashmap_first_key(HashmapBase *h) _pure_; +static inline void *hashmap_first_key(Hashmap *h) { + return internal_hashmap_first_key(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { + return internal_hashmap_first_key(HASHMAP_BASE(h)); +} + +void *internal_hashmap_first(HashmapBase *h) _pure_; +static inline void *hashmap_first(Hashmap *h) { + return internal_hashmap_first(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_first(OrderedHashmap *h) { + return internal_hashmap_first(HASHMAP_BASE(h)); +} + +/* no hashmap_next */ +void *ordered_hashmap_next(OrderedHashmap *h, const void *key); + +char **internal_hashmap_get_strv(HashmapBase *h); +static inline char **hashmap_get_strv(Hashmap *h) { + return internal_hashmap_get_strv(HASHMAP_BASE(h)); +} +static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) { + return internal_hashmap_get_strv(HASHMAP_BASE(h)); +} + +/* + * Hashmaps are iterated in unpredictable order. + * OrderedHashmaps are an exception to this. They are iterated in the order + * the entries were inserted. + * It is safe to remove the current entry. + */ #define HASHMAP_FOREACH(e, h, i) \ - for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL)) + for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); \ + (e); \ + (e) = hashmap_iterate((h), &(i), NULL)) + +#define ORDERED_HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = ordered_hashmap_iterate((h), &(i), NULL); \ + (e); \ + (e) = ordered_hashmap_iterate((h), &(i), NULL)) + +#define HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); \ + (e); \ + (e) = hashmap_iterate((h), &(i), (const void**) &(k))) + +#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = ordered_hashmap_iterate((h), &(i), (const void**) &(k)); \ + (e); \ + (e) = ordered_hashmap_iterate((h), &(i), (const void**) &(k))) DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); + #define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) +#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) +#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) +#define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) +#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) +#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) diff --git a/src/shared/list.h b/src/shared/list.h new file mode 100644 index 0000000000..c020f7e936 --- /dev/null +++ b/src/shared/list.h @@ -0,0 +1,138 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + 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. + + 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define LIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define LIST_FIELDS(t,name) \ + t *name##_next, *name##_prev + +/* Initialize the list's head */ +#define LIST_HEAD_INIT(head) \ + do { \ + (head) = NULL; } \ + while(false) + +/* Initialize a list item */ +#define LIST_INIT(name,item) \ + do { \ + typeof(*(item)) *_item = (item); \ + assert(_item); \ + _item->name##_prev = _item->name##_next = NULL; \ + } while(false) + +/* Prepend an item to the list */ +#define LIST_PREPEND(name,head,item) \ + do { \ + typeof(*(head)) **_head = &(head), *_item = (item); \ + assert(_item); \ + if ((_item->name##_next = *_head)) \ + _item->name##_next->name##_prev = _item; \ + _item->name##_prev = NULL; \ + *_head = _item; \ + } while(false) + +/* Remove an item from the list */ +#define LIST_REMOVE(name,head,item) \ + do { \ + typeof(*(head)) **_head = &(head), *_item = (item); \ + assert(_item); \ + if (_item->name##_next) \ + _item->name##_next->name##_prev = _item->name##_prev; \ + if (_item->name##_prev) \ + _item->name##_prev->name##_next = _item->name##_next; \ + else { \ + assert(*_head == _item); \ + *_head = _item->name##_next; \ + } \ + _item->name##_next = _item->name##_prev = NULL; \ + } while(false) + +/* Find the head of the list */ +#define LIST_FIND_HEAD(name,item,head) \ + do { \ + typeof(*(item)) *_item = (item); \ + if (!_item) \ + (head) = NULL; \ + else { \ + while (_item->name##_prev) \ + _item = _item->name##_prev; \ + (head) = _item; \ + } \ + } while (false) + +/* Find the tail of the list */ +#define LIST_FIND_TAIL(name,item,tail) \ + do { \ + typeof(*(item)) *_item = (item); \ + if (!_item) \ + (tail) = NULL; \ + else { \ + while (_item->name##_next) \ + _item = _item->name##_next; \ + (tail) = _item; \ + } \ + } while (false) + +/* Insert an item after another one (a = where, b = what) */ +#define LIST_INSERT_AFTER(name,head,a,b) \ + do { \ + typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if ((_b->name##_next = *_head)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->name##_next = _a->name##_next)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = _a; \ + _a->name##_next = _b; \ + } \ + } while(false) + +#define LIST_JUST_US(name,item) \ + (!(item)->name##_prev && !(item)->name##_next) \ + +#define LIST_FOREACH(name,i,head) \ + for ((i) = (head); (i); (i) = (i)->name##_next) + +#define LIST_FOREACH_SAFE(name,i,n,head) \ + for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) + +#define LIST_FOREACH_BEFORE(name,i,p) \ + for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) + +#define LIST_FOREACH_AFTER(name,i,p) \ + for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) + +/* Loop starting from p->next until p->prev. + p can be adjusted meanwhile. */ +#define LIST_LOOP_BUT_ONE(name,i,head,p) \ + for ((i) = (p)->name##_next ? (p)->name##_next : (head); \ + (i) != (p); \ + (i) = (i)->name##_next ? (i)->name##_next : (head)) diff --git a/src/shared/log.c b/src/shared/log.c index 70708cbf0e..92361b0cbc 100644 --- a/src/shared/log.c +++ b/src/shared/log.c @@ -102,8 +102,8 @@ void log_close_syslog(void) { } static int create_log_socket(int type) { - int fd; struct timeval tv; + int fd; fd = socket(AF_UNIX, type|SOCK_CLOEXEC, 0); if (fd < 0) @@ -118,18 +118,20 @@ static int create_log_socket(int type) { timeval_store(&tv, 10 * USEC_PER_MSEC); else timeval_store(&tv, 10 * USEC_PER_SEC); - setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + (void) setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); return fd; } static int log_open_syslog(void) { - int r; - union sockaddr_union sa = { + + static const union sockaddr_union sa = { .un.sun_family = AF_UNIX, .un.sun_path = "/dev/log", }; + int r; + if (syslog_fd >= 0) return 0; @@ -234,10 +236,11 @@ void log_set_max_level(int level) { static int write_to_console( int level, - const char*file, + int error, + const char *file, int line, const char *func, - const char *object_name, + const char *object_field, const char *object, const char *buffer) { @@ -288,15 +291,16 @@ static int write_to_console( } static int write_to_syslog( - int level, - const char*file, - int line, - const char *func, - const char *object_name, - const char *object, - const char *buffer) { + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { - char header_priority[16], header_time[64], header_pid[16]; + char header_priority[1 + DECIMAL_STR_MAX(int) + 2], header_time[64], header_pid[1 + DECIMAL_STR_MAX(pid_t) + 4]; struct iovec iovec[5] = {}; struct msghdr msghdr = { .msg_iov = iovec, @@ -350,15 +354,16 @@ static int write_to_syslog( } static int write_to_kmsg( - int level, - const char*file, - int line, - const char *func, - const char *object_name, - const char *object, - const char *buffer) { + int level, + int error, + const char*file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { - char header_priority[16], header_pid[16]; + char header_priority[1 + DECIMAL_STR_MAX(int) + 2], header_pid[1 + DECIMAL_STR_MAX(pid_t) + 4]; struct iovec iovec[5] = {}; if (kmsg_fd < 0) @@ -382,45 +387,15 @@ static int write_to_kmsg( return 1; } -static int log_do_header(char *header, size_t size, - int level, - const char *file, int line, const char *func, - const char *object_name, const char *object) { - snprintf(header, size, - "PRIORITY=%i\n" - "SYSLOG_FACILITY=%i\n" - "%s%.*s%s" - "%s%.*i%s" - "%s%.*s%s" - "%s%.*s%s" - "SYSLOG_IDENTIFIER=%s\n", - LOG_PRI(level), - LOG_FAC(level), - file ? "CODE_FILE=" : "", - file ? LINE_MAX : 0, file, /* %.0s means no output */ - file ? "\n" : "", - line ? "CODE_LINE=" : "", - line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ - line ? "\n" : "", - func ? "CODE_FUNCTION=" : "", - func ? LINE_MAX : 0, func, - func ? "\n" : "", - object ? object_name : "", - object ? LINE_MAX : 0, object, /* %.0s means no output */ - object ? "\n" : "", - program_invocation_short_name); - header[size - 1] = '\0'; - return 0; -} - static int log_dispatch( - int level, - const char*file, - int line, - const char *func, - const char *object_name, - const char *object, - char *buffer) { + int level, + int error, + const char*file, + int line, + const char *func, + const char *object_field, + const char *object, + char *buffer) { int r = 0; @@ -449,8 +424,7 @@ static int log_dispatch( if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || log_target == LOG_TARGET_SYSLOG) { - k = write_to_syslog(level, file, line, func, - object_name, object, buffer); + k = write_to_syslog(level, error, file, line, func, object_field, object, buffer); if (k < 0) { if (k != -EAGAIN) log_close_syslog(); @@ -465,8 +439,7 @@ static int log_dispatch( log_target == LOG_TARGET_SYSLOG_OR_KMSG || log_target == LOG_TARGET_KMSG)) { - k = write_to_kmsg(level, file, line, func, - object_name, object, buffer); + k = write_to_kmsg(level, error, file, line, func, object_field, object, buffer); if (k < 0) { log_close_kmsg(); log_open_console(); @@ -475,8 +448,7 @@ static int log_dispatch( } if (k <= 0) { - k = write_to_console(level, file, line, func, - object_name, object, buffer); + k = write_to_console(level, error, file, line, func, object_field, object, buffer); if (k < 0) return k; } @@ -487,8 +459,9 @@ static int log_dispatch( return r; } -int log_metav( +int log_internalv( int level, + int error, const char*file, int line, const char *func, @@ -504,27 +477,34 @@ int log_metav( vsnprintf(buffer, sizeof(buffer), format, ap); char_array_0(buffer); - return log_dispatch(level, file, line, func, NULL, NULL, buffer); + return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); } -int log_meta( - int level, - const char*file, - int line, - const char *func, - const char *format, ...) { +int log_internal( + int level, + int error, + const char*file, + int line, + const char *func, + const char *format, ...) { int r; va_list ap; va_start(ap, format); - r = log_metav(level, file, line, func, format, ap); + r = log_internalv(level, error, file, line, func, format, ap); va_end(ap); return r; } -static void log_assert(int level, const char *text, const char *file, int line, const char *func, const char *format) { +static void log_assert( + int level, + const char *text, + const char *file, + int line, + const char *func, + const char *format) { static char buffer[LINE_MAX]; if (_likely_(LOG_PRI(level) > log_max_level)) @@ -537,7 +517,7 @@ static void log_assert(int level, const char *text, const char *file, int line, char_array_0(buffer); log_abort_msg = buffer; - log_dispatch(level, file, line, func, NULL, NULL, buffer); + log_dispatch(level, 0, file, line, func, NULL, NULL, buffer); } noreturn void log_assert_failed(const char *text, const char *file, int line, const char *func) { @@ -551,7 +531,7 @@ noreturn void log_assert_failed_unreachable(const char *text, const char *file, } int log_oom_internal(const char *file, int line, const char *func) { - log_meta(LOG_ERR, file, line, func, "Out of memory."); + log_internal(LOG_ERR, ENOMEM, file, line, func, "Out of memory."); return -ENOMEM; } diff --git a/src/shared/log.h b/src/shared/log.h index 30f0cacb65..485f3fa0f6 100644 --- a/src/shared/log.h +++ b/src/shared/log.h @@ -52,26 +52,29 @@ void log_close_journal(void); void log_close_kmsg(void); void log_close_console(void); -int log_meta( +int log_internal( int level, - const char*file, + int error, + const char *file, int line, const char *func, - const char *format, ...) _printf_(5,6); + const char *format, ...) _printf_(6,7); -int log_metav( +int log_internalv( int level, - const char*file, + int error, + const char *file, int line, const char *func, const char *format, - va_list ap) _printf_(5,0); + va_list ap) _printf_(6,0); int log_oom_internal( const char *file, int line, const char *func); +/* Logging for various assertions */ noreturn void log_assert_failed( const char *text, const char *file, @@ -85,17 +88,32 @@ noreturn void log_assert_failed_unreachable( const char *func); -#define log_full(level, ...) \ -do { \ - if (log_get_max_level() >= (level)) \ - log_meta((level), __FILE__, __LINE__, __func__, __VA_ARGS__); \ -} while (0) - -#define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) -#define log_info(...) log_full(LOG_INFO, __VA_ARGS__) -#define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) -#define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) -#define log_error(...) log_full(LOG_ERR, __VA_ARGS__) +/* Logging with level */ +#define log_full_errno(level, error, ...) \ + ({ \ + int _l = (level), _e = (error); \ + (log_get_max_level() >= LOG_PRI(_l)) \ + ? log_internal(_l, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + : -abs(_e); \ + }) + +#define log_full(level, ...) log_full_errno(level, 0, __VA_ARGS__) + +/* Normal logging */ +#define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) +#define log_info(...) log_full(LOG_INFO, __VA_ARGS__) +#define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) +#define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) +#define log_error(...) log_full(LOG_ERR, __VA_ARGS__) +#define log_emergency(...) log_full(getpid() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__) + +/* Logging triggered by an errno-like error */ +#define log_debug_errno(error, ...) log_full_errno(LOG_DEBUG, error, __VA_ARGS__) +#define log_info_errno(error, ...) log_full_errno(LOG_INFO, error, __VA_ARGS__) +#define log_notice_errno(error, ...) log_full_errno(LOG_NOTICE, error, __VA_ARGS__) +#define log_warning_errno(error, ...) log_full_errno(LOG_WARNING, error, __VA_ARGS__) +#define log_error_errno(error, ...) log_full_errno(LOG_ERR, error, __VA_ARGS__) +#define log_emergency_errno(error, ...) log_full_errno(getpid() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__) #ifdef LOG_TRACE # define log_trace(...) log_debug(__VA_ARGS__) @@ -103,7 +121,7 @@ do { \ # define log_trace(...) do {} while(0) #endif -#define log_struct(level, ...) log_struct_internal(level, __FILE__, __LINE__, __func__, __VA_ARGS__) +/* This modifies the buffer passed! */ #define log_oom() log_oom_internal(__FILE__, __LINE__, __func__) diff --git a/src/shared/macro.h b/src/shared/macro.h index cea13182ec..41fe61fb68 100644 --- a/src/shared/macro.h +++ b/src/shared/macro.h @@ -35,6 +35,7 @@ #define _likely_(x) (__builtin_expect(!!(x),1)) #define _unlikely_(x) (__builtin_expect(!!(x),0)) #define _public_ __attribute__ ((visibility("default"))) +#define _alignas_(x) __attribute__((aligned(__alignof(x)))) #define _cleanup_(x) __attribute__((cleanup(x))) /* Temporarily disable some warnings */ @@ -236,4 +237,8 @@ static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { #endif #endif +#define UID_INVALID ((uid_t) -1) +#define GID_INVALID ((gid_t) -1) +#define MODE_INVALID ((mode_t) -1) + #include "log.h" diff --git a/src/shared/mempool.c b/src/shared/mempool.c index b39a37f2db..d5d98d8829 100644 --- a/src/shared/mempool.c +++ b/src/shared/mempool.c @@ -74,6 +74,15 @@ void* mempool_alloc_tile(struct mempool *mp) { return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; } +void* mempool_alloc0_tile(struct mempool *mp) { + void *p; + + p = mempool_alloc_tile(mp); + if (p) + memzero(p, mp->tile_size); + return p; +} + void mempool_free_tile(struct mempool *mp, void *p) { * (void**) p = mp->freelist; mp->freelist = p; diff --git a/src/shared/mempool.h b/src/shared/mempool.h index 8b0bf381bf..42f473bee1 100644 --- a/src/shared/mempool.h +++ b/src/shared/mempool.h @@ -34,6 +34,7 @@ struct mempool { }; void* mempool_alloc_tile(struct mempool *mp); +void* mempool_alloc0_tile(struct mempool *mp); void mempool_free_tile(struct mempool *mp, void *p); #define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ diff --git a/src/shared/missing.h b/src/shared/missing.h index 572e17117a..00b5f7811c 100644 --- a/src/shared/missing.h +++ b/src/shared/missing.h @@ -42,6 +42,39 @@ /* If RLIMIT_RTTIME is not defined, then we cannot use RLIMIT_NLIMITS as is */ #define _RLIMIT_MAX (RLIMIT_RTTIME+1 > RLIMIT_NLIMITS ? RLIMIT_RTTIME+1 : RLIMIT_NLIMITS) +#ifndef __NR_getrandom +# if defined __x86_64__ +# define __NR_getrandom 318 +# elif defined(__i386__) +# define __NR_getrandom 355 +# elif defined(__arm__) || defined(__aarch64__) +# define __NR_getrandom 384 +# elif defined(__ia64__) +# define __NR_getrandom 1339 +# elif defined(__m68k__) +# define __NR_getrandom 352 +# elif defined(__s390x__) +# define __NR_getrandom 349 +# else +# warning "__NR_getrandom unknown for your architecture" +# define __NR_getrandom 0xffffffff +# endif +#endif + +#if !HAVE_DECL_GETRANDOM +static inline int getrandom(void *buffer, size_t count, unsigned flags) { + return syscall(__NR_getrandom, buffer, count, flags); +} +#endif + +#ifndef GRND_NONBLOCK +#define GRND_NONBLOCK 0x0001 +#endif + +#ifndef GRND_RANDOM +#define GRND_RANDOM 0x0002 +#endif + #ifndef BTRFS_IOCTL_MAGIC #define BTRFS_IOCTL_MAGIC 0x94 #endif diff --git a/src/shared/mkdir.c b/src/shared/mkdir.c index 3bc5131e40..dc400d9b91 100644 --- a/src/shared/mkdir.c +++ b/src/shared/mkdir.c @@ -42,8 +42,8 @@ int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkd if ((st.st_mode & 0007) > (mode & 0007) || (st.st_mode & 0070) > (mode & 0070) || (st.st_mode & 0700) > (mode & 0700) || - (uid != (uid_t) -1 && st.st_uid != uid) || - (gid != (gid_t) -1 && st.st_gid != gid) || + (uid != UID_INVALID && st.st_uid != uid) || + (gid != GID_INVALID && st.st_gid != gid) || !S_ISDIR(st.st_mode)) { errno = EEXIST; return -errno; diff --git a/src/shared/path-util.c b/src/shared/path-util.c index ccd5c7ecc6..b1f997aef3 100644 --- a/src/shared/path-util.c +++ b/src/shared/path-util.c @@ -112,7 +112,7 @@ char *path_make_absolute_cwd(const char *p) { if (!cwd) return NULL; - return path_make_absolute(p, cwd); + return strjoin(cwd, "/", p, NULL); } char **path_strv_resolve(char **l, const char *prefix) { @@ -183,6 +183,7 @@ char **path_strv_resolve(char **l, const char *prefix) { } else { /* canonicalized path goes outside of * prefix, keep the original path instead */ + free(u); u = orig; orig = NULL; } @@ -318,10 +319,11 @@ int path_is_mount_point(const char *t, bool allow_symlink) { .handle.handle_bytes = MAX_HANDLE_SZ }; - int mount_id, mount_id_parent; + int mount_id = -1, mount_id_parent = -1; _cleanup_free_ char *parent = NULL; struct stat a, b; int r; + bool nosupp = false; /* We are not actually interested in the file handles, but * name_to_handle_at() also passes us the mount ID, hence use @@ -332,16 +334,19 @@ int path_is_mount_point(const char *t, bool allow_symlink) { r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0); if (r < 0) { - if (IN_SET(errno, ENOSYS, EOPNOTSUPP)) + if (errno == ENOSYS) + /* This kernel does not support name_to_handle_at() + * fall back to the traditional stat() logic. */ + goto fallback; + else if (errno == EOPNOTSUPP) /* This kernel or file system does not support * name_to_handle_at(), hence fallback to the * traditional stat() logic */ - goto fallback; - - if (errno == ENOENT) + nosupp = true; + else if (errno == ENOENT) return 0; - - return -errno; + else + return -errno; } r = path_get_parent(t, &parent); @@ -349,18 +354,23 @@ int path_is_mount_point(const char *t, bool allow_symlink) { return r; h.handle.handle_bytes = MAX_HANDLE_SZ; - r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, 0); - if (r < 0) { - /* The parent can't do name_to_handle_at() but the - * directory we are interested in can? If so, it must - * be a mount point */ + r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, AT_SYMLINK_FOLLOW); + if (r < 0) if (errno == EOPNOTSUPP) - return 1; - - return -errno; - } - - return mount_id != mount_id_parent; + if (nosupp) + /* Neither parent nor child do name_to_handle_at()? + We have no choice but to fall back. */ + goto fallback; + else + /* The parent can't do name_to_handle_at() but + * the directory we are interested in can? + * Or the other way around? + * If so, it must be a mount point. */ + return 1; + else + return -errno; + else + return mount_id != mount_id_parent; fallback: if (allow_symlink) @@ -375,11 +385,14 @@ fallback: return -errno; } + free(parent); + parent = NULL; + r = path_get_parent(t, &parent); if (r < 0) return r; - r = lstat(parent, &b); + r = stat(parent, &b); if (r < 0) return -errno; diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index e2ccef2865..7f8cc0eb76 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -231,7 +231,7 @@ int mac_selinux_get_our_label(char **label) { return r; } -int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, char **label) { +int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) { int r = -EOPNOTSUPP; #ifdef HAVE_SELINUX @@ -255,11 +255,7 @@ int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, char **label if (r < 0) return -errno; - r = getexeccon(&fcon); - if (r < 0) - return -errno; - - if (!fcon) { + if (!exec_label) { /* If there is no context set for next exec let's use context of target executable */ r = getfilecon(exe, &fcon); @@ -330,9 +326,13 @@ int mac_selinux_create_file_prepare(const char *path, mode_t mode) { r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode); } - if (r < 0 && errno != ENOENT) + /* No context specified by the policy? Proceed without setting it. */ + if (r < 0 && errno == ENOENT) + return 0; + + if (r < 0) r = -errno; - else if (r == 0) { + else { r = setfscreatecon(filecon); if (r < 0) { log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, path); diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h index dffd144513..744b1bcf45 100644 --- a/src/shared/selinux-util.h +++ b/src/shared/selinux-util.h @@ -35,7 +35,7 @@ int mac_selinux_apply(const char *path, const char *label); int mac_selinux_get_create_label_from_exe(const char *exe, char **label); int mac_selinux_get_our_label(char **label); -int mac_selinux_get_child_mls_label(int socket_fd, const char *exec, char **label); +int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label); void mac_selinux_free(char *label); int mac_selinux_create_file_prepare(const char *path, mode_t mode); diff --git a/src/shared/set.c b/src/shared/set.c deleted file mode 100644 index 087d874598..0000000000 --- a/src/shared/set.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - This file is part of eudev, forked from systemd. - - Copyright 2010 Lennart Poettering - - 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. - - 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <stdlib.h> - -#include "set.h" -#include "hashmap.h" - -#define MAKE_SET(h) ((Set*) (h)) -#define MAKE_HASHMAP(s) ((Hashmap*) (s)) - -/* For now this is not much more than a wrapper around a hashmap */ - -Set *set_new(const struct hash_ops *hash_ops) { - return MAKE_SET(hashmap_new(hash_ops)); -} - -void set_free(Set* s) { - hashmap_free(MAKE_HASHMAP(s)); -} - -int set_put(Set *s, void *value) { - return hashmap_put(MAKE_HASHMAP(s), value, value); -} - -void *set_get(Set *s, void *value) { - return hashmap_get(MAKE_HASHMAP(s), value); -} - -bool set_contains(Set *s, void *value) { - return hashmap_contains(MAKE_HASHMAP(s), value); -} - -void *set_iterate(Set *s, Iterator *i) { - return hashmap_iterate(MAKE_HASHMAP(s), i, NULL); -} - -int set_reserve(Set *s, unsigned entries_add) { - return hashmap_reserve(MAKE_HASHMAP(s), entries_add); -} diff --git a/src/shared/set.h b/src/shared/set.h index ae3090a09f..4605ecd2c1 100644 --- a/src/shared/set.h +++ b/src/shared/set.h @@ -1,5 +1,9 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + /*** - This file is part of eudev, forked from systemd. + This file is part of systemd. Copyright 2010 Lennart Poettering @@ -17,29 +21,114 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#pragma once - -/* Pretty straightforward set implementation. Internally based on the - * hashmap. That means that as a minor optimization a NULL set - * object will be treated as empty set for all read - * operations. That way it is not necessary to instantiate an object - * for each set use. */ - #include "hashmap.h" #include "util.h" -typedef struct Set Set; +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) + + +static inline void set_free(Set *s) { + internal_hashmap_free(HASHMAP_BASE(s)); +} + +static inline void set_free_free(Set *s) { + internal_hashmap_free_free(HASHMAP_BASE(s)); +} + +/* no set_free_free_free */ + +static inline Set *set_copy(Set *s) { + return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); +} + +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) + +int set_put(Set *s, const void *key); +/* no set_update */ +/* no set_replace */ +static inline void *set_get(Set *s, void *key) { + return internal_hashmap_get(HASHMAP_BASE(s), key); +} +/* no set_get2 */ + +static inline bool set_contains(Set *s, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(s), key); +} + +static inline void *set_remove(Set *s, void *key) { + return internal_hashmap_remove(HASHMAP_BASE(s), key); +} -Set *set_new(const struct hash_ops *hash_ops); -void set_free(Set* s); +/* no set_remove2 */ +/* no set_remove_value */ +int set_remove_and_put(Set *s, const void *old_key, const void *new_key); +/* no set_remove_and_replace */ +int set_merge(Set *s, Set *other); -int set_put(Set *s, void *value); -void *set_get(Set *s, void *value); -bool set_contains(Set *s, void *value); +static inline int set_reserve(Set *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} -int set_reserve(Set *s, unsigned entries_add); +static inline int set_move(Set *s, Set *other) { + return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other)); +} + +static inline int set_move_one(Set *s, Set *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); +} + +static inline unsigned set_size(Set *s) { + return internal_hashmap_size(HASHMAP_BASE(s)); +} + +static inline bool set_isempty(Set *s) { + return set_size(s) == 0; +} + +static inline unsigned set_buckets(Set *s) { + return internal_hashmap_buckets(HASHMAP_BASE(s)); +} void *set_iterate(Set *s, Iterator *i); +static inline void set_clear(Set *s) { + internal_hashmap_clear(HASHMAP_BASE(s)); +} + +static inline void set_clear_free(Set *s) { + internal_hashmap_clear_free(HASHMAP_BASE(s)); +} + +/* no set_clear_free_free */ + +static inline void *set_steal_first(Set *s) { + return internal_hashmap_steal_first(HASHMAP_BASE(s)); +} + +/* no set_steal_first_key */ +/* no set_first_key */ + +static inline void *set_first(Set *s) { + return internal_hashmap_first(HASHMAP_BASE(s)); +} + +/* no set_next */ + +static inline char **set_get_strv(Set *s) { + return internal_hashmap_get_strv(HASHMAP_BASE(s)); +} + +int set_consume(Set *s, void *value); +int set_put_strdup(Set *s, const char *p); +int set_put_strdupv(Set *s, char **l); + +#define SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i))) + DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); + #define _cleanup_set_free_ _cleanup_(set_freep) +#define _cleanup_set_free_free_ _cleanup_(set_free_freep) diff --git a/src/shared/smack-util.c b/src/shared/smack-util.c index 407a8e1aa3..65bbb94e7c 100644 --- a/src/shared/smack-util.c +++ b/src/shared/smack-util.c @@ -25,6 +25,7 @@ #include "util.h" #include "path-util.h" +#include "fileio.h" #include "smack-util.h" #define SMACK_FLOOR_LABEL "_" @@ -123,14 +124,38 @@ int mac_smack_apply_ip_in_fd(int fd, const char *label) { return r; } -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { +int mac_smack_apply_pid(pid_t pid, const char *label) { + +#ifdef HAVE_SMACK + const char *p; +#endif int r = 0; + assert(label); + +#ifdef HAVE_SMACK + if (!mac_smack_use()) + return 0; + + p = procfs_file_alloca(pid, "attr/current"); + r = write_string_file(p, label); + if (r < 0) + return r; +#endif + + return r; +} + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + #ifdef HAVE_SMACK struct stat st; +#endif + int r = 0; assert(path); +#ifdef HAVE_SMACK if (!mac_smack_use()) return 0; @@ -174,8 +199,7 @@ int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { if (ignore_erofs && errno == EROFS) return 0; - log_debug("Unable to fix SMACK label of %s: %m", path); - r = -errno; + r = log_debug_errno(errno, "Unable to fix SMACK label of %s: %m", path); } #endif diff --git a/src/shared/smack-util.h b/src/shared/smack-util.h index 22aef5ac23..587c135308 100644 --- a/src/shared/smack-util.h +++ b/src/shared/smack-util.h @@ -29,5 +29,6 @@ int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs); int mac_smack_apply(const char *path, const char *label); int mac_smack_apply_fd(int fd, const char *label); +int mac_smack_apply_pid(pid_t pid, const char *label); int mac_smack_apply_ip_in_fd(int fd, const char *label); int mac_smack_apply_ip_out_fd(int fd, const char *label); diff --git a/src/shared/sparse-endian.h b/src/shared/sparse-endian.h index 51694bebb7..c913fda8c5 100644 --- a/src/shared/sparse-endian.h +++ b/src/shared/sparse-endian.h @@ -21,9 +21,9 @@ #ifndef SPARSE_ENDIAN_H #define SPARSE_ENDIAN_H +#include <byteswap.h> #include <endian.h> #include <stdint.h> -#include <byteswap.h> #ifdef __CHECKER__ #define __bitwise __attribute__((bitwise)) diff --git a/src/shared/strv.c b/src/shared/strv.c index 85ae556c16..5cfab8f23e 100644 --- a/src/shared/strv.c +++ b/src/shared/strv.c @@ -26,7 +26,7 @@ #include "util.h" #include "strv.h" -void strv_free(char **l) { +void strv_clear(char **l) { char **k; if (!l) @@ -35,6 +35,11 @@ void strv_free(char **l) { for (k = l; *k; k++) free(*k); + *l = NULL; +} + +void strv_free(char **l) { + strv_clear(l); free(l); } diff --git a/src/shared/strv.h b/src/shared/strv.h index 22c8132c57..94bdbe62aa 100644 --- a/src/shared/strv.h +++ b/src/shared/strv.h @@ -28,6 +28,8 @@ void strv_free(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); #define _cleanup_strv_free_ _cleanup_(strv_freep) +void strv_clear(char **l); + char **strv_copy(char * const *l); unsigned strv_length(char * const *l) _pure_; diff --git a/src/shared/utf8.c b/src/shared/utf8.c index 403166d7f9..fec2f4df34 100644 --- a/src/shared/utf8.c +++ b/src/shared/utf8.c @@ -148,10 +148,12 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { int encoded_len, val; encoded_len = utf8_encoded_valid_unichar((const char *) p); - val = utf8_encoded_to_unichar((const char*) p); - if (encoded_len < 0 || - val < 0 || + (size_t) encoded_len > length) + return false; + + val = utf8_encoded_to_unichar((const char*) p); + if (val < 0 || is_unicode_control(val) || (!newline && val == '\n')) return false; @@ -175,39 +177,91 @@ char *ascii_is_valid(const char *str) { return (char*) str; } +/** + * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 + * @out_utf8: output buffer of at least 4 bytes or NULL + * @g: UCS-4 character to encode + * + * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8. + * The length of the character is returned. It is not zero-terminated! If the + * output buffer is NULL, only the length is returned. + * + * Returns: The length in bytes that the UTF-8 representation does or would + * occupy. + */ +size_t utf8_encode_unichar(char *out_utf8, uint32_t g) { + if (g < (1 << 7)) { + if (out_utf8) + out_utf8[0] = g & 0x7f; + return 1; + } else if (g < (1 << 11)) { + if (out_utf8) { + out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f); + out_utf8[1] = 0x80 | (g & 0x3f); + } + return 2; + } else if (g < (1 << 16)) { + if (out_utf8) { + out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f); + out_utf8[1] = 0x80 | ((g >> 6) & 0x3f); + out_utf8[2] = 0x80 | (g & 0x3f); + } + return 3; + } else if (g < (1 << 21)) { + if (out_utf8) { + out_utf8[0] = 0xf0 | ((g >> 18) & 0x07); + out_utf8[1] = 0x80 | ((g >> 12) & 0x3f); + out_utf8[2] = 0x80 | ((g >> 6) & 0x3f); + out_utf8[3] = 0x80 | (g & 0x3f); + } + return 4; + } else { + return 0; + } +} + char *utf16_to_utf8(const void *s, size_t length) { - char *r; const uint8_t *f; - uint8_t *t; + char *r, *t; - r = new(char, (length*3+1)/2 + 1); + r = new(char, (length * 4 + 1) / 2 + 1); if (!r) return NULL; - t = (uint8_t*) r; - - for (f = s; f < (const uint8_t*) s + length; f += 2) { - uint16_t c; - - c = (f[1] << 8) | f[0]; - - if (c == 0) { - *t = 0; - return r; - } else if (c < 0x80) { - *(t++) = (uint8_t) c; - } else if (c < 0x800) { - *(t++) = (uint8_t) (0xc0 | (c >> 6)); - *(t++) = (uint8_t) (0x80 | (c & 0x3f)); - } else { - *(t++) = (uint8_t) (0xe0 | (c >> 12)); - *(t++) = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); - *(t++) = (uint8_t) (0x80 | (c & 0x3f)); + f = s; + t = r; + + while (f < (const uint8_t*) s + length) { + uint16_t w1, w2; + + /* see RFC 2781 section 2.2 */ + + w1 = f[1] << 8 | f[0]; + f += 2; + + if (!utf16_is_surrogate(w1)) { + t += utf8_encode_unichar(t, w1); + + continue; } + + if (utf16_is_trailing_surrogate(w1)) + continue; + else if (f >= (const uint8_t*) s + length) + break; + + w2 = f[1] << 8 | f[0]; + f += 2; + + if (!utf16_is_trailing_surrogate(w2)) { + f -= 2; + continue; + } + + t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2)); } *t = 0; - return r; } diff --git a/src/shared/utf8.h b/src/shared/utf8.h index 24232247f5..143b0ba08c 100644 --- a/src/shared/utf8.h +++ b/src/shared/utf8.h @@ -30,7 +30,20 @@ _pure_ static inline bool utf8_is_printable(const char* str, size_t length) { return utf8_is_printable_newline(str, length, true); } +size_t utf8_encode_unichar(char *out_utf8, uint32_t g); char *utf16_to_utf8(const void *s, size_t length); int utf8_encoded_valid_unichar(const char *str); int utf8_encoded_to_unichar(const char *str); + +static inline bool utf16_is_surrogate(uint16_t c) { + return (0xd800 <= c && c <= 0xdfff); +} + +static inline bool utf16_is_trailing_surrogate(uint16_t c) { + return (0xdc00 <= c && c <= 0xdfff); +} + +static inline uint32_t utf16_surrogate_pair_to_unichar(uint16_t lead, uint16_t trail) { + return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; +} diff --git a/src/shared/util.c b/src/shared/util.c index 3cad297873..6ef53b05ef 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -37,7 +37,6 @@ #include <linux/tiocl.h> #include <termios.h> #include <stdarg.h> -#include <sys/inotify.h> #include <poll.h> #include <ctype.h> #include <sys/prctl.h> @@ -124,6 +123,69 @@ char* endswith(const char *s, const char *postfix) { return (char*) s + sl - pl; } +static size_t cescape_char(char c, char *buf) { + char * buf_old = buf; + + switch (c) { + + case '\a': + *(buf++) = '\\'; + *(buf++) = 'a'; + break; + case '\b': + *(buf++) = '\\'; + *(buf++) = 'b'; + break; + case '\f': + *(buf++) = '\\'; + *(buf++) = 'f'; + break; + case '\n': + *(buf++) = '\\'; + *(buf++) = 'n'; + break; + case '\r': + *(buf++) = '\\'; + *(buf++) = 'r'; + break; + case '\t': + *(buf++) = '\\'; + *(buf++) = 't'; + break; + case '\v': + *(buf++) = '\\'; + *(buf++) = 'v'; + break; + case '\\': + *(buf++) = '\\'; + *(buf++) = '\\'; + break; + case '"': + *(buf++) = '\\'; + *(buf++) = '"'; + break; + case '\'': + *(buf++) = '\\'; + *(buf++) = '\''; + break; + + default: + /* For special chars we prefer octal over + * hexadecimal encoding, simply because glib's + * g_strescape() does the same */ + if ((c < ' ') || (c >= 127)) { + *(buf++) = '\\'; + *(buf++) = octchar((unsigned char) c >> 6); + *(buf++) = octchar((unsigned char) c >> 3); + *(buf++) = octchar((unsigned char) c); + } else + *(buf++) = c; + break; + } + + return buf - buf_old; +} + int close_nointr(int fd) { assert(fd >= 0); @@ -197,7 +259,7 @@ int parse_uid(const char *s, uid_t* ret_uid) { if ((unsigned long) uid != ul) return -ERANGE; - /* Some libc APIs use (uid_t) -1 as special placeholder */ + /* Some libc APIs use UID_INVALID as special placeholder */ if (uid == (uid_t) 0xFFFFFFFF) return -ENXIO; @@ -285,7 +347,7 @@ int safe_atolli(const char *s, long long int *ret_lli) { static size_t strcspn_escaped(const char *s, const char *reject) { bool escaped = false; - size_t n; + int n; for (n=0; s[n]; n++) { if (escaped) @@ -295,6 +357,7 @@ static size_t strcspn_escaped(const char *s, const char *reject) { else if (strchr(reject, s[n])) break; } + /* if s ends in \, return index of previous char */ return n - escaped; } @@ -322,7 +385,7 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo *l = strcspn_escaped(current + 1, quotechars); if (current[*l + 1] == '\0' || (current[*l + 2] && !strchr(separator, current[*l + 2]))) { - /* right quote missing or garbage at the end*/ + /* right quote missing or garbage at the end */ *state = current; return NULL; } @@ -330,6 +393,11 @@ const char* split(const char **state, size_t *l, const char *separator, bool quo *state = current++ + *l + 2; } else if (quoted) { *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } *state = current + *l; } else { *l = strcspn(current, separator); @@ -346,6 +414,39 @@ char *truncate_nl(char *s) { return s; } +int get_process_environ(pid_t pid, char **env) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *outcome = NULL; + int c; + const char *p; + size_t allocated = 0, sz = 0; + + assert(pid >= 0); + assert(env); + + p = procfs_file_alloca(pid, "environ"); + + f = fopen(p, "re"); + if (!f) + return -errno; + + while ((c = fgetc(f)) != EOF) { + if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) + return -ENOMEM; + + if (c == '\0') + outcome[sz++] = '\n'; + else + sz += cescape_char(c, outcome + sz); + } + + outcome[sz] = '\0'; + *env = outcome; + outcome = NULL; + + return 0; +} + char *strnappend(const char *s, const char *suffix, size_t b) { size_t a; char *r; @@ -427,6 +528,115 @@ int rmdir_parents(const char *path, const char *stop) { return 0; } +int get_process_comm(pid_t pid, char **name) { + const char *p; + int r; + + assert(name); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "comm"); + + r = read_one_line_file(p, name); + if (r == -ENOENT) + return -ESRCH; + + return r; +} + +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char *r = NULL, *k; + const char *p; + int c; + + assert(line); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cmdline"); + + f = fopen(p, "re"); + if (!f) + return -errno; + + if (max_length == 0) { + size_t len = 0, allocated = 0; + + while ((c = getc(f)) != EOF) { + + if (!GREEDY_REALLOC(r, allocated, len+2)) { + free(r); + return -ENOMEM; + } + + r[len++] = isprint(c) ? c : ' '; + } + + if (len > 0) + r[len-1] = 0; + + } else { + bool space = false; + size_t left; + + r = new(char, max_length); + if (!r) + return -ENOMEM; + + k = r; + left = max_length; + while ((c = getc(f)) != EOF) { + + if (isprint(c)) { + if (space) { + if (left <= 4) + break; + + *(k++) = ' '; + left--; + space = false; + } + + if (left <= 4) + break; + + *(k++) = (char) c; + left--; + } else + space = true; + } + + if (left <= 4) { + size_t n = MIN(left-1, 3U); + memcpy(k, "...", n); + k[n] = 0; + } else + *k = 0; + } + + /* Kernel threads have no argv[] */ + if (isempty(r)) { + _cleanup_free_ char *t = NULL; + int h; + + free(r); + + if (!comm_fallback) + return -ENOENT; + + h = get_process_comm(pid, &t); + if (h < 0) + return h; + + r = strjoin("[", t, "]", NULL); + if (!r) + return -ENOMEM; + } + + *line = r; + return 0; +} + char hexchar(int x) { static const char table[16] = "0123456789abcdef"; @@ -450,63 +660,7 @@ char *cescape(const char *s) { return NULL; for (f = s, t = r; *f; f++) - - switch (*f) { - - case '\a': - *(t++) = '\\'; - *(t++) = 'a'; - break; - case '\b': - *(t++) = '\\'; - *(t++) = 'b'; - break; - case '\f': - *(t++) = '\\'; - *(t++) = 'f'; - break; - case '\n': - *(t++) = '\\'; - *(t++) = 'n'; - break; - case '\r': - *(t++) = '\\'; - *(t++) = 'r'; - break; - case '\t': - *(t++) = '\\'; - *(t++) = 't'; - break; - case '\v': - *(t++) = '\\'; - *(t++) = 'v'; - break; - case '\\': - *(t++) = '\\'; - *(t++) = '\\'; - break; - case '"': - *(t++) = '\\'; - *(t++) = '"'; - break; - case '\'': - *(t++) = '\\'; - *(t++) = '\''; - break; - - default: - /* For special chars we prefer octal over - * hexadecimal encoding, simply because glib's - * g_strescape() does the same */ - if ((*f < ' ') || (*f >= 127)) { - *(t++) = '\\'; - *(t++) = octchar((unsigned char) *f >> 6); - *(t++) = octchar((unsigned char) *f >> 3); - *(t++) = octchar((unsigned char) *f); - } else - *(t++) = *f; - break; - } + t += cescape_char(*f, t); *t = 0; @@ -542,7 +696,7 @@ char *xescape(const char *s, const char *bad) { return r; } -_pure_ static bool ignore_file_allow_backup(const char *filename) { +_pure_ static bool hidden_file_allow_backup(const char *filename) { assert(filename); return @@ -559,13 +713,13 @@ _pure_ static bool ignore_file_allow_backup(const char *filename) { endswith(filename, ".swp"); } -bool ignore_file(const char *filename) { +bool hidden_file(const char *filename) { assert(filename); if (endswith(filename, "~")) return true; - return ignore_file_allow_backup(filename); + return hidden_file_allow_backup(filename); } int open_terminal(const char *name, int mode) { @@ -660,21 +814,25 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { ssize_t k; k = read(fd, p, nbytes); - if (k < 0 && errno == EINTR) - continue; + if (k < 0) { + if (errno == EINTR) + continue; - if (k < 0 && errno == EAGAIN && do_poll) { + if (errno == EAGAIN && do_poll) { - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ - fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; + fd_wait_for_event(fd, POLLIN, USEC_INFINITY); + continue; + } + + return n > 0 ? n : -errno; } - if (k <= 0) - return n > 0 ? n : (k < 0 ? -errno : 0); + if (k == 0) + return n; p += k; nbytes -= k; @@ -685,14 +843,53 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { } int dev_urandom(void *p, size_t n) { - _cleanup_close_ int fd; + static int have_syscall = -1; + int r, fd; ssize_t k; + /* Gathers some randomness from the kernel. This call will + * never block, and will always return some data from the + * kernel, regardless if the random pool is fully initialized + * or not. It thus makes no guarantee for the quality of the + * returned entropy, but is good enough for or usual usecases + * of seeding the hash functions for hashtable */ + + /* Use the getrandom() syscall unless we know we don't have + * it, or when the requested size is too large for it. */ + if (have_syscall != 0 || (size_t) (int) n != n) { + r = getrandom(p, n, GRND_NONBLOCK); + if (r == (int) n) { + have_syscall = true; + return 0; + } + + if (r < 0) { + if (errno == ENOSYS) + /* we lack the syscall, continue with + * reading from /dev/urandom */ + have_syscall = false; + else if (errno == EAGAIN) + /* not enough entropy for now. Let's + * remember to use the syscall the + * next time, again, but also read + * from /dev/urandom for now, which + * doesn't care about the current + * amount of entropy. */ + have_syscall = true; + else + return -errno; + } else + /* too short read? */ + return -EIO; + } + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return errno == ENOENT ? -ENOSYS : -errno; k = loop_read(fd, p, n, true); + safe_close(fd); + if (k < 0) return (int) k; if ((size_t) k != n) @@ -701,8 +898,36 @@ int dev_urandom(void *p, size_t n) { return 0; } -void random_bytes(void *p, size_t n) { +void initialize_srand(void) { static bool srand_called = false; + unsigned x; +#ifdef HAVE_SYS_AUXV_H + void *auxv; +#endif + + if (srand_called) + return; + + x = 0; + +#ifdef HAVE_SYS_AUXV_H + /* The kernel provides us with a bit of entropy in auxv, so + * let's try to make use of that to seed the pseudo-random + * generator. It's better than nothing... */ + + auxv = (void*) getauxval(AT_RANDOM); + if (auxv) + x ^= *(unsigned*) auxv; +#endif + + x ^= (unsigned) now(CLOCK_REALTIME); + x ^= (unsigned) gettid(); + + srand(x); + srand_called = true; +} + +void random_bytes(void *p, size_t n) { uint8_t *q; int r; @@ -713,28 +938,7 @@ void random_bytes(void *p, size_t n) { /* If some idiot made /dev/urandom unavailable to us, he'll * get a PRNG instead. */ - if (!srand_called) { - unsigned x = 0; - -#ifdef HAVE_SYS_AUXV_H - /* The kernel provides us with a bit of entropy in - * auxv, so let's try to make use of that to seed the - * pseudo-random generator. It's better than - * nothing... */ - - void *auxv; - - auxv = (void*) getauxval(AT_RANDOM); - if (auxv) - x ^= *(unsigned*) auxv; -#endif - - x ^= (unsigned) now(CLOCK_REALTIME); - x ^= (unsigned) gettid(); - - srand(x); - srand_called = true; - } + initialize_srand(); for (q = p; q < (uint8_t*) p + n; q ++) *q = rand(); @@ -754,11 +958,11 @@ int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { * first change the access mode and only then hand out * ownership to avoid a window where access is too open. */ - if (mode != (mode_t) -1) + if (mode != MODE_INVALID) if (chmod(path, mode) < 0) return -errno; - if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (uid != UID_INVALID || gid != GID_INVALID) if (chown(path, uid, gid) < 0) return -errno; @@ -807,7 +1011,7 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { de->d_type != DT_UNKNOWN) return false; - if (ignore_file_allow_backup(de->d_name)) + if (hidden_file_allow_backup(de->d_name)) return false; return endswith(de->d_name, suffix); @@ -849,15 +1053,15 @@ int fd_wait_for_event(int fd, int event, usec_t t) { int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { FILE *f; char *t; - int fd; + int r, fd; assert(path); assert(_f); assert(_temp_path); - t = tempfn_xxxxxx(path); - if (!t) - return -ENOMEM; + r = tempfn_xxxxxx(path, &t); + if (r < 0) + return r; #if HAVE_DECL_MKOSTEMP fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); @@ -1248,6 +1452,26 @@ bool in_initrd(void) { return saved; } +bool filename_is_valid(const char *p) { + + if (isempty(p)) + return false; + + if (strchr(p, '/')) + return false; + + if (streq(p, ".")) + return false; + + if (streq(p, "..")) + return false; + + if (strlen(p) > FILENAME_MAX) + return false; + + return true; +} + /* hey glibc, APIs with callbacks without a user pointer are so useless */ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *, void *), void *arg) { @@ -1271,31 +1495,39 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, return NULL; } -int proc_cmdline(char **ret) { - int r; +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { + size_t a, newalloc; + void *q; - if (detect_container(NULL) > 0) { - char *buf = NULL, *p; - size_t sz = 0; + assert(p); + assert(allocated); - r = read_full_file("/proc/1/cmdline", &buf, &sz); - if (r < 0) - return r; + if (*allocated >= need) + return *p; - for (p = buf; p + 1 < buf + sz; p++) - if (*p == 0) - *p = ' '; + newalloc = MAX(need * 2, 64u / size); + a = newalloc * size; - *p = 0; - *ret = buf; - return 1; - } + /* check for overflows */ + if (a < size * need) + return NULL; - r = read_one_line_file("/proc/cmdline", ret); - if (r < 0) - return r; + q = realloc(*p, a); + if (!q) + return NULL; - return 1; + *p = q; + *allocated = newalloc; + return q; +} + +int proc_cmdline(char **ret) { + assert(ret); + + if (detect_container(NULL) > 0) + return get_process_cmdline(1, 0, false, ret); + else + return read_one_line_file("/proc/cmdline", ret); } int getpeercred(int fd, struct ucred *ucred) { @@ -1317,6 +1549,10 @@ int getpeercred(int fd, struct ucred *ucred) { * to namespacing issues */ if (u.pid <= 0) return -ENODATA; + if (u.uid == UID_INVALID) + return -ENODATA; + if (u.gid == GID_INVALID) + return -ENODATA; *ucred = u; return 0; @@ -1354,35 +1590,45 @@ int mkstemp_safe(char *pattern) { return fd; } -char *tempfn_xxxxxx(const char *p) { +int tempfn_xxxxxx(const char *p, char **ret) { const char *fn; char *t; - size_t k; assert(p); + assert(ret); - t = new(char, strlen(p) + 1 + 6 + 1); - if (!t) - return NULL; + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldoXXXXXX + */ fn = basename(p); - k = fn - p; + if (!filename_is_valid(fn)) + return -EINVAL; - strcpy(stpcpy(stpcpy(mempcpy(t, p, k), "."), fn), "XXXXXX"); + t = new(char, strlen(p) + 2 + 6 + 1); + if (!t) + return -ENOMEM; - return t; + strcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), "XXXXXX"); + + *ret = path_kill_slashes(t); + return 0; } int is_dir(const char* path, bool follow) { struct stat st; + int r; - if (follow) { - if (stat(path, &st) < 0) - return -errno; - } else { - if (lstat(path, &st) < 0) - return -errno; - } + if (follow) + r = stat(path, &st); + else + r = lstat(path, &st); + if (r < 0) + return -errno; return !!S_ISDIR(st.st_mode); } @@ -1396,7 +1642,7 @@ int execute_command(const char *command, char *const argv[]) { return status; if ((pid = fork()) < 0) { - log_error("Failed to fork: %m"); + log_error_errno(errno, "Failed to fork: %m"); return pid; } @@ -1404,7 +1650,7 @@ int execute_command(const char *command, char *const argv[]) { execvp(command, argv); - log_error("Failed to execute %s: %m", command); + log_error_errno(errno, "Failed to execute %s: %m", command); _exit(EXIT_FAILURE); } else while (1) diff --git a/src/shared/util.h b/src/shared/util.h index 9550aa19f9..bce4f0fa77 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -31,6 +31,7 @@ #include <stddef.h> #include <unistd.h> #include <sys/socket.h> +#include <sys/inotify.h> #include "time-util.h" #include "missing.h" @@ -62,7 +63,7 @@ #if SIZEOF_TIME_T == 8 # define PRI_TIME PRIu64 -#elif SIZEOF_GID_T == 4 +#elif SIZEOF_TIME_T == 4 # define PRI_TIME PRIu32 #else # error Unknown time_t size @@ -98,7 +99,7 @@ #define ANSI_HIGHLIGHT_OFF "\x1B[0m" #define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" -size_t page_size(void); +size_t page_size(void) _pure_; #define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) #define streq(a,b) (strcmp((a),(b)) == 0) @@ -113,6 +114,10 @@ bool streq_ptr(const char *a, const char *b) _pure_; #define new0(t, n) ((t*) calloc((n), sizeof(t))) #define malloc0(n) (calloc((n), 1)) +static inline const char* one_zero(bool b) { + return b ? "1" : "0"; +} + static inline bool isempty(const char *p) { return !p || !p[0]; } @@ -142,7 +147,7 @@ int safe_atollu(const char *s, unsigned long long *ret_u); int safe_atolli(const char *s, long long int *ret_i); -#if __WORDSIZE == 32 +#if LONG_MAX == INT_MAX static inline int safe_atolu(const char *s, unsigned long *ret_u) { assert_cc(sizeof(unsigned long) == sizeof(unsigned)); return safe_atou(s, (unsigned*) ret_u); @@ -180,6 +185,11 @@ char *strnappend(const char *s, const char *suffix, size_t length); char *truncate_nl(char *s); int rmdir_parents(const char *path, const char *stop); + +int get_process_comm(pid_t pid, char **name); +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); +int get_process_environ(pid_t pid, char **environ); + char hexchar(int x) _const_; char octchar(int x) _const_; @@ -188,10 +198,11 @@ char *xescape(const char *s, const char *bad); bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; -bool ignore_file(const char *filename) _pure_; +bool hidden_file(const char *filename) _pure_; int dev_urandom(void *p, size_t n); void random_bytes(void *p, size_t n); +void initialize_srand(void); /* For basic lookup tables with strictly enumerated entries */ #define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ @@ -348,6 +359,7 @@ _alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t a, size_t b) return realloc(p, a * b); } +bool filename_is_valid(const char *p) _pure_; /** * Check if a string contains any glob patterns. */ @@ -368,12 +380,36 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, break; \ } else +static inline void *mempset(void *s, int c, size_t n) { + memset(s, c, n); + return (uint8_t*)s + n; +} + + +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); +#define GREEDY_REALLOC(array, allocated, need) \ + greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0])) static inline void _reset_errno_(int *saved_errno) { errno = *saved_errno; } #define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno +static inline unsigned log2u(unsigned x) { + assert(x > 0); + + return sizeof(unsigned) * 8 - __builtin_clz(x) - 1; +} + +static inline unsigned log2u_round_up(unsigned x) { + assert(x > 0); + + if (x == 1) + return 0; + + return log2u(x - 1) + 1; +} + int unlink_noerrno(const char *path); #define strappenda(a, ...) \ @@ -391,6 +427,19 @@ int unlink_noerrno(const char *path); _d_; \ }) +#define procfs_file_alloca(pid, field) \ + ({ \ + pid_t _pid_ = (pid); \ + const char *_r_; \ + if (_pid_ == 0) { \ + _r_ = ("/proc/self/" field); \ + } else { \ + _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ + sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_); \ + } \ + _r_; \ + }) + static inline void qsort_safe(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { if (nmemb) { @@ -410,8 +459,20 @@ union file_handle_union { char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ]; }; -char *tempfn_xxxxxx(const char *p); +int tempfn_xxxxxx(const char *p, char **ret); int is_dir(const char *path, bool follow); int execute_command(const char *command, char *const argv[]); + +#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) + +#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ + for ((e) = &buffer.ev; \ + (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ + (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) + +union inotify_event_buffer { + struct inotify_event ev; + uint8_t raw[INOTIFY_EVENT_MAX]; +}; diff --git a/src/udev/Makefile.am b/src/udev/Makefile.am index f9af997ffe..652cb67006 100644 --- a/src/udev/Makefile.am +++ b/src/udev/Makefile.am @@ -34,7 +34,10 @@ udevadm_SOURCES = \ udevadm-settle.c \ udevadm-trigger.c \ udevadm-test.c \ - udevadm-test-builtin.c + udevadm-test-builtin.c \ + udevadm-util.c \ + udevadm-util.h + udevadm_LDADD = \ libudev-core.la diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index f97f516462..93563d867b 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -138,6 +138,7 @@ static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool t blkid_probe pr; const char *data; const char *name; + const char *prtype = NULL; int nvals; int i; int err = 0; @@ -172,7 +173,8 @@ static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool t blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | - BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION); + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION | + BLKID_SUBLKS_BADCSUM); if (noraid) blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); @@ -194,6 +196,15 @@ static int builtin_blkid(struct udev_device *dev, int argc, char *argv[], bool t err = probe_superblocks(pr); if (err < 0) goto out; + if (blkid_probe_has_value(pr, "SBBADCSUM")) { + if (!blkid_probe_lookup_value(pr, "TYPE", &prtype, NULL)) + log_warning("incorrect %s checksum on %s", + prtype, udev_device_get_devnode(dev)); + else + log_warning("incorrect checksum on %s", + udev_device_get_devnode(dev)); + goto out; + } nvals = blkid_probe_numof_values(pr); for (i = 0; i < nvals; i++) { @@ -214,6 +225,6 @@ out: const struct udev_builtin udev_builtin_blkid = { .name = "blkid", .cmd = builtin_blkid, - .help = "filesystem and partition probing", + .help = "Filesystem and partition probing", .run_once = true, }; diff --git a/src/udev/udev-builtin-btrfs.c b/src/udev/udev-builtin-btrfs.c index 2e2112fd92..3643596a70 100644 --- a/src/udev/udev-builtin-btrfs.c +++ b/src/udev/udev-builtin-btrfs.c @@ -24,6 +24,7 @@ #include <fcntl.h> #include <errno.h> #include <sys/ioctl.h> + #ifdef HAVE_LINUX_BTRFS_H #include <linux/btrfs.h> #endif @@ -32,7 +33,7 @@ #include "udev.h" static int builtin_btrfs(struct udev_device *dev, int argc, char *argv[], bool test) { - struct btrfs_ioctl_vol_args args; + struct btrfs_ioctl_vol_args args = {}; _cleanup_close_ int fd = -1; int err; @@ -48,7 +49,7 @@ static int builtin_btrfs(struct udev_device *dev, int argc, char *argv[], bool t if (err < 0) return EXIT_FAILURE; - udev_builtin_add_property(dev, test, "ID_BTRFS_READY", err == 0 ? "1" : "0"); + udev_builtin_add_property(dev, test, "ID_BTRFS_READY", one_zero(err == 0)); return EXIT_SUCCESS; } diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 695a31a12f..1eddf86b61 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -214,5 +214,5 @@ const struct udev_builtin udev_builtin_hwdb = { .init = builtin_hwdb_init, .exit = builtin_hwdb_exit, .validate = builtin_hwdb_validate, - .help = "hardware database", + .help = "Hardware database", }; diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c index 1a1121ef94..26c723225d 100644 --- a/src/udev/udev-builtin-input_id.c +++ b/src/udev/udev-builtin-input_id.c @@ -1,9 +1,11 @@ /* - * compose persistent device path + * expose input properties via udev * * Copyright (C) 2009 Martin Pitt <martin.pitt@ubuntu.com> * Portions Copyright (C) 2004 David Zeuthen, <david@fubar.dk> * Copyright (C) 2011 Kay Sievers <kay@vrfy.org> + * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org> + * Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +31,7 @@ #include <linux/input.h> #include "udev.h" +#include "util.h" /* we must use this kernel-compatible implementation */ #define BITS_PER_LONG (sizeof(unsigned long) * 8) @@ -38,6 +41,34 @@ #define LONG(x) ((x)/BITS_PER_LONG) #define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) +static inline int abs_size_mm(const struct input_absinfo *absinfo) { + /* Resolution is defined to be in units/mm for ABS_X/Y */ + return (absinfo->maximum - absinfo->minimum) / absinfo->resolution; +} + +static void extract_info(struct udev_device *dev, const char *devpath, bool test) { + char width[DECIMAL_STR_MAX(int)], height[DECIMAL_STR_MAX(int)]; + struct input_absinfo xabsinfo = {}, yabsinfo = {}; + _cleanup_close_ int fd = -1; + + fd = open(devpath, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return; + + if (ioctl(fd, EVIOCGABS(ABS_X), &xabsinfo) < 0 || + ioctl(fd, EVIOCGABS(ABS_Y), &yabsinfo) < 0) + return; + + if (xabsinfo.resolution <= 0 || yabsinfo.resolution <= 0) + return; + + snprintf(width, sizeof(width), "%d", abs_size_mm(&xabsinfo)); + snprintf(height, sizeof(height), "%d", abs_size_mm(&yabsinfo)); + + udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width); + udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height); +} + /* * Read a capability attribute and return bitmask. * @param dev udev_device @@ -48,12 +79,17 @@ static void get_cap_mask(struct udev_device *dev, struct udev_device *pdev, const char* attr, unsigned long *bitmask, size_t bitmask_size, bool test) { + const char *v; char text[4096]; unsigned i; char* word; unsigned long val; - snprintf(text, sizeof(text), "%s", udev_device_get_sysattr_value(pdev, attr)); + v = udev_device_get_sysattr_value(pdev, attr); + if (!v) + v = ""; + + snprintf(text, sizeof(text), "%s", v); log_debug("%s raw kernel attribute: %s", attr, text); memzero(bitmask, bitmask_size); @@ -196,6 +232,7 @@ static int builtin_input_id(struct udev_device *dev, int argc, char *argv[], boo unsigned long bitmask_abs[NBITS(ABS_MAX)]; unsigned long bitmask_key[NBITS(KEY_MAX)]; unsigned long bitmask_rel[NBITS(REL_MAX)]; + const char *sysname, *devnode; /* walk up the parental chain until we find the real input device; the * argument is very likely a subdevice of this, like eventN */ @@ -203,24 +240,28 @@ static int builtin_input_id(struct udev_device *dev, int argc, char *argv[], boo while (pdev != NULL && udev_device_get_sysattr_value(pdev, "capabilities/ev") == NULL) pdev = udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL); - /* not an "input" class device */ - if (pdev == NULL) - return EXIT_SUCCESS; - - /* Use this as a flag that input devices were detected, so that this - * program doesn't need to be called more than once per device */ - udev_builtin_add_property(dev, test, "ID_INPUT", "1"); - get_cap_mask(dev, pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test); - get_cap_mask(dev, pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test); - get_cap_mask(dev, pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test); - get_cap_mask(dev, pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test); - test_pointers(dev, bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel, test); - test_key(dev, bitmask_ev, bitmask_key, test); + if (pdev) { + /* Use this as a flag that input devices were detected, so that this + * program doesn't need to be called more than once per device */ + udev_builtin_add_property(dev, test, "ID_INPUT", "1"); + get_cap_mask(dev, pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test); + get_cap_mask(dev, pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test); + get_cap_mask(dev, pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test); + get_cap_mask(dev, pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test); + test_pointers(dev, bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel, test); + test_key(dev, bitmask_ev, bitmask_key, test); + } + + devnode = udev_device_get_devnode(dev); + sysname = udev_device_get_sysname(dev); + if (devnode && sysname && startswith(sysname, "event")) + extract_info(dev, devnode, test); + return EXIT_SUCCESS; } const struct udev_builtin udev_builtin_input_id = { .name = "input_id", .cmd = builtin_input_id, - .help = "input device properties", + .help = "Input device properties", }; diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c index d6b7dbbac0..118e797c96 100644 --- a/src/udev/udev-builtin-keyboard.c +++ b/src/udev/udev-builtin-keyboard.c @@ -62,7 +62,7 @@ static int install_force_release(struct udev_device *dev, const unsigned int *re log_debug("keyboard: updating force-release list with '%s'", codes); ret = udev_device_set_sysattr_value(atkbd, "force_release", codes); if (ret < 0) - log_error("Error writing force-release attribute: %s", strerror(-ret)); + log_error_errno(ret, "Error writing force-release attribute: %m"); return ret; } @@ -140,7 +140,7 @@ static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], boo fd = open(udev_device_get_devnode(dev), O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (fd < 0) { - log_error("Error, opening device '%s': %m", node); + log_error_errno(errno, "Error, opening device '%s': %m", node); return EXIT_FAILURE; } @@ -149,7 +149,7 @@ static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], boo log_debug("keyboard: mapping scan code %d (0x%x) to key code %d (0x%x)", map[i].scan, map[i].scan, map[i].key, map[i].key); if (ioctl(fd, EVIOCSKEYCODE, &map[i]) < 0) - log_error("Error calling EVIOCSKEYCODE on device node '%s' (scan code 0x%x, key code %d): %m", node, map[i].scan, map[i].key); + log_error_errno(errno, "Error calling EVIOCSKEYCODE on device node '%s' (scan code 0x%x, key code %d): %m", node, map[i].scan, map[i].key); } /* install list of force-release codes */ @@ -165,5 +165,5 @@ static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], boo const struct udev_builtin udev_builtin_keyboard = { .name = "keyboard", .cmd = builtin_keyboard, - .help = "keyboard scan code to key mapping", + .help = "Keyboard scan code to key mapping", }; diff --git a/src/udev/udev-builtin-kmod.c b/src/udev/udev-builtin-kmod.c index b83fd523d9..f39b01a452 100644 --- a/src/udev/udev-builtin-kmod.c +++ b/src/udev/udev-builtin-kmod.c @@ -34,7 +34,7 @@ #include "udev.h" -static struct kmod_ctx *ctx; +static struct kmod_ctx *ctx = NULL; static int load_module(struct udev *udev, const char *alias) { int err; @@ -47,18 +47,18 @@ static int load_module(struct udev *udev, const char *alias) { return err; if (list == NULL) - log_debug("no module matches '%s'", alias); + log_debug("No module matches '%s'", alias); kmod_list_foreach(l, list) { struct kmod_module *mod = kmod_module_get_module(l); err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); if (err == KMOD_PROBE_APPLY_BLACKLIST) - log_debug("module '%s' is blacklisted", kmod_module_get_name(mod)); + log_debug("Module '%s' is blacklisted", kmod_module_get_name(mod)); else if (err == 0) - log_debug("inserted '%s'", kmod_module_get_name(mod)); + log_debug("Inserted '%s'", kmod_module_get_name(mod)); else - log_debug("failed to insert '%s'", kmod_module_get_name(mod)); + log_debug("Failed to insert '%s'", kmod_module_get_name(mod)); kmod_module_unref(mod); } @@ -85,10 +85,8 @@ static int load_module(struct udev *udev, const char *alias) { return err; } -_printf_(6,0) -static void udev_kmod_log(void *data, int priority, const char *file, int line, - const char *fn, const char *format, va_list args) { - udev_main_log(data, priority, file, line, fn, format, args); +_printf_(6,0) static void udev_kmod_log(void *data, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { + log_internalv(priority, 0, file, line, fn, format, args); } static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool test) { @@ -106,7 +104,7 @@ static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool te } for (i = 2; argv[i]; i++) { - log_debug("execute '%s' '%s'", argv[1], argv[i]); + log_debug("Execute '%s' '%s'", argv[1], argv[i]); load_module(udev, argv[i]); } @@ -123,7 +121,7 @@ static int builtin_kmod_init(struct udev *udev) { if (!ctx) return -ENOMEM; - log_debug("load module index"); + log_debug("Load module index"); kmod_set_log_fn(ctx, udev_kmod_log, udev); kmod_load_resources(ctx); #endif @@ -133,7 +131,7 @@ static int builtin_kmod_init(struct udev *udev) { /* called on udev shutdown and reload request */ static void builtin_kmod_exit(struct udev *udev) { #ifdef HAVE_LIBKMOD - log_debug("unload module index"); + log_debug("Unload module index"); ctx = kmod_unref(ctx); #endif } @@ -141,7 +139,7 @@ static void builtin_kmod_exit(struct udev *udev) { #ifdef HAVE_LIBKMOD /* called every couple of seconds during event activity; 'true' if config has changed */ static bool builtin_kmod_validate(struct udev *udev) { - log_debug("validate module index"); + log_debug("Validate module index"); if (!ctx) return false; return (kmod_validate_resources(ctx) != KMOD_RESOURCES_OK); @@ -156,6 +154,6 @@ const struct udev_builtin udev_builtin_kmod = { #ifdef HAVE_LIBKMOD .validate = builtin_kmod_validate, #endif - .help = "kernel module loader", + .help = "Kernel module loader", .run_once = false, }; diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index 4731a860b0..5f350abd68 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -307,7 +307,7 @@ static int names_usb(struct udev_device *dev, struct netnames *names) { s[0] = '\0'; interf = s+1; - /* prefix every port number in the chain with "u"*/ + /* prefix every port number in the chain with "u" */ s = ports; while ((s = strchr(s, '.'))) s[0] = 'u'; @@ -562,5 +562,5 @@ out: const struct udev_builtin udev_builtin_net_id = { .name = "net_id", .cmd = builtin_net_id, - .help = "network device properties", + .help = "Network device properties", }; diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index 0d247f6b5a..b6749aab76 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -118,7 +118,7 @@ out: return parent; } -static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path) { +static struct udev_device *handle_scsi_sas_wide_port(struct udev_device *parent, char **path) { struct udev *udev = udev_device_get_udev(parent); struct udev_device *targetdev; struct udev_device *target_parent; @@ -154,6 +154,100 @@ out: return parent; } +static struct udev_device *handle_scsi_sas(struct udev_device *parent, char **path) +{ + struct udev *udev = udev_device_get_udev(parent); + struct udev_device *targetdev; + struct udev_device *target_parent; + struct udev_device *port; + struct udev_device *expander; + struct udev_device *target_sasdev = NULL; + struct udev_device *expander_sasdev = NULL; + struct udev_device *port_sasdev = NULL; + const char *sas_address = NULL; + const char *phy_id; + const char *phy_count; + char *lun = NULL; + + targetdev = udev_device_get_parent_with_subsystem_devtype(parent, "scsi", "scsi_target"); + if (targetdev == NULL) + return NULL; + + target_parent = udev_device_get_parent(targetdev); + if (target_parent == NULL) + return NULL; + + /* Get sas device */ + target_sasdev = udev_device_new_from_subsystem_sysname(udev, + "sas_device", udev_device_get_sysname(target_parent)); + if (target_sasdev == NULL) + return NULL; + + /* The next parent is sas port */ + port = udev_device_get_parent(target_parent); + if (port == NULL) { + parent = NULL; + goto out; + } + + /* Get port device */ + port_sasdev = udev_device_new_from_subsystem_sysname(udev, + "sas_port", udev_device_get_sysname(port)); + + phy_count = udev_device_get_sysattr_value(port_sasdev, "num_phys"); + if (phy_count == NULL) { + parent = NULL; + goto out; + } + + /* Check if we are simple disk */ + if (strncmp(phy_count, "1", 2) != 0) { + parent = handle_scsi_sas_wide_port(parent, path); + goto out; + } + + /* Get connected phy */ + phy_id = udev_device_get_sysattr_value(target_sasdev, "phy_identifier"); + if (phy_id == NULL) { + parent = NULL; + goto out; + } + + /* The port's parent is either hba or expander */ + expander = udev_device_get_parent(port); + if (expander == NULL) { + parent = NULL; + goto out; + } + + /* Get expander device */ + expander_sasdev = udev_device_new_from_subsystem_sysname(udev, + "sas_device", udev_device_get_sysname(expander)); + if (expander_sasdev != NULL) { + /* Get expander's address */ + sas_address = udev_device_get_sysattr_value(expander_sasdev, + "sas_address"); + if (sas_address == NULL) { + parent = NULL; + goto out; + } + } + + format_lun_number(parent, &lun); + if (sas_address) + path_prepend(path, "sas-exp%s-phy%s-%s", sas_address, phy_id, lun); + else + path_prepend(path, "sas-phy%s-%s", phy_id, lun); + + if (lun) + free(lun); +out: + udev_device_unref(target_sasdev); + udev_device_unref(expander_sasdev); + udev_device_unref(port_sasdev); + return parent; +} + static struct udev_device *handle_scsi_iscsi(struct udev_device *parent, char **path) { struct udev *udev = udev_device_get_udev(parent); struct udev_device *transportdev; @@ -548,9 +642,9 @@ static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool } /* - * Do return devices with have an unknown type of parent device, they - * might produce conflicting IDs below multiple independent parent - * devices. + * Do not return devices with an unknown parent device type. They + * might produce conflicting IDs if the parent does not provide a + * unique and predictable name. */ if (!supported_parent) { free(path); @@ -558,9 +652,9 @@ static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool } /* - * Do not return a have-only a single-parent block devices, some - * have entire hidden buses behind it, and not create predictable - * IDs that way. + * Do not return block devices without a well-known transport. Some + * devices do not expose their buses and do not provide a unique + * and predictable name that way. */ if (streq(udev_device_get_subsystem(dev), "block") && !supported_transport) { free(path); @@ -609,6 +703,6 @@ out: const struct udev_builtin udev_builtin_path_id = { .name = "path_id", .cmd = builtin_path_id, - .help = "compose persistent device path", + .help = "Compose persistent device path", .run_once = true, }; diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c index 06d4191584..ab0d96e377 100644 --- a/src/udev/udev-builtin-usb_id.c +++ b/src/udev/udev-builtin-usb_id.c @@ -471,6 +471,6 @@ fallback: const struct udev_builtin udev_builtin_usb_id = { .name = "usb_id", .cmd = builtin_usb_id, - .help = "usb device properties", + .help = "USB device properties", .run_once = true, }; diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 70b7ab4332..e4440a6e99 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -86,7 +86,7 @@ void udev_builtin_list(struct udev *udev) { unsigned int i; for (i = 0; i < ELEMENTSOF(builtins); i++) - fprintf(stderr, " %-12s %s\n", builtins[i]->name, builtins[i]->help); + fprintf(stderr, " %-14s %s\n", builtins[i]->name, builtins[i]->help); } const char *udev_builtin_name(enum udev_builtin_cmd cmd) { diff --git a/src/udev/udev-ctrl.c b/src/udev/udev-ctrl.c index 2fdf4561a9..898ef54514 100644 --- a/src/udev/udev-ctrl.c +++ b/src/udev/udev-ctrl.c @@ -85,7 +85,7 @@ struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd) { if (fd < 0) { uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); if (uctrl->sock < 0) { - log_error("error getting socket: %m"); + log_error_errno(errno, "error getting socket: %m"); udev_ctrl_unref(uctrl); return NULL; } @@ -95,7 +95,7 @@ struct udev_ctrl *udev_ctrl_new_from_fd(struct udev *udev, int fd) { } r = setsockopt(uctrl->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); if (r < 0) - log_warning("could not set SO_PASSCRED: %m"); + log_warning_errno(errno, "could not set SO_PASSCRED: %m"); uctrl->saddr.sun_family = AF_LOCAL; strscpy(uctrl->saddr.sun_path, sizeof(uctrl->saddr.sun_path), "/run/udev/control"); @@ -119,14 +119,14 @@ int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl) { if (err < 0) { err = -errno; - log_error("bind failed: %m"); + log_error_errno(errno, "bind failed: %m"); return err; } err = listen(uctrl->sock, 0); if (err < 0) { err = -errno; - log_error("listen failed: %m"); + log_error_errno(errno, "listen failed: %m"); return err; } @@ -209,14 +209,14 @@ struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) { if (conn->sock < 0) { if (errno != EINTR) - log_error("unable to receive ctrl connection: %m"); + log_error_errno(errno, "unable to receive ctrl connection: %m"); goto err; } /* check peer credential of connection */ r = getpeercred(conn->sock, &ucred); if (r < 0) { - log_error("unable to receive credentials of ctrl connection: %s", strerror(-r)); + log_error_errno(r, "unable to receive credentials of ctrl connection: %m"); goto err; } if (ucred.uid > 0) { @@ -227,7 +227,7 @@ struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) { /* enable receiving of the sender credentials in the messages */ r = setsockopt(conn->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); if (r < 0) - log_warning("could not set SO_PASSCRED: %m"); + log_warning_errno(errno, "could not set SO_PASSCRED: %m"); udev_ctrl_ref(uctrl); return conn; @@ -383,7 +383,7 @@ struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn) { goto err; } else { if (!(pfd[0].revents & POLLIN)) { - log_error("ctrl connection error: %m"); + log_error_errno(errno, "ctrl connection error: %m"); goto err; } } @@ -396,7 +396,7 @@ struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn) { size = recvmsg(conn->sock, &smsg, 0); if (size < 0) { - log_error("unable to receive ctrl message: %m"); + log_error_errno(errno, "unable to receive ctrl message: %m"); goto err; } cmsg = CMSG_FIRSTHDR(&smsg); diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index e86f9cb059..5c1352b1b9 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -388,7 +388,7 @@ static int spawn_exec(struct udev_event *event, if (fd_stderr < 0) dup2(fd, STDERR_FILENO); } else - log_error("open /dev/null failed: %m"); + log_error_errno(errno, "open /dev/null failed: %m"); /* connect pipes to std{out,err} */ if (fd_stdout >= 0) { @@ -410,7 +410,7 @@ static int spawn_exec(struct udev_event *event, execve(argv[0], argv, envp); /* exec failed */ - log_error("failed to execute '%s' '%s': %m", argv[0], cmd); + log_error_errno(errno, "failed to execute '%s' '%s': %m", argv[0], cmd); return -errno; } @@ -438,14 +438,14 @@ static void spawn_read(struct udev_event *event, fd_ep = epoll_create1(EPOLL_CLOEXEC); if (fd_ep < 0) { - log_error("error creating epoll fd: %m"); + log_error_errno(errno, "error creating epoll fd: %m"); return; } if (fd_stdout >= 0) { r = epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stdout, &ep_outpipe); if (r < 0) { - log_error("fail to add stdout fd to epoll: %m"); + log_error_errno(errno, "fail to add stdout fd to epoll: %m"); return; } } @@ -453,7 +453,7 @@ static void spawn_read(struct udev_event *event, if (fd_stderr >= 0) { r = epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stderr, &ep_errpipe); if (r < 0) { - log_error("fail to add stderr fd to epoll: %m"); + log_error_errno(errno, "fail to add stderr fd to epoll: %m"); return; } } @@ -482,7 +482,7 @@ static void spawn_read(struct udev_event *event, if (fdcount < 0) { if (errno == EINTR) continue; - log_error("failed to poll: %m"); + log_error_errno(errno, "failed to poll: %m"); return; } else if (fdcount == 0) { log_error("timeout '%s'", cmd); @@ -528,7 +528,7 @@ static void spawn_read(struct udev_event *event, } else if (ev[i].events & EPOLLHUP) { r = epoll_ctl(fd_ep, EPOLL_CTL_DEL, *fd, NULL); if (r < 0) { - log_error("failed to remove fd from epoll: %m"); + log_error_errno(errno, "failed to remove fd from epoll: %m"); return; } *fd = -1; @@ -577,7 +577,7 @@ static int spawn_wait(struct udev_event *event, if (errno == EINTR) continue; err = -errno; - log_error("failed to poll: %m"); + log_error_errno(errno, "failed to poll: %m"); goto out; } if (fdcount == 0) { @@ -588,7 +588,7 @@ static int spawn_wait(struct udev_event *event, if (errno == EINTR) continue; err = -errno; - log_error("failed to poll: %m"); + log_error_errno(errno, "failed to poll: %m"); goto out; } if (fdcount == 0) { @@ -677,7 +677,6 @@ int udev_event_spawn(struct udev_event *event, usec_t timeout_warn_usec, const char *cmd, char **envp, const sigset_t *sigmask, char *result, size_t ressize) { - struct udev *udev = event->udev; int outpipe[2] = {-1, -1}; int errpipe[2] = {-1, -1}; pid_t pid; @@ -690,17 +689,17 @@ int udev_event_spawn(struct udev_event *event, udev_build_argv(event->udev, arg, NULL, argv); /* pipes from child to parent */ - if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) { + if (result != NULL || log_get_max_level() >= LOG_INFO) { if (pipe2(outpipe, O_NONBLOCK) != 0) { err = -errno; - log_error("pipe failed: %m"); + log_error_errno(errno, "pipe failed: %m"); goto out; } } - if (udev_get_log_priority(udev) >= LOG_INFO) { + if (log_get_max_level() >= LOG_INFO) { if (pipe2(errpipe, O_NONBLOCK) != 0) { err = -errno; - log_error("pipe failed: %m"); + log_error_errno(errno, "pipe failed: %m"); goto out; } } @@ -737,7 +736,7 @@ int udev_event_spawn(struct udev_event *event, _exit(2 ); case -1: - log_error("fork of '%s' failed: %m", cmd); + log_error_errno(errno, "fork of '%s' failed: %m", cmd); err = -1; goto out; default: @@ -785,7 +784,7 @@ static int rename_netif_dev_fromname_toname(struct udev_device *dev,const char * sk = socket(PF_INET, SOCK_DGRAM, 0); if (sk < 0) { err = -errno; - log_error("error opening socket: %m\n"); + log_error("error opening socket: %m"); return err; } @@ -799,11 +798,11 @@ static int rename_netif_dev_fromname_toname(struct udev_device *dev,const char * int loop; if (err == 0) { - log_info("renamed network interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname); + log_info("renamed network interface %s to %s", ifr.ifr_name, ifr.ifr_newname); goto out; } /* keep trying if the destination interface name already exists */ - log_debug("collision on rename of network interface %s to %s , retrying until timeout\n", + log_debug("collision on rename of network interface %s to %s , retrying until timeout", ifr.ifr_name, ifr.ifr_newname); err = -errno; @@ -819,7 +818,7 @@ static int rename_netif_dev_fromname_toname(struct udev_device *dev,const char * err = ioctl(sk, SIOCSIFNAME, &ifr); if (err == 0) { - log_info("renamed network interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname); + log_info("renamed network interface %s to %s", ifr.ifr_name, ifr.ifr_newname); break; } err = -errno; @@ -829,13 +828,13 @@ static int rename_netif_dev_fromname_toname(struct udev_device *dev,const char * out: if (err < 0) - log_error("error changing net interface name %s to %s: %m\n", ifr.ifr_name, ifr.ifr_newname); + log_error("error changing net interface name %s to %s: %m", ifr.ifr_name, ifr.ifr_newname); #else if (err >= 0) { - log_info("renamed network interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname); + log_info("renamed network interface %s to %s", ifr.ifr_name, ifr.ifr_newname); } else { err = -errno; - log_error("error changing net interface name %s to %s: %m\n", ifr.ifr_name, ifr.ifr_newname); + log_error("error changing net interface name %s to %s: %m", ifr.ifr_name, ifr.ifr_newname); } #endif @@ -848,9 +847,10 @@ static int rename_netif(struct udev_event *event) { } void udev_event_execute_rules(struct udev_event *event, - usec_t timeout_usec, - usec_t timeout_warn_usec, - struct udev_rules *rules, const sigset_t *sigmask) { + usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list, + struct udev_rules *rules, + const sigset_t *sigmask) { struct udev_device *dev = event->dev; if (udev_device_get_subsystem(dev) == NULL) @@ -864,7 +864,10 @@ void udev_event_execute_rules(struct udev_event *event, if (major(udev_device_get_devnum(dev)) != 0) udev_watch_end(event->udev, dev); - udev_rules_apply_to_event(rules, event, timeout_usec, timeout_warn_usec, sigmask); + udev_rules_apply_to_event(rules, event, + timeout_usec, timeout_warn_usec, + properties_list, + sigmask); if (major(udev_device_get_devnum(dev)) != 0) udev_node_remove(dev); @@ -898,7 +901,10 @@ void udev_event_execute_rules(struct udev_event *event, } } - udev_rules_apply_to_event(rules, event, timeout_usec, timeout_warn_usec, sigmask); + udev_rules_apply_to_event(rules, event, + timeout_usec, timeout_warn_usec, + properties_list, + sigmask); /* rename a new network interface, if needed */ @@ -955,9 +961,9 @@ void udev_event_execute_rules(struct udev_event *event, r = rename_netif_dev_fromname_toname(dev,udev_device_get_sysname(dev),newifname); if (r == 0) { finalifname = newifname; - log_debug("renamed netif to '%s' for collision avoidance\n", newifname); + log_debug("renamed netif to '%s' for collision avoidance", newifname); } else { - log_error("could not rename netif to '%s' for collision avoidance\n",newifname); + log_error("could not rename netif to '%s' for collision avoidance",newifname); } } /* rename it now to its final target if its not already there */ diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index 4ac6f71490..c30a428ea1 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -112,12 +112,12 @@ static int node_symlink(struct udev_device *dev, const char *node, const char *s mac_selinux_create_file_clear(); } while (err == -ENOENT); if (err != 0) { - log_error("symlink '%s' '%s' failed: %m", target, slink_tmp); + log_error_errno(errno, "symlink '%s' '%s' failed: %m", target, slink_tmp); goto exit; } err = rename(slink_tmp, slink); if (err != 0) { - log_error("rename '%s' '%s' failed: %m", slink_tmp, slink); + log_error_errno(errno, "rename '%s' '%s' failed: %m", slink_tmp, slink); unlink(slink_tmp); } exit: @@ -264,7 +264,7 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, if (lstat(devnode, &stats) != 0) { err = -errno; - log_debug("can not stat() node '%s' (%m)", devnode); + log_debug_errno(errno, "can not stat() node '%s' (%m)", devnode); goto out; } @@ -283,10 +283,10 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, log_debug("set permissions %s, %#o, uid=%u, gid=%u", devnode, mode, uid, gid); err = chmod(devnode, mode); if (err < 0) - log_warning("setting mode of %s to %#o failed: %m", devnode, mode); + log_warning_errno(errno, "setting mode of %s to %#o failed: %m", devnode, mode); err = chown(devnode, uid, gid); if (err < 0) - log_warning("setting owner of %s to uid=%u, gid=%u failed: %m", devnode, uid, gid); + 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); } @@ -304,7 +304,7 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, r = mac_selinux_apply(devnode, label); if (r < 0) - log_error("SECLABEL: failed to set SELinux label '%s': %s", label, strerror(-r)); + log_error_errno(r, "SECLABEL: failed to set SELinux label '%s': %m", label); else log_debug("SECLABEL: set SELinux label '%s'", label); @@ -313,7 +313,7 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, r = mac_smack_apply(devnode, label); if (r < 0) - log_error("SECLABEL: failed to set SMACK label '%s': %s", label, strerror(-r)); + log_error_errno(r, "SECLABEL: failed to set SMACK label '%s': %m", label); else log_debug("SECLABEL: set SMACK label '%s'", label); @@ -323,7 +323,7 @@ static int node_permissions_apply(struct udev_device *dev, bool apply, /* set the defaults */ if (!selinux) - label_fix(devnode, true, false); + mac_selinux_fix(devnode, true, false); if (!smack) mac_smack_apply(devnode, NULL); } diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index b8fc35e11a..f51691143b 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -479,9 +479,9 @@ static uid_t add_uid(struct udev_rules *rules, const char *owner) { r = get_user_creds(&owner, &uid, NULL, NULL, NULL); if (r < 0) { if (r == -ENOENT || r == -ESRCH) - udev_err(rules->udev, "specified user '%s' unknown\n", owner); + log_error("specified user '%s' unknown", owner); else - udev_err(rules->udev, "error resolving user '%s': %s\n", owner, strerror(-r)); + log_error_errno(r, "error resolving user '%s': %m", owner); } /* grow buffer if needed */ @@ -526,9 +526,9 @@ static gid_t add_gid(struct udev_rules *rules, const char *group) { r = get_group_creds(&group, &gid); if (r < 0) { if (r == -ENOENT || r == -ESRCH) - udev_err(rules->udev, "specified group '%s' unknown\n", group); + log_error("specified group '%s' unknown", group); else - udev_err(rules->udev, "error resolving group '%s': %s\n", group, strerror(-r)); + log_error_errno(r, "error resolving group '%s': %m", group); } /* grow buffer if needed */ @@ -1047,11 +1047,11 @@ static int add_rule(struct udev_rules *rules, char *line, const char *filename, unsigned int filename_off, unsigned int lineno) { char *linepos; const char *attr; - struct rule_tmp rule_tmp; + struct rule_tmp rule_tmp = { + .rules = rules, + .rule.type = TK_RULE, + }; - memzero(&rule_tmp, sizeof(struct rule_tmp)); - rule_tmp.rules = rules; - rule_tmp.rule.type = TK_RULE; /* the offset in the rule is limited to unsigned short */ if (filename_off < USHRT_MAX) rule_tmp.rule.rule.filename_off = filename_off; @@ -1072,14 +1072,14 @@ static int add_rule(struct udev_rules *rules, char *line, /* If we aren't at the end of the line, this is a parsing error. * Make a best effort to describe where the problem is. */ - if (*linepos != '\n') { - char buf[2] = {linepos[1]}; + if (!strchr(NEWLINE, *linepos)) { + char buf[2] = {*linepos}; _cleanup_free_ char *tmp; tmp = cescape(buf); - log_error("invalid key/value pair in file %s on line %u, starting at character %tu ('%s')\n", + log_error("invalid key/value pair in file %s on line %u, starting at character %tu ('%s')", filename, lineno, linepos - line + 1, tmp); - if (linepos[1] == '#') + if (*linepos == '#') log_error("hint: comments can only start at beginning of line"); } break; @@ -1420,12 +1420,12 @@ static int add_rule(struct udev_rules *rules, char *line, } else { if (streq(value, "%k")) { log_error("NAME=\"%%k\" is ignored, because it breaks kernel supplied names, " - "please remove it from %s:%u\n", filename, lineno); + "please remove it from %s:%u", filename, lineno); continue; } if (value[0] == '\0') { log_debug("NAME=\"\" is ignored, because udev will not delete any device nodes, " - "please remove it from %s:%u\n", filename, lineno); + "please remove it from %s:%u", filename, lineno); continue; } rule_add_key(&rule_tmp, TK_A_NAME, op, value, NULL); @@ -1690,7 +1690,7 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) { r = conf_files_list_strv(&files, ".rules", NULL, rules_dirs); if (r < 0) { - log_error("failed to enumerate rules files: %s", strerror(-r)); + log_error_errno(r, "failed to enumerate rules files: %m"); return udev_rules_unref(rules); } @@ -1916,9 +1916,9 @@ int udev_rules_assigning_name_to(struct udev_rules *rules, const char *match_nam strscpy(name_str,UTIL_PATH_SIZE,name); count = util_replace_chars(name_str, "/"); if (count > 0) - log_debug("%i character(s) replaced\n", count); + log_debug("%i character(s) replaced", count); if (streq(name_str,match_name)) { - log_debug("found a match! NAME assigns %s in: %s:%u\n", + log_debug("found a match! NAME assigns %s in: %s:%u", name, rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); @@ -1945,6 +1945,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list, const sigset_t *sigmask) { struct token *cur; struct token *rule; @@ -2334,9 +2335,9 @@ int udev_rules_apply_to_event(struct udev_rules *rules, r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL); if (r < 0) { if (r == -ENOENT || r == -ESRCH) - udev_err(event->udev, "specified user '%s' unknown\n", owner); + log_error("specified user '%s' unknown", owner); else - udev_err(event->udev, "error resolving user '%s': %s\n", owner, strerror(-r)); + log_error_errno(r, "error resolving user '%s': %m", owner); event->uid = 0; } @@ -2360,9 +2361,9 @@ int udev_rules_apply_to_event(struct udev_rules *rules, r = get_group_creds(&gr, &event->gid); if (r < 0) { if (r == -ENOENT || r == -ESRCH) - udev_err(event->udev, "specified group '%s' unknown\n", group); + log_error("specified group '%s' unknown", group); else - udev_err(event->udev, "error resolving group '%s': %s\n", group, strerror(-r)); + log_error_errno(r, "error resolving group '%s': %m", group); event->gid = 0; } @@ -2517,7 +2518,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, if (major(udev_device_get_devnum(event->dev)) && (!streq(name_str, udev_device_get_devnode(event->dev) + strlen("/dev/")))) { log_error("NAME=\"%s\" ignored, kernel device nodes " - "can not be renamed; please fix it in %s:%u\n", name, + "can not be renamed; please fix it in %s:%u", name, rules_str(rules, rule->rule.filename_off), rule->rule.filename_line); break; } @@ -2592,10 +2593,10 @@ int udev_rules_apply_to_event(struct udev_rules *rules, f = fopen(attr, "we"); if (f != NULL) { if (fprintf(f, "%s", value) <= 0) - log_error("error writing ATTR{%s}: %m", attr); + log_error_errno(errno, "error writing ATTR{%s}: %m", attr); fclose(f); } else { - log_error("error opening ATTR{%s} for writing: %m", attr); + log_error_errno(errno, "error opening ATTR{%s} for writing: %m", attr); } break; } @@ -2710,7 +2711,7 @@ int udev_rules_apply_static_dev_perms(struct udev_rules *rules) { strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL); r = mkdir_p(tags_dir, 0755); if (r < 0) { - log_error("failed to create %s: %s", tags_dir, strerror(-r)); + log_error_errno(r, "failed to create %s: %m", tags_dir); return r; } @@ -2718,10 +2719,10 @@ int udev_rules_apply_static_dev_perms(struct udev_rules *rules) { strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL); r = symlink(device_node, tag_symlink); - if (r < 0 && errno != EEXIST) { - log_error("failed to create symlink %s -> %s: %m", tag_symlink, device_node); - return -errno; - } else + if (r < 0 && errno != EEXIST) + return log_error_errno(errno, "failed to create symlink %s -> %s: %m", + tag_symlink, device_node); + else r = 0; } } diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 061bd05875..6ba8674d77 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -39,7 +39,7 @@ static int inotify_fd = -1; int udev_watch_init(struct udev *udev) { inotify_fd = inotify_init1(IN_CLOEXEC); if (inotify_fd < 0) - log_error("inotify_init failed: %m"); + log_error_errno(errno, "inotify_init failed: %m"); return inotify_fd; } @@ -56,7 +56,7 @@ void udev_watch_restore(struct udev *udev) { dir = opendir("/run/udev/watch.old"); if (dir == NULL) { - log_error("unable to open old watches dir /run/udev/watch.old; old watches will not be restored: %m"); + log_error_errno(errno, "unable to open old watches dir /run/udev/watch.old; old watches will not be restored: %m"); return; } @@ -88,7 +88,7 @@ unlink: rmdir("/run/udev/watch.old"); } else if (errno != ENOENT) { - log_error("unable to move watches dir /run/udev/watch; old watches will not be restored: %m"); + log_error_errno(errno, "unable to move watches dir /run/udev/watch; old watches will not be restored: %m"); } } @@ -103,7 +103,7 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) { log_debug("adding watch on '%s'", udev_device_get_devnode(dev)); wd = inotify_add_watch(inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); if (wd < 0) { - log_error("inotify_add_watch(%d, %s, %o) failed: %m", + log_error_errno(errno, "inotify_add_watch(%d, %s, %o) failed: %m", inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE); return; } @@ -113,7 +113,7 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) { unlink(filename); r = symlink(udev_device_get_id_filename(dev), filename); if (r < 0) - log_error("Failed to create symlink %s: %m", filename); + log_error_errno(errno, "Failed to create symlink %s: %m", filename); udev_device_set_watch_handle(dev, wd); } diff --git a/src/udev/udev.h b/src/udev/udev.h index 5a1f5488cb..fae0408672 100644 --- a/src/udev/udev.h +++ b/src/udev/udev.h @@ -71,7 +71,9 @@ struct udev_rules; struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names); struct udev_rules *udev_rules_unref(struct udev_rules *rules); bool udev_rules_check_timestamp(struct udev_rules *rules); -int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec, +int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, + usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list, const sigset_t *sigmask); int udev_rules_apply_static_dev_perms(struct udev_rules *rules); @@ -86,8 +88,11 @@ int udev_event_spawn(struct udev_event *event, usec_t timeout_warn_usec, const char *cmd, char **envp, const sigset_t *sigmask, char *result, size_t ressize); -void udev_event_execute_rules(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec, - struct udev_rules *rules, const sigset_t *sigset); +void udev_event_execute_rules(struct udev_event *event, + usec_t timeout_usec, usec_t timeout_warn_usec, + struct udev_list *properties_list, + struct udev_rules *rules, + const sigset_t *sigset); void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_t timeout_warn_usec, const sigset_t *sigset); int udev_build_argv(struct udev *udev, char *cmd, int *argc, char *argv[]); @@ -193,11 +198,6 @@ int udev_builtin_add_property(struct udev_device *dev, bool test, const char *ke int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *prefix, const char *modalias, const char *filter, bool test); -/* udev logging */ -void udev_main_log(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) _printf_(6, 0); - /* udevadm commands */ struct udevadm_cmd { const char *name; diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 6e3bc2ae98..6af7163d47 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -29,16 +29,19 @@ #include "udev-util.h" static void print_help(void) { - printf("Usage: udevadm control COMMAND\n" - " -e,--exit instruct the daemon to cleanup and exit\n" - " -l,--log-priority=LEVEL set the udev log level for the daemon\n" - " -s,--stop-exec-queue do not execute events, queue only\n" - " -S,--start-exec-queue execute events, flush queue\n" - " -R,--reload reload rules and databases\n" - " -p,--property=KEY=VALUE set a global property for all events\n" - " -m,--children-max=N maximum number of children\n" - " --timeout=SECONDS maximum time to block for a reply\n" - " -h,--help print this help text\n\n"); + printf("%s control COMMAND\n\n" + "Control the udev daemon.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -e --exit Instruct the daemon to cleanup and exit\n" + " -l --log-priority=LEVEL Set the udev log level for the daemon\n" + " -s --stop-exec-queue Do not execute events, queue only\n" + " -S --start-exec-queue Execute events, flush queue\n" + " -R --reload Reload rules and databases\n" + " -p --property=KEY=VALUE Set a global property for all events\n" + " -m --children-max=N Maximum number of children\n" + " --timeout=SECONDS Maximum time to block for a reply\n" + , program_invocation_short_name); } static int adm_control(struct udev *udev, int argc, char *argv[]) { @@ -161,5 +164,5 @@ static int adm_control(struct udev *udev, int argc, char *argv[]) { const struct udevadm_cmd udevadm_control = { .name = "control", .cmd = adm_control, - .help = "control the udev daemon", + .help = "Control the udev daemon", }; diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index 4a3f35c2af..a3f93ba343 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -435,6 +435,10 @@ static int insert_data(struct trie *trie, struct udev_list *match_list, value[0] = '\0'; value++; + /* libudev requires properties to start with a space */ + while (isblank(line[0]) && isblank(line[1])) + line++; + if (line[0] == '\0' || value[0] == '\0') { log_error("Error, empty key or value '%s' in '%s':", line, filename); return -EINVAL; @@ -615,7 +619,7 @@ static int adm_hwdb(struct udev *udev, int argc, char *argv[]) { err = conf_files_list_strv(&files, ".hwdb", root, conf_file_dirs); if (err < 0) { - log_error("failed to enumerate hwdb files: %s", strerror(-err)); + log_error_errno(err, "failed to enumerate hwdb files: %m"); rc = EXIT_FAILURE; goto out; } @@ -648,7 +652,7 @@ static int adm_hwdb(struct udev *udev, int argc, char *argv[]) { mkdir_parents(hwdb_bin, 0755); err = trie_store(trie, hwdb_bin); if (err < 0) { - log_error("Failure writing database %s: %s", hwdb_bin, strerror(-err)); + log_error_errno(err, "Failure writing database %s: %m", hwdb_bin); rc = EXIT_FAILURE; } } diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 3cb503fc1f..c845df7a23 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -31,6 +31,7 @@ #include "udev.h" #include "udev-util.h" +#include "udevadm-util.h" #include "util.h" static bool skip_attribute(const char *name) { @@ -258,33 +259,29 @@ static void cleanup_db(struct udev *udev) { } } -static struct udev_device *find_device(struct udev *udev, const char *id, const char *prefix) { - char name[UTIL_PATH_SIZE]; - - if (prefix && !startswith(id, prefix)) { - strscpyl(name, sizeof(name), prefix, id, NULL); - id = name; - } - - if (startswith(id, "/dev/")) { - struct stat statbuf; - char type; - - if (stat(id, &statbuf) < 0) - return NULL; - - if (S_ISBLK(statbuf.st_mode)) - type = 'b'; - else if (S_ISCHR(statbuf.st_mode)) - type = 'c'; - else - return NULL; - - return udev_device_new_from_devnum(udev, type, statbuf.st_rdev); - } else if (startswith(id, "/sys/")) - return udev_device_new_from_syspath(udev, id); - else - return NULL; +static void help(void) { + + printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n" + "Query sysfs or the udev database.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n" + " -q --query=TYPE Query device information:\n" + " name Name of device node\n" + " symlink Pointing to node\n" + " path sysfs device path\n" + " property The device properties\n" + " all All values\n" + " -p --path=SYSPATH sysfs device path used for query or attribute walk\n" + " -n --name=NAME Node or symlink name used for query or attribute walk\n" + " -r --root Prepend dev directory to path names\n" + " -a --attribute-walk Print all key matches walking along the chain\n" + " of parent devices\n" + " -d --device-id-of-file=FILE Print major:minor of device containing this file\n" + " -x --export Export key/value pairs\n" + " -P --export-prefix Export the key name with a prefix\n" + " -e --export-db Export the content of the udev database\n" + " -c --cleanup-db Clean up the udev database\n" + , program_invocation_short_name); } static int uinfo(struct udev *udev, int argc, char *argv[]) { @@ -312,27 +309,6 @@ static int uinfo(struct udev *udev, int argc, char *argv[]) { {} }; - static const char *usage = - "Usage: udevadm info [OPTIONS] [DEVPATH|FILE]\n" - " -q,--query=TYPE query device information:\n" - " name name of device node\n" - " symlink pointing to node\n" - " path sys device path\n" - " property the device properties\n" - " all all values\n" - " -p,--path=SYSPATH sys device path used for query or attribute walk\n" - " -n,--name=NAME node or symlink name used for query or attribute walk\n" - " -r,--root prepend dev directory to path names\n" - " -a,--attribute-walk print all key matches walking along the chain\n" - " of parent devices\n" - " -d,--device-id-of-file=FILE print major:minor of device containing this file\n" - " -x,--export export key/value pairs\n" - " -P,--export-prefix export the key name with a prefix\n" - " -e,--export-db export the content of the udev database\n" - " -c,--cleanup-db cleanup the udev database\n" - " --version print version of the program\n" - " -h,--help print this message\n"; - enum action_type { ACTION_QUERY, ACTION_ATTRIBUTE_WALK, @@ -417,7 +393,7 @@ static int uinfo(struct udev *udev, int argc, char *argv[]) { printf("%s\n", UDEV_VERSION); return 0; case 'h': - printf("%s\n", usage); + help(); return 0; default: return 1; @@ -427,7 +403,7 @@ static int uinfo(struct udev *udev, int argc, char *argv[]) { case ACTION_QUERY: if (!device) { if (!argv[optind]) { - fprintf(stderr, "%s\n", usage); + help(); return 2; } device = find_device(udev, argv[optind], NULL); @@ -518,5 +494,5 @@ static int uinfo(struct udev *udev, int argc, char *argv[]) { const struct udevadm_cmd udevadm_info = { .name = "info", .cmd = uinfo, - .help = "query sysfs or the udev database", + .help = "Query sysfs or the udev database", }; diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index e776fb99dc..15ded09339 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -64,13 +64,16 @@ static void print_device(struct udev_device *device, const char *source, int pro } static void help(void) { - printf("Usage: udevadm monitor [--property] [--kernel] [--udev] [--help]\n" - " -p,--property print the event properties\n" - " -k,--kernel print kernel uevents\n" - " -u,--udev print udev events\n" - " -s,--subsystem-match=SUBSYSTEM[/DEVTYPE] filter events by subsystem\n" - " -t,--tag-match=TAG filter events by tag\n" - " -h,--help\n\n"); + printf("%s monitor [--property] [--kernel] [--udev] [--help]\n\n" + "Listen to kernel and udev events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --property Print the event properties\n" + " -k --kernel Print kernel uevents\n" + " -u --udev Print udev events\n" + " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" + " -t --tag-match=TAG Filter events by tag\n" + , program_invocation_short_name); } static int adm_monitor(struct udev *udev, int argc, char *argv[]) { @@ -155,7 +158,7 @@ static int adm_monitor(struct udev *udev, int argc, char *argv[]) { fd_ep = epoll_create1(EPOLL_CLOEXEC); if (fd_ep < 0) { - log_error("error creating epoll fd: %m"); + log_error_errno(errno, "error creating epoll fd: %m"); return 1; } @@ -195,7 +198,7 @@ static int adm_monitor(struct udev *udev, int argc, char *argv[]) { ep_udev.events = EPOLLIN; ep_udev.data.fd = fd_udev; if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) { - log_error("fail to add fd to epoll: %m"); + log_error_errno(errno, "fail to add fd to epoll: %m"); return 2; } @@ -229,7 +232,7 @@ static int adm_monitor(struct udev *udev, int argc, char *argv[]) { ep_kernel.events = EPOLLIN; ep_kernel.data.fd = fd_kernel; if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_kernel, &ep_kernel) < 0) { - log_error("fail to add fd to epoll: %m"); + log_error_errno(errno, "fail to add fd to epoll: %m"); return 5; } @@ -276,5 +279,5 @@ static int adm_monitor(struct udev *udev, int argc, char *argv[]) { const struct udevadm_cmd udevadm_monitor = { .name = "monitor", .cmd = adm_monitor, - .help = "listen to kernel and udev events", + .help = "Listen to kernel and udev events", }; diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index 0a8f535c4a..fff5de7a8b 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -37,20 +37,23 @@ #include "util.h" static void help(void) { - printf("Usage: udevadm settle OPTIONS\n" - " -t,--timeout=<seconds> maximum time to wait for events\n" - " -E,--exit-if-exists=<file> stop waiting if file exists\n" - " -h,--help\n\n"); + printf("%s settle OPTIONS\n\n" + "Wait for pending udev events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --timeout=SECONDS Maximum time to wait for events\n" + " -E --exit-if-exists=FILE Stop waiting if file exists\n" + , program_invocation_short_name); } static int adm_settle(struct udev *udev, int argc, char *argv[]) { static const struct option options[] = { - { "seq-start", required_argument, NULL, '\0' }, /* removed */ - { "seq-end", required_argument, NULL, '\0' }, /* removed */ { "timeout", required_argument, NULL, 't' }, { "exit-if-exists", required_argument, NULL, 'E' }, - { "quiet", no_argument, NULL, 'q' }, /* removed */ { "help", no_argument, NULL, 'h' }, + { "seq-start", required_argument, NULL, 's' }, /* removed */ + { "seq-end", required_argument, NULL, 'e' }, /* removed */ + { "quiet", no_argument, NULL, 'q' }, /* removed */ {} }; const char *exists = NULL; @@ -60,8 +63,9 @@ static int adm_settle(struct udev *udev, int argc, char *argv[]) { struct udev_queue *queue; int rc = EXIT_FAILURE; - while ((c = getopt_long(argc, argv, "s:e:t:E:qh", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "t:E:hs:e:q", options, NULL)) >= 0) { switch (c) { + case 't': { int r; @@ -73,14 +77,24 @@ static int adm_settle(struct udev *udev, int argc, char *argv[]) { }; break; } + case 'E': exists = optarg; break; + case 'h': help(); return EXIT_SUCCESS; + + case 's': + case 'e': + case 'q': + log_info("Option -%c no longer supported.", c); + return EXIT_FAILURE; + case '?': return EXIT_FAILURE; + default: assert_not_reached("Unknown argument"); } @@ -145,5 +159,5 @@ out: const struct udevadm_cmd udevadm_settle = { .name = "settle", .cmd = adm_settle, - .help = "wait for pending udev events", + .help = "Wait for pending udev events", }; diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index f25608c486..baaeca9352 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -34,10 +34,14 @@ #include "udev.h" static void help(struct udev *udev) { - fprintf(stderr, "\n"); - fprintf(stderr, "Usage: udevadm builtin [--help] COMMAND SYSPATH\n"); + printf("%s builtin [--help] COMMAND SYSPATH\n\n" + "Test a built-in command.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n\n" + "Commands:\n" + , program_invocation_short_name); + udev_builtin_list(udev); - fprintf(stderr, "\n"); } static int adm_builtin(struct udev *udev, int argc, char *argv[]) { @@ -112,6 +116,6 @@ out: const struct udevadm_cmd udevadm_test_builtin = { .name = "test-builtin", .cmd = adm_builtin, - .help = "test a built-in command", + .help = "Test a built-in command", .debug = true, }; diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 4738b611c3..4922b5b6ac 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -31,6 +31,17 @@ #include "udev.h" #include "udev-util.h" +static void help(void) { + + printf("%s test OPTIONS <syspath>\n\n" + "Test an event run.\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -a --action=ACTION Set action string\n" + " -N --resolve-names=early|late|never When to resolve names\n" + , program_invocation_short_name); +} + static int adm_test(struct udev *udev, int argc, char *argv[]) { int resolve_names = 1; char filename[UTIL_PATH_SIZE]; @@ -71,11 +82,7 @@ static int adm_test(struct udev *udev, int argc, char *argv[]) { } break; case 'h': - printf("Usage: udevadm test OPTIONS <syspath>\n" - " -a,--action=ACTION set action string\n" - " -N,--resolve-names=early|late|never when to resolve names\n" - " -h,--help print this help string\n" - "\n"); + help(); exit(EXIT_SUCCESS); case '?': exit(EXIT_FAILURE); @@ -136,7 +143,11 @@ static int adm_test(struct udev *udev, int argc, char *argv[]) { goto out; } - udev_event_execute_rules(event, 60 * USEC_PER_SEC, 20 * USEC_PER_SEC, rules, &sigmask_orig); + udev_event_execute_rules(event, + 60 * USEC_PER_SEC, 20 * USEC_PER_SEC, + NULL, + rules, + &sigmask_orig); udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); @@ -157,6 +168,6 @@ out: const struct udevadm_cmd udevadm_test = { .name = "test", .cmd = adm_test, - .help = "test an event run", + .help = "Test an event run", .debug = true, }; diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index a52d1b5ad5..4dc756a28b 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -32,6 +32,7 @@ #include "udev.h" #include "udev-util.h" +#include "udevadm-util.h" #include "util.h" static int verbose; @@ -53,7 +54,7 @@ static void exec_list(struct udev_enumerate *udev_enumerate, const char *action) if (fd < 0) continue; if (write(fd, action, strlen(action)) < 0) - log_debug("error writing '%s' to '%s': %m", action, filename); + log_debug_errno(errno, "error writing '%s' to '%s': %m", action, filename); close(fd); } } @@ -72,39 +73,48 @@ static const char *keyval(const char *str, const char **val, char *buf, size_t s } static void help(void) { - printf("Usage: udevadm trigger OPTIONS\n" - " -v,--verbose print the list of devices while running\n" - " -n,--dry-run do not actually trigger the events\n" - " -t,--type= type of events to trigger\n" - " devices sys devices (default)\n" - " subsystems sys subsystems and drivers\n" - " -c,--action=<action> event action value, default is \"change\"\n" - " -s,--subsystem-match=<subsystem> trigger devices from a matching subsystem\n" - " -S,--subsystem-nomatch=<subsystem> exclude devices from a matching subsystem\n" - " -a,--attr-match=<file[=<value>]> trigger devices with a matching attribute\n" - " -A,--attr-nomatch=<file[=<value>]> exclude devices with a matching attribute\n" - " -p,--property-match=<key>=<value> trigger devices with a matching property\n" - " -g,--tag-match=<key>=<value> trigger devices with a matching property\n" - " -y,--sysname-match=<name> trigger devices with a matching name\n" - " -b,--parent-match=<name> trigger devices with that parent device\n" - " -h,--help\n\n"); + printf("%s trigger OPTIONS\n\n" + "Request events from the kernel.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -v --verbose Print the list of devices while running\n" + " -n --dry-run Do not actually trigger the events\n" + " -t --type= Type of events to trigger\n" + " devices sysfs devices (default)\n" + " subsystems sysfs subsystems and drivers\n" + " -c --action=ACTION Event action value, default is \"change\"\n" + " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n" + " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n" + " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n" + " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n" + " -p --property-match=KEY=VALUE Trigger devices with a matching property\n" + " -g --tag-match=KEY=VALUE Trigger devices with a matching property\n" + " -y --sysname-match=NAME Trigger devices with this /sys path\n" + " --name-match=NAME Trigger devices with this /dev name\n" + " -b --parent-match=NAME Trigger devices with that parent device\n" + , program_invocation_short_name); } static int adm_trigger(struct udev *udev, int argc, char *argv[]) { + enum { + ARG_NAME = 0x100, + }; + static const struct option options[] = { - { "verbose", no_argument, NULL, 'v' }, - { "dry-run", no_argument, NULL, 'n' }, - { "type", required_argument, NULL, 't' }, - { "action", required_argument, NULL, 'c' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "subsystem-nomatch", required_argument, NULL, 'S' }, - { "attr-match", required_argument, NULL, 'a' }, - { "attr-nomatch", required_argument, NULL, 'A' }, - { "property-match", required_argument, NULL, 'p' }, - { "tag-match", required_argument, NULL, 'g' }, - { "sysname-match", required_argument, NULL, 'y' }, - { "parent-match", required_argument, NULL, 'b' }, - { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "dry-run", no_argument, NULL, 'n' }, + { "type", required_argument, NULL, 't' }, + { "action", required_argument, NULL, 'c' }, + { "subsystem-match", required_argument, NULL, 's' }, + { "subsystem-nomatch", required_argument, NULL, 'S' }, + { "attr-match", required_argument, NULL, 'a' }, + { "attr-nomatch", required_argument, NULL, 'A' }, + { "property-match", required_argument, NULL, 'p' }, + { "tag-match", required_argument, NULL, 'g' }, + { "sysname-match", required_argument, NULL, 'y' }, + { "name-match", required_argument, NULL, ARG_NAME }, + { "parent-match", required_argument, NULL, 'b' }, + { "help", no_argument, NULL, 'h' }, {} }; enum { @@ -174,25 +184,31 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) { udev_enumerate_add_match_sysname(udev_enumerate, optarg); break; case 'b': { - char path[UTIL_PATH_SIZE]; - struct udev_device *dev; - - /* add sys dir if needed */ - if (!startswith(optarg, "/sys")) - strscpyl(path, sizeof(path), "/sys", optarg, NULL); - else - strscpy(path, sizeof(path), optarg); - util_remove_trailing_chars(path, '/'); - dev = udev_device_new_from_syspath(udev, path); + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, optarg, "/sys"); + if (dev == NULL) { + log_error("unable to open the device '%s'", optarg); + return 2; + } + + udev_enumerate_add_match_parent(udev_enumerate, dev); + break; + } + + case ARG_NAME: { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, optarg, "/dev/"); if (dev == NULL) { log_error("unable to open the device '%s'", optarg); return 2; } + udev_enumerate_add_match_parent(udev_enumerate, dev); - /* drop reference immediately, enumerate pins the device as long as needed */ - udev_device_unref(dev); break; } + case 'h': help(); return 0; @@ -203,9 +219,16 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) { } } - if (optind < argc) { - fprintf(stderr, "Extraneous argument: '%s'\n", argv[optind]); - return 1; + for (; optind < argc; optind++) { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, argv[optind], NULL); + if (dev == NULL) { + log_error("unable to open the device '%s'", argv[optind]); + return 2; + } + + udev_enumerate_add_match_parent(udev_enumerate, dev); } switch (device_type) { @@ -225,5 +248,5 @@ static int adm_trigger(struct udev *udev, int argc, char *argv[]) { const struct udevadm_cmd udevadm_trigger = { .name = "trigger", .cmd = adm_trigger, - .help = "request events from the kernel", + .help = "Request events from the kernel", }; diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c new file mode 100644 index 0000000000..37e80c31df --- /dev/null +++ b/src/udev/udevadm-util.c @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008-2009 Kay Sievers <kay@vrfy.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "udevadm-util.h" + +struct udev_device *find_device(struct udev *udev, + const char *id, + const char *prefix) { + + assert(udev); + assert(id); + + if (prefix && !startswith(id, prefix)) + id = strappenda(prefix, id); + + if (startswith(id, "/dev/")) { + struct stat statbuf; + char type; + + if (stat(id, &statbuf) < 0) + return NULL; + + if (S_ISBLK(statbuf.st_mode)) + type = 'b'; + else if (S_ISCHR(statbuf.st_mode)) + type = 'c'; + else + return NULL; + + return udev_device_new_from_devnum(udev, type, statbuf.st_rdev); + } else if (startswith(id, "/sys/")) + return udev_device_new_from_syspath(udev, id); + else + return NULL; +} diff --git a/src/udev/udevadm-util.h b/src/udev/udevadm-util.h new file mode 100644 index 0000000000..dba651fddb --- /dev/null +++ b/src/udev/udevadm-util.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "udev.h" + +struct udev_device *find_device(struct udev *udev, + const char *id, + const char *prefix); diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index fda5aecd83..3e57cf6dee 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -26,12 +26,6 @@ #include "udev.h" -void udev_main_log(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) { - log_metav(priority, file, line, fn, format, args); -} - static int adm_version(struct udev *udev, int argc, char *argv[]) { printf("%s\n", UDEV_VERSION); return 0; @@ -65,11 +59,14 @@ static const struct udevadm_cmd *udevadm_cmds[] = { static int adm_help(struct udev *udev, int argc, char *argv[]) { unsigned int i; - fprintf(stderr, "Usage: udevadm [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n"); + printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n" + "Send control commands or test the device manager.\n\n" + "Commands:\n" + , program_invocation_short_name); + for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++) if (udevadm_cmds[i]->help != NULL) - printf(" %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help); - fprintf(stderr, "\n"); + printf(" %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help); return 0; } @@ -97,7 +94,6 @@ int main(int argc, char *argv[]) { goto out; log_open(); - udev_set_log_fn(udev, udev_main_log); mac_selinux_init("/dev"); while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0) @@ -105,7 +101,6 @@ int main(int argc, char *argv[]) { case 'd': log_set_max_level(LOG_DEBUG); - udev_set_log_priority(udev, LOG_DEBUG); break; case 'h': diff --git a/src/udev/udevd.c b/src/udev/udevd.c index a74906aa62..e835bef8df 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -53,12 +53,6 @@ #include "dev-setup.h" #include "fileio.h" -void udev_main_log(struct udev *udev, int priority, - const char *file, int line, const char *fn, - const char *format, va_list args) { - log_metav(priority, file, line, fn, format, args); -} - static struct udev_rules *rules; static struct udev_ctrl *udev_ctrl; static struct udev_monitor *monitor; @@ -80,6 +74,7 @@ static sigset_t sigmask_orig; static UDEV_LIST(event_list); static UDEV_LIST(worker_list); static char *udev_cgroup; +static struct udev_list properties_list; static bool udev_exit; enum event_state { @@ -223,14 +218,14 @@ static void worker_new(struct event *event) { sigfillset(&mask); fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); if (fd_signal < 0) { - log_error("error creating signalfd %m"); + log_error_errno(errno, "error creating signalfd %m"); rc = 2; goto out; } fd_ep = epoll_create1(EPOLL_CLOEXEC); if (fd_ep < 0) { - log_error("error creating epoll fd: %m"); + log_error_errno(errno, "error creating epoll fd: %m"); rc = 3; goto out; } @@ -246,7 +241,7 @@ static void worker_new(struct event *event) { if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 || epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_monitor, &ep_monitor) < 0) { - log_error("fail to add fds to epoll: %m"); + log_error_errno(errno, "fail to add fds to epoll: %m"); rc = 4; goto out; } @@ -296,7 +291,7 @@ static void worker_new(struct event *event) { if (d) { fd_lock = open(udev_device_get_devnode(d), O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); if (fd_lock >= 0 && flock(fd_lock, LOCK_SH|LOCK_NB) < 0) { - log_debug("Unable to flock(%s), skipping event handling: %m", udev_device_get_devnode(d)); + log_debug_errno(errno, "Unable to flock(%s), skipping event handling: %m", udev_device_get_devnode(d)); err = -EWOULDBLOCK; fd_lock = safe_close(fd_lock); goto skip; @@ -305,9 +300,15 @@ static void worker_new(struct event *event) { } /* apply rules, create node, symlinks */ - udev_event_execute_rules(udev_event, arg_event_timeout_usec, arg_event_timeout_warn_usec, rules, &sigmask_orig); + udev_event_execute_rules(udev_event, + arg_event_timeout_usec, arg_event_timeout_warn_usec, + &properties_list, + rules, + &sigmask_orig); - udev_event_execute_run(udev_event, arg_event_timeout_usec, arg_event_timeout_warn_usec, &sigmask_orig); + udev_event_execute_run(udev_event, + arg_event_timeout_usec, arg_event_timeout_warn_usec, + &sigmask_orig); /* apply/restore inotify watch */ if (udev_event->inotify_watch) { @@ -349,7 +350,7 @@ skip: if (fdcount < 0) { if (errno == EINTR) continue; - log_error("failed to poll: %m"); + log_error_errno(errno, "failed to poll: %m"); goto out; } @@ -389,7 +390,7 @@ out: udev_monitor_unref(worker_monitor); event->state = EVENT_QUEUED; free(worker); - log_error("fork of child failed: %m"); + log_error_errno(errno, "fork of child failed: %m"); break; default: /* close monitor, but keep address around */ @@ -420,7 +421,7 @@ static void event_run(struct event *event) { count = udev_monitor_send_device(monitor, worker->monitor, event->dev); if (count < 0) { - log_error("worker [%u] did not accept message %zi (%m), kill it", worker->pid, count); + log_error_errno(errno, "worker [%u] did not accept message %zi (%m), kill it", worker->pid, count); kill(worker->pid, SIGKILL); worker->state = WORKER_KILLED; continue; @@ -636,7 +637,6 @@ static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl) { if (i >= 0) { log_debug("udevd message (SET_LOG_LEVEL) received, log_priority=%i", i); log_set_max_level(i); - udev_set_log_priority(udev, i); worker_kill(udev); } @@ -669,10 +669,10 @@ static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl) { val = &val[1]; if (val[0] == '\0') { log_debug("udevd message (ENV) received, unset '%s'", key); - udev_add_property(udev, key, NULL); + udev_list_entry_add(&properties_list, key, NULL); } else { log_debug("udevd message (ENV) received, set '%s=%s'", key, val); - udev_add_property(udev, key, val); + udev_list_entry_add(&properties_list, key, val); } } else { log_error("wrong key format '%s'", key); @@ -807,41 +807,34 @@ static int synthesize_change(struct udev_device *dev) { } static int handle_inotify(struct udev *udev) { - int nbytes, pos; - char *buf; - struct inotify_event *ev; - int r; + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; - r = ioctl(fd_inotify, FIONREAD, &nbytes); - if (r < 0 || nbytes <= 0) - return -errno; + l = read(fd_inotify, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; - buf = malloc(nbytes); - if (!buf) { - log_error("error getting buffer for inotify"); - return -ENOMEM; + return log_error_errno(errno, "Failed to read inotify fd: %m"); } - nbytes = read(fd_inotify, buf, nbytes); - - for (pos = 0; pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) { + FOREACH_INOTIFY_EVENT(e, buffer, l) { struct udev_device *dev; - ev = (struct inotify_event *)(buf + pos); - dev = udev_watch_lookup(udev, ev->wd); + dev = udev_watch_lookup(udev, e->wd); if (!dev) continue; - log_debug("inotify event: %x for %s", ev->mask, udev_device_get_devnode(dev)); - if (ev->mask & IN_CLOSE_WRITE) + log_debug("inotify event: %x for %s", e->mask, udev_device_get_devnode(dev)); + if (e->mask & IN_CLOSE_WRITE) synthesize_change(dev); - else if (ev->mask & IN_IGNORED) + else if (e->mask & IN_IGNORED) udev_watch_end(udev, dev); udev_device_unref(dev); } - free(buf); return 0; } @@ -966,7 +959,7 @@ static void static_dev_create_from_modules(struct udev *udev) { strscpyl(filename, sizeof(filename), "/dev/", devname, NULL); mkdir_parents_label(filename, 0755); mac_selinux_create_file_prepare(filename, mode); - log_debug("mknod '%s' %c%u:%u\n", filename, type, maj, min); + log_debug("mknod '%s' %c%u:%u", filename, type, maj, min); if (mknod(filename, mode, makedev(maj, min)) < 0 && errno == EEXIST) utimensat(AT_FDCWD, filename, NULL, 0); mac_selinux_create_file_clear(); @@ -976,7 +969,7 @@ static void static_dev_create_from_modules(struct udev *udev) { } /* - * read the kernel commandline, in case we need to get into debug mode + * read the kernel command line, in case we need to get into debug mode * udev.log-priority=<level> syslog priority * udev.children-max=<number of workers> events are fully serialized if set to 1 * udev.exec-delay=<number of seconds> delay execution of every executed program @@ -988,13 +981,13 @@ static void kernel_cmdline_options(struct udev *udev) { int r; r = proc_cmdline(&line); - if (r < 0) - log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); - if (r <= 0) + if (r < 0) { + log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); return; + } FOREACH_WORD_QUOTED(word, l, line, state) { - char *s, *opt; + char *s, *opt, *value; s = strndup(word, l); if (!s) @@ -1006,24 +999,23 @@ static void kernel_cmdline_options(struct udev *udev) { else opt = s; - if (startswith(opt, "udev.log-priority=")) { + if ((value = startswith(opt, "udev.log-priority="))) { int prio; - prio = util_log_priority(opt + 18); + prio = util_log_priority(value); log_set_max_level(prio); - udev_set_log_priority(udev, prio); - } else if (startswith(opt, "udev.children-max=")) { - r = safe_atoi(opt + 18, &arg_children_max); + } else if ((value = startswith(opt, "udev.children-max="))) { + r = safe_atoi(value, &arg_children_max); if (r < 0) - log_warning("Invalid udev.children-max ignored: %s", opt + 18); - } else if (startswith(opt, "udev.exec-delay=")) { - r = safe_atoi(opt + 16, &arg_exec_delay); + log_warning("Invalid udev.children-max ignored: %s", value); + } else if ((value = startswith(opt, "udev.exec-delay="))) { + r = safe_atoi(value, &arg_exec_delay); if (r < 0) - log_warning("Invalid udev.exec-delay ignored: %s", opt + 16); - } else if (startswith(opt, "udev.event-timeout=")) { - r = safe_atou64(opt + 16, &arg_event_timeout_usec); + log_warning("Invalid udev.exec-delay ignored: %s", value); + } else if ((value = startswith(opt, "udev.event-timeout="))) { + r = safe_atou64(value, &arg_event_timeout_usec); if (r < 0) { - log_warning("Invalid udev.event-timeout ignored: %s", opt + 16); + log_warning("Invalid udev.event-timeout ignored: %s", value); break; } arg_event_timeout_usec *= USEC_PER_SEC; @@ -1037,14 +1029,15 @@ static void kernel_cmdline_options(struct udev *udev) { static void help(void) { printf("%s [OPTIONS...]\n\n" "Manages devices.\n\n" - " --daemon\n" - " --debug\n" - " --children-max=<maximum number of workers>\n" - " --exec-delay=<seconds to wait before executing RUN=>\n" - " --event-timeout=<seconds to wait before terminating an event>\n" - " --resolve-names=early|late|never\n" - " --version\n" - " --help\n" + " -h --help Print this message\n" + " --version Print version of the program\n" + " --daemon Detach and run in the background\n" + " --debug Enable debug output\n" + " --children-max=INT Set maximum number of workers\n" + " --exec-delay=SECONDS Seconds to wait before executing RUN=\n" + " --event-timeout=SECONDS Seconds to wait before terminating an event\n" + " --resolve-names=early|late|never\n" + " When to resolve users and groups\n" , program_invocation_short_name); } @@ -1146,19 +1139,14 @@ int main(int argc, char *argv[]) { log_set_target(LOG_TARGET_AUTO); log_open(); - udev_set_log_fn(udev, udev_main_log); - log_set_max_level(udev_get_log_priority(udev)); - r = parse_argv(argc, argv); if (r <= 0) goto exit; kernel_cmdline_options(udev); - if (arg_debug) { + if (arg_debug) log_set_max_level(LOG_DEBUG); - udev_set_log_priority(udev, LOG_DEBUG); - } if (getuid() != 0) { log_error("root privileges required"); @@ -1167,22 +1155,24 @@ int main(int argc, char *argv[]) { r = mac_selinux_init("/dev"); if (r < 0) { - log_error("could not initialize labelling: %s", strerror(-r)); + log_error_errno(r, "could not initialize labelling: %m"); goto exit; } /* set umask before creating any file/directory */ r = chdir("/"); if (r < 0) { - log_error("could not change dir to /: %m"); + log_error_errno(errno, "could not change dir to /: %m"); goto exit; } umask(022); + udev_list_init(udev, &properties_list, true); + r = mkdir("/run/udev", 0755); if (r < 0 && errno != EEXIST) { - log_error("could not create /run/udev: %m"); + log_error_errno(errno, "could not create /run/udev: %m"); goto exit; } @@ -1237,7 +1227,7 @@ int main(int argc, char *argv[]) { udev_monitor_set_receive_buffer_size(monitor, 128 * 1024 * 1024); - log_info("starting version " VERSION "\n"); + log_info("starting version " VERSION); udev_builtin_init(udev); @@ -1249,7 +1239,7 @@ int main(int argc, char *argv[]) { rc = udev_rules_apply_static_dev_perms(rules); if (rc < 0) - log_error("failed to apply permissions on static device nodes - %s", strerror(-rc)); + log_error_errno(rc, "failed to apply permissions on static device nodes - %m"); if (arg_daemonize) { pid_t pid; @@ -1259,7 +1249,7 @@ int main(int argc, char *argv[]) { case 0: break; case -1: - log_error("fork of daemon failed: %m"); + log_error_errno(errno, "fork of daemon failed: %m"); rc = 4; goto exit; default: @@ -1332,7 +1322,7 @@ int main(int argc, char *argv[]) { fd_ep = epoll_create1(EPOLL_CLOEXEC); if (fd_ep < 0) { - log_error("error creating epoll fd: %m"); + log_error_errno(errno, "error creating epoll fd: %m"); goto exit; } if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_ctrl, &ep_ctrl) < 0 || @@ -1340,7 +1330,7 @@ int main(int argc, char *argv[]) { epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 || epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_netlink, &ep_netlink) < 0 || epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_worker, &ep_worker) < 0) { - log_error("fail to add fds to epoll: %m"); + log_error_errno(errno, "fail to add fds to epoll: %m"); goto exit; } diff --git a/src/v4l_id/Makefile.am b/src/v4l_id/Makefile.am index 074417f909..6ca3da1146 100644 --- a/src/v4l_id/Makefile.am +++ b/src/v4l_id/Makefile.am @@ -1,5 +1,8 @@ ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_CPPFLAGS = \ + -I $(top_srcdir)/src/shared + udevlibexec_PROGRAMS = \ v4l_id diff --git a/src/v4l_id/v4l_id.c b/src/v4l_id/v4l_id.c index d5463b27e5..0e3d3003cb 100644 --- a/src/v4l_id/v4l_id.c +++ b/src/v4l_id/v4l_id.c @@ -13,6 +13,10 @@ * General Public License for more details: */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + #include <stdio.h> #include <errno.h> #include <string.h> @@ -26,17 +30,18 @@ #include <sys/ioctl.h> #include <linux/videodev2.h> -int main (int argc, char *argv[]) -{ +#include "util.h" + +int main(int argc, char *argv[]) { static const struct option options[] = { { "help", no_argument, NULL, 'h' }, {} }; - int fd; + _cleanup_close_ int fd = -1; char *device; struct v4l2_capability v2cap; - while (1) { + for (;;) { int option; option = getopt_long(argc, argv, "h", options, NULL); @@ -45,7 +50,10 @@ int main (int argc, char *argv[]) switch (option) { case 'h': - printf("Usage: v4l_id [--help] <device file>\n\n"); + printf("%s [-h,--help] <device file>\n\n" + "Video4Linux device identification.\n\n" + " -h Print this message\n" + , program_invocation_short_name); return 0; default: return 1; @@ -55,11 +63,11 @@ int main (int argc, char *argv[]) if (device == NULL) return 2; - fd = open (device, O_RDONLY); + fd = open(device, O_RDONLY); if (fd < 0) return 3; - if (ioctl (fd, VIDIOC_QUERYCAP, &v2cap) == 0) { + if (ioctl(fd, VIDIOC_QUERYCAP, &v2cap) == 0) { printf("ID_V4L_VERSION=2\n"); printf("ID_V4L_PRODUCT=%s\n", v2cap.card); printf("ID_V4L_CAPABILITIES=:"); @@ -78,6 +86,5 @@ int main (int argc, char *argv[]) printf("\n"); } - close (fd); return 0; } |