/** * Moves a file from the temp to the destination directory */ void move_file_individual(struct group_list_t *group, const char *local_temp, const char *local_dest, const char *filename) { char temppath[MAXPATHNAME], destpath[MAXPATHNAME]; stat_struct temp_stat, dest_stat; int len, found_dir; len = snprintf(temppath, sizeof(temppath), "%s%c%s", local_temp, PATH_SEP, filename); if ((len >= sizeof(temppath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", local_temp, PATH_SEP, filename); return; } len = snprintf(destpath, sizeof(destpath), "%s%c%s", local_dest, PATH_SEP, filename); if ((len >= sizeof(destpath)) || (len == -1)) { glog0(group, "Max pathname length exceeded: %s%c%s", local_dest, PATH_SEP, filename); return; } if (lstat_func(temppath, &temp_stat) == -1) { gsyserror(group, "Error getting file status for %s", temppath); return; } if (S_ISDIR(temp_stat.st_mode)) { found_dir = 0; if (lstat_func(destpath, &dest_stat) != -1) { if (!S_ISDIR(dest_stat.st_mode)) { clear_path(destpath, group); } else { found_dir = 1; } } if (!found_dir) { if (mkdir(destpath, 0755) == -1) { gsyserror(group, "Failed to create directory %s", destpath); return; } } move_files_individual(group, temppath, destpath); } else { clear_path(destpath, group); #ifdef WINDOWS if (!MoveFile(temppath, destpath)) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf),NULL); glog0(group, "error (%d): %s", GetLastError(), errbuf); } #else if (rename(temppath, destpath) == -1) { gsyserror(group, "Couldn't move file"); } #endif run_postreceive(group, destpath); } }
/** * Perform FILEINFO processing specific to a regular file in sync mode * Returns 1 if a COMPLETE was sent in response, 0 otherwise */ int handle_fileinfo_sync(struct group_list_t *group) { stat_struct statbuf; if (lstat_func(group->fileinfo.filepath, &statbuf) != -1) { // If source is newer, skip // If source is older, overwrite // If timestamps same, skip if sizes are also same int skip; if (group->fileinfo.tstamp < statbuf.st_mtime) { skip = 1; } else if (group->fileinfo.tstamp > statbuf.st_mtime) { skip = 0; } else if (S_ISREG(statbuf.st_mode) && (statbuf.st_size == group->fileinfo.size)) { skip = 1; } else { skip = 0; } if (skip) { glog2(group, "skipping file, in sync"); early_complete(group, COMP_STAT_SKIPPED, 0); return 1; } else { glog2(group, "overwriting out of sync file"); group->fileinfo.comp_status = COMP_STAT_OVERWRITE; if (group->sync_preview) { glog2(group, "Sync preview mode, skipping receive"); early_complete(group, COMP_STAT_OVERWRITE, 0); return 1; } if (!tempfile) { move_to_backup(group); } } } else { glog2(group, "copying new file"); if (group->sync_preview) { glog2(group, "Sync preview mode, skipping receive"); early_complete(group, COMP_STAT_NORMAL, 0); return 1; } if (!tempfile) { move_to_backup(group); } } return 0; }
/** * Perform FILEINFO processing specific to a regular file in restart mode * Returns 1 if a COMPLETE was sent in response, 0 otherwise */ int handle_fileinfo_restart(struct group_list_t *group) { stat_struct statbuf; if ((!strcmp(group->fileinfo.name, group->restartinfo->name)) && (group->fileinfo.size == group->restartinfo->size) && (group->fileinfo.blocks == group->restartinfo->blocks) && (group->fileinfo.sections == group->restartinfo->sections)) { // Flag this file to restart a failed transfer group->fileinfo.restart = 1; return 0; } else if ((lstat_func(group->fileinfo.filepath, &statbuf) != -1) && S_ISREG(statbuf.st_mode) && (statbuf.st_size == group->fileinfo.size)) { // This file was finished on the last attempt, // so respond with a COMPLETE right away early_complete(group, COMP_STAT_NORMAL, 0); return 1; } return 0; }
/** * Process an incoming FILEINFO message. * Expected in the middle of a group with no current file. */ void handle_fileinfo(struct group_list_t *group, const unsigned char *message, unsigned meslen, struct timeval rxtime) { stat_struct statbuf; int found_dir; if (!read_fileinfo(group, message, meslen, rxtime)) { return; } glog2(group, "Name of file to receive: %s", group->fileinfo.name); switch (group->fileinfo.ftype) { case FTYPE_REG: glog2(group, "Bytes: %s, Blocks: %d, Sections: %d", printll(group->fileinfo.size), group->fileinfo.blocks, group->fileinfo.sections); glog3(group, "small section size: %d, " "big section size: %d, # big sections: %d", group->fileinfo.secsize_small, group->fileinfo.secsize_big, group->fileinfo.big_sections); break; case FTYPE_DIR: glog2(group, "Empty directory"); break; case FTYPE_LINK: glog2(group, "Symbolic link to %s", group->fileinfo.linkname); break; case FTYPE_DELETE: glog2(group, "Deleting file/directory"); break; case FTYPE_FREESPACE: glog2(group, "Get free space for path"); break; default: glog1(group, "Invalid file type: %d", group->fileinfo.ftype); send_abort(group, "Invalid file type"); return; } if (!setup_dest_file(group)) { // A rejected file is still a success because we responded with a // COMPLETE with status=rejected instead of with an ABORT return; } // Make sure the path to the destination file exists and // remove or back up any existing file if (!create_path_to_file(group, group->fileinfo.filepath)) { glog0(group, "Error creating path to data file"); early_complete(group, COMP_STAT_REJECTED, 0); return; } found_dir = 0; if (tempfile && !group->sync_preview) { clear_path(group->fileinfo.temppath, group); } if ((group->fileinfo.ftype != FTYPE_DELETE) || (group->fileinfo.ftype != FTYPE_FREESPACE)) { // Don't do path checks for metafile commands } else if (lstat_func(group->fileinfo.filepath, &statbuf) != -1) { glog3(group, "checking existing file"); if ((group->fileinfo.ftype != FTYPE_DIR) || !S_ISDIR(statbuf.st_mode)) { if ((group->fileinfo.ftype != FTYPE_REG) || !S_ISREG(statbuf.st_mode) || ((!group->restart) && (!group->sync_mode))) { // Don't clear/backup if we're receiving a regular file // and we're in either restart mode or sync mode glog3(group, "calling move_to_backup"); if (!tempfile) { move_to_backup(group); } } } else { glog3(group, "found dir"); found_dir = 1; } } else if (errno != ENOENT) { gsyserror(group, "Error checking file %s",group->fileinfo.filepath); } switch (group->fileinfo.ftype) { case FTYPE_REG: handle_fileinfo_regular(group); break; case FTYPE_DIR: handle_fileinfo_dir(group, found_dir); break; case FTYPE_LINK: handle_fileinfo_link(group); break; case FTYPE_DELETE: handle_fileinfo_delete(group); break; case FTYPE_FREESPACE: handle_fileinfo_freespace(group); break; default: glog0(group, "Error handling FILEINFO: shouldn't get here!"); } }
/** * Creates all directories in the given file's path, removing existing files. * Returns 1 on success, 0 on failure */ int create_path_to_file(int listidx, const char *filename) { char *dir, *base; stat_struct statbuf; int rval; split_path(filename, &dir, &base); if (!dir) { log1(group_list[listidx].group_id, group_list[listidx].file_id, "Invalid path element %s", filename); rval = 0; goto end; } #ifdef WINDOWS if ((base == NULL) || ((strlen(dir) == 2) && (dir[1] == ':'))) { #else if ((!strcmp(dir, ".")) || (!strcmp(dir, "/"))) { #endif // At top level directory, so stop recursion rval = 1; goto end; } if (lstat_func(dir, &statbuf) != -1) { if (!S_ISDIR(statbuf.st_mode)) { if (unlink(dir) == -1) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed to delete path element %s", dir); rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed to create path element %s", dir); rval = 0; goto end; } } } else { // If the file's directory does not exist, recurse first to make sure // all parent directories exist if (!create_path_to_file(listidx, dir)) { rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed to create path element %s", dir); rval = 0; goto end; } } rval = 1; end: free(dir); free(base); return rval; }
/** * For the current file in a group, move the existing file to * the appropriate backup directory, if it exists. * In the event of a failure, delete the original file */ void move_to_backup(int listidx) { stat_struct statbuf; char backup_file[MAXBACKUPPATHNAME], *trim_name; int len; if (lstat_func(group_list[listidx].fileinfo.filepath, &statbuf) == -1) { return; } if (backupcnt == 0) { clear_path(group_list[listidx].fileinfo.filepath, listidx); return; } #ifdef WINDOWS if ((group_list[listidx].fileinfo.filepath[1] == ':') && (group_list[listidx].fileinfo.filepath[2] == '\\')) { trim_name = &group_list[listidx].fileinfo.filepath[3]; } else { trim_name = group_list[listidx].fileinfo.filepath; } #else trim_name = group_list[listidx].fileinfo.filepath; #endif len = snprintf(backup_file, sizeof(backup_file), "%s%c%s%c%s%c%s", backupdir[group_list[listidx].fileinfo.destdiridx], PATH_SEP, group_list[listidx].start_date, PATH_SEP, group_list[listidx].start_time, PATH_SEP, trim_name); if (len >= sizeof(backup_file)) { log0(group_list[listidx].group_id, group_list[listidx].file_id, "Max pathname length exceeded for backup file, deleting", group_list[listidx].fileinfo.filepath); clear_path(group_list[listidx].fileinfo.filepath, listidx); return; } clear_path(backup_file, listidx); if (!create_path_to_file(listidx, backup_file)) { log0(group_list[listidx].group_id, group_list[listidx].file_id, "Error creating path to backup file"); clear_path(group_list[listidx].fileinfo.filepath, listidx); } #ifdef WINDOWS if (!MoveFile(group_list[listidx].fileinfo.filepath, backup_file)) { char errbuf[300]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errbuf, sizeof(errbuf), NULL); log0(group_list[listidx].group_id, group_list[listidx].file_id, "Couldn't rename from %s to %s, deleting: (%d): %s", group_list[listidx].fileinfo.filepath, backup_file, GetLastError(), errbuf); clear_path(group_list[listidx].fileinfo.filepath, listidx); } else { log1(group_list[listidx].group_id, group_list[listidx].file_id, "Backed up existing file to %s", backup_file); } #else if (rename(group_list[listidx].fileinfo.filepath, backup_file) == -1) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Couldn't rename from %s to %s, deleting", group_list[listidx].fileinfo.filepath, backup_file); clear_path(group_list[listidx].fileinfo.filepath, listidx); } else { log1(group_list[listidx].group_id, group_list[listidx].file_id, "Backed up existing file to %s", backup_file); } #endif }
/** * Removes a full path from disk */ void clear_path(const char *path, int listidx) { stat_struct statbuf; char filename[MAXPATHNAME]; int len; if (lstat_func(path, &statbuf) == -1) { if (errno != ENOENT) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Error getting file status for %s", path); } return; } if (!S_ISDIR(statbuf.st_mode)) { unlink(path); } else { #ifdef WINDOWS intptr_t ffhandle; struct _finddatai64_t finfo; char dirglob[MAXPATHNAME]; snprintf(dirglob, sizeof(dirglob), "%s%c*", path, PATH_SEP, group_list[listidx].group_id, PATH_SEP); if ((ffhandle = _findfirsti64(dirglob, &finfo)) == -1) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed to open directory %s", path); return; } do { len = snprintf(filename, sizeof(filename), "%s%c%s", path, PATH_SEP, finfo.name); if ((len >= sizeof(filename)) || (len == -1)) { log0(group_list[listidx].group_id, group_list[listidx].file_id, "Max pathname length exceeded: %s%c%s", filename, PATH_SEP, finfo.name); continue; } if (strcmp(finfo.name, ".") && strcmp(finfo.name, "..")) { clear_path(filename, listidx); } } while (_findnexti64(ffhandle, &finfo) == 0); _findclose(ffhandle); #else DIR *dir; struct dirent *de; if ((dir = opendir(path)) == NULL) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed to open directory %s", path); return; } // errno needs to be set to 0 before calling readdir, otherwise // we'll report a false error when we exhaust the directory while ((errno = 0, de = readdir(dir)) != NULL) { len = snprintf(filename, sizeof(filename), "%s%c%s", path, PATH_SEP, de->d_name); if ((len >= sizeof(filename)) || (len == -1)) { log0(group_list[listidx].group_id, group_list[listidx].file_id, "Max pathname length exceeded: %s%c%s", path, PATH_SEP, de->d_name); continue; } if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { clear_path(filename, listidx); } } if (errno && (errno != ENOENT)) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed to read directory %s", path); } closedir(dir); #endif if (rmdir(path) == -1) { syserror(group_list[listidx].group_id, group_list[listidx].file_id, "Failed remove directory %s", path); } } }
/** * Performs the send for a particular file/directory. If a directory is * specified, get the list of files and call recursively for each. * Returns 0 if a file was sent and none received it, 1 otherwise */ int send_file(const char *basedir, const char *filename, const char *n_destfname, uint32_t group_id) { static uint16_t file_id = 1; struct finfo_t finfo; stat_struct statbuf; char path[MAXPATHNAME], destpath[MAXPATHNAME]; int len, rval, fd, emptydir; log(0, 0, "----- %s -----", filename); len = snprintf(path, sizeof(path), "%s%c%s", basedir, PATH_SEP, filename); if ((len >= sizeof(path)) || (len == -1)) { log(0, 0, "Max pathname length exceeded: %s%c%s", basedir, PATH_SEP, filename); return 1; } if (follow_links) { rval = stat_func(path, &statbuf); } else { rval = lstat_func(path, &statbuf); } if (rval == -1) { syserror(0, 0, "Error getting file status for %s", filename); return 1; } if (file_excluded(filename)) { log(0, 0, "Skipping %s", filename); return 1; } rval = 1; if (S_ISREG(statbuf.st_mode)) { if ((fd = open(path, OPENREAD, 0)) == -1) { syserror(0, 0, "Error reading file %s", filename); return 1; } close(fd); memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_REG; finfo.basedir = basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.group_id = group_id; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.size = statbuf.st_size; finfo.blocks = (int32_t)((finfo.size / blocksize) + (finfo.size % blocksize ? 1 :0)); finfo.sections = (finfo.blocks / (blocksize * 8)) + (finfo.blocks % (blocksize * 8) ? 1 : 0); finfo.naklist = calloc(finfo.blocks, 1); finfo.deststate = calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); if ((finfo.naklist == NULL) || (finfo.deststate == NULL)) { syserror(0, 0, "calloc failed!"); exit(1); } finfo.partial = 1; rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } free(finfo.deststate); free(finfo.naklist); #ifndef WINDOWS } else if (S_ISLNK(statbuf.st_mode)) { char linkname[MAXPATHNAME]; memset(linkname, 0, sizeof(linkname)); if (readlink(path, linkname, sizeof(linkname)-1) == -1) { syserror(0, 0, "Failed to read symbolic link %s", path); return 1; } // Both the file name and the link have to fit into a fileinfo_h.name if (strlen(linkname) + strlen(filename) + 2 > MAXPATHNAME) { log(0, 0, "Combined file name %s and link %s too long", filename, linkname); return 1; } memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_LINK; finfo.basedir = basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.linkname = linkname; finfo.group_id = group_id; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.deststate = calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); if (finfo.deststate == NULL) { syserror(0, 0, "calloc failed!"); exit(1); } finfo.partial = 1; rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } #endif } else if (S_ISDIR(statbuf.st_mode)) { // read directory and do recursive send #ifdef WINDOWS intptr_t ffhandle; struct _finddatai64_t ffinfo; char dirglob[MAXPATHNAME]; snprintf(dirglob, sizeof(dirglob), "%s%c%s%c*", basedir, PATH_SEP, filename, PATH_SEP); if ((ffhandle = _findfirsti64(dirglob, &ffinfo)) == -1) { syserror(0, 0, "Failed to open directory %s%c%s", basedir, PATH_SEP, filename); return 1; } emptydir = 1; do { len = snprintf(path, sizeof(path), "%s/%s", filename, ffinfo.name); if ((len >= sizeof(path)) || (len == -1)) { log(0, 0, "Max pathname length exceeded: %s/%s", filename, ffinfo.name); continue; } len = snprintf(destpath, sizeof(destpath), "%s/%s", n_destfname, ffinfo.name); if ((len >= sizeof(destpath)) || (len == -1)) { log(0, 0, "Max pathname length exceeded: %s/%s", n_destfname, ffinfo.name); continue; } if (strcmp(ffinfo.name, ".") && strcmp(ffinfo.name, "..")) { emptydir = 0; if (!send_file(basedir, path, destpath, group_id)) { rval = 0; break; } } } while (_findnexti64(ffhandle, &ffinfo) == 0); _findclose(ffhandle); #else DIR *dir; struct dirent *de; char dirname[MAXPATHNAME]; snprintf(dirname, sizeof(dirname), "%s%c%s", basedir,PATH_SEP,filename); if ((dir = opendir(dirname)) == NULL) { syserror(0, 0, "Failed to open directory %s", dirname); return 1; } // errno needs to be set to 0 before calling readdir, otherwise // we'll report a false error when we exhaust the directory emptydir = 1; while ((errno = 0, de = readdir(dir)) != NULL) { len = snprintf(path, sizeof(path), "%s/%s", filename, de->d_name); if ((len >= sizeof(path)) || (len == -1)) { log(0, 0, "Max pathname length exceeded: %s/%s", filename, de->d_name); continue; } len = snprintf(destpath, sizeof(destpath), "%s/%s", n_destfname, de->d_name); if ((len >= sizeof(destpath)) || (len == -1)) { log(0, 0, "Max pathname length exceeded: %s/%s", n_destfname, de->d_name); continue; } if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { emptydir = 0; if (!send_file(basedir, path, destpath, group_id)) { rval = 0; break; } } } if (errno && (errno != ENOENT)) { syserror(0, 0, "Failed to read directory %s", filename); } closedir(dir); #endif if (emptydir) { memset(&finfo, 0, sizeof(struct finfo_t)); finfo.ftype = FTYPE_DIR; finfo.basedir = basedir; finfo.filename = filename; finfo.destfname = n_destfname; finfo.group_id = group_id; finfo.file_id = file_id++; if (file_id == 0) { file_id = 1; } finfo.deststate = calloc(destcount ? destcount : MAXDEST, sizeof(struct deststate_t)); if (finfo.deststate == NULL) { syserror(0, 0, "calloc failed!"); exit(1); } finfo.partial = 1; rval = announce_phase(&finfo); if (rval) { rval = transfer_phase(&finfo); } } } else { log(0, 0, "Skipping special file %s", filename); } return rval; }
/** * Creates all directories in the given file's path, removing existing files. * Returns 1 on success, 0 on failure */ int create_path_to_file(struct group_list_t *group, const char *filename) { char *dir, *base; stat_struct statbuf; int rval; split_path(filename, &dir, &base); if (!dir) { glog1(group, "Invalid path element %s", filename); rval = 0; goto end; } #ifdef WINDOWS if ((base == NULL) || ((strlen(dir) == 2) && (dir[1] == ':'))) { #else if ((!strcmp(dir, ".")) || (!strcmp(dir, "/"))) { #endif // At top level directory, so stop recursion rval = 1; goto end; } if (lstat_func(dir, &statbuf) != -1) { if (!S_ISDIR(statbuf.st_mode)) { if (unlink(dir) == -1) { gsyserror(group, "Failed to delete path element %s", dir); rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { gsyserror(group, "Failed to create path element %s", dir); rval = 0; goto end; } } } else { // If the file's directory does not exist, recurse first to make sure // all parent directories exist if (!create_path_to_file(group, dir)) { rval = 0; goto end; } if (mkdir(dir, 0755) == -1) { gsyserror(group, "Failed to create path element %s", dir); rval = 0; goto end; } } rval = 1; end: free(dir); free(base); return rval; } void new_loss_event(struct group_list_t *group, uint16_t txseq) { uint32_t seq_long; uint16_t count; int bytes, avgbytes, rate, grtt_usec; glog4(group, "Seq %d starts new loss event", txseq); // Found a new loss event if (txseq < group->max_txseq - MAXMISORDER) { glog5(group, "wrap check, i=%u, maxseq=%u", txseq, group->max_txseq); seq_long = ((group->seq_wrap - 1) << 16) | txseq; } else { seq_long = (group->seq_wrap << 16) | txseq; } if (group->slowstart) { group->slowstart = 0; // Initialize loss history count = group->max_txseq; bytes = 0; grtt_usec = (int)(group->grtt * 1000000); while ((count != group->start_txseq) && (diff_usec(group->loss_history[txseq].t, group->loss_history[count].t) < grtt_usec)) { bytes += group->loss_history[count--].size; } rate = (int)(bytes / group->grtt); glog4(group, "End slowstart, calculated rate = %d", rate); avgbytes= bytes / ((int16_t)(group->max_txseq - count)); group->loss_events[0].len = (int)(0 + pow( (rate * ((group->rtt != 0) ? group->rtt : group->grtt)) / (sqrt(1.5) * 8 * avgbytes), 2)); glog4(group, "Calculated prior event len = %d (rtt=%f, avgbytes=%d)", group->loss_events[0].len, group->rtt,avgbytes); } else { group->loss_events[0].len = seq_long - group->loss_events[0].start_seq; glog4(group, "Prior event length = %d (i=%u, start=%u)", group->loss_events[0].len, seq_long, group->loss_events[0].start_seq); } memmove(&group->loss_events[1], &group->loss_events[0], sizeof(struct loss_event_t) * 8); group->loss_events[0].start_seq = seq_long; group->loss_events[0].len = 0; group->loss_events[0].t = group->loss_history[txseq].t; }