1 /*
2 sitecopy, for managing remote web sites. File handling.
3 Copyright (C) 1998-2006, Joe Orton <joe@manyfish.co.uk>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <config.h>
22
23 #include <sys/types.h>
24
25 /* Needed for S_IXUSR */
26 #include <sys/stat.h>
27
28 #ifdef HAVE_STDLIB_H
29 #include <stdlib.h>
30 #endif
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 #ifdef HAVE_STRING_H
35 #include <string.h>
36 #endif
37
38 #include <ctype.h>
39
40 #include "basename.h"
41
42 #include <ne_md5.h>
43 #include <ne_string.h>
44 #include <ne_alloc.h>
45
46 /* We pick up FNM_LEADING_DIR fnmatch() extension, since we define
47 * _GNU_SOURCE in config.h. */
48 #include <fnmatch.h>
49
50 #include "frontend.h"
51 #include "sitesi.h"
52
53 /* fnmatch the filename against list */
54 inline int fnlist_match(const char *filename, const struct fnlist *list);
55
56 /* Deletes the given file from the given site */
file_delete(struct site * site,struct site_file * item)57 void file_delete(struct site *site, struct site_file *item)
58 {
59 site_stats_decrease(item, site);
60 site_stats_update(site);
61 if (item->prev) {
62 /* Not first in list */
63 item->prev->next = item->next;
64 } else {
65 /* Not last in list */
66 site->files = item->next;
67 }
68 if (item->next) {
69 /* Not last in list */
70 item->next->prev = item->prev;
71 } else {
72 /* Last in list */
73 site->files_tail = item->prev;
74 }
75
76 /* Now really destroy the file */
77 file_state_destroy(&item->local);
78 file_state_destroy(&item->stored);
79 file_state_destroy(&item->server);
80 free(item);
81 }
82
83
84 /* Inserts a file into the files list, position chosen by type. Must
85 * be in critical section on calling. file_set_* ensure this. */
file_insert(enum file_type type,struct site * site)86 static struct site_file *file_insert(enum file_type type, struct site *site)
87 {
88 struct site_file *file;
89 file = ne_calloc(sizeof(struct site_file));
90 if (site->files == NULL) {
91 /* Empty list */
92 site->files = file;
93 site->files_tail = file;
94 } else if (type == file_dir) {
95 /* Append file */
96 site->files_tail->next = file;
97 file->prev = site->files_tail;
98 site->files_tail = file;
99 } else {
100 /* Prepend file */
101 site->files->prev = file;
102 file->next = site->files;
103 site->files = file;
104 }
105 return file;
106 }
107
108 #define FS_ALPHA(f) ((struct file_state *) (((char *)(f)) + alpha_off))
109 #define FS_BETA(f) ((struct file_state *) (((char *)(f)) + beta_off))
110
111 /* file_set implementation, used to update the files list.
112 *
113 * file_set takes a file type 'type', a file state 'state', the site,
114 * two structure offsets, alpha_off and beta_off, and a default diff
115 * type, 'default_diff'. alpha_off represents the offset into a
116 * site_file structure for the state which 'state' represents;
117 * beta_off represents the offset into the structure for the state
118 * against which this state should be compared.
119 */
file_set(enum file_type type,struct file_state * state,struct site * site,size_t alpha_off,size_t beta_off,enum file_diff default_diff)120 static struct site_file *file_set(enum file_type type, struct file_state *state,
121 struct site *site,
122 size_t alpha_off, size_t beta_off,
123 enum file_diff default_diff)
124 {
125 struct site_file *file, *direct = NULL, *moved = NULL, *frename = NULL;
126 enum file_diff dir_diff;
127 char *bname = NULL; /* init to shut up gcc */
128
129 if (site->checkmoved && type == file_file) {
130 bname = base_name(state->filename);
131 }
132
133 for (file = site->files; file; file = file->next) {
134 struct file_state *beta = FS_BETA(file);
135
136 if (beta->exists && direct == NULL
137 && file->type == type
138 && strcmp(beta->filename, state->filename) == 0) {
139 /* Direct match found! */
140 NE_DEBUG(DEBUG_FILES, "Direct match found.\n");
141 direct = file;
142 }
143 /* If this is not a direct match, check for a move/rename candidate,
144 * unless the file already has a complete state and diff is unchanged. */
145 else if (site->checkmoved
146 && type == file_file && file->type == file_file
147 && file->diff != file_unchanged
148 && file_compare(file_file, state,
149 beta, site) == file_moved) {
150 /* TODO: There is a slight fuzz here - if checkrenames is true,
151 * we'll always match the first 'direct move' candidate as a
152 * 'rename move'. This shouldn't matter, since we prefer
153 * the move to the rename in the single candidate case,
154 * and in the multiple candidate case. */
155 if (!moved
156 && strcmp(bname, base_name(beta->filename)) == 0) {
157 NE_DEBUG(DEBUG_FILES, "Move candidate: %s\n",
158 beta->filename);
159 moved = file;
160 } else if (site->checkrenames && frename == NULL) {
161 NE_DEBUG(DEBUG_FILES, "Rename move candidate: %s\n",
162 beta->filename);
163 frename = file;
164 }
165 }
166
167 /* If all candidates are found, stop looking. */
168 if (direct && moved && frename) {
169 break;
170 }
171 }
172 NE_DEBUG(DEBUG_FILES, "Found: %s-%s-%s\n",
173 direct?"direct":"", moved?"moved":"", frename?"rename":"");
174 /* We prefer a direct move to a rename */
175 if (moved == NULL) moved = frename;
176 if (direct != NULL) {
177 dir_diff = file_compare(type, state, FS_BETA(direct), site);
178 NE_DEBUG(DEBUG_FILES, "Direct compare: %s\n",
179 DEBUG_GIVE_DIFF(dir_diff));
180 } else {
181 dir_diff = default_diff;
182 }
183
184 /* We prefer a move to a CHANGED direct match. */
185 if ((direct == NULL && moved == NULL)
186 || (direct != NULL && direct->diff == file_moved
187 && moved == NULL && dir_diff != file_unchanged)) {
188 NE_DEBUG(DEBUG_FILES, "Creating new file.\n");
189 file = file_insert(type, site);
190 file->type = type;
191 file->diff = default_diff;
192 if (type == file_file) {
193 file->ignore = file_isignored(state->filename, site);
194 }
195 } else {
196 /* Overwrite file case...
197 * Again, we still prefer a move to a direct match */
198 if (moved != NULL && dir_diff != file_unchanged) {
199 NE_DEBUG(DEBUG_FILES, "Using moved file.\n");
200 file = moved;
201 site_stats_decrease(file, site);
202 file->diff = file_moved;
203 } else {
204 NE_DEBUG(DEBUG_FILES, "Using direct match.\n");
205 file = direct;
206 site_stats_decrease(file, site);
207 file->diff = dir_diff;
208 }
209
210 if (FS_ALPHA(file)->exists) {
211 /* SHOVE! */
212 struct site_file *other;
213 NE_DEBUG(DEBUG_FILES, "Shoving file:\n");
214 other = file_insert(file->type, site);
215 other->type = file->type;
216 other->diff = default_diff;
217 other->ignore = file->ignore;
218 /* Copy over the stored state for the moved file. */
219 memcpy(FS_ALPHA(other), FS_ALPHA(file), sizeof(struct file_state));
220 DEBUG_DUMP_FILE_PROPS(DEBUG_FILES, file, site);
221 site_stats_increase(other, site);
222 }
223 }
224
225 /* Finish up - write over the new state */
226 memcpy(FS_ALPHA(file), state, sizeof(struct file_state));
227
228 /* And update the stats */
229 site_stats_increase(file, site);
230 site_stats_update(site);
231
232 return file;
233 }
234
235 #ifndef offsetof
236 #define offsetof(t, m) ((size_t) (((char *)&(((t *)NULL)->m)) - (char *)NULL))
237 #endif
238
file_set_local(enum file_type type,struct file_state * state,struct site * site)239 struct site_file *file_set_local(enum file_type type, struct file_state *state,
240 struct site *site)
241 {
242 return file_set(type, state, site,
243 offsetof(struct site_file, local),
244 offsetof(struct site_file, stored),
245 file_new);
246 }
247
file_set_stored(enum file_type type,struct file_state * state,struct site * site)248 struct site_file *file_set_stored(enum file_type type, struct file_state *state,
249 struct site *site)
250 {
251 return file_set(type, state, site,
252 offsetof(struct site_file, stored),
253 offsetof(struct site_file, local),
254 file_deleted);
255 }
256
257 /* Prepends an item to the fnlist. Returns the item. */
fnlist_prepend(struct fnlist ** list)258 struct fnlist *fnlist_prepend(struct fnlist **list)
259 {
260 struct fnlist *item = ne_malloc(sizeof(struct fnlist));
261 item->next = *list;
262 item->prev = NULL;
263 if (*list != NULL) {
264 (*list)->prev = item;
265 }
266 *list = item;
267 return item;
268 }
269
270 /* Returns a deep copy of the given fnlist */
fnlist_deep_copy(const struct fnlist * src)271 struct fnlist *fnlist_deep_copy(const struct fnlist *src)
272 {
273 const struct fnlist *iter;
274 struct fnlist *dest = NULL, *prev = NULL, *item = NULL;
275 for (iter = src; iter != NULL; iter = iter->next) {
276 item = ne_malloc(sizeof(struct fnlist));
277 item->pattern = ne_strdup(iter->pattern);
278 item->haspath = iter->haspath;
279 if (prev != NULL) {
280 prev->next = item;
281 } else {
282 /* First item in list */
283 dest = item;
284 }
285 item->prev = prev;
286 item->next = NULL;
287 prev = item;
288 }
289 return dest;
290 }
291
292 /* Performs fnmatch() of all the strings in the given string list again
293 * the given filename. Returns true if a pattern matches, else false. */
fnlist_match(const char * filename,const struct fnlist * list)294 inline int fnlist_match(const char *filename, const struct fnlist *list)
295 {
296 const struct fnlist *item;
297 const char *bname = base_name(filename);
298
299 for (item=list; item != NULL; item=item->next) {
300 NE_DEBUG(DEBUG_FILES, "%s ", item->pattern);
301 if (item->haspath) {
302 if (fnmatch(item->pattern, filename,
303 FNM_PATHNAME | FNM_LEADING_DIR) == 0)
304 break;
305 } else {
306 if (fnmatch(item->pattern, bname, 0) == 0)
307 break;
308 }
309 }
310
311 #ifdef DEBUGGING
312 if (item) {
313 NE_DEBUG(DEBUG_FILES, "- matched.\n");
314 } else if (list) {
315 NE_DEBUG(DEBUG_FILES, "\n");
316 } else {
317 NE_DEBUG(DEBUG_FILES, "(none)\n");
318 }
319 #endif /* DEBUGGING */
320
321 return (item!=NULL);
322 }
323
324 /* Returns whether the given filename is excluded from the
325 * given site */
file_isexcluded(const char * filename,struct site * site)326 int file_isexcluded(const char *filename, struct site *site)
327 {
328 NE_DEBUG(DEBUG_FILES, "Matching excludes for %s:\n", filename);
329 return fnlist_match(filename, site->excludes);
330 }
331
file_isignored(const char * filename,struct site * site)332 int file_isignored(const char *filename, struct site *site)
333 {
334 NE_DEBUG(DEBUG_FILES, "Matching ignores for %s:\n", filename);
335 return fnlist_match(filename, site->ignores);
336 }
337
file_isascii(const char * filename,struct site * site)338 int file_isascii(const char *filename, struct site *site)
339 {
340 NE_DEBUG(DEBUG_FILES, "Matching asciis for %s:\n", filename);
341 return fnlist_match(filename, site->asciis);
342 }
343
site_stats_update(struct site * site)344 void site_stats_update(struct site *site)
345 {
346 NE_DEBUG(DEBUG_FILES,
347 "Stats: moved=%d new=%d %sdeleted=%d%s changed=%d"
348 " ignored=%d unchanged=%d\n",
349 site->nummoved, site->numnew, site->nodelete?"[":"",
350 site->numdeleted, site->nodelete?"]":"", site->numchanged,
351 site->numignored, site->numunchanged);
352 site->remote_is_different = (site->nummoved + site->numnew +
353 (site->nodelete?0:site->numdeleted) +
354 site->numchanged) > 0;
355 site->local_is_different = (site->nummoved + site->numnew +
356 site->numdeleted + site->numchanged +
357 site->numignored) > 0;
358 NE_DEBUG(DEBUG_FILES, "Remote: %s Local: %s\n",
359 site->remote_is_different?"yes":"no",
360 site->local_is_different?"yes":"no");
361 }
362
file_set_diff(struct site_file * file,struct site * site)363 void file_set_diff(struct site_file *file, struct site *site)
364 {
365 site_stats_decrease(file, site);
366 file->diff = file_compare(file->type, &file->local, &file->stored, site);
367 site_stats_increase(file, site);
368 site_stats_update(site);
369 }
370
file_state_copy(struct file_state * dest,const struct file_state * src,struct site * site)371 void file_state_copy(struct file_state *dest, const struct file_state *src,
372 struct site *site)
373 {
374 file_state_destroy(dest);
375 memcpy(dest, src, sizeof(struct file_state));
376 if (src->linktarget != NULL) {
377 dest->linktarget = ne_strdup(src->linktarget);
378 }
379 if (src->filename != NULL) {
380 dest->filename = ne_strdup(src->filename);
381 }
382 }
383
file_state_destroy(struct file_state * state)384 void file_state_destroy(struct file_state *state)
385 {
386 if (state->linktarget != NULL) {
387 free(state->linktarget);
388 state->linktarget = NULL;
389 }
390 if (state->filename != NULL) {
391 free(state->filename);
392 state->filename = NULL;
393 }
394 }
395
396 /* Checksum the file.
397 * We pass the site atm, since it's likely we will add different
398 * methods of checksumming later on, with better-faster-happier
399 * algorithms.
400 * Returns:
401 * 0 on success
402 * non-zero on error (e.g., couldn't open file)
403 */
file_checksum(const char * fname,struct file_state * state,struct site * s)404 int file_checksum(const char *fname, struct file_state *state, struct site *s)
405 {
406 int ret;
407 FILE *f;
408 f = fopen(fname, "r" FOPEN_BINARY_FLAGS);
409 if (f == NULL) {
410 return -1;
411 }
412 ret = ne_md5_stream(f, state->checksum);
413 fclose(f); /* worth checking return value? */
414 #ifdef DEBUGGING
415 {
416 char tmp[33] = {0};
417 ne_md5_to_ascii(state->checksum, tmp);
418 NE_DEBUG(DEBUG_FILES, "Checksum: %s = [%32s]\n", fname, tmp);
419 }
420 #endif /* DEBUGGING */
421 return ret;
422 }
423
file_full_remote(struct file_state * state,struct site * site)424 char *file_full_remote(struct file_state *state, struct site *site)
425 {
426 char *ret;
427 ret = ne_malloc(strlen(site->remote_root) + strlen(state->filename) + 1);
428 strcpy(ret, site->remote_root);
429 if (site->lowercase) {
430 int n, off, len;
431 /* Write the remote filename in lower case */
432 off = strlen(site->remote_root);
433 len = strlen(state->filename) + 1; /* +1 for \0 */
434 for (n = 0; n < len; n++) {
435 ret[off+n] = tolower(state->filename[n]);
436 }
437 } else {
438 strcat(ret, state->filename);
439 }
440 return ret;
441 }
442
file_full_local(struct file_state * state,struct site * site)443 char *file_full_local(struct file_state *state, struct site *site)
444 {
445 return ne_concat(site->local_root, state->filename, NULL);
446 }
447
file_name(const struct site_file * file)448 char *file_name(const struct site_file *file)
449 {
450 if (file->diff == file_deleted) {
451 return file->stored.filename;
452 } else {
453 return file->local.filename;
454 }
455 }
456
457 /* Returns whether the file "contents" have changed or not.
458 * TODO: better name needed. */
file_contents_changed(struct site_file * file,struct site * site)459 int file_contents_changed(struct site_file *file, struct site *site)
460 {
461 int ret = false;
462 if (site->state_method == state_checksum) {
463 if (memcmp(file->stored.checksum, file->local.checksum, 16))
464 ret = true;
465 } else {
466 if (file->stored.size != file->local.size ||
467 file->stored.time != file->local.time)
468 ret = true;
469 }
470 if (file->stored.ascii != file->local.ascii)
471 ret = true;
472 return ret;
473 }
474
475 /* Return true if the permission of the given file need changing. */
file_perms_changed(struct site_file * file,struct site * site)476 int file_perms_changed(struct site_file *file, struct site *site)
477 {
478 /* Slightly obscure boolean here...
479 * If ('permissions all' OR ('permissions exec' and file is chmod u+x)
480 * AND
481 * EITHER (in tempupload mode or nooverwrite mode)
482 * OR the stored file perms are different from the local ones
483 * OR the file doesn't exist locally or remotely,
484 *
485 * Note that in tempupload and nooverwrite mode, we are
486 * creating a new file, so the permissions on the new file
487 * will always be "wrong".
488 */
489 if (((site->perms == sitep_all) ||
490 (((file->local.mode | file->stored.mode) & S_IXUSR) &&
491 (site->perms == sitep_exec))) &&
492 (site->tempupload || site->nooverwrite ||
493 file->local.mode != file->stored.mode ||
494 file->local.exists != file->stored.exists)) {
495 return true;
496 } else {
497 return false;
498 }
499 }
500
file_uploaded(struct site_file * file,struct site * site)501 void file_uploaded(struct site_file *file, struct site *site)
502 {
503 file->stored.size = file->local.size;
504 if (site->state_method == state_checksum) {
505 memcpy(file->stored.checksum, file->local.checksum, 16);
506 } else {
507 file->stored.time = file->local.time;
508 }
509 /* Now the filename */
510 if (file->stored.filename) free(file->stored.filename);
511 file->stored.filename = ne_strdup(file->local.filename);
512 file->stored.ascii = file->local.ascii;
513 file->stored.exists = file->local.exists;
514 file->stored.mode = file->local.mode;
515 /* Update the diff */
516 file_set_diff(file, site);
517 }
518
file_downloaded(struct site_file * file,struct site * site)519 void file_downloaded(struct site_file *file, struct site *site)
520 {
521 file->local.size = file->stored.size;
522 if (site->state_method == state_checksum) {
523 memcpy(file->local.checksum, file->stored.checksum, 16);
524 } else {
525 file->local.time = file->stored.time;
526 }
527 /* Now the filename */
528 if (file->local.filename) free(file->local.filename);
529 file->local.filename = ne_strdup(file->stored.filename);
530 file->local.ascii = file->stored.ascii;
531 file->local.exists = file->stored.exists;
532 file->local.mode = file->stored.mode;
533 file_set_diff(file, site);
534 }
535