/* sitecopy, for managing remote web sites. Copyright (C) 1998-2004, Joe Orton This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* This is the core functionality of sitecopy, performing updates * and checking files etc. */ #include #include #include #include #include #include #include #include #ifdef HAVE_STDLIB_H #include #endif /* HAVE_STDLIB_H */ #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_STRINGS_H #include #endif #include #include #include #include #include "common.h" #include "frontend.h" #include "protocol.h" #include "sitesi.h" /* Shorthand for protocol driver methods */ #define CALL(a) (*site->driver->a) #define DRIVER_ERR ((*site->driver->error)(session)) /* This holds ALL the sites defined in the rcfile */ struct site *all_sites; static int proto_init(struct site *site, void **session); static void proto_finish(struct site *site, void *session); static void proto_seterror(struct site *site, void *session); struct site *site_find(const char *sitename) { struct site *current; for (current = all_sites; current!=NULL; current=current->next) { if (strcmp(current->name, sitename) == 0) { /* We found it */ return current; } } return NULL; } static int synch_create_directories(struct site *site) { struct site_file *current; char *full_local; int ret; ret = 0; for_each_file(current, site) { if ((current->type==file_dir) && (current->diff==file_deleted)) { full_local = file_full_local(¤t->stored, site); fe_synching(current); if (mkdir(full_local, 0755) == 0) { fe_synched(current, true, NULL); } else { ret = 1; fe_synched(current, false, strerror(errno)); file_downloaded(current, site); } free(full_local); } } return ret; } static int synch_files(struct site *site, void *session) { struct site_file *current; int ret; ret = 0; for_each_file(current, site) { char *full_local, *full_remote; if (current->type != file_file) continue; switch (current->diff) { case file_changed: if (!file_contents_changed(current, site)) { /* Just chmod it */ full_local = file_full_local(¤t->stored, site); fe_setting_perms(current); if (chmod(full_local, current->stored.mode) < 0) { fe_set_perms(current, false, strerror(errno)); } else { fe_set_perms(current, true, NULL); } free(full_local); break; } /*** fall-through */ case file_deleted: full_local = file_full_local(¤t->stored, site); full_remote = file_full_remote(¤t->stored, site); fe_synching(current); if (CALL(file_download)(session, full_local, full_remote, current->stored.ascii) != SITE_OK) { fe_synched(current, false, DRIVER_ERR); ret = 1; } else { /* Successfull download */ fe_synched(current, true, NULL); if (site->state_method == state_timesize) { struct utimbuf times; /* Change the modtime of the local file so it doesn't look * like it's changed already */ times.actime = current->stored.time; times.modtime = current->stored.time; if (utime(full_local, ×) < 0) { fe_warning(_("Could not set modification time of local file."), full_local, strerror(errno)); } } if (file_perms_changed(current, site)) { fe_setting_perms(current); if (chmod(full_local, current->stored.mode) < 0) { fe_set_perms(current, false, strerror(errno)); } else { fe_set_perms(current, true, NULL); } } /* TODO: not strictly true if the chmod failed. */ file_downloaded(current, site); } free(full_local); free(full_remote); break; case file_new: full_local = file_full_local(¤t->local, site); fe_synching(current); if (unlink(full_local) != 0) { fe_synched(current, false, strerror(errno)); ret = 1; } else { fe_synched(current, true, NULL); } free(full_local); break; case file_moved: { char *old_full_local = file_full_local(¤t->stored, site); full_local = file_full_local(¤t->local, site); fe_synching(current); if (rename(full_local, old_full_local) == 0) { fe_synched(current, true, NULL); } else { fe_synched(current, false, strerror(errno)); ret = 1; } free(old_full_local); free(full_local); } default: break; } } return ret; } static int synch_delete_directories(struct site *site) { struct site_file *current, *prev; int ret; ret = 0; for (current=site->files_tail; current!=NULL; current=prev) { prev = current->prev; if ((current->type==file_dir) && (current->diff==file_new)) { char *full_local = file_full_local(¤t->local, site); fe_synching(current); if (rmdir(full_local) == -1) { fe_synched(current, false, strerror(errno)); ret = 3; } else { fe_synched(current, true, NULL); file_delete(site, current); } free(full_local); } } return ret; } /* Resyncs the LOCAL site with the REMOTE site. * This is site_update backwards, and is essentially the same in structure, * except with the logic reversed. */ int site_synch(struct site *site) { int ret, need_conn; void *session; /* Do we need to connect to the server: note that ignored files * are treated as changed files in synch mode. */ need_conn = (site->numchanged + site->numdeleted + site->numignored > 0); if (need_conn) { ret = proto_init(site, &session); if (ret != SITE_OK) { proto_finish(site, session); return ret; } } ret = synch_create_directories(site); if (ret == 0 || site->keep_going) { ret = synch_files(site, session); if (ret == 0 || site->keep_going) { ret = synch_delete_directories(site); } } if (need_conn) { proto_finish(site, session); } if (ret == 0) { ret = SITE_OK; } else { ret = SITE_ERRORS; } return ret; } static int file_chmod(struct site_file *file, struct site *site, void *session) { int ret = 0; /* chmod it if necessary */ if (file_perms_changed(file, site)) { char *full_remote = file_full_remote(&file->local, site); fe_setting_perms(file); if (CALL(file_chmod)(session, full_remote, file->local.mode) != SITE_OK) { fe_set_perms(file, false, DRIVER_ERR); ret = 1; } else { file->stored.mode = file->local.mode; fe_set_perms(file, true, NULL); file_set_diff(file, site); } free(full_remote); } return ret; } static void file_retrieve_server(struct site_file *file, struct site *site, void *session) { time_t rtime; char *full_remote = file_full_remote(&file->local, site); if (CALL(file_get_modtime)(session, full_remote, &rtime) == SITE_OK) { file->server.time = rtime; file->server.exists = true; } else { file->server.exists = false; fe_warning(_("Upload succeeded, but could not retrieve modification time.\n" "If this message persists, turn off safe mode."), full_remote, DRIVER_ERR); } free(full_remote); } /* Create new directories and change permissions on existing directories. */ static int update_create_directories(struct site *site, void *session) { struct site_file *current; int ret = 0; for_each_file(current, site) { if ((current->type == file_dir) && (current->diff == file_new || current->diff == file_changed)) { /* New or changed directory! */ char *full_remote; int oret; if (!fe_can_update(current)) continue; full_remote = file_full_remote(¤t->local, site); if (current->diff == file_new) { fe_updating(current); oret = CALL(dir_create)(session, full_remote); if (oret != SITE_OK) { fe_updated(current, false, DRIVER_ERR); } else { fe_updated(current, true, NULL); } } else { oret = SITE_OK; } if (site->dirperms && oret == SITE_OK) { fe_setting_perms(current); oret = CALL(file_chmod)(session, full_remote, current->local.mode); if (oret == SITE_OK) { fe_set_perms(current, true, NULL); } else { fe_set_perms(current, false, DRIVER_ERR); } } if (oret != SITE_OK) { ret = 1; } else { file_uploaded(current, site); } free(full_remote); } } return ret; } /* Returns the filename to use for tempupload mode, g_malloc-allocated. * (pass the site since we may have different tempupload modes in the * future.) * FIXME: implement it efficiently */ static char *temp_upload_filename(const char *filename, struct site *site) { char *pnt, *ret; /* Insert a '.in.' prefix into the filename, AFTER * any directories */ ret = g_malloc(strlen(filename) + 4 + 1); strcpy(ret, filename); pnt = strrchr(ret, '/'); if (pnt == NULL) { pnt = ret; } else { pnt++; } /* Shove the name segment along four bytes so we can insert * the '.in.' */ memmove(pnt+4, pnt, strlen(pnt) + 1); memcpy(pnt, ".in.", 4); return ret; } static int update_delete_files(struct site *site, void *session) { struct site_file *current, *next; int ret = 0; for (current=site->files; current!=NULL; current=next) { next = current->next; /* Skip directories and links, and only do deleted files on * this pass */ if (current->diff == file_deleted && current->type == file_file) { char *full_remote; if (!fe_can_update(current)) continue; full_remote = file_full_remote(¤t->stored, site); fe_updating(current); if (CALL(file_delete)(session, full_remote) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); ret = 1; } else { /* Successful update - file was deleted */ fe_updated(current, true, NULL); file_delete(site, current); } free(full_remote); } } return ret; } static int update_move_files(struct site *site, void *session) { int ret = 0; struct site_file *current; char *old_full_remote, *full_remote; for_each_file(current, site) { if (current->diff != file_moved) continue; full_remote = file_full_remote(¤t->local, site); /* The file has been moved */ if (!fe_can_update(current)) continue; fe_updating(current); old_full_remote = file_full_remote(¤t->stored, site); if (CALL(file_move)(session, old_full_remote, full_remote) != SITE_OK) { ret = 1; fe_updated(current, false, DRIVER_ERR); } else { /* Successful update - file was moved */ fe_updated(current, true, NULL); file_uploaded(current, site); } free(old_full_remote); free(full_remote); } return ret; } /* Does everything but file deletes */ static int update_files(struct site *site, void *session) { struct site_file *current; char *full_local, *full_remote; int ret = 0; for_each_file(current, site) { /* This loop only handles changed and new files, so * skip everything else. */ if (current->type != file_file || current->diff == file_deleted || current->diff == file_moved || current->diff == file_unchanged) continue; full_local = file_full_local(¤t->local, site); full_remote = file_full_remote(¤t->local, site); switch (current->diff) { case file_changed: /* File has changed, upload it */ if (current->ignore) break; if (!file_contents_changed(current, site)) { /* If the file contents haven't changed, then we can * just chmod it */ if (file_chmod(current, site, session)) ret = 1; break; } /*** fall-through ***/ case file_new: /* File is new, upload it */ if (!fe_can_update(current)) continue; if ((current->diff == file_changed) && site->nooverwrite) { /* Must delete remote file before uploading new copy. * FIXME: Icky hack to convince the FE we are about to * delete the file */ current->diff = file_deleted; fe_updating(current); if (CALL(file_delete)(session, full_remote) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); ret = 1; current->diff = file_changed; /* Don't upload it! */ break; } else { fe_updated(current, true, NULL); current->diff = file_changed; } } fe_updating(current); /* Now, upload it */ if (site->safemode && current->server.exists) { /* Only do this for files we do know the remote modtime for */ int cret; cret = CALL(file_upload_cond)(session, full_local, full_remote, current->local.ascii, current->server.time); switch (cret) { case SITE_ERRORS: fe_updated(current, false, DRIVER_ERR); ret = 1; break; case SITE_FAILED: fe_updated(current, false, _("Remote file has been modified - not overwriting with local changes")); ret = 1; break; default: /* Success case */ fe_updated(current, true, NULL); file_retrieve_server(current, site, session); if (file_chmod(current, site, session)) ret = 1; file_uploaded(current, site); break; } } else if (site->tempupload && (current->diff == file_changed)) { /* Do temp file upload followed by a move */ char *temp_remote = temp_upload_filename(full_remote, site); if (CALL(file_upload)(session, full_local, temp_remote, current->local.ascii != SITE_OK)) { fe_updated(current, false, DRIVER_ERR); ret = 1; } else { /* Successful upload... now move it */ if (CALL(file_move)(session, temp_remote, full_remote) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); /* Originally coded to delete the temporary file * here, but, on second thoughts... if something * is broken, let's not try to be too clever, else * we might make it worse. */ ret = 1; } else { /* Successful move */ fe_updated(current, true, NULL); if (site->safemode) { file_retrieve_server(current, site, session); } if (file_chmod(current, site, session)) ret = 1; file_uploaded(current, site); } } free(temp_remote); } else { /* Normal unconditional upload */ if (CALL(file_upload)(session, full_local, full_remote, current->local.ascii) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); ret = 1; } else { /* Successful upload. */ fe_updated(current, true, NULL); if (site->safemode) { file_retrieve_server(current, site, session); } if (file_chmod(current, site, session)) ret = 1; file_uploaded(current, site); } } break; default: /* Ignore everything else */ break; } free(full_remote); free(full_local); } return ret; } static int update_delete_directories(struct site *site, void *session) { struct site_file *current, *prev; int ret = 0; /* This one must iterate through the list BACKWARDS, so * directories are deleted bottom up */ for (current=site->files_tail; current!=NULL; current=prev) { prev = current->prev; if ((current->type==file_dir) && (current->diff == file_deleted)) { char *full_remote; if (!fe_can_update(current)) continue; full_remote = file_full_remote(¤t->stored, site); fe_updating(current); if (CALL(dir_remove)(session, full_remote) != SITE_OK) { ret = 1; fe_updated(current, false, DRIVER_ERR); } else { /* Successful delete */ fe_updated(current, true, NULL); file_delete(site, current); } free(full_remote); } } return ret; } static int update_links(struct site *site, void *session) { struct site_file *current, *next; int ret = 0; for (current=site->files; current!=NULL; current=next) { char *full_remote; next = current->next; if (current->type != file_link) continue; full_remote = file_full_remote(¤t->local, site); switch (current->diff) { case file_new: fe_updating(current); if (CALL(link_create)(session, full_remote, current->local.linktarget) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); ret = 1; } else { fe_updated(current, true, NULL); current->diff = file_unchanged; } break; case file_changed: fe_updating(current); if (CALL(link_change)(session, full_remote, current->local.linktarget) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); ret = 1; } else { fe_updated(current, true, NULL); current->diff = file_unchanged; } break; case file_deleted: fe_updating(current); if (CALL(link_delete)(session, full_remote) != SITE_OK) { fe_updated(current, false, DRIVER_ERR); ret = 1; } else { fe_updated(current, true, NULL); file_delete(site, current); } default: break; } free(full_remote); } return ret; } static void proto_finish(struct site *site, void *session) { proto_seterror(site, session); CALL(finish)(session); } static void proto_seterror(struct site *site, void *session) { site->last_error = g_strdup(DRIVER_ERR); } const char *site_get_protoname(struct site *site) { if (site->driver) return site->driver->protocol_name; else return site->proto_string; } static int proto_init(struct site *site, void **session) { int ret; if (site->last_error) { free(site->last_error); site->last_error = NULL; } ret = CALL(init)(session, site); if (ret != SITE_OK) { proto_seterror(site, *session); return ret; } return SITE_OK; } /* Updates the remote site. * * Executes each of the site_update_* functions in turn (if their * guard evaluates to true). */ int site_update(struct site *site) { int ret = 0, num; const struct handler { int (*func)(struct site *, void *session); int guard; } handlers[] = { { update_create_directories, 1 }, { update_delete_files, !site->nodelete }, { update_move_files, site->checkmoved }, { update_files, 1 }, { update_links, site->symlinks == sitesym_maintain }, { update_delete_directories, !site->nodelete }, { NULL, 1 } }; void *session; ret = proto_init(site, &session); if (ret != SITE_OK) { proto_finish(site, session); return ret; } for (num = 0; handlers[num].func != NULL && (ret == 0 || site->keep_going); num++) { if (handlers[num].guard) { int newret; newret = (*handlers[num].func)(site, session); if (newret != 0) { ret = newret; } } } if (ret == 0) { /* Site updated successfully. */ ret = SITE_OK; } else { /* Update not totally successfull */ ret = SITE_ERRORS; } proto_finish(site, session); return ret; } /* This reads off the remote files and the local files. */ int site_readfiles(struct site *site) { int ret; site_destroy(site); ret = site_read_stored_state(site); if (ret >= SITE_OK) { site_read_local_state(site); } return ret; } /* Read the local site files... * A stack is used for directories within the site - this is not recursive. * Each item on the stack is a FULL PATH to the directory, i.e., including * the local site root. */ /* Initial size of directory stack, and amount it grows * each time we fill it. */ #define DIRSTACKSIZE 128 void site_read_local_state(struct site *site) { char **dirstack, *this, *full = NULL; int dirtop = 0, /* points to item above top stack item */ dirmax = DIRSTACKSIZE; /* size of stack */ dirstack = g_malloc(sizeof(char *) * DIRSTACKSIZE); /* Push the root directory on to the stack */ dirstack[dirtop++] = g_strdup(site->local_root); /* Now, for all items in the stack, process all the files, and * add the dirs to the stack. Everything we put on the stack is * temporary and gets freed eventually. */ while (dirtop > 0) { DIR *curdir; struct dirent *ent; /* Pop the stack */ this = dirstack[--dirtop]; NE_DEBUG(DEBUG_FILES, "Scanning: %s\n", this); curdir = opendir(this); if (curdir == NULL) { fe_warning("Could not read directory", this, strerror(errno)); free(this); continue; } /* Now read all the directory entries */ while ((ent = readdir(curdir)) != NULL) { char *fname; struct stat item; struct site_file *current; struct file_state local = {0}; enum file_type type; size_t dnlen = strlen(ent->d_name); /* Exclude the special directory entries. This test comes * high since it kills two stat calls per directory. */ if (ent->d_name[0] == '.' && (dnlen == 1 || (ent->d_name[1] == '.' && dnlen==2))) { continue; } if (full != NULL) free(full); full = g_strconcat(this, ent->d_name, NULL); #ifdef __EMX__ /* There are no symlinks under OS/2, use stat() instead */ #define USE_STAT stat #else #define USE_STAT lstat #endif if (USE_STAT(full, &item) == -1) { fe_warning(_("Could not examine file."), full, strerror(errno)); continue; } #undef USE_STAT #ifndef __EMX__ /* Is this a symlink? */ if (S_ISLNK(item.st_mode)) { NE_DEBUG(DEBUG_FILES, "symlink - "); if (site->symlinks == sitesym_ignore) { /* Just skip it */ NE_DEBUG(DEBUG_FILES, "ignoring.\n"); continue; } else if (site->symlinks == sitesym_follow) { NE_DEBUG(DEBUG_FILES, "followed - "); /* Else, carry on as normal, stat the real file */ if (stat(full, &item) == -1) { /* It's probably a broken link */ NE_DEBUG(DEBUG_FILES, "broken.\n"); continue; } } else { NE_DEBUG(DEBUG_FILES, "maintained:\n"); } } #endif /* __EMX__ */ /* Now process it */ /* This is the filename of this file - i.e., everything * apart from the local root */ fname = (char *)full+strlen(site->local_root); /* Check for excludes */ if (file_isexcluded(fname, site)) continue; if (S_ISREG(item.st_mode)) { switch (site->state_method) { case state_timesize: local.time = item.st_mtime; break; case state_checksum: if (file_checksum(full, &local, site) != 0) { fe_warning(_("Could not checksum file"), full, strerror(errno)); continue; } break; } local.size = item.st_size; local.ascii = file_isascii(fname, site); type = file_file; } #ifndef __EMX__ else if (S_ISLNK(item.st_mode)) { char tmp[BUFSIZ] = {0}; type = file_link; NE_DEBUG(DEBUG_FILES, "symlink being maintained.\n"); if (readlink(full, tmp, BUFSIZ) == -1) { fe_warning(_("The target of the symlink could not be read."), full, strerror(errno)); continue; } local.linktarget = g_strdup(tmp); } #endif /* __EMX__ */ else if (S_ISDIR(item.st_mode)) { type = file_dir; if (dirtop == dirmax) { /* Grow the stack */ dirmax += DIRSTACKSIZE; dirstack = realloc(dirstack, sizeof(char *) * dirmax); } /* Add it to the search stack */ dirstack[dirtop] = g_strconcat(full, "/", NULL); dirtop++; } else { NE_DEBUG(DEBUG_FILES, "something else.\n"); continue; } /* Set up rest of the local state */ local.mode = item.st_mode & 0777; local.exists = true; local.filename = g_strdup(fname); current = file_set_local(type, &local, site); DEBUG_DUMP_FILE_PROPS(DEBUG_FILES, current, site); } /* Close the open directory */ closedir(curdir); /* And we're finished with this */ free(this); } free(dirstack); } #undef DIRSTACKSIZE /* Pretend the remote site is the same as the local site. */ void site_catchup(struct site *site) { struct site_file *current, *next; site_enter(site); for (current=site->files; current!=NULL; current=next) { next = current->next; switch (current->diff) { case file_deleted: file_delete(site, current); break; case file_changed: case file_new: case file_moved: file_state_copy(¤t->stored, ¤t->local, site); file_set_diff(current, site); break; case file_unchanged: /* noop */ break; } } site_leave(site); } /* Reinitializes the site - clears any remote files * from the list, and marks all other files as 'new locally'. */ void site_initialize(struct site *site) { /* So simple. Be sure we have our abstraction layers at least * half-decent when things fall out this simple. */ site_destroy_stored(site); } /* Munge modtimes of 'file' accordingly; when modtime of file on * server is 'remote_mtime'. */ static void munge_modtime(struct site_file *file, time_t remote_mtime, struct site *site) { /* If this is a file, and we are using timesize mode, and we have * a local copy of this file already, we have to cope with the * modtimes problem. The problem is that the modtime locally will * ALWAYS be different from the modtime on the SERVER. */ if (file->type == file_file && site->state_method == state_timesize) { if (file->local.exists) { /* If we are in safe mode, we can actually check whether * the remote file has changed or not when we are using * timesize mode, by comparing what we thought the server * modtime was with what the actual (fetched) server * modtime is. Got that? */ NE_DEBUG(DEBUG_FILES, "Fetch: %ld vs %ld\n", file->server.time, remote_mtime); if (site->safemode && file->server.exists && file->server.time != remote_mtime) { NE_DEBUG(DEBUG_FILES, "Fetch: Marking changed file changed.\n"); file->stored.time = file->local.time + 1; } else { NE_DEBUG(DEBUG_FILES, "Fetch: Marking unchanged files same.\n"); file->stored.time = file->local.time; } } else { /* If the local file doesn't exist, pretend the file was * last uploaded "now" (an arbitrary time is adequate, but * "now" is the least confusing). */ file->stored.time = time(NULL); } /* update the diff. */ file_set_diff(file, site); } } /* This walks the files list as returned by the protocol driver, * and converts it into a 'real' files list. */ static void site_fetch_walk(struct site *site, struct proto_file *files) { struct proto_file *this_file, *next_file; this_file = files; while (this_file != NULL) { if (!file_isexcluded(this_file->filename, site)) { enum file_type type = file_file; /* init to shut up gcc */ struct site_file *file; struct file_state state = {0}; switch (this_file->type) { case proto_file: type = file_file; break; case proto_dir: type = file_dir; break; case proto_link: type = file_link; break; } state.size = this_file->size; state.time = this_file->modtime; state.exists = true; state.filename = this_file->filename; state.mode = this_file->mode; state.ascii = file_isascii(this_file->filename, site); memcpy(state.checksum, this_file->checksum, 16); file = file_set_stored(type, &state, site); fe_fetch_found(file); munge_modtime(file, this_file->modtime, site); if (site->safemode) { /* Store the server modtime. */ file->server.time = this_file->modtime; file->server.exists = true; } } else { free(this_file->filename); } next_file = this_file->next; free(this_file); this_file = next_file; } } static void site_fetch_csum_read(void *userdata, const char *s, size_t len) { } /* Retrieve the remote checksum for all files */ static int site_fetch_checksum(struct proto_file *files, struct site *site, void *session) { return -1; } /* Updates the remote file list... site_fetch_callback is called for * every remote file found. */ int site_fetch(struct site *site) { struct proto_file *files = NULL; int ret; void *session; ret = proto_init(site, &session); if (ret != SITE_OK) { proto_finish(site, session); return ret; } if (CALL(fetch_list) == NULL) { proto_finish(site, session); return SITE_UNSUPPORTED; } /* we want modtimes if in safe mode, or in timesize mode. */ ret = CALL(fetch_list)(session, site->remote_root, site->safemode || site->state_method == state_timesize, &files); if (ret == SITE_OK && site->state_method == state_checksum) { site_fetch_checksum(files, site, session); } proto_finish(site, session); if (ret == SITE_OK) { /* Copy over the list of files */ site_destroy_stored(site); site_fetch_walk(site, files); return SITE_OK; } else { return SITE_FAILED; } } /* Compares files list with files. * Returns SITE_OK on match, SITE_ERRORS on no match. */ /* Ahhhh, this is crap too. * If we had a generic file_set this would be easy and clean and spot * moved files too. We need a generic file_set. */ static int site_verify_compare(struct site *site, const struct proto_file *files, int *numremoved) { struct site_file *file; const struct proto_file *lfile; int numremote = 0; /* Clear live state */ for_each_file(file, site) { if (file->stored.exists) { numremote++; } } for (lfile = files; lfile != NULL; lfile = lfile->next) { enum file_diff diff = file_new; numremote--; for_each_file(file, site) { if (file->stored.exists && (strcmp(file->stored.filename, lfile->filename) == 0)) { /* Do a mini file_compare job */ diff = file_unchanged; if (site->state_method == state_checksum) { if (memcmp(file->stored.checksum, lfile->checksum, 16)) diff = file_changed; } else { if ((file->stored.size != lfile->size) || (site->safemode && (file->server.time != lfile->modtime))) { diff = file_changed; } } break; } } /* If new files were added, adjust the count */ if (diff == file_new) numremote++; fe_verified(lfile->filename, diff); } *numremoved = numremote; if (numremote != 0) { return SITE_ERRORS; } else { return SITE_OK; } } /* Compares what's on the server with what we THINK is on the server. * Returns SITE_OK if match, SITE_ERRORS if doesn't match. */ int site_verify(struct site *site, int *numremoved) { struct proto_file *files = NULL; void *session; int ret; ret = proto_init(site, &session); if (ret != SITE_OK) return ret; if (CALL(fetch_list) == NULL) { return SITE_UNSUPPORTED; } ret = CALL(fetch_list)(session, site->remote_root, 1, &files); if (site->state_method == state_checksum) { site_fetch_checksum(files, site, session); } proto_finish(site, session); if (ret == SITE_OK) { /* Return whether they matched or not */ return site_verify_compare(site, files, numremoved); } else { return SITE_FAILED; } } /* Destroys the stored state of files in the files list for the given * site. Removes any files which do not exist locally from the list. */ void site_destroy_stored(struct site *site) { struct site_file *current, *next; site_enter(site); current = site->files; while (current != NULL) { next = current->next; if (!current->local.exists) { /* It doesn't exist locally... nuke it */ file_delete(site, current); } else { /* Just nuke the stored state... * TODO-ngm: verify this. */ file_state_destroy(¤t->stored); /* Could just do .exists = false */ memset(¤t->stored, 0, sizeof(struct file_state)); /* And set the diff */ file_set_diff(current, site); } current = next; } site_leave(site); } /* Called to delete all the files associated with the site */ void site_destroy(struct site *site) { struct site_file *current, *next; site_enter(site); current = site->files; while (current != NULL) { next = current->next; file_delete(site, current); current = next; } site_leave(site); } /* Produces a section of the flat listing output, of all the items * with the given diff type in the given site, using the given section * name. */ static void site_flatlist_items(FILE *f, struct site *site, enum file_diff diff, const char *name) { struct site_file *current; fprintf(f, "sectstart|%s", name); putc('\n', f); for_each_file(current, site) { if (current->diff == diff) { fprintf(f, "item|%s%s", file_name(current), (current->type==file_dir)?"/":""); if (current->diff == file_moved) { fprintf(f, "|%s\n", current->stored.filename); } else { putc('\n', f); } } } fprintf(f, "sectend|%s\n", name); } /* Produce the flat listing output for the given site */ void site_flatlist(FILE *f, struct site *site) { fprintf(f, "sitestart|%s", site->name); if (site->url) fprintf(f, "|%s", site->url); putc('\n', f); if (site->numnew > 0) site_flatlist_items(f, site, file_new, "added"); if (site->numchanged > 0) site_flatlist_items(f, site, file_changed, "changed"); if (site->numdeleted > 0) site_flatlist_items(f, site, file_deleted, "deleted"); if (site->nummoved > 0) site_flatlist_items(f, site, file_moved, "moved"); fprintf(f, "siteend|%s\n", site->local_is_different?"changed":"unchanged"); } void site_sock_progress_cb(void *userdata, off_t progress, off_t total) { fe_transfer_progress(progress, total); } void fe_initialize(void) { }