"Fossies" - the Fresh Open Source Software Archive

Member "sitecopy-0.16.6/src/sites.c" (9 Jul 2008, 36732 Bytes) of archive /linux/www/sitecopy-0.16.6.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format (assuming markdown format). Alternatively you can here view or download the uninterpreted source code file. A member file download can also be achieved by clicking within a package contents listing on the according byte size field.

/* sitecopy, for managing remote web sites. Copyright © 1998-2008, Joe Orton joe@manyfish.co.uk

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 <config.h>

include <sys/types.h>

include <sys/stat.h>

include <errno.h>

include <dirent.h>

include <fnmatch.h>

include <fcntl.h>

include <stdio.h>

ifdef HAVE_STDLIB_H

include <stdlib.h>

endif / HAVE_STDLIB_H /

ifdef HAVE_UNISTD_H

include <unistd.h>

endif / HAVE_UNISTD_H /

ifdef HAVE_STRING_H

include <string.h>

endif

ifdef HAVE_STRINGS_H

include <strings.h>

endif

include <time.h>

include <utime.h>

/ neon /

include <ne_string.h>

include <ne_alloc.h>

include <ne_md5.h>

include <ne_socket.h>

ifdef HAVE_SNPRINTF_H

include “snprintf.h”

endif / !HAVE_SNPRINTF_H /

include “basename.h”

include “i18n.h”

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(&current->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(&current->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(&current->stored, site);
    full_remote = file_full_remote(&current->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, &times) < 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(&current->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(&current->stored, site);
    full_local = file_full_local(&current->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(&current->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(&current->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, ne_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 = ne_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(&current->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(&current->local, site); / The file has been moved / if (!fe_can_update(current)) continue; fe_updating(current); old_full_remote = file_full_remote(&current->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(&current->local, site);
full_remote = file_full_remote(&current->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) {
    /* 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(&current->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(&current->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 = ne_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_delete_files, !site->nodelete }, { update_create_directories, 1 }, { 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 (1024)

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 = ne_malloc(sizeof(char *) * DIRSTACKSIZE);
/* Push the root directory on to the stack */
dirstack[dirtop++] = ne_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 = ne_concat(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 = ne_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] = ne_concat(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 = ne_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);

}

/ Pretend the remote site is the same as the local site. / void site_catchup(struct site site) { struct site_file current, next; 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(&current->stored, &current->local, site); file_set_diff(current, site); break; case file_unchanged: / noop */ break; } } }

/ 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);
}

}

/ Return a site_file structure given a proto_file structure fetched * by the protocol driver. / static struct site_file fetch_add_file(struct site site, const struct proto_file pf) { enum file_type type = file_file; / init to shut up gcc / struct site_file file; struct file_state state = {0};

switch (pf->type) {
case proto_file:
    type = file_file;
    break;
case proto_dir:
    type = file_dir;
    break;
case proto_link:
    type = file_link;
    break;
}

state.size = pf->size;
state.time = pf->modtime;
state.exists = true;
state.filename = pf->filename;
state.mode = pf->mode;
state.ascii = file_isascii(pf->filename, site);
memcpy(state.checksum, pf->checksum, 16);

file = file_set_stored(type, &state, site);

munge_modtime(file, pf->modtime, site);

if (site->safemode) {
    /* Store the server modtime. */
    file->server.time = pf->modtime;
    file->server.exists = true;
}

return file;

}

static

if NE_VERSION_MINOR == 24

void

else

int

endif

site_fetch_csum_read(void userdata, const char s, size_t len) { struct ne_md5_ctx *md5 = userdata; ne_md5_process_bytes(s, len, md5);

if NE_VERSION_MINOR != 24

return 0;

endif

}

/ Retrieve the remote checksum for all files / static int fetch_checksum_file(struct proto_file file, struct site site, void *session) {

if NE_VERSION_MINOR > 25

struct ne_md5_ctx *md5;

define MD5_PTR md5

else

struct ne_md5_ctx md5;

define MD5_PTR &md5

endif

char *full_remote = ne_concat(site->remote_root, file->filename, NULL);
int ret = 0;

if NE_VERSION_MINOR > 25

md5 = ne_md5_create_ctx();

else

ne_md5_init_ctx(&md5);

endif

fe_checksumming(file->filename);
if (CALL(file_read)(session, full_remote, 
                    site_fetch_csum_read, MD5_PTR) != SITE_OK) {
    ret = 1;
    fe_checksummed(full_remote, false, DRIVER_ERR);
} else {
    ne_md5_finish_ctx(MD5_PTR, file->checksum);
    fe_checksummed(full_remote, true, NULL);
}
free(full_remote);

if NE_VERSION_MINOR > 25

ne_md5_destroy_ctx(md5);

endif

return ret;

}

/ Updates the remote file list… site_fetch_callback is called for * every remote file found. / int site_fetch(struct site site) { int ret, need_modtimes; void session; const char dirstack[DIRSTACKSIZE]; size_t dirtop; struct proto_file files = NULL;

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;
}

/* The remote modtimes are needed if timesize is used or in safe
 * mode: */
need_modtimes = site->safemode || site->state_method == state_timesize;

dirtop = 1;
dirstack[0] = "";

do {
    struct proto_file *newfiles = NULL, *f, *lastf = NULL;
    const char *reldir = dirstack[--dirtop];
    const char *slash = reldir[0] == '\0' ? "" : "/";
    char *curdir;

    curdir = ne_concat(site->remote_root, reldir, slash, NULL);

    ret = CALL(fetch_list)(session, curdir, need_modtimes, &newfiles);
    if (ret != SITE_OK) break;

    for (f = newfiles; f; f = f->next) {
        char *relfn;

        relfn = ne_concat(reldir, slash, f->filename, NULL);
        ne_free(f->filename);
        f->filename = relfn;

        if (!file_isexcluded(relfn, site)) {
            if (f->type == proto_dir && dirtop < DIRSTACKSIZE) {
                dirstack[dirtop++] = relfn;
            } else if (f->type == proto_file 
                       && site->state_method == state_checksum) {
                fetch_checksum_file(f, site, session);
            }
        }

        lastf = f;
    }

    if (lastf) {
        lastf->next = files;
        files = newfiles;
    }

    ne_free(curdir);
} while (dirtop > 0);

if (ret == SITE_OK) {
    struct proto_file *f, *nextf;

    /* Remove existing stored state for the site. */
    site_destroy_stored(site);

    /* And replace it with the fetched state. */
    for (f = files; f; f = nextf) {
        if (!file_isexcluded(f->filename, site)) {
            struct site_file *sf = fetch_add_file(site, f);
            fe_fetch_found(sf);
        }
        nextf = f->next;
        ne_free(f);
    }
} else {
    ret = SITE_FAILED;
}

proto_finish(site, session);

return ret;

}

/ 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 0

if (site->state_method == state_checksum) {
site_fetch_checksum(files, site, session);
}

endif

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; 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(&current->stored); / Could just do .exists = false / memset(&current->stored, 0, sizeof(struct file_state)); / And set the diff */ file_set_diff(current, site); } current = next; } }

/ Called to delete all the files associated with the site / void site_destroy(struct site site) { struct site_file current, *next;

current = site->files;
while (current != NULL) {
next = current->next;
file_delete(site, current);
current = next;
}

}

/ 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”, current->stored.filename); } if (current->ignore) fputs(“|ignored”, f); 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->remote_is_different?“changed”:“unchanged”); }

void site_sock_progress_cb(void *userdata, ne_off_t progress, ne_off_t total) { fe_transfer_progress(progress, total); }

void fe_initialize(void) { ne_sock_init(); }