/* frees memory. called from more than one place because fork'd copies * still have globals hanging around before they are exited. * * frees: * - config * - configfile * - watchnode elements * - pipe list * * called from: * - handle_quit_signal (in case of main exiting) * - exit_handler (in case of handler exiting) */ void free_all_globals() { struct pipe_list* tmp_pipe; /* free config here */ keyval_node_free_all(config); /* free watchnode elements */ free_watchnodes(); free(g_watchnode); /* free / close any remaining pipes in the list */ if (pipe_list_head) { tmp_pipe = pipe_list_head->next; while(tmp_pipe) tmp_pipe = pipe_list_remove(pipe_list_head, tmp_pipe); free(pipe_list_head); } free(configfile); }
int main(int argc, char** argv) { int ifd, len = 0, i = 0, selectret = 0, maxfd, retryselect, pid; char buf[BUF_LEN]; char *configdir; char *pidfilename; char *statusfilename; char *statusbin; char *error_str; char *version_str = PACKAGE_STRING; char *pbuf; char *filename; FILE *pidfile; FILE *statusfile; fd_set set; struct inotify_event *event; struct argument *argument = argument_new(); struct pipe_list *pipe_list_cur; struct stat file_stat; struct watchnode *node; /* alloc pipe list */ pipe_list_head = malloc(sizeof(struct pipe_list)); pipe_list_head->next = NULL; /* set up signals for exiting/reaping */ signal(SIGINT, &handle_quit_signal); signal(SIGTERM, &handle_quit_signal); signal(SIGCHLD, &handle_child_signal); signal(SIGHUP, &handle_hup_signal); /* add command line arguments */ argument_register(argument, "help", "Prints this help text.", 0); argument_register(argument, "version", "Prints version information.", 0); argument_register(argument, "daemon", "Run as a daemon.", 0); argument_register(argument, "verbose", "Turns on debug text.", 0); argument_register(argument, "sync", "Sync mode (for debugging).", 0); argument_register(argument, "log-to-stdout", "Deprecated, use \"--log-to=stdout\" instead", 0); argument_register(argument, "log-to", "Log messages with specified way. " #ifdef USE_SYSLOG "Can be: stdout, file, syslog. \"file\" by default.", 1); #else "Can be: stdout, file. \"file\" by default.", 1); #endif if ((error_str = argument_parse(argument, argc, argv))) { fprintf(stderr, "Error in arguments: %s", error_str); free(error_str); return -1; } if (argument_exists(argument, "help")) { char *help_txt = argument_get_help_text(argument); printf("%s", help_txt); free(help_txt); return 0; } if (argument_exists(argument, "version")) { printf("%s\n", version_str); return 0; } if (argument_exists(argument, "verbose")) { verbose = 1; } if (argument_exists(argument, "daemon") && fork()) return 0; if (argument_exists(argument, "sync")) syncmode = 1; if (argument_exists(argument, "log-to-stdout")) fprintf(stderr, "Warning, this option is deprecated, " \ "please use new syntax: \"--log-to=stdout\".\n"); logtype = LOG_FILE; if (argument_exists(argument, "log-to") && \ (log_arg = argument_get_value(argument, "log-to")) != NULL) { if (strcmp(log_arg, "stdout") == 0) logtype = LOG_STDOUT; #ifdef USE_SYSLOG else if (strcmp(log_arg, "syslog") == 0) logtype = LOG_SYS; #endif else /* logtype already set to 'file' above */ fprintf(stderr, "Warning, selected unknown logging type. " \ "Will use \"--log-to=file\" instead.\n"); } /* get config dir (must free this) */ configdir = get_config_dir(); /* if a config file has not been specified, use default */ if (argument_get_extra(argument)) { configfile = strdup(argument_get_extra(argument)); } else { configfile = malloc (strlen(configdir) + strlen ("/config") + 1); sprintf(configfile, "%s/config", configdir); } argument_free(argument); free(configdir); if (access(configfile, R_OK) != 0) { fprintf(stderr, "error: could not open config file: %s\n", configfile); return -1; } /* create a pid file */ pidfilename = get_pid_filename(); if (stat(pidfilename, &file_stat) == 0) /* pidfile exists */ { pidfile = fopen(pidfilename, "r"); if (fscanf(pidfile, "%d", &pid) == 1) /* pidfile has a pid inside */ { char *binaryname; char *scanformat; if ((binaryname = strrchr(argv[0], '/')) != NULL) { binaryname++; } else { binaryname = argv[0]; } scanformat = malloc(strlen("Name: %") + strlen(binaryname) + strlen("s") + 1); statusfilename = malloc(strlen("/proc/") + 6 + strlen("/status") + 1); sprintf(statusfilename, "/proc/%d/status", pid); if (stat(statusfilename, &file_stat) != 0) /* write pid file if the process no longer exists */ { write_pid_file(pidfilename); } else /* process exists, so check owner and binary name */ { statusfile = fopen(statusfilename, "r"); statusbin = malloc(strlen(binaryname) + 2); /* the binary name may start with "fsniper" but be longer */ sprintf(scanformat, "Name: %%%ds", strlen(binaryname) + 1); fscanf(statusfile, scanformat, statusbin); free(statusfilename); fclose(statusfile); fclose(pidfile); if (strcmp(binaryname, statusbin) == 0 && file_stat.st_uid == getuid()) /* exit if the process is fsniper and is owned by the current user */ { printf("%s: already running instance found with pid %d. exiting.\n", binaryname, pid); exit(1); } else /* the pid file contains an old pid, one that isn't fsniper, or one not owned by the current user */ { write_pid_file(pidfilename); } } } else /* pidfile is invalid */ { fclose(pidfile); write_pid_file(pidfilename); } } else /* the pidfile doesn't exist */ { write_pid_file(pidfilename); } free(pidfilename); /* start up log */ if (!log_open()) { fprintf(stderr, "Error: could not start log.\n"); return -1; } ifd = inotify_init(); if (ifd < 0) { perror("inotify_init"); return -1; } if (verbose) log_write("Parsing config file: %s\n", configfile); config = keyval_parse_file(configfile); if ((error_str = keyval_get_error())) { fprintf(stderr, "%s", error_str); free(error_str); exit(1); } validate_config(config); /* add nodes to the inotify descriptor */ g_watchnode = add_watches(ifd); /* wait for events and then handle them */ while (1) { /* set up fds and max */ FD_ZERO(&set); FD_SET(ifd, &set); maxfd = ifd; for (pipe_list_cur = pipe_list_head->next; pipe_list_cur; pipe_list_cur = pipe_list_cur->next) { FD_SET(pipe_list_cur->pfd[0], &set); if (pipe_list_cur->pfd[0] > maxfd) maxfd = pipe_list_cur->pfd[0]; } retryselect = 1; while (retryselect) { /* use select to get activity on any of the fds */ selectret = select(maxfd + 1, &set, NULL, NULL, NULL); if (selectret == -1) { if (errno == EINTR) retryselect = 1; else handle_quit_signal(-2); } else retryselect = 0; } /* handle any events on the inotify fd */ if (FD_ISSET(ifd, &set)) { len = read(ifd, buf, BUF_LEN); while (i < len) { event = (struct inotify_event *) &buf[i]; if (event->len && (event->mask & IN_CLOSE_WRITE || event->mask & IN_MOVED_TO)) { /* if sync mode, just call handle_exec */ if (syncmode == 1) { handle_event(event, fileno(_logfd)); } else { /* create new pipe_list entry */ for (pipe_list_cur = pipe_list_head; pipe_list_cur->next != NULL; pipe_list_cur = pipe_list_cur->next) {} pipe_list_cur->next = malloc(sizeof(struct pipe_list)); pipe_list_cur->next->next = NULL; /* create pipe */ pipe(pipe_list_cur->next->pfd); if (fork() == 0) { /* child, close 0 */ close(pipe_list_cur->next->pfd[0]); log_close(); signal(SIGINT, &handle_child_quit_signal); signal(SIGTERM, &handle_child_quit_signal); handle_event(event, pipe_list_cur->next->pfd[1]); } else { /* parent, close 1 */ close(pipe_list_cur->next->pfd[1]); } } } else if (event->len && (event->mask & IN_CREATE && event->mask & IN_ISDIR)) { for (node = g_watchnode->next; node; node = node->next) if (node->wd == event->wd) break; if (node) { /* combine the name inotify gives with the full path to the file */ filename = malloc(strlen(node->path) + strlen("/") + strlen(event->name) + 1); sprintf(filename, "%s/%s", node->path, event->name); watch_dir(node, ifd, strdup(filename), node->section); free(filename); } } else if (event->len && (event->mask & IN_DELETE && event->mask & IN_ISDIR)) { for (node = g_watchnode->next; node; node = node->next) if (node->wd == event->wd) break; if (node) { /* combine the name inotify gives with the full path to the file */ filename = malloc(strlen(node->path) + strlen("/") + strlen(event->name) + 1); sprintf(filename, "%s/%s", node->path, event->name); unwatch_dir(filename, ifd); free(filename); } } i += EVENT_SIZE + event->len; } i = 0; } /* now lets see if we have any pipe activity */ pipe_list_cur = pipe_list_head->next; while (pipe_list_cur) { if (FD_ISSET(pipe_list_cur->pfd[0], &set)) { len = read(pipe_list_cur->pfd[0], buf, BUF_LEN); if (len == 0) { close(pipe_list_cur->pfd[0]); /* remove this item from the list */ pipe_list_cur = pipe_list_remove(pipe_list_head, pipe_list_cur); } else { /* print it somewhere */ pbuf = malloc(len + 1); snprintf(pbuf, len, "%s", buf); log_write("%s\n", pbuf); free(pbuf); pipe_list_cur = pipe_list_cur->next; } } else { pipe_list_cur = pipe_list_cur->next; } } } }
static ssize_t do_write(Output *output, ChunkList *chunks, Opts *options, SpillControl *spillage, int index) { ssize_t bytes = 0; Chunk *curr_chunk = output->curr_chunk; while (curr_chunk->next != NULL || output->offset < curr_chunk->len) { /* While there's something to write ... */ assert(NULL != curr_chunk->data); if (output->offset < curr_chunk->len) { /* Data available in the current Chunk */ ssize_t b; /* Send it */ do { b = write(output->fd, curr_chunk->data + output->offset, curr_chunk->len - output->offset); } while (b < 0 && EINTR == errno); if (b < 0) { /* Error */ if (EAGAIN == errno || EWOULDBLOCK == errno) break; /* Blocking is OK */ if (EPIPE == errno) return -2; /* Got EPIPE, file should be closed */ fprintf(stderr, "Error writing to %s : %s\n", output->name, strerror(errno)); return -1; } if (b == 0) break; /* Wrote nothing, try again later */ /* Update amount read */ output->written += b; output->offset += b; bytes += b; /* Record time and update linked list */ if (!output->is_reg) { output->write_time = get_time(); if (output->write_time < 0) { return -1; } while (NULL != output->next && output->next->written < output->written) { Output *n = output->next; assert(n->prev == output); pipe_list_remove(output, spillage); pipe_list_insert(output, n, spillage); } if (output == spillage->blocking_output) { spillage->blocking_output = spillage->pipe_list_head; } } } assert(output->offset <= curr_chunk->len); /* Check if at end of current Chunk */ if (output->offset == curr_chunk->len) { /* Stop sending if no more Chunks yet */ if (NULL == curr_chunk->next) break; /* Otherwise, move on to the next Chunk */ output->curr_chunk = curr_chunk->next; output->offset = 0; --curr_chunk->nwriters; if (0 != release_chunk(chunks, curr_chunk, options, spillage)) { return -1; } curr_chunk = output->curr_chunk; curr_chunk->nwriters++; if (NULL == curr_chunk->data) { /* Need to re-read spilled data */ if (0 != reread_data(curr_chunk, spillage)) return -1; } } } if (verbosity > 2 && bytes > 0) { fprintf(stderr, "%.3f Wrote %zd bytes to output #%d (%s); %lld (%sB) so far.\n", get_time(), bytes, index, output->name, (long long) output->written, human_size(output->written)); } return bytes; }
static int do_copy(Opts *options, Input *in, int noutputs, Output *outputs, int nregular, int *regular, int npipes, int *pipes, SpillControl *spillage) { ChunkList chunks = { NULL, NULL, NULL }; /* Linked list of Chunks */ struct pollfd *polls; /* structs for poll(2) */ int *poll_idx; /* indexes in outputs corresponding to entries in polls */ int *closing_pipes; /* Pipes that need to be closed */ int *closing_reg; /* Regular files that need to be closed */ int i, keeping_up = npipes, read_eof = 0, nclosed = 0; chunks.head = chunks.tail = calloc(1, sizeof(Chunk)); /* Initial Chunk */ polls = malloc((noutputs + 1) * sizeof(struct pollfd)); poll_idx = malloc((noutputs + 1) * sizeof(int)); closing_pipes = malloc((npipes + 1) * sizeof(int)); closing_reg = malloc((nregular + 1) * sizeof(int)); if (NULL == chunks.head || NULL == polls || NULL == poll_idx || NULL == closing_pipes || NULL == closing_reg) { perror("do_copy"); return -1; } chunks.head->ref_count = noutputs; chunks.head->nwriters = noutputs; /* Point all outputs to the initial Chunk and build linked list of pipes */ for (i = 0; i < noutputs; i++) { outputs[i].curr_chunk = chunks.head; if (!outputs[i].is_reg) pipe_list_push(&outputs[i], spillage); } do { /* Main loop */ int npolls = 0, pipe_close = 0, reg_close = 0; /* Are we waiting for a slow output? */ int blocked = (NULL != spillage->blocking_output && spillage->alloced >= options->max); int timeout = -1; int should_read; if (blocked) { /* Work out how long to wait in poll */ double now = get_time(); double left; if (now < 0) return -1; if (now < spillage->blocking_output->write_time) { /* Someone fiddled with the clock? */ spillage->blocking_output->write_time = now; } left = options->wait_time - (now - spillage->blocking_output->write_time); timeout = left > 0 ? (int)(left * 1000) : 0; if (timeout == 0) { /* Remove the block */ blocked = 0; spillage->blocking_output = NULL; } } /* Only try to read if not at EOF; either there are no pipes or at least one pipe has nothing left to write; and we aren't waiting for a slow output in order to prevent spillage */ should_read = (!read_eof && (npipes == 0 || keeping_up > 0) && !blocked); if (should_read) { if (in->reg) { /* If reading a regular file, do it now */ if (0 != do_read(options, in, spillage, &chunks, &read_eof, noutputs - nclosed)) { return -1; } } else { /* Otherwise add it to the poll list */ polls[npolls].fd = in->fd; poll_idx[npolls] = -1; polls[npolls].events = POLLIN; polls[npolls++].revents = 0; } } keeping_up = 0; /* Number of pipes that are keeping up */ /* Add all the pipe outputs that have something to write to the poll list */ for (i = 0; i < npipes; i++) { if (outputs[pipes[i]].curr_chunk != chunks.tail || outputs[pipes[i]].offset < chunks.tail->len) { /* Something to write */ polls[npolls].fd = outputs[pipes[i]].fd; poll_idx[npolls] = i; polls[npolls].events = POLLOUT|POLLERR|POLLHUP; polls[npolls++].revents = 0; } else { /* Keeping up or finished */ if (read_eof) { closing_pipes[pipe_close++] = i; timeout = 0; /* Ensure pipe gets closed promptly */ } else { keeping_up++; } } } if (npolls > 0) { /* Need to do some polling */ int ready; do { ready = poll(polls, npolls, timeout); } while (ready < 0 && EINTR == errno); if (ready < 0) { perror("poll failed in do_copy"); return -1; } for (i = 0; i < npolls && ready > 0; i++) { if (polls[i].revents) { /* Got some events */ --ready; if (poll_idx[i] < 0) { /* Input, try to read from it. */ if (0 != do_read(options, in, spillage, &chunks, &read_eof, noutputs - nclosed)) { return -1; } } else { /* Output, try to write to it. */ Output *output = &outputs[pipes[poll_idx[i]]]; ssize_t res = do_write(output, &chunks, options, spillage, pipes[poll_idx[i]]); if (-2 == res) { /* Got EPIPE, add to closing_pipes list */ closing_pipes[pipe_close++] = poll_idx[i]; continue; } else if (res < 0) { /* other write error, give up */ return -1; } if (output->curr_chunk == chunks.tail && output->offset == chunks.tail->len) { /* All the data so far has been written to this output */ if (read_eof) { /* If finished reading, add to closing_pipes */ closing_pipes[pipe_close++] = poll_idx[i]; } else { /* otherwise, add to keeping_up count, to encourage more reading */ keeping_up++; } } } } } } /* End of polling section */ /* Deal with regular output files */ for (i = 0; i < nregular; i++) { /* Try to write */ if (do_write(&outputs[regular[i]], &chunks, options, spillage, regular[i]) < 0) { return -1; } if (read_eof && outputs[regular[i]].curr_chunk == chunks.tail && outputs[regular[i]].offset == chunks.tail->len) { /* If all data written and finished reading, add to closing_reg list */ closing_reg[reg_close++] = i; } } /* Close any regular files that have finished */ for (i = reg_close - 1; i >= 0; i--) { int to_close = regular[closing_reg[i]]; assert(outputs[to_close].fd >= 0); if (0 != close(outputs[to_close].fd)) { fprintf(stderr, "Error closing %s : %s\n", outputs[to_close].name, strerror(errno)); return -1; } if (verbosity > 1) { fprintf(stderr, "%.3f Closed output #%d (%s)\n", get_time(), to_close, outputs[to_close].name); } outputs[to_close].fd = -1; /* Remove the entry from the regular array */ if (closing_reg[i] < nregular - 1) { memmove(®ular[closing_reg[i]], ®ular[closing_reg[i] + 1], (nregular - closing_reg[i] - 1) * sizeof(regular[0])); } if (0 != release_chunks(&chunks, outputs[to_close].curr_chunk, options, spillage)) { return -1; } nclosed++; nregular--; } /* Close any poll-able files that have finished */ for (i = pipe_close - 1; i >= 0; i--) { int to_close = pipes[closing_pipes[i]]; assert(outputs[to_close].fd >= 0); if (0 != close(outputs[to_close].fd)) { fprintf(stderr, "Error closing %s : %s\n", outputs[to_close].name, strerror(errno)); return -1; } if (verbosity > 1) { fprintf(stderr, "%.3f Closed output #%d (%s)\n", get_time(), to_close, outputs[to_close].name); } outputs[to_close].fd = -1; /* Remove the entry from the pipes array */ if (closing_pipes[i] < npipes - 1) { memmove(&pipes[closing_pipes[i]], &pipes[closing_pipes[i] + 1], (npipes - closing_pipes[i] - 1) * sizeof(pipes[0])); } /* Remove from spillage linked list */ pipe_list_remove(&outputs[to_close], spillage); /* Release any data referenced by this output */ if (0 != release_chunks(&chunks, outputs[to_close].curr_chunk, options, spillage)) { return -1; } nclosed++; npipes--; } } while (nclosed < noutputs); if (verbosity > 0) { double now = get_time(); fprintf(stderr, "%.3f Read %lld bytes (%sB) from input (%s)\n", now, (long long) in->pos, human_size(in->pos), options->in_name); for (i = 0; i < noutputs; i++) { fprintf(stderr, "%.3f Wrote %lld bytes (%sB) to output #%d (%s)\n", now, (long long) outputs[i].written, human_size(outputs[i].written), i, outputs[i].name); } fprintf(stderr, "%.3f Maximum buffer used = %zd bytes (%sB)\n", now, spillage->max_alloced, human_size(spillage->max_alloced)); if (!in->reg) { fprintf(stderr, "%.3f Spilled %lld bytes (%sB) to temporary files\n", now, (long long) spillage->total_spilled, human_size(spillage->total_spilled)); if (spillage->total_spilled > 0) { fprintf(stderr, "%.3f Maximum spilled at one time = %lld bytes (%sB)\n", now, (long long) spillage->max_spilled, human_size(spillage->max_spilled)); fprintf(stderr, "%.3f Total temporary files opened = %d\n", now, spillage->spill_file_count); fprintf(stderr, "%.3f Maximum temporary files in use at one time = %d\n", now, spillage->max_spill_files); } } } return 0; }