+/* Stores in '*un' a sockaddr_un that refers to file 'name'. Stores in
+ * '*un_len' the size of the sockaddr_un.
+ *
+ * Returns 0 on success, otherwise a positive errno value. On success,
+ * '*dirfdp' is either -1 or a nonnegative file descriptor that the caller
+ * should close after using '*un' to bind or connect. On failure, '*dirfdp' is
+ * -1. */
+static int
+make_sockaddr_un(const char *name, struct sockaddr_un *un, socklen_t *un_len,
+ int *dirfdp)
+{
+ enum { MAX_UN_LEN = sizeof un->sun_path - 1 };
+
+ *dirfdp = -1;
+ if (strlen(name) > MAX_UN_LEN) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+ if (LINUX) {
+ /* 'name' is too long to fit in a sockaddr_un, but we have a
+ * workaround for that on Linux: shorten it by opening a file
+ * descriptor for the directory part of the name and indirecting
+ * through /proc/self/fd/<dirfd>/<basename>. */
+ char *dir, *base;
+ char *short_name;
+ int dirfd;
+
+ dir = dir_name(name);
+ base = base_name(name);
+
+ dirfd = open(dir, O_DIRECTORY | O_RDONLY);
+ if (dirfd < 0) {
+ free(base);
+ free(dir);
+ return errno;
+ }
+
+ short_name = xasprintf("/proc/self/fd/%d/%s", dirfd, base);
+ free(dir);
+ free(base);
+
+ if (strlen(short_name) <= MAX_UN_LEN) {
+ make_sockaddr_un__(short_name, un, un_len);
+ free(short_name);
+ *dirfdp = dirfd;
+ return 0;
+ }
+ free(short_name);
+ close(dirfd);
+
+ VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum "
+ "%d bytes (even shortened)", name, MAX_UN_LEN);
+ } else {
+ /* 'name' is too long and we have no workaround. */
+ VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum "
+ "%d bytes", name, MAX_UN_LEN);
+ }
+
+ return ENAMETOOLONG;
+ } else {
+ make_sockaddr_un__(name, un, un_len);
+ return 0;
+ }
+}
+