"Fossies" - the Fresh Open Source Software archive 
Member "sitecopy-0.16.6/src/sites.c" of archive sitecopy-0.16.6.tar.gz:
/*
sitecopy, for managing remote web sites.
Copyright (C) 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(¤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, 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(¤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) {
/* 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 = 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(¤t->stored, ¤t->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(¤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;
}
}
/* 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();
}