/** * Check if the state file @p fd is too old to be believed -- probably * because it was left over from a client that was killed. * * If so, close @p fd, unlink the file, and return EXIT_GONE. * * fd is closed on failure. **/ static int dcc_mon_kill_old(int fd, char *fullpath) { struct stat st; time_t now; /* Check if the file is old. */ if (fstat(fd, &st) == -1) { dcc_close(fd); rs_log_warning("error statting %s: %s", fullpath, strerror(errno)); return EXIT_IO_ERROR; } time(&now); /* Time you hear the siren / it's already too late */ if (now - st.st_mtime > dcc_phase_max_age) { dcc_close(fd); /* close first for windoze */ rs_trace("unlink %s", fullpath); if (unlink(fullpath) == -1) { rs_log_warning("unlink %s failed: %s", fullpath, strerror(errno)); return EXIT_IO_ERROR; } return EXIT_GONE; } return 0; }
/* TODO: Make the returned strings consistent with the other * implementation. */ int dcc_sockaddr_to_string(struct sockaddr *sa, size_t salen, char **p_buf) { int err; char host[1024]; char port[32]; if (!sa) { *p_buf = strdup("NOTSOCKET"); return 0; } else if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) { err = getnameinfo(sa, salen, host, sizeof host, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV); if (err) { rs_log_warning("getnameinfo failed: %s", gai_strerror(err)); *p_buf = strdup("(UNKNOWN)"); return 0; /* it's still a valid string */ } asprintf(p_buf, "%s:%s", host, port); } else if (sa->sa_family == AF_UNIX) { /* NB: The word 'sun' is predefined on Solaris */ struct sockaddr_un *sa_un = (struct sockaddr_un *) sa; asprintf(p_buf, "UNIX-DOMAIN %s", sa_un->sun_path); } else { asprintf(p_buf, "UNKNOWN-FAMILY %d", sa->sa_family); } return 0; }
/** * Return a static string holding DISTCC_DIR, or ~/.distcc. * The directory is created if it does not exist. **/ int dcc_get_top_dir(char **path_ret) { char *env; static char *cached; int ret; if (cached) { *path_ret = cached; return 0; } if ((env = getenv("DISTCC_DIR"))) { if ((cached = strdup(env)) == NULL) { return EXIT_OUT_OF_MEMORY; } else { *path_ret = cached; return 0; } } if ((env = getenv("HOME")) == NULL) { rs_log_warning("HOME is not set; can't find distcc directory"); return EXIT_BAD_ARGUMENTS; } if (asprintf(path_ret, "%s/.distcc", env) == -1) { rs_log_error("asprintf failed"); return EXIT_OUT_OF_MEMORY; } ret = dcc_mkdir(*path_ret); if (ret == 0) cached = *path_ret; return ret; }
/* * Update the ELF file residing at @p path, replacing all occurrences * of @p search with @p replace in the section named @p desired_section_name. * The replacement string must be the same length or shorter than * the search string. */ static void update_section(const char *path, const void *base, off_t size, const char *desired_section_name, const char *search, const char *replace) { const void *desired_section = NULL; int desired_section_size = 0; if (FindElfSection(base, size, desired_section_name, &desired_section, &desired_section_size) && desired_section_size > 0) { /* The local variable below works around a bug in some versions * of gcc (4.2.1?), which issues an erroneous warning if * 'desired_section_rw' is replaced with '(void *) desired_section' * in the call below, causing compile errors with -Werror. */ void *desired_section_rw = (void *) desired_section; int count = replace_string(desired_section_rw, desired_section_size, search, replace); if (count == 0) { rs_trace("\"%s\" section of file %s has no occurrences of \"%s\"", desired_section_name, path, search); } else { rs_log_info("updated \"%s\" section of file \"%s\": " "replaced %d occurrences of \"%s\" with \"%s\"", desired_section_name, path, count, search, replace); if (count > 1) { rs_log_warning("only expected to replace one occurrence!"); } } } else { rs_trace("file %s has no \"%s\" section", path, desired_section_name); } }
/* Subroutine of dcc_expand_preprocessor_options(). * Convert a "-Wp,..." option into one or more regular gcc options. * Copy the resulting gcc options to dest_argv, which should be * pre-allocated by the caller. * Destructively modifies dash_Wp_option as it goes. * Returns 0 on success, nonzero for error (out of memory). */ static int copy_extra_args(char **dest_argv, char *dash_Wp_option, int extra_args) { int i = 0; char *comma = dash_Wp_option + strlen("-Wp"); while (comma != NULL) { char *opt = comma + 1; comma = strchr(opt, ','); if (comma) *comma = '\0'; dest_argv[i] = strdup(opt); if (!dest_argv[i]) return EXIT_OUT_OF_MEMORY; i++; if (strcmp(opt, "-MD") == 0 || strcmp(opt, "-MMD") == 0) { char *filename; if (!comma) { rs_log_warning("'-Wp,-MD' or '-Wp,-MMD' option is missing " "filename argument"); break; } filename = comma + 1; comma = strchr(filename, ','); if (comma) *comma = '\0'; dest_argv[i] = strdup("-MF"); if (!dest_argv[i]) return EXIT_OUT_OF_MEMORY; i++; dest_argv[i] = strdup(filename); if (!dest_argv[i]) return EXIT_OUT_OF_MEMORY; i++; } } assert(i == extra_args); return 0; }
/* Called when a resolve call completes */ static void resolve_reply( AvahiServiceResolver *UNUSED(r), AvahiIfIndex UNUSED(interface), AvahiProtocol UNUSED(protocol), AvahiResolverEvent event, const char *name, const char *UNUSED(type), const char *UNUSED(domain), const char *UNUSED(host_name), const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags UNUSED(flags), void *userdata) { struct host *h = userdata; switch (event) { case AVAHI_RESOLVER_FOUND: { AvahiStringList *i; /* Look for the number of CPUs in TXT RRs */ for (i = txt; i; i = i->next) { char *key, *value; if (avahi_string_list_get_pair(i, &key, &value, NULL) < 0) continue; if (!strcmp(key, "cpus")) if ((h->n_cpus = atoi(value)) <= 0) h->n_cpus = 1; avahi_free(key); avahi_free(value); } h->address = *a; h->port = port; avahi_service_resolver_free(h->resolver); h->resolver = NULL; /* Write modified hosts file */ write_hosts(h->daemon_data); break; } case AVAHI_RESOLVER_FAILURE: rs_log_warning("Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(h->daemon_data->client))); free_host(h); break; } }
static int dcc_mon_read_state(int fd, char *fullpath, struct dcc_task_state *lp) { int nread; /* Don't use dcc_readx(), because not being able to read it is not * a big deal. */ nread = read(fd, lp, sizeof *lp); if (nread == -1) { rs_trace("failed to read state from %s: %s", fullpath, strerror(errno)); return EXIT_IO_ERROR; } else if (nread == 0) { /* empty file; just bad timing. */ return EXIT_IO_ERROR; } else if (nread != sizeof *lp) { rs_trace("short read getting state from %s", fullpath); return EXIT_IO_ERROR; } /* sanity-check some fields */ if (lp->magic != DCC_STATE_MAGIC) { rs_log_warning("wrong magic number: %s", fullpath); return EXIT_IO_ERROR; } if (lp->struct_size != sizeof (struct dcc_task_state)) { rs_log_warning("wrong structure size: %s: version mismatch?", fullpath); return EXIT_IO_ERROR; } lp->file[sizeof lp->file - 1] = '\0'; lp->host[sizeof lp->host - 1] = '\0'; if (lp->curr_phase > DCC_PHASE_DONE) { lp->curr_phase = DCC_PHASE_COMPILE; } lp->next = 0; return 0; }
int dcc_remove_if_exists(const char *fname) { if (unlink(fname) && errno != ENOENT) { rs_log_warning("failed to unlink %s: %s", fname, strerror(errno)); return EXIT_IO_ERROR; } return 0; }
/** * Ignore or unignore SIGPIPE. * * The server and child ignore it, because distcc code wants to see * EPIPE errors if something goes wrong. However, for invoked * children it is set back to the default value, because they may not * handle the error properly. **/ int dcc_ignore_sigpipe(int val) { if (signal(SIGPIPE, val ? SIG_IGN : SIG_DFL) == SIG_ERR) { rs_log_warning("signal(SIGPIPE, %s) failed: %s", val ? "ignore" : "default", strerror(errno)); return EXIT_DISTCC_FAILED; } return 0; }
/** * Remove our pid file on exit. * * Must be reentrant -- called from signal handler. **/ void dcc_remove_pid(void) { if (!arg_pid_file) return; if (unlink(arg_pid_file)) { rs_log_warning("failed to remove pid file %s: %s", arg_pid_file, strerror(errno)); } }
/** * Find the absolute path for the first occurrence of @p compiler_name on the * PATH. Print a warning if it looks like a symlink to distcc. * * We want to guard against somebody accidentally running the server with a * masqueraded compiler on its $PATH. The worst that's likely to happen here * is wasting some time running a distcc or ccache client that does nothing, * so it's not a big deal. (This could be easy to do if it's on the default * PATH and they start the daemon from the command line.) * * At the moment we don't look for the compiler too. **/ static int dcc_check_compiler_masq(char *compiler_name) { const char *envpath, *p, *n; char *buf = NULL; struct stat sb; int len; char linkbuf[MAXPATHLEN]; if (compiler_name[0] == '/') return 0; if (!(envpath = getenv("PATH"))) { rs_trace("PATH seems not to be defined"); return 0; } for (n = p = envpath; *n; p = n) { n = strchr(p, ':'); if (n) len = n++ - p; else { len = strlen(p); n = p + len; } if (asprintf(&buf, "%.*s/%s", len, p, compiler_name) == -1) { rs_log_crit("asnprintf failed"); return EXIT_DISTCC_FAILED; } if (lstat(buf, &sb) == -1) continue; /* ENOENT, EACCESS, etc */ if (!S_ISLNK(sb.st_mode)) { rs_trace("%s is not a symlink", buf); break; /* found it */ } if ((len = readlink(buf, linkbuf, sizeof linkbuf)) <= 0) continue; linkbuf[len] = '\0'; if (strstr(linkbuf, "distcc")) { rs_log_warning("%s on distccd's path is %s and really a link to %s", compiler_name, buf, linkbuf); break; /* but use it anyhow */ } else { rs_trace("%s is a safe symlink to %s", buf, linkbuf); break; /* found it */ } } free(buf); return 0; }
/** * Read in @p filename from inside @p dirname, and try to parse it as * a status file. * * If a new entry is read, a pointer to it is returned in @p lp. **/ static int dcc_mon_do_file(char *dirname, char *filename, struct dcc_task_state **lp) { int fd; char *fullpath; int ret; *lp = NULL; /* Is this a file we want to see */ if (!str_startswith(dcc_state_prefix, filename)) { /* rs_trace("skipped"); */ return 0; } checked_asprintf(&fullpath, "%s/%s", dirname, filename); if (fullpath == NULL) { return EXIT_OUT_OF_MEMORY; } rs_trace("process %s", fullpath); /* Remember that the file might disappear at any time, so open it * now so that we can hang on. */ if ((fd = open(fullpath, O_RDONLY|O_BINARY, 0)) == -1) { if (errno == ENOENT) { rs_trace("%s disappeared", fullpath); ret = 0; goto out_free; } else { /* hm */ rs_log_warning("failed to open %s: %s", fullpath, strerror(errno)); ret = EXIT_IO_ERROR; goto out_free; } } if ((ret = dcc_mon_kill_old(fd, fullpath))) { /* closes fd on failure */ goto out_free; } ret = dcc_mon_load_state(fd, fullpath, lp); dcc_close(fd); out_free: free(fullpath); return ret; /* ok */ }
void dcc_exit(int exitcode) { struct rusage self_ru, children_ru; if (getrusage(RUSAGE_SELF, &self_ru)) { rs_log_warning("getrusage(RUSAGE_SELF) failed: %s", strerror(errno)); memset(&self_ru, 0, sizeof self_ru); } if (getrusage(RUSAGE_CHILDREN, &children_ru)) { rs_log_warning("getrusage(RUSAGE_CHILDREN) failed: %s", strerror(errno)); memset(&children_ru, 0, sizeof children_ru); } /* NB fields must match up for microseconds */ rs_log(RS_LOG_INFO, "exit: code %d; self: %d.%06d user %d.%06d sys; children: %d.%06d user %d.%06d sys", exitcode, (int) self_ru.ru_utime.tv_sec, (int) self_ru.ru_utime.tv_usec, (int) self_ru.ru_stime.tv_sec, (int) self_ru.ru_stime.tv_usec, (int) children_ru.ru_utime.tv_sec, (int) children_ru.ru_utime.tv_usec, (int) children_ru.ru_stime.tv_sec, (int) children_ru.ru_stime.tv_usec); exit(exitcode); }
/** * Callback when the timer triggers, causing a refresh. Loads the * current state from the state monitor and puts it into the table * model, which should then redraw itself. **/ static gint dcc_gnome_update_cb (gpointer UNUSED(view_void)) { struct dcc_task_state *task_list; if (dcc_mon_poll (&task_list)) { rs_log_warning("poll failed"); return TRUE; } dcc_update_store_from_tasks (task_list); dcc_task_state_free (task_list); return TRUE; /* please call again */ }
/** * Check that the process named by the file still exists; if not, * return EXIT_GONE. **/ static int dcc_mon_check_orphans(struct dcc_task_state *monl) { /* signal 0 just checks if it exists */ if (!kill(monl->cpid, 0)) { return 0; /* it's here */ } else if (errno == EPERM) { /* It's here, but it's not ours. Assume it's still a real * distcc process. */ return 0; } else if (errno == ESRCH) { return EXIT_GONE; /* no such pid */ } else { rs_log_warning("kill %ld, 0 failed: %s", (long) monl->cpid, strerror(errno)); return EXIT_GONE; } }
/* Ask for the server not to be awakened until some data has arrived * on the socket. This works for our protocol because the client * sends a request immediately after connection without waiting for * anything from the server. */ void dcc_defer_accept(int POSSIBLY_UNUSED(listen_fd)) { #ifdef TCP_DEFER_ACCEPT int val = 1; if (!dcc_getenv_bool("DISTCC_TCP_DEFER_ACCEPT", 1)) { rs_trace("TCP_DEFER_ACCEPT disabled"); return; } if (setsockopt(listen_fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof val) == -1) { rs_log_warning("failed to set TCP_DEFER_ACCEPT: %s", strerror(errno)); } else { rs_trace("TCP_DEFER_ACCEPT turned on"); } #endif }
static int dcc_remove_log_to_file(void) { if (dcc_compile_log_fd == -1) { rs_log_warning("compile log not open?"); return 0; /* continue? */ } /* must exactly match call in dcc_add_log_to_file */ rs_remove_logger(rs_logger_file, RS_LOG_WARNING, NULL, dcc_compile_log_fd); dcc_close(dcc_compile_log_fd); dcc_compile_log_fd = -1; return 0; }
/* * Try to setup dnotify on the state directory. This returns the * descriptor of a pipe in @p dummy_fd. Every time the state changes, * a single byte is written to this pipe. A caller who select()s on * the pipe will therefore be woken every time there is a change. * * If we can do dnotify, create the dummy pipe and turn it on. * * @fixme One problem here is that if the state directory is deleted * and recreated, then we'll never notice and find the new one. I * don't know of any good fix, other than perhaps polling every so * often. So just don't do that. * * @fixme If this function is called repeatedly it will leak FDs. * * @todo Reimplement this on top of kevent for BSD. */ int dcc_mon_setup_notify (int *dummy_fd) { #ifdef F_NOTIFY char *state_dir; int ret; int fd; if (signal (SIGIO, dcc_mon_siginfo_handler) == SIG_ERR) { rs_log_error ("signal(SIGINFO) failed: %s", strerror(errno)); return EXIT_IO_ERROR; } if (pipe ((int *) pipe_fd) == -1) { rs_log_error ("pipe failed: %s", strerror (errno)); return EXIT_IO_ERROR; } *dummy_fd = pipe_fd[0]; /* read end */ dcc_set_nonblocking (pipe_fd[0]); dcc_set_nonblocking (pipe_fd[1]); if ((ret = dcc_get_state_dir (&state_dir))) return ret; if ((fd = open (state_dir, O_RDONLY)) == -1) { rs_log_error ("failed to open %s: %s", state_dir, strerror (errno)); free (state_dir); return EXIT_IO_ERROR; } /* CAUTION! Signals can start arriving immediately. Be ready. */ if (fcntl (fd, F_NOTIFY, DN_RENAME|DN_DELETE|DN_MULTISHOT) == -1) { rs_log_warning ("setting F_NOTIFY failed: %s", strerror (errno)); free (state_dir); return EXIT_IO_ERROR; } return 0; #else /* F_NOTIFY */ return EXIT_IO_ERROR; #endif /* F_NOTIFY */ }
int dcc_get_cpp_lock() { int lock_fd; char *lockdir; int i, ncpus, sleepTime = 10000; if (dcc_get_lock_dir(&lockdir)) return -1; if (dcc_ncpus(&ncpus)) ncpus = 1; ncpus++; for (i=0; i<ncpus; i++) { sprintf(cpp_lock_filename, "%s/%s_%d", lockdir, "cpp_lock", i); if (dcc_open_lockfile(cpp_lock_filename, &lock_fd)) lock_fd = -1; else { if (sys_lock(lock_fd, 0) != 0) { rs_trace("someone already has cpp lock: %s (%s)", cpp_lock_filename, strerror(errno)); close(lock_fd); lock_fd = -1; } else { break; } } } if (lock_fd == -1) { srandom(getpid()); sprintf(cpp_lock_filename, "%s/%s_%d", lockdir, "cpp_lock", random()%(ncpus)); rs_trace("blocking for cpp lock: %s (%s)", cpp_lock_filename, strerror(errno)); if (dcc_open_lockfile(cpp_lock_filename, &lock_fd)) lock_fd = -1; else { if (sys_lock(lock_fd, 1) != 0) { rs_log_warning("failed to get cpp lock: %s (%s)", cpp_lock_filename, strerror(errno)); close(lock_fd); lock_fd = -1; } } } if (lock_fd != -1) rs_trace("got cpp lock: %s", cpp_lock_filename); cpp_lock = lock_fd; return lock_fd; }
/** * Stick a TCP cork in the socket. It's not clear that this will help * performance, but it might. * * This is a no-op if we don't think this platform has corks. **/ int tcp_cork_sock(int fd, int corked) { #ifdef TCP_CORK if (!dcc_getenv_bool("DISTCC_TCP_CORK", 1)) return 0; if (setsockopt(fd, SOL_TCP, TCP_CORK, &corked, sizeof corked) == -1) { if (errno == ENOSYS || errno == ENOTSUP) { if (corked) rs_trace("no corks allowed on fd%d", fd); /* no need to complain about not uncorking */ } else { rs_log_warning("setsockopt(corked=%d) failed: %s", corked, strerror(errno)); /* continue anyhow */ } } #endif /* def TCP_CORK */ return 0; }
/** * Remove the state file for this process. * * This can be called from atexit(). **/ void dcc_remove_state_file (void) { char *fname; int ret; if ((ret = dcc_get_state_filename(&fname))) return; if (unlink(fname) == -1) { /* It's OK if we never created it */ if (errno != ENOENT) { rs_log_warning("failed to unlink %s: %s", fname, strerror(errno)); ret = EXIT_IO_ERROR; } } free(fname); (void) ret; }
/** * Try to find an appropriate uid,gid to change to. * * In order, we try "distcc" or the user on the command line, or "nobody", or * failing that the traditional value for nobody of 65534. */ static int dcc_preferred_user(uid_t *puid, gid_t *pgid) { struct passwd *pw; if ((pw = getpwnam(opt_user))) { *puid = pw->pw_uid; *pgid = pw->pw_gid; return 0; /* cool */ } /* Note getpwnam() does not set errno */ rs_log_warning("no such user as \"%s\"", opt_user); /* try something else */ if ((pw = getpwnam("nobody"))) { *puid = pw->pw_uid; *pgid = pw->pw_gid; return 0; /* cool */ } /* just use traditional value */ *puid = *pgid = 65534; return 0; }
static ssize_t sys_sendfile(int ofd, int ifd, off_t *offset, size_t size) { rs_log_warning("no sendfile implementation on this platform"); errno = ENOSYS; return -1; }
/** * distcc daemon. May run from inetd, or standalone. Accepts * requests from clients to compile files. **/ int main(int argc, char *argv[]) { int ret; const char *tmp; dcc_setup_startup_log(); if (distccd_parse_options(argc, (const char **) argv)) dcc_exit(EXIT_DISTCC_FAILED); /* check this before redirecting the logs, so that it's really obvious */ if (!dcc_should_be_inetd()) if (opt_allowed == NULL) { rs_log_error("--allow option is now mandatory; " "you must specify which clients are allowed to connect"); ret = EXIT_BAD_ARGUMENTS; goto out; } if ((ret = dcc_set_lifetime()) != 0) dcc_exit(ret); /* do this before giving away root */ if (nice(opt_niceness) == -1) { rs_log_warning("nice %d failed: %s", opt_niceness, strerror(errno)); /* continue anyhow */ } if ((ret = dcc_discard_root()) != 0) dcc_exit(ret); /* Discard privileges before opening log so that if it's created, it has * the right ownership. */ dcc_setup_real_log(); /* Do everything from root directory. Allows start directory to be * unmounted, should make accidental writing of local files cause a * failure... */ if ((ret = dcc_get_tmp_top(&tmp))) goto out; if (chdir(tmp) == -1) { rs_log_error("failed to chdir to %s: %s", tmp, strerror(errno)); ret = EXIT_IO_ERROR; goto out; } else { rs_trace("chdir to %s", tmp); } if ((ret = dcc_setup_daemon_path())) goto out; if (dcc_should_be_inetd()) ret = dcc_inetd_server(); else ret = dcc_standalone_server(); out: dcc_exit(ret); }
/** * Load a whole file into a new string in a malloc'd memory buffer. * * Files larger than a certain reasonableness limit are not loaded, because * this is only used for reasonably short text files. * * Files that do not exist cause EXIT_NO_SUCH_FILE, but no error message. * (This suits our case of loading configuration files. It could be made * optional.) **/ int dcc_load_file_string(const char *filename, char **retbuf) { int fd; int ret; ssize_t read_bytes; struct stat sb; char *buf; /* Open the file */ if ((fd = open(filename, O_RDONLY)) == -1) { if (errno == EEXIST) return EXIT_NO_SUCH_FILE; else { rs_log_warning("failed to open %s: %s", filename, strerror(errno)); return EXIT_IO_ERROR; } } /* Find out how big the file is */ if (fstat(fd, &sb) == -1) { rs_log_error("fstat %s failed: %s", filename, strerror(errno)); ret = EXIT_IO_ERROR; goto out_close; } if (sb.st_size > 1<<20) { rs_log_error("%s is too large to load (%ld bytes)", filename, (long) sb.st_size); ret = EXIT_OUT_OF_MEMORY; goto out_close; } /* Allocate a buffer, allowing space for a nul. */ if ((*retbuf = buf = malloc((size_t) sb.st_size + 1)) == NULL) { rs_log_error("failed to allocate %ld byte file buffer", (long) sb.st_size); ret = EXIT_OUT_OF_MEMORY; goto out_close; } /* Read everything */ if ((read_bytes = read(fd, buf, (size_t) sb.st_size)) == -1) { rs_log_error("failed to read %s: %s", filename, strerror(errno)); ret = EXIT_IO_ERROR; goto out_free; } /* Null-terminate. It's OK if we read a bit less than we expected to. */ buf[read_bytes] = '\0'; ret = 0; out_close: dcc_close(fd); return ret; out_free: free(*retbuf); dcc_close(fd); return ret; }
/** * Create a file inside the temporary directory and register it for * later cleanup, and return its name. * * The file will be reopened later, possibly in a child. But we know * that it exists with appropriately tight permissions. **/ int dcc_make_tmpnam(const char *prefix, const char *suffix, char **name_ret) { char *s = NULL; const char *tempdir; int ret; unsigned long random_bits; int fd; if ((ret = dcc_get_tmp_top(&tempdir))) return ret; if (access(tempdir, W_OK|X_OK) == -1) { rs_log_error("can't use TMPDIR \"%s\": %s", tempdir, strerror(errno)); return EXIT_IO_ERROR; } random_bits = (unsigned long) getpid() << 16; # if HAVE_GETTIMEOFDAY { struct timeval tv; gettimeofday(&tv, NULL); random_bits ^= tv.tv_usec << 16; random_bits ^= tv.tv_sec; } # else random_bits ^= time(NULL); # endif #if 0 random_bits = 0; /* FOR TESTING */ #endif do { free(s); if (asprintf(&s, "%s/%s_%08lx%s", tempdir, prefix, random_bits & 0xffffffffUL, suffix) == -1) return EXIT_OUT_OF_MEMORY; /* Note that if the name already exists as a symlink, this * open call will fail. * * The permissions are tight because nobody but this process * and our children should do anything with it. */ fd = open(s, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd == -1) { /* try again */ rs_trace("failed to create %s: %s", s, strerror(errno)); random_bits += 7777; /* fairly prime */ continue; } if (close(fd) == -1) { /* huh? */ rs_log_warning("failed to close %s: %s", s, strerror(errno)); return EXIT_IO_ERROR; } break; } while (1); if ((ret = dcc_add_cleanup(s))) { /* bailing out */ unlink(s); free(s); return ret; } *name_ret = s; return 0; }
/* Called whenever a new service is found or removed */ static void browse_reply( AvahiServiceBrowser *UNUSED(b), AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags UNUSED(flags), void *userdata) { struct daemon_data *d = userdata; assert(d); switch (event) { case AVAHI_BROWSER_NEW: { struct host *h; h = malloc(sizeof(struct host)); assert(h); rs_log_info("new service: %s\n", name); if (!(h->resolver = avahi_service_resolver_new(d->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_reply, h))) { rs_log_warning("Failed to create service resolver for '%s': %s\n", name, avahi_strerror(avahi_client_errno(d->client))); free(h); } else { /* Fill in missing data */ h->service = strdup(name); assert(h->service); h->domain = strdup(domain); assert(h->domain); h->daemon_data = d; h->interface = interface; h->protocol = protocol; h->next = d->hosts; h->n_cpus = 1; d->hosts = h; } break; } case AVAHI_BROWSER_REMOVE: rs_log_info("Removed service: %s\n", name); remove_service(d, interface, protocol, name, domain); write_hosts(d); break; case AVAHI_BROWSER_FAILURE: rs_log_crit("Service Browser failure '%s': %s\n", name, avahi_strerror(avahi_client_errno(d->client))); avahi_simple_poll_quit(d->simple_poll); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: case AVAHI_BROWSER_ALL_FOR_NOW: ; } }
/* The main function of the background daemon */ static int daemon_proc(const char *host_file, const char *lock_file, int n_slots) { int ret = 1; int lock_fd = -1; struct daemon_data d; time_t clip_time; int error; char machine[64], version[64], stype[128]; rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0); /* Prepare daemon data structure */ d.fd = -1; d.hosts = NULL; d.n_slots = n_slots; d.simple_poll = NULL; d.browser = NULL; d.client = NULL; clip_time = time(NULL); rs_log_info("Zeroconf daemon running.\n"); /* Open daemon lock file and lock it */ if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) { rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno)); goto finish; } if (generic_lock(lock_fd, 1, 1, 0) < 0) { /* lock failed, there's probably already another daemon running */ goto finish; } /* Open host file */ if ((d.fd = open(host_file, O_RDWR|O_CREAT, 0666)) < 0) { rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno)); goto finish; } /* Clear host file */ write_hosts(&d); if (!(d.simple_poll = avahi_simple_poll_new())) { rs_log_crit("Failed to create simple poll object.\n"); goto finish; } if (!(d.client = avahi_client_new( avahi_simple_poll_get(d.simple_poll), 0, client_callback, &d, &error))) { rs_log_crit("Failed to create Avahi client object: %s\n", avahi_strerror(error)); goto finish; } if (dcc_get_gcc_version(version, sizeof(version)) && dcc_get_gcc_machine(machine, sizeof(machine))) { dcc_make_dnssd_subtype(stype, sizeof(stype), version, machine); } else { rs_log_warning("Warning, failed to get CC version and machine type.\n"); strncpy(stype, DCC_DNS_SERVICE_TYPE, sizeof(stype)); stype[sizeof(stype)-1] = 0; } rs_log_info("Browsing for '%s'.\n", stype); if (!(d.browser = avahi_service_browser_new( d.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, stype, NULL, 0, browse_reply, &d))) { rs_log_crit("Failed to create service browser object: %s\n", avahi_strerror(avahi_client_errno(d.client))); goto finish; } /* Check whether the host file has been used recently */ while (fd_last_used(d.fd, clip_time) <= MAX_IDLE_TIME) { /* Iterate the main loop for 5s */ if (avahi_simple_poll_iterate(d.simple_poll, 5000) != 0) { rs_log_crit("Event loop exited abnormaly.\n"); goto finish; } } /* Wer are idle */ rs_log_info("Zeroconf daemon unused.\n"); ret = 0; finish: /* Cleanup */ if (lock_fd >= 0) { generic_lock(lock_fd, 1, 0, 0); close(lock_fd); } if (d.fd >= 0) close(d.fd); while (d.hosts) { struct host *h = d.hosts; d.hosts = d.hosts->next; free_host(h); } if (d.client) avahi_client_free(d.client); if (d.simple_poll) avahi_simple_poll_free(d.simple_poll); rs_log_info("zeroconf daemon ended.\n"); return ret; }
static void register_stuff(struct context *ctx) { if (!ctx->group) { if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) { rs_log_crit("Failed to create entry group: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } } if (avahi_entry_group_is_empty(ctx->group)) { char cpus[32] = ""; char machine[64] = "cc_machine=", version[64] = "cc_version="; char *m = NULL, *v = NULL; if (ctx->advertise_capabilities) { snprintf(cpus, sizeof(cpus), "cpus=%i", ctx->n_cpus); v = dcc_get_gcc_version(version+11, sizeof(version)-11); m = dcc_get_gcc_machine(machine+11, sizeof(machine)-11); } /* Register our service */ if (avahi_entry_group_add_service( ctx->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, ctx->name, ctx->service_type, NULL, NULL, ctx->port, !ctx->advertise_capabilities ? NULL : "txtvers=1", cpus, "distcc="PACKAGE_VERSION, "gnuhost="GNU_HOST, v ? version : NULL, m ? machine : NULL, NULL) < 0) { rs_log_crit("Failed to add service: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } if (ctx->advertise_capabilities) { if (v && m) { char stype[128]; dcc_make_dnssd_subtype(stype, sizeof(stype), v, m); if (avahi_entry_group_add_service_subtype( ctx->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, ctx->name, ctx->service_type, NULL, stype) < 0) { rs_log_crit("Failed to add service: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } } else rs_log_warning("Failed to determine CC version, not registering DNS-SD service subtype!"); } if (avahi_entry_group_commit(ctx->group) < 0) { rs_log_crit("Failed to commit entry group: %s", avahi_strerror(avahi_client_errno(ctx->client))); goto fail; } } return; fail: avahi_threaded_poll_quit(ctx->threaded_poll); }