static int munmap_file(void *base, const char *path, int fd, const struct stat *st) { int status = 0; #ifdef HAVE_SYS_MMAP_H if (munmap(base, st->st_size) != 0) { rs_log_error("munmap of file '%s' failed: %s", path, strerror(errno)); status = 1; } #else errno = 0; if (lseek(fd, 0, SEEK_SET) == -1) { rs_log_error("can't seek to start of %s: %s", path, strerror(errno)); status = 1; } else if (write(fd, base, st->st_size) != st->st_size) { rs_log_error("can't write %ld bytes to %s: %s", (long) st->st_size, path, strerror(errno)); status = 1; } #endif if (close(fd) != 0) { rs_log_error("close of file '%s' failed: %s", path, strerror(errno)); status = 1; } return status; }
static int dcc_set_file_extension(const char *sfile, const char *new_ext, char **ofile) { char *dot, *o; o = strdup(sfile); if (!o) { rs_log_error("strdup failed (out of memory?)"); return EXIT_DISTCC_FAILED; } dot = dcc_find_extension(o); if (!dot) { rs_log_error("couldn't find extension in \"%s\"", o); return EXIT_DISTCC_FAILED; } if (strlen(dot) < strlen(new_ext)) { rs_log_error("not enough space for new extension"); return EXIT_DISTCC_FAILED; } strcpy(dot, new_ext); *ofile = o; return 0; }
void rs_sig_handle(int signum) { int err = errno; switch(signum) { case SIGPIPE: /* ignore */ break; case SIGINT: rs_quit = 1; rs_log_error(RS_LOG_INFO, 0, "get a SIGINT signal"); break; case SIGTERM: rs_quit = 1; rs_log_error(RS_LOG_INFO, 0, "get a SIGTERM signal"); break; case SIGQUIT: rs_quit = 1; rs_log_error(RS_LOG_INFO, 0, "get a SIGQUIT signal"); break; case SIGHUP: rs_reload = 1; rs_log_error(RS_LOG_INFO, 0, "get a SIGHUP signal"); } errno = err; return; }
/** * Read the "DONE" token from the network that introduces a response. **/ int dcc_r_result_header(int ifd, enum dcc_protover expect_ver) { unsigned vers; int ret; if ((ret = dcc_r_token_int(ifd, "DONE", &vers))) rs_log_error("server provided no answer. " "Is the server configured to allow access from your IP" " address? Is the server performing authentication and" " your client isn't? Does the server have the compiler" " installed? Is the server configured to access the" " compiler?"); return ret; if (vers != expect_ver) { rs_log_error("got version %d not %d in response from server", vers, expect_ver); return EXIT_PROTOCOL_ERROR; } rs_trace("got response header"); return 0; }
/** * Write bytes to an fd. Keep writing until we're all done or something goes * wrong. * * @returns 0 or exit code. **/ int dcc_writex(int fd, const void *buf, size_t len) { ssize_t r; int ret; while (len > 0) { r = write(fd, buf, len); if (r == -1 && errno == EAGAIN) { if ((ret = dcc_select_for_write(fd, dcc_io_timeout))) return ret; else continue; } else if (r == -1 && errno == EINTR) { continue; } else if (r == -1) { rs_log_error("failed to write: %s", strerror(errno)); return EXIT_IO_ERROR; } else if (r == 0) { rs_log_error("unexpected eof on fd%d", fd); return EXIT_TRUNCATED; } else { buf = &((char *) buf)[r]; len -= r; } } return 0; }
int rs_redis_append_command(rs_slave_info_t *si, const char *fmt, ...) { va_list args; redisContext *c; int i, err; i = 0; err = 0; c = si->c; for( ;; ) { if(c == NULL) { /* retry connect*/ c = redisConnect(si->redis_addr, si->redis_port); if(c->err) { if(i % 60 == 0) { i = 0; rs_log_error(RS_LOG_ERR, rs_errno, "redisConnect(\"%s\", " "%d) failed, %s" , si->redis_addr, si->redis_port, c->errstr); } redisFree(c); c = NULL; i += RS_REDIS_CONNECT_RETRY_SLEEP_SEC; sleep(RS_REDIS_CONNECT_RETRY_SLEEP_SEC); continue; } } va_start(args, fmt); err = redisvAppendCommand(c, fmt, args); va_end(args); break; } si->c = c; if(err != REDIS_OK) { rs_log_error(RS_LOG_ERR, rs_errno, "redisvAppendCommand() failed"); return RS_ERR; } si->cmdn++; return RS_OK; }
/** * Obtain the CPU speed in Hz. **/ int dcc_cpuspeed(unsigned long long *speed) { #if defined(__APPLE__) size_t len = sizeof(*speed); if (sysctlbyname("hw.cpufrequency", speed, &len, NULL, 0) == 0) return 0; rs_log_error("sysctlbyname(\"hw.cpufrequency\") failed: %s", strerror(errno)); *speed = 1; return EXIT_DISTCC_FAILED; #elif defined(linux) /* This fetches the maximum speed for cpu0, on the assumption that all * CPUs in the system are the same speed, and the maximum speed is the * speed that the CPU will run at if needed. The maximum speed may be * greater than the current speed due to scaling. */ FILE *f; long long khz; int rv; f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); if (!f) { rs_log_error("open cpuinfo_max_freq failed: %s", strerror(errno)); *speed = 1; return EXIT_DISTCC_FAILED; } rv = fscanf(f, "%lld", &khz); fclose(f); if (rv != 1 || khz <= 0) { rs_log_error("cpuinfo_max_freq makes no sense"); *speed = 1; return EXIT_DISTCC_FAILED; } *speed = khz * 1000; return 0; #else /* linux */ #warning "Please port this function" *speed = 1; return EXIT_DISTCC_FAILED; #endif /* linux */ }
int main(int argc, char *argv[]) { rs_trace_set_level(RS_LOG_DEBUG); rs_add_logger(rs_logger_file, RS_LOG_DEBUG, NULL, STDERR_FILENO); if (argc < 2) { rs_log_error(USAGE); return 1; } if (strcmp(argv[1], "dcc_fresh_dependency_exists") == 0) { if (argc != 5) { rs_log_error("dcc_fresh_dependency_exists expects DOTD_FNAME " "EXCL_PAT REF_TIME"); return 1; } errno = 0; char *ptr; time_t ref_time = (time_t)strtol(argv[4], &ptr, 0); if (errno || (*ptr != '\0')) { rs_log_error("strtol failed"); return 1; } else { char *result; int ret; ret = dcc_fresh_dependency_exists((const char *)argv[2], (const char *)argv[3], ref_time, &result); if (ret) printf("h_compile.c: UNEXPECTED RETURN VALUE\n"); else printf("result %s\n", result ? result : "(NULL)"); if (result) free(result); } } else if (strcmp(argv[1], "dcc_discrepancy_filename") == 0) { if (argc != 2) { rs_log_error("dcc_discrepancy_filename expects no arguments"); return 1; } char *result; int ret = dcc_discrepancy_filename(&result); if (ret) printf("h_compile.c: UNEXPECTED RETURN VALUE\n"); else printf("%s", result ? result : "(NULL)"); } else { rs_log_error(USAGE); return 1; } return 0; }
/* * Transmit the body of a file using sendfile(). * * Linux at the moment requires the input be page-based -- ie a disk file, and * only on particular filesystems. If the sendfile() call fails in a way that * makes us think that regular IO might work, then we try that instead. For * example, the /tmp filesystem may not support sendfile(). */ int dcc_pump_sendfile(int ofd, int ifd, size_t size) { ssize_t sent; off_t offset = 0; int ret; while (size) { /* Handle possibility of partial transmission, e.g. if * sendfile() is interrupted by a signal. size is decremented * as we go. */ sent = sys_sendfile(ofd, ifd, &offset, size); if (sent == -1) { if ((errno == ENOSYS || errno == EINVAL) && offset == 0) { /* The offset==0 tests is because we may be part way through * the file. We can't just naively go back to read/write * because sendfile() does not update the file pointer: we * would need to lseek() first. That case is not handled at * the moment because it's unlikely that sendfile() would * suddenly be unsupported while we're using it. A failure * halfway through probably indicates a genuine error.*/ rs_log_info("decided to use read/write rather than sendfile"); return dcc_pump_readwrite(ofd, ifd, size); } else if (errno == EAGAIN) { /* Sleep until we're able to write out more data. */ if ((ret = dcc_select_for_write(ofd, dcc_io_timeout)) != 0) return ret; rs_trace("select() returned, continuing to write"); } else if (errno == EINTR) { rs_trace("sendfile() interrupted, continuing"); } else { rs_log_error("sendfile failed: %s", strerror(errno)); return EXIT_IO_ERROR; } } else if (sent == 0) { rs_log_error("sendfile returned 0? can't cope"); return EXIT_IO_ERROR; } else if (sent != (ssize_t) size) { /* offset is automatically updated by sendfile. */ size -= sent; rs_log_notice("sendfile: partial transmission of %ld bytes; retrying %ld @%ld", (long) sent, (long) size, (long) offset); } else { /* normal case, everything was sent. */ break; } } return 0; }
/** * Become a daemon, discarding the controlling terminal. * * Borrowed from rsync. * * This function returns in the child, but not in the parent. **/ static void dcc_detach(void) { int i; pid_t pid; pid_t sid; dcc_ignore_sighup(); if ((pid = fork()) == -1) { rs_log_error("fork failed: %s", strerror(errno)); exit(EXIT_DISTCC_FAILED); } else if (pid != 0) { /* In the parent. This guy is about to go away so as to * detach from the controlling process, but first save the * child's pid. */ dcc_save_pid(pid); _exit(0); } /* This is called in the detached child */ /* detach from the terminal */ #ifdef HAVE_SETSID if ((sid = setsid()) == -1) { rs_log_error("setsid failed: %s", strerror(errno)); } else { rs_trace("setsid to session %d", (int) sid); } #else /* no HAVE_SETSID */ #ifdef TIOCNOTTY i = open("/dev/tty", O_RDWR); if (i >= 0) { ioctl(i, (int) TIOCNOTTY, (char *)0); close(i); } #endif /* TIOCNOTTY */ #endif /* not HAVE_SETSID */ /* make sure that stdin, stdout an stderr don't stuff things up (library functions, for example) */ for (i=0;i<3;i++) { close(i); open("/dev/null", O_RDWR); } /* If there's a lifetime limit on this server (for testing) then it needs * to apply after detaching as well. */ dcc_set_lifetime(); }
/** * Fork a child to repeatedly accept and handle incoming connections. * * To protect against leaks, we quit after 50 requests and let the parent * recreate us. **/ static int dcc_preforked_child(int listen_fd) { int ireq; const int child_lifetime = 50; for (ireq = 0; ireq < child_lifetime; ireq++) { int acc_fd; struct dcc_sockaddr_storage cli_addr; socklen_t cli_len; cli_len = sizeof cli_addr; do { acc_fd = accept(listen_fd, (struct sockaddr *) &cli_addr, &cli_len); } while (acc_fd == -1 && errno == EINTR); if (acc_fd == -1) { rs_log_error("accept failed: %s", strerror(errno)); dcc_exit(EXIT_CONNECT_FAILED); } dcc_service_job(acc_fd, acc_fd, (struct sockaddr *) &cli_addr, cli_len); dcc_close(acc_fd); } rs_log_info("worn out"); return 0; }
/** * Change object file or suffix of -o to @p ofname * Frees the old value, if it exists. * * It's crucially important that in every case where an output file is * detected by dcc_scan_args(), it's also correctly identified here. * It might be better to make the code shared. **/ int dcc_set_output(char **a, char *ofname) { int i; for (i = 0; a[i]; i++) if (0 == strcmp(a[i], "-o") && a[i+1] != NULL) { rs_trace("changed output from \"%s\" to \"%s\"", a[i+1], ofname); free(a[i+1]); a[i+1] = strdup(ofname); if (a[i+1] == NULL) { rs_log_crit("failed to allocate space for output parameter"); return EXIT_OUT_OF_MEMORY; } dcc_trace_argv("command after", a); return 0; } else if (0 == strncmp(a[i], "-o", 2)) { char *newptr; rs_trace("changed output from \"%s\" to \"%s\"", a[i]+2, ofname); free(a[i]); if (asprintf(&newptr, "-o%s", ofname) == -1) { rs_log_crit("failed to allocate space for output parameter"); return EXIT_OUT_OF_MEMORY; } a[i] = newptr; dcc_trace_argv("command after", a); return 0; } rs_log_error("failed to find \"-o\""); return EXIT_DISTCC_FAILED; }
static ssize_t sys_sendfile(int ofd, int ifd, off_t *offset, size_t size) { off_t sent_bytes; int ret; /* According to the manual, this can never partially complete on a * socket open for blocking IO. */ ret = sendfile(ifd, ofd, *offset, size, 0, &sent_bytes, 0); if (ret == -1) { /* http://cvs.apache.org/viewcvs.cgi/apr/network_io/unix/sendrecv.c?rev=1.95&content-type=text/vnd.viewcvs-markup */ if (errno == EAGAIN) { if (sent_bytes == 0) { /* Didn't send anything. Return error with errno == EAGAIN. */ return -1; } else { /* We sent some bytes, but they we would block. Treat this as * success for now. */ *offset += sent_bytes; return sent_bytes; } } else { /* some other error */ return -1; } } else if (ret == 0) { *offset += size; return size; } else { rs_log_error("don't know how to handle return %d from BSD sendfile", ret); return -1; } }
int dcc_select_for_write(int fd, int timeout) { fd_set fds; int rs; struct timeval tv; tv.tv_sec = timeout; tv.tv_usec = 0; while (1) { FD_ZERO(&fds); FD_SET(fd, &fds); rs_trace("select for write on fd%d", fd); rs = select(fd + 1, NULL, &fds, &fds, &tv); if (rs == -1 && errno == EINTR) { rs_trace("select was interrupted"); continue; } else if (rs == -1) { rs_log_error("select failed: %s", strerror(errno)); return EXIT_IO_ERROR; } else { return 0; } } }
/* * DESCRIPTION * send slave dump cmd * format : slave.info\n,filter.tables,\0ringbuf_sleep_usec(binary) * eaxmplae : /data/mysql-bin.00001,0\n,test.test,\01000(binary) * * */ static int rs_send_dumpcmd(rs_slave_info_t *si) { int32_t l; ssize_t n; l = rs_strlen(si->dump_info) + 2 + rs_strlen(si->filter_tables) + 2 + 4; char buf[4 + l], *p; p = buf; p = rs_cpymem(buf, &l, 4); if(snprintf(p, l + 1, "%s\n,%s,%c", si->dump_info, si->filter_tables, 0) < 0) { rs_log_error(RS_LOG_ERR, rs_errno, "snprintf() failed"); return RS_ERR; } rs_memcpy(p + l - 4, &(si->rb_esusec), 4); n = rs_write(si->svr_fd, buf, 4 + l); if(n != 4 + l) { return RS_ERR; } return RS_OK; }
/** * Main loop for no-fork mode. * * Much slower and may leak. Should only be used when you want to run gdb on * distccd. **/ static void dcc_nofork_parent(int listen_fd) { while (1) { int acc_fd; struct dcc_sockaddr_storage cli_addr; socklen_t cli_len; rs_log_info("waiting to accept connection"); cli_len = sizeof cli_addr; acc_fd = accept(listen_fd, (struct sockaddr *) &cli_addr, &cli_len); if (acc_fd == -1 && errno == EINTR) { ; } else if (acc_fd == -1) { rs_log_error("accept failed: %s", strerror(errno)); #ifdef HAVE_GSSAPI if (dcc_auth_enabled) { dcc_gssapi_release_credentials(); if (opt_blacklist_enabled || opt_whitelist_enabled) { dcc_gssapi_free_list(); } } #endif dcc_exit(EXIT_CONNECT_FAILED); } else { dcc_service_job(acc_fd, acc_fd, (struct sockaddr *) &cli_addr, cli_len); dcc_close(acc_fd); } } }
/** * Set log to the final destination after options have been read. **/ static void dcc_setup_real_log(void) { int fd; /* Even in inetd mode, we might want to log to stderr, because that will * work OK for ssh connections. */ if (opt_log_stderr) { rs_remove_all_loggers(); rs_add_logger(rs_logger_file, opt_log_level_num, 0, STDERR_FILENO); return; } if (arg_log_file) { /* Don't remove loggers yet, in case this fails and needs to go to the * default. */ if ((fd = open(arg_log_file, O_CREAT|O_APPEND|O_WRONLY, 0666)) == -1) { rs_log_error("failed to open %s: %s", arg_log_file, strerror(errno)); /* continue and use syslog */ } else { rs_remove_all_loggers(); rs_add_logger(rs_logger_file, opt_log_level_num, NULL, fd); return; } } rs_remove_all_loggers(); openlog("distccd", LOG_PID, LOG_DAEMON); rs_add_logger(rs_logger_syslog, opt_log_level_num, NULL, 0); }
/** * Main loop for the parent process with the new preforked implementation. * The parent is just responsible for keeping a pool of children and they * accept connections themselves. **/ int dcc_preforking_parent(int listen_fd) { while (1) { pid_t kid; while (dcc_nkids < dcc_max_kids) { if ((kid = fork()) == -1) { rs_log_error("fork failed: %s", strerror(errno)); return EXIT_OUT_OF_MEMORY; /* probably */ } else if (kid == 0) { dcc_exit(dcc_preforked_child(listen_fd)); } else { /* in parent */ ++dcc_nkids; rs_trace("up to %d children", dcc_nkids); } /* Don't start them too quickly, or we might overwhelm a machine * that's having trouble. */ sleep(1); dcc_reap_kids(FALSE); } /* wait for any children to exit, and then start some more */ dcc_reap_kids(TRUE); /* Another little safety brake here: since children should not exit * too quickly, pausing before starting them should be harmless. */ sleep(1); } }
int dcc_ncpus(int *ncpus) { #if defined(_SC_NPROCESSORS_ONLN) /* Linux, Solaris, Tru64, UnixWare 7, and Open UNIX 8 */ *ncpus = sysconf(_SC_NPROCESSORS_ONLN); #elif defined(_SC_NPROC_ONLN) /* IRIX */ *ncpus = sysconf(_SC_NPROC_ONLN); #else #warning "Please port this function" *ncpus = -1; /* unknown */ #endif if (*ncpus == -1) { rs_log_error("sysconf(_SC_NPROCESSORS_ONLN) failed: %s", strerror(errno)); *ncpus = 1; return EXIT_DISTCC_FAILED; } else if (*ncpus == 0) { /* if there are no cpus, what are we running on? But it has * apparently been observed to happen on ARM Linux */ *ncpus = 1; } return 0; }
/** * We got a mismatch on a token, which indicates either a bug in distcc, or * that somebody (inetd?) is interfering with our network stream, or perhaps * some other network problem. Whatever's happened, a bit more debugging * information would be handy. **/ static int dcc_explain_mismatch(const char *buf, size_t buflen, int ifd) { ssize_t ret; char extrabuf[200]; char *p; size_t l; memcpy(extrabuf, buf, buflen); /* Read a bit more context, and find the printable prefix. */ ret = read(ifd, extrabuf + buflen, sizeof extrabuf - 1 - buflen); if (ret == -1) { ret = 0; /* pah, use what we've got */ } l = buflen + ret; extrabuf[l] = '\0'; for (p = extrabuf; *p; p++) if (!(isprint(*p) || *p == ' ' || *p == '\t')) { *p = '\0'; break; } rs_log_error("error context: \"%s\"", extrabuf); return 0; /* i just feel really sad... */ }
/* * Compress from a file to a newly malloc'd block. */ int dcc_compress_file_lzo1x(int in_fd, size_t in_len, char **out_buf, size_t *out_len) { char *in_buf = NULL; int ret; if ((in_buf = malloc(in_len)) == NULL) { rs_log_error("allocation of %ld byte buffer failed", (long) in_len); ret = EXIT_OUT_OF_MEMORY; goto out; } if ((ret = dcc_readx(in_fd, in_buf, in_len))) goto out; if ((ret = dcc_compress_lzo1x_alloc(in_buf, in_len, out_buf, out_len))) goto out; out: if (in_buf != NULL) { free(in_buf); } return ret; }
/* Set the PATH environment variable to the indicated value. */ int dcc_set_path(const char *newpath) { char *buf; if (asprintf(&buf, "PATH=%s", newpath) <= 0 || !buf) { rs_log_error("failed to allocate buffer for new PATH"); return EXIT_OUT_OF_MEMORY; } rs_trace("setting %s", buf); if (putenv(buf) < 0) { rs_log_error("putenv PATH failed"); return EXIT_FAILURE; } /* We must leave "buf" allocated. */ return 0; }
static ssize_t sys_sendfile(int ofd, int ifd, off_t *offset, size_t size) { off_t sent_bytes = size; int ret = sendfile(ifd, ofd, *offset, &sent_bytes, NULL, 0); if (ret == -1) { if (errno == EAGAIN) { if (sent_bytes == 0) { return -1; } else { *offset += sent_bytes; return sent_bytes; } } else { return -1; } } else if (ret == 0) { *offset += size; return size; } else { rs_log_error("don't know how to handle return %d from Darwin sendfile", ret); return -1; } }
/** * Run @p argv in a child asynchronously. * * stdin, stdout and stderr are redirected as shown, unless those * filenames are NULL. In that case they are left alone. * * @warning When called on the daemon, where stdin/stdout may refer to random * network sockets, all of the standard file descriptors must be redirected! **/ int dcc_spawn_child(char **argv, pid_t *pidptr, const char *stdin_file, const char *stdout_file, const char *stderr_file) { pid_t pid; dcc_trace_argv("forking to execute", argv); pid = fork(); if (pid == -1) { rs_log_error("failed to fork: %s", strerror(errno)); return EXIT_OUT_OF_MEMORY; /* probably */ } else if (pid == 0) { /* If this is a remote compile, * put the child in a new group, so we can * kill it and all its descendents without killing distccd * FIXME: if you kill distccd while it's compiling, and * the compiler has an infinite loop bug, the new group * will run forever until you kill it. */ if (stdout_file != NULL) { if (dcc_new_pgrp() != 0) rs_trace("Unable to start a new group\n"); } dcc_inside_child(argv, stdin_file, stdout_file, stderr_file); /* !! NEVER RETURN FROM HERE !! */ } else { *pidptr = pid; rs_trace("child started as pid%d", (int) pid); return 0; } }
/** * Replace this program with another in the same process. * * Does not return, either execs the compiler in place, or exits with * a message. **/ static void dcc_execvp(char **argv) { char *slash; execvp(argv[0], argv); /* If we're still running, the program was not found on the path. One * thing that might have happened here is that the client sent an absolute * compiler path, but the compiler's located somewhere else on the server. * In the absence of anything better to do, we search the path for its * basename. * * Actually this code is called on both the client and server, which might * cause unintnded behaviour in contrived cases, like giving a full path * to a file that doesn't exist. I don't think that's a problem. */ slash = strrchr(argv[0], '/'); if (slash) execvp(slash + 1, argv); /* shouldn't be reached */ rs_log_error("failed to exec %s: %s", argv[0], strerror(errno)); dcc_exit(EXIT_COMPILER_MISSING); /* a generalization, i know */ }
/** * Open a socket to a tcp remote host with the specified port. **/ int dcc_connect_by_name(const char *host, int port, int *p_fd) { struct addrinfo hints; struct addrinfo *res; int error; int ret; char portname[20]; rs_trace("connecting to %s port %d", host, port); /* Unfortunately for us, getaddrinfo wants the port (service) as a string */ snprintf(portname, sizeof portname, "%d", port); memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(host, portname, &hints, &res); if (error) { rs_log_error("failed to resolve host %s port %d: %s", host, port, gai_strerror(error)); return EXIT_CONNECT_FAILED; } /* Try each of the hosts possible addresses. */ do { ret = dcc_connect_by_addr(res->ai_addr, res->ai_addrlen, p_fd); } while (ret != 0 && (res = res->ai_next)); return ret; }
/** * Create the directory @p path, and register it for deletion when this * compilation finished. If it already exists as a directory * we succeed, but we don't register the directory for deletion. **/ int dcc_mk_tmpdir(const char *path) { struct stat buf; int ret; if (stat(path, &buf) == -1) { if (mkdir(path, 0777) == -1) { return EXIT_IO_ERROR; } if ((ret = dcc_add_cleanup(path))) { /* bailing out */ rmdir(path); return ret; } } else { /* we could stat the file successfully; if it's a directory, * all is well, but we should not it delete later, since we did * not make it. */ if (S_ISDIR(buf.st_mode)) { return 0; } else { rs_log_error("mkdir '%s' failed: %s", path, strerror(errno)); return EXIT_IO_ERROR; } } 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; }
/** * Make sure that distccd never runs as root, by discarding privileges if we * have them. * * This used to also check gid!=0, but on BSD that is group wheel and is * apparently common for daemons or users. * * This is run before dissociating from the calling terminal so any errors go * to stdout. **/ int dcc_discard_root(void) { uid_t uid; gid_t gid; int ret; if (getuid() != 0 && geteuid() != 0) { /* Already not root. No worries. */ return 0; } if ((ret = dcc_preferred_user(&uid, &gid)) != 0) return ret; /* GNU C Library Manual says that when run by root, setgid() and setuid() * permanently discard privileges: both the real and effective uid are * set. */ if (setgid(gid)) { rs_log_error("setgid(%d) failed: %s", (int) gid, strerror(errno)); return EXIT_SETUID_FAILED; } #ifdef HAVE_SETGROUPS /* Get rid of any supplementary groups this process might have * inherited. */ /* XXX: OS X Jaguar broke setgroups so that setting it to 0 fails. */ if (setgroups(1, &gid)) { rs_log_error("setgroups failed: %s", strerror(errno)); return EXIT_SETUID_FAILED; } #endif if (setuid(uid)) { rs_log_error("setuid(%d) failed: %s", (int) uid, strerror(errno)); return EXIT_SETUID_FAILED; } if (getuid() == 0 || geteuid() == 0) { rs_log_crit("still have root privileges after trying to discard them!"); return EXIT_SETUID_FAILED; } rs_trace("discarded root privileges, changed to uid=%d gid=%d", (int) uid, (int) gid); return 0; }
int dcc_close(int fd) { if (close(fd) != 0) { rs_log_error("failed to close fd%d: %s", fd, strerror(errno)); return EXIT_IO_ERROR; } return 0; }