/* pfs.c:
*
****************************************************************
* Copyright (C) 2003 Tom Lord
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "hackerlab/bugs/panic.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/arrays/ar.h"
#include "libfsutils/tmp-files.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/pfs-dav.h"
#include "libarch/pfs-sftp.h"
#include "libarch/pfs-ftp.h"
#include "libarch/pfs-fs.h"
#include "libarch/pfs.h"
#include "libarch/cached-archive.h"
/* __STDC__ prototypes for static functions */
static int dot_listings_equal (rel_table a, rel_table b);
void
arch_pfs_rmrf_file (struct arch_pfs_session * pfs, t_uchar * path)
{
int dir_check;
dir_check = arch_pfs_is_dir (pfs, path);
if (!dir_check)
{
arch_pfs_rm (pfs, path, 1);
}
else if (dir_check > 0)
{
rel_table contents = 0;
int x;
contents = arch_pfs_directory_files (pfs, path, 1);
for (x = 0; x < rel_n_records (contents); ++x)
if (str_cmp (".", contents[x][0]) && str_cmp ("..", contents[x][0]))
{
t_uchar * subpath = 0;
subpath = file_name_in_vicinity (0, path, contents[x][0]);
arch_pfs_rmrf_file (pfs, subpath);
lim_free (0, subpath);
}
arch_pfs_rmdir (pfs, path, 1);
rel_free_table (contents);
}
/* dir_check < 0 indicates an error, presumably ENOENT */
}
int
arch_pfs_set_file_contents (struct arch_pfs_session *pfs, t_uchar const *path, t_uchar const *contents, int soft_errors)
{
t_uchar * tmp_path = tmp_file_name_in_tmp (",,pfs-sftp-file-contents");
int fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
int err;
safe_unlink (tmp_path);
safe_printfmt (fd, "%s\n", contents);
safe_lseek (fd, (off_t)0, SEEK_SET);
err = pfs->vtable->put_file (pfs, (t_uchar *)path, 0666, fd, 1);
safe_close (fd);
lim_free (0, tmp_path);
return err;
}
int
arch_pfs_set_file_contents_in_dir (struct arch_pfs_session *pfs, t_uchar const *dir, t_uchar const *relpath, t_uchar const *contents, int soft_errors)
{
t_uchar * tmp_path = file_name_in_vicinity (0, dir, relpath);
int result = arch_pfs_set_file_contents (pfs, tmp_path, contents, soft_errors);
lim_free (0, tmp_path);
return result;
}
void
arch_pfs_pfs_make_archive (t_uchar * name, t_uchar * uri, t_uchar * version, t_uchar *mirror_of, int dot_listing_lossage, int signed_archive)
{
struct arch_pfs_session *pfs;
t_uchar * meta_info_path = 0;
t_uchar * tmp_path;
t_uchar * archdir;
t_uchar * hosturi;
/* TODO: handle URIs like 'sftp://amit@cvs:~f' semi-reasonably
* The following code can mangle URIs, since it doesn't ignore the protocol
* part. IMHO, it doesn't belong in a general function like this
* one. - aaron.bentley@utoronto.ca
*/
tmp_path = file_name_from_directory (0, uri); /* remove any trailing '/' */
archdir = file_name_tail (0, tmp_path);
hosturi = file_name_directory (0, tmp_path);
lim_free (0, tmp_path);
pfs = arch_pfs_connect (hosturi, 0);
pfs->vtable->mkdir (pfs, archdir, 0777, 0);
meta_info_path = file_name_in_vicinity (0, archdir, "=meta-info");
pfs->vtable->mkdir (pfs, meta_info_path, 0777, 0);
if (mirror_of)
{
arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "name", mirror_of, 0);
arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "mirror", mirror_of, 0);
}
else
arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "name", name, 0);
if (dot_listing_lossage)
arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "http-blows", "it sure does", 0);
if (signed_archive)
arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "signed-archive", "system cracking is (nearly always) lame", 0);
arch_pfs_set_file_contents_in_dir (pfs, archdir, ".archive-version", version, 0);
if (dot_listing_lossage)
{
arch_pfs_update_listing_file (pfs, meta_info_path);
arch_pfs_update_root_listing_file (pfs, archdir);
}
lim_free (0, meta_info_path);
lim_free (0, hosturi);
lim_free (0, archdir);
}
/* TODO: layer violation here - sftp urls are not the only
* ones to probe - RBC 20050130
*/
extern int
arch_valid_uri (t_uchar * uri)
{
int answer = 1;
if (arch_pfs_sftp_supported_protocol (uri))
{
t_uchar * user = 0, * hostname = 0, * port = 0;
char * path = 0;
answer = (arch_pfs_sftp_parse_uri (&user, &hostname, &port, &path, uri) == 0);
lim_free (0, user);
lim_free (0, hostname);
lim_free (0, port);
lim_free (0, path);
}
return answer;
}
struct arch_pfs_session *
arch_pfs_connect (t_uchar * uri, int soft_errors)
{
struct arch_pfs_session * answer;
if (arch_pfs_dav_supported_protocol (uri))
{
answer = arch_pfs_dav_connect (uri, soft_errors);
}
else if (arch_pfs_sftp_supported_protocol (uri))
{
return arch_pfs_sftp_connect (uri, soft_errors);
}
else if (arch_pfs_ftp_supported_protocol (uri))
{
return arch_pfs_ftp_connect (uri, soft_errors);
}
else
{
return arch_pfs_fs_connect (uri, soft_errors);
}
if (!answer && !soft_errors)
panic ("Failed to connect to archive with soft_errors off");
return answer;
}
void
arch_pfs_disconnect (struct arch_pfs_session **session)
{
if (*session)
(*session)->vtable->disconnect (session);
*session = NULL;
}
int
arch_pfs_is_local(t_uchar const * uri)
{
t_uchar * colon = str_chr_index (uri, ':');
t_uchar * slash = str_chr_index (uri, '/');
return (colon == 0 || (slash != 0 && slash < colon ) );
}
/* TODO: move this to hackerlab or fsutils */
static t_uchar *
path_normalise (t_uchar const * in_path)
{
/* FIXME, this is a random hack for now.
* expected semantics are - /./'s are removed
* foo/../ are removed
*/
t_uchar * result = NULL;
rel_table sections = rel_delim_split (in_path, "/");
rel_table answer_sections = 0;
int index = 0;
for (index = 0; index < rel_n_records(sections); ++index)
{
int answer_len;
if (!str_cmp(sections[index][0], "."))
continue;
else if (!str_cmp(sections[index][0], "..") &&
(answer_len = rel_n_records(answer_sections)) &&
str_cmp (answer_sections[answer_len - 1][0], ".."))
{
rel_remove_records (&answer_sections, answer_len - 1, answer_len - 1);
}
else
rel_add_records (&answer_sections, rel_copy_record (sections[index]), 0);
}
result = str_save (0, "");
for (index = 0; index < rel_n_records(answer_sections); ++index)
{
result = str_realloc_cat_many (0, result, "/", answer_sections[index][0], str_end);
}
rel_free_table(sections);
rel_free_table(answer_sections);
return result;
}
t_uchar *
arch_pfs_abs_path (t_uchar * path)
{
if (!arch_pfs_is_local (path))
return str_save (0, path);
else
return arch_abs_path (path);
}
/**
* \brief Determines the absolute filesystem path for a file
* \param path The path to absolutize
* \return the absolute path
*/
t_uchar *
arch_abs_path (t_uchar const * const path)
{
t_uchar * cwd;
t_uchar * temp;
t_uchar * result;
if (!str_length (path))
return safe_current_working_directory ();
if (path[0]=='/')
return path_normalise (path);
cwd = safe_current_working_directory ();
temp = str_realloc_cat (0, file_name_as_directory(0, cwd), path);
result = path_normalise(temp);
lim_free (0, cwd);
lim_free (0, temp);
return result;
}
t_uchar *
arch_pfs_file_contents (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
return pfs->vtable->file_contents (pfs, path, soft_errors);
}
rel_table
arch_pfs_directory_files (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
return pfs->vtable->directory_files (pfs, path, soft_errors);
}
int
arch_pfs_file_exists (struct arch_pfs_session * pfs, t_uchar * path)
{
return pfs->vtable->file_exists (pfs, path);
}
int
arch_pfs_get_file (struct arch_pfs_session * pfs, int out_fd, t_uchar * path, int soft_errors)
{
return pfs->vtable->get_file (pfs, out_fd, path, soft_errors);
}
int
arch_pfs_put_file (struct arch_pfs_session * pfs, t_uchar * path, mode_t perms, int in_fd, int soft_errors)
{
return pfs->vtable->put_file (pfs, path, perms, in_fd, soft_errors);
}
int
arch_pfs_put_atomic (struct arch_pfs_session * pfs, t_uchar ** errstr, t_uchar * path, mode_t perms, int in_fd, int replace, int soft_errors)
{
int result = 0;
t_uchar * temp_dir = file_name_directory_file (0, path);
t_uchar * tmp_file = talloc_tmp_file_name (talloc_context, temp_dir, ",,upload-tmp");
arch_pfs_rmrf_file (pfs, tmp_file);
if (!arch_pfs_put_file (pfs, tmp_file, perms, in_fd, soft_errors))
{
if (replace)
arch_pfs_rmrf_file (pfs, path);
if (arch_pfs_rename (pfs, errstr, tmp_file, path, soft_errors))
result = -1;
}
else
result = -1;
lim_free (0, temp_dir);
talloc_free (tmp_file);
return result;
}
int
arch_pfs_mkdir (struct arch_pfs_session * pfs, t_uchar * path, mode_t perms, int soft_errors)
{
return pfs->vtable->mkdir (pfs, path, perms, soft_errors);
}
int
arch_pfs_rename (struct arch_pfs_session * pfs, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors)
{
return pfs->vtable->rename (pfs, errstr, from, to, soft_errors);
}
int
arch_pfs_is_dir (struct arch_pfs_session * pfs, t_uchar * path)
{
return pfs->vtable->is_dir (pfs, path);
}
int
arch_pfs_rmdir (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
return pfs->vtable->rmdir (pfs, path, soft_errors);
}
int
arch_pfs_rm (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
return pfs->vtable->rm (pfs, path, soft_errors);
}
void
arch_pfs_update_listing_file (struct arch_pfs_session * session, t_uchar * dir)
{
arch_pfs_update_listing_file_full (session, dir, 0);
}
void
arch_pfs_update_root_listing_file (struct arch_pfs_session * session, t_uchar *dir)
{
rel_table archive_version_list = 0;
rel_add_records (&archive_version_list,
rel_make_record (".archive-version", 0), 0);
arch_pfs_update_listing_file_full (session, dir, archive_version_list);
rel_free_table (archive_version_list);
}
void
arch_pfs_update_listing_file_full (struct arch_pfs_session * session, t_uchar * dir, rel_table additional_files)
{
t_uchar * tmp_path = 0;
int tmp_fd = 0;
t_uchar * dot_listing_path = 0;
t_uchar * dot_listing_tmp = 0;
rel_table files_before = 0;
rel_table files_after = 0;
tmp_path = tmp_file_name_in_tmp (",,pfs-dot-listing");
tmp_fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
safe_unlink (tmp_path);
dot_listing_path = file_name_in_vicinity (0, dir, ".listing");
dot_listing_tmp = archive_tmp_file_name (dir, ",,dot-listing");
while (1)
{
int x;
files_before = arch_pfs_directory_files (session, dir, 0);
if (files_before) rel_sort_table_by_field (0, files_before, 0);
/* TODO: remove files in files_before that are present in additional_files */
for (x = 0; x < rel_n_records (files_before); ++x)
{
if (('.' != files_before[x][0][0]) && (',' != files_before[x][0][0]))
{
safe_printfmt (tmp_fd, "%s\r\n", files_before[x][0]);
}
}
if (additional_files)
for (x = 0; x < rel_n_records (additional_files); ++x)
{
safe_printfmt (tmp_fd, "%s\r\n", additional_files[x][0]);
}
safe_lseek (tmp_fd, (off_t)0, SEEK_SET);
arch_pfs_put_atomic (session, 0, dot_listing_path, 0444, tmp_fd, 1, 0);
files_after = arch_pfs_directory_files (session, dir, 0);
if (files_after) rel_sort_table_by_field (0, files_after, 0);
if (!dot_listings_equal (files_before, files_after))
{
safe_ftruncate (tmp_fd, (long)0);
rel_free_table (files_before);
rel_free_table (files_after);
files_before = 0;
files_after = 0;
}
else
{
break;
}
}
safe_close (tmp_fd);
lim_free (0, dot_listing_path);
lim_free (0, dot_listing_tmp);
rel_free_table (files_before);
rel_free_table (files_after);
lim_free (0, tmp_path);
}
static int
dot_listings_equal (rel_table a, rel_table b)
{
int ax;
int bx;
if ((!a && b) || (a && !b))
return 0;
ax = 0;
bx = 0;
while ((ax < rel_n_records (a)) || (bx < rel_n_records (b)))
{
if ((ax < rel_n_records (a)) && (a[ax][0][0] == '.'))
{
++ax;
continue;
}
if ((ax < rel_n_records (a)) && (a[ax][0][0] == ','))
{
++ax;
continue;
}
if ((bx < rel_n_records (b)) && (b[bx][0][0] == '.'))
{
++bx;
continue;
}
if ((bx < rel_n_records (b)) && (b[bx][0][0] == ','))
{
++bx;
continue;
}
if ((ax == rel_n_records (a)) || (bx == rel_n_records (b)))
return 0;
if (str_cmp (a[ax][0], b[bx][0]))
return 0;
++ax;
++bx;
}
if ((ax != rel_n_records (a)) || (bx != rel_n_records (b)))
return 0;
return 1;
}
/**
* \brief adjust a parsed uri for user-data-entry heuristics
*
* \param uri the uri to apply heuristics too
*/
void
arch_uri_heuristics (ne_uri *parsed_uri)
{
char *at_pos = str_chr_index (parsed_uri->host, '@');
if (!at_pos)
return;
parsed_uri->userinfo = str_replace (parsed_uri->userinfo,
str_alloc_cat (0, parsed_uri->userinfo, "@"));
parsed_uri->userinfo = str_replace (parsed_uri->userinfo,
str_alloc_cat_n (0, parsed_uri->userinfo, parsed_uri->host, at_pos - parsed_uri->host));
parsed_uri->host = str_replace (parsed_uri->host, str_save (0, at_pos + 1));
}
/* RFC2396 spake:
* "Data must be escaped if it does not have a representation
* using an unreserved character".
*/
/* Lookup table: character classes from 2396. (This is overkill) */
#define SP 0 /* space = <US-ASCII coded character 20 hexadecimal> */
#define CO 0 /* control = <US-ASCII coded characters 00-1F and 7F hexadecimal> */
#define DE 0 /* delims = "<" | ">" | "#" | "%" | <"> */
#define UW 0 /* unwise = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`" */
#define MA 1 /* mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" */
#define AN 2 /* alphanum = alpha | digit */
#define RE 2 /* reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," */
static const char uri_chars[128] = {
/* +2 +4 +6 +8 +10 +12 +14 */
/* 0 */ CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO,
/* 16 */ CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO,
/* 32 */ SP, MA, DE, DE, RE, DE, RE, MA, MA, MA, MA, RE, RE, MA, MA, RE,
/* 48 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, RE, RE, DE, RE, DE, RE,
/* 64 */ RE, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN,
/* 80 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, UW, UW, UW, UW, MA,
/* 96 */ UW, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN,
/* 112 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, UW, UW, UW, MA, CO
};
#define ESCAPE(ch) (((const signed char)(ch) < 0 || \
uri_chars[(unsigned int)(ch)] == 0))
char *oldneon_ne_path_escape(const char *abs_path)
{
const char *pnt;
char *ret, *retpos;
int count = 0;
for (pnt = abs_path; *pnt != '\0'; pnt++) {
if (ESCAPE(*pnt)) {
count++;
}
}
if (count == 0) {
return ne_strdup(abs_path);
}
/* An escaped character is "%xx", i.e., two MORE
* characters than the original string */
retpos = ret = ne_malloc(strlen(abs_path) + 2*count + 1);
for (pnt = abs_path; *pnt != '\0'; pnt++) {
if (ESCAPE(*pnt)) {
/* Escape it - %<hex><hex> */
sprintf(retpos, "%%%02x", (unsigned char) *pnt);
retpos += 3;
} else {
/* It's cool */
*retpos++ = *pnt;
}
}
*retpos = '\0';
return ret;
}
/**
* \brief escape a location
*
* will eventuall break into sections and be smart about things
*/
t_uchar *
escape_location (t_uchar const *location)
{
return oldneon_ne_path_escape(location);
}
/**
* \brief unescape a location
*
* will eventuall break into sections and be smart about things
*/
t_uchar *
unescape_location (t_uchar const *location)
{
return ne_path_unescape(location);
}
/* tag: Tom Lord Thu Jun 5 15:12:22 2003 (pfs.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1