1647 lines
39 KiB
C
1647 lines
39 KiB
C
/*@-modfilesys@*/
|
|
/** \ingroup rpmio
|
|
* \file rpmio/rpmdav.c
|
|
*/
|
|
|
|
#include "system.h"
|
|
|
|
#if defined(HAVE_PTHREAD_H) && !defined(__LCLINT__)
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
#include <neon/ne_alloc.h>
|
|
#include <neon/ne_auth.h>
|
|
#include <neon/ne_basic.h>
|
|
#include <neon/ne_dates.h>
|
|
#include <neon/ne_locks.h>
|
|
#include <neon/ne_props.h>
|
|
#include <neon/ne_request.h>
|
|
#include <neon/ne_socket.h>
|
|
#include <neon/ne_string.h>
|
|
#include <neon/ne_utils.h>
|
|
|
|
#include <rpmio_internal.h>
|
|
|
|
#define _RPMDAV_INTERNAL
|
|
#include <rpmdav.h>
|
|
|
|
#include "argv.h"
|
|
#include "ugid.h"
|
|
#include "debug.h"
|
|
|
|
/*@access DIR @*/
|
|
/*@access FD_t @*/
|
|
/*@access urlinfo @*/
|
|
|
|
#if 0 /* HACK: reasonable value needed. */
|
|
#define TIMEOUT_SECS 60
|
|
#else
|
|
#define TIMEOUT_SECS 5
|
|
#endif
|
|
/*@unchecked@*/
|
|
static int httpTimeoutSecs = TIMEOUT_SECS;
|
|
|
|
/**
|
|
* Wrapper to free(3), hides const compilation noise, permit NULL, return NULL.
|
|
* @param p memory to free
|
|
* @retval NULL always
|
|
*/
|
|
/*@unused@*/ static inline /*@null@*/ void *
|
|
_free(/*@only@*/ /*@null@*/ /*@out@*/ const void * p)
|
|
/*@modifies p@*/
|
|
{
|
|
if (p != NULL) free((void *)p);
|
|
return NULL;
|
|
}
|
|
|
|
/* =============================================================== */
|
|
static int davFree(urlinfo u)
|
|
/*@globals internalState @*/
|
|
/*@modifies u, internalState @*/
|
|
{
|
|
if (u != NULL && u->sess != NULL) {
|
|
u->capabilities = _free(u->capabilities);
|
|
if (u->lockstore != NULL)
|
|
ne_lockstore_destroy(u->lockstore);
|
|
u->lockstore = NULL;
|
|
ne_session_destroy(u->sess);
|
|
u->sess = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void davProgress(void * userdata, off_t current, off_t total)
|
|
/*@*/
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
|
|
assert(u != NULL);
|
|
sess = u->sess;
|
|
assert(sess != NULL);
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
u->current = current;
|
|
u->total = total;
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davProgress(%p,0x%x:0x%x) sess %p u %p\n", userdata, (unsigned int)current, (unsigned int)total, sess, u);
|
|
}
|
|
|
|
static void davNotify(void * userdata,
|
|
ne_conn_status connstatus, const char * info)
|
|
/*@*/
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
/*@observer@*/
|
|
static const char * connstates[] = {
|
|
"namelookup",
|
|
"connecting",
|
|
"connected",
|
|
"secure",
|
|
"unknown"
|
|
};
|
|
|
|
assert(u != NULL);
|
|
sess = u->sess;
|
|
assert(sess != NULL);
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
#ifdef REFERENCE
|
|
typedef enum {
|
|
ne_conn_namelookup, /* lookup up hostname (info = hostname) */
|
|
ne_conn_connecting, /* connecting to host (info = hostname) */
|
|
ne_conn_connected, /* connected to host (info = hostname) */
|
|
ne_conn_secure /* connection now secure (info = crypto level) */
|
|
} ne_conn_status;
|
|
#endif
|
|
|
|
u->connstatus = connstatus;
|
|
|
|
/*@-boundsread@*/
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davNotify(%p,%d,%p) sess %p u %p %s\n", userdata, connstatus, info, sess, u, connstates[ (connstatus < 4 ? connstatus : 4)]);
|
|
/*@=boundsread@*/
|
|
|
|
}
|
|
|
|
static void davCreateRequest(ne_request * req, void * userdata,
|
|
const char * method, const char * uri)
|
|
/*@*/
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
void * private = NULL;
|
|
const char * id = "urlinfo";
|
|
|
|
assert(u != NULL);
|
|
assert(u->sess != NULL);
|
|
assert(req != NULL);
|
|
sess = ne_get_session(req);
|
|
assert(sess == u->sess);
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
assert(sess != NULL);
|
|
private = ne_get_session_private(sess, id);
|
|
assert(u == private);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davCreateRequest(%p,%p,%s,%s) %s:%p\n", req, userdata, method, uri, id, private);
|
|
}
|
|
|
|
static void davPreSend(ne_request * req, void * userdata, ne_buffer * buf)
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
const char * id = "fd";
|
|
FD_t fd = NULL;
|
|
|
|
assert(u != NULL);
|
|
assert(u->sess != NULL);
|
|
assert(req != NULL);
|
|
sess = ne_get_session(req);
|
|
assert(sess == u->sess);
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
fd = ne_get_request_private(req, id);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davPreSend(%p,%p,%p) sess %p %s %p\n", req, userdata, buf, sess, id, fd);
|
|
if (_dav_debug)
|
|
fprintf(stderr, "-> %s\n", buf->data);
|
|
|
|
}
|
|
|
|
static int davPostSend(ne_request * req, void * userdata, const ne_status * status)
|
|
/*@*/
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
const char * id = "fd";
|
|
FD_t fd = NULL;
|
|
|
|
assert(u != NULL);
|
|
assert(u->sess != NULL);
|
|
assert(req != NULL);
|
|
sess = ne_get_session(req);
|
|
assert(sess == u->sess);
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
fd = ne_get_request_private(req, id);
|
|
|
|
/*@-evalorder@*/
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davPostSend(%p,%p,%p) sess %p %s %p %s\n", req, userdata, status, sess, id, fd, ne_get_error(sess));
|
|
/*@=evalorder@*/
|
|
return NE_OK;
|
|
}
|
|
|
|
static void davDestroyRequest(ne_request * req, void * userdata)
|
|
/*@*/
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
const char * id = "fd";
|
|
FD_t fd = NULL;
|
|
|
|
assert(u != NULL);
|
|
assert(u->sess != NULL);
|
|
assert(req != NULL);
|
|
sess = ne_get_session(req);
|
|
assert(sess == u->sess);
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
fd = ne_get_request_private(req, id);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davDestroyRequest(%p,%p) sess %p %s %p\n", req, userdata, sess, id, fd);
|
|
}
|
|
|
|
static void davDestroySession(void * userdata)
|
|
/*@*/
|
|
{
|
|
urlinfo u = userdata;
|
|
ne_session * sess;
|
|
void * private = NULL;
|
|
const char * id = "urlinfo";
|
|
|
|
assert(u != NULL);
|
|
assert(u->sess != NULL);
|
|
sess = u->sess;
|
|
assert(u == ne_get_session_private(sess, "urlinfo"));
|
|
|
|
assert(sess != NULL);
|
|
private = ne_get_session_private(sess, id);
|
|
assert(u == private);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davDestroySession(%p) sess %p %s %p\n", userdata, sess, id, private);
|
|
}
|
|
|
|
static int
|
|
davVerifyCert(void *userdata, int failures, const ne_ssl_certificate *cert)
|
|
/*@*/
|
|
{
|
|
const char *hostname = userdata;
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davVerifyCert(%p,%d,%p) %s\n", userdata, failures, cert, hostname);
|
|
|
|
return 0; /* HACK: trust all server certificates. */
|
|
}
|
|
|
|
static int davConnect(urlinfo u)
|
|
/*@globals internalState @*/
|
|
/*@modifies u, internalState @*/
|
|
{
|
|
const char * path = NULL;
|
|
int rc;
|
|
|
|
/* HACK: hkp:// has no steenkin' options */
|
|
if (!(u->urltype == URL_IS_HTTP || u->urltype == URL_IS_HTTPS))
|
|
return 0;
|
|
|
|
/* HACK: where should server capabilities be read? */
|
|
(void) urlPath(u->url, &path);
|
|
/* HACK: perhaps capture Allow: tag, look for PUT permitted. */
|
|
rc = ne_options(u->sess, path, u->capabilities);
|
|
switch (rc) {
|
|
case NE_OK:
|
|
break;
|
|
case NE_ERROR:
|
|
/* HACK: "301 Moved Permanently" on empty subdir. */
|
|
if (!strncmp("301 ", ne_get_error(u->sess), sizeof("301 ")-1))
|
|
break;
|
|
/*@fallthrough@*/
|
|
case NE_CONNECT:
|
|
case NE_LOOKUP:
|
|
default:
|
|
if (_dav_debug)
|
|
fprintf(stderr, "*** Connect to %s:%d failed(%d):\n\t%s\n",
|
|
u->host, u->port, rc, ne_get_error(u->sess));
|
|
break;
|
|
}
|
|
|
|
/* HACK: sensitive to error returns? */
|
|
u->httpVersion = (ne_version_pre_http11(u->sess) ? 0 : 1);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int davInit(const char * url, urlinfo * uret)
|
|
/*@globals internalState @*/
|
|
/*@modifies *uret, internalState @*/
|
|
{
|
|
urlinfo u = NULL;
|
|
int rc = 0;
|
|
|
|
/*@-globs@*/ /* FIX: h_errno annoyance. */
|
|
if (urlSplit(url, &u))
|
|
return -1; /* XXX error returns needed. */
|
|
/*@=globs@*/
|
|
|
|
if (u->url != NULL && u->sess == NULL)
|
|
switch (u->urltype) {
|
|
default:
|
|
assert(u->urltype != u->urltype);
|
|
/*@notreached@*/ break;
|
|
case URL_IS_HTTPS:
|
|
case URL_IS_HTTP:
|
|
case URL_IS_HKP:
|
|
{ ne_server_capabilities * capabilities;
|
|
|
|
/* HACK: oneshots should be done Somewhere Else Instead. */
|
|
/*@-noeffect@*/
|
|
rc = ((_dav_debug < 0) ? NE_DBG_HTTP : 0);
|
|
ne_debug_init(stderr, rc); /* XXX oneshot? */
|
|
/*@=noeffect@*/
|
|
rc = ne_sock_init(); /* XXX oneshot? */
|
|
|
|
u->lockstore = ne_lockstore_create(); /* XXX oneshot? */
|
|
|
|
u->capabilities = capabilities = xcalloc(1, sizeof(*capabilities));
|
|
u->sess = ne_session_create(u->scheme, u->host, u->port);
|
|
|
|
ne_lockstore_register(u->lockstore, u->sess);
|
|
|
|
if (u->proxyh != NULL)
|
|
ne_session_proxy(u->sess, u->proxyh, u->proxyp);
|
|
|
|
#if 0
|
|
{ const ne_inet_addr ** addrs;
|
|
unsigned int n;
|
|
ne_set_addrlist(u->sess, addrs, n);
|
|
}
|
|
#endif
|
|
|
|
ne_set_progress(u->sess, davProgress, u);
|
|
ne_set_status(u->sess, davNotify, u);
|
|
|
|
ne_set_persist(u->sess, 1);
|
|
ne_set_read_timeout(u->sess, httpTimeoutSecs);
|
|
ne_set_useragent(u->sess, PACKAGE "/" PACKAGE_VERSION);
|
|
|
|
/* XXX check that neon is ssl enabled. */
|
|
if (!strcasecmp(u->scheme, "https"))
|
|
ne_ssl_set_verify(u->sess, davVerifyCert, (char *)u->host);
|
|
|
|
ne_set_session_private(u->sess, "urlinfo", u);
|
|
|
|
ne_hook_destroy_session(u->sess, davDestroySession, u);
|
|
|
|
ne_hook_create_request(u->sess, davCreateRequest, u);
|
|
ne_hook_pre_send(u->sess, davPreSend, u);
|
|
ne_hook_post_send(u->sess, davPostSend, u);
|
|
ne_hook_destroy_request(u->sess, davDestroyRequest, u);
|
|
|
|
/* HACK: where should server capabilities be read? */
|
|
rc = davConnect(u);
|
|
if (rc)
|
|
goto exit;
|
|
} break;
|
|
}
|
|
|
|
exit:
|
|
/*@-boundswrite@*/
|
|
if (rc == 0 && uret != NULL)
|
|
*uret = urlLink(u, __FUNCTION__);
|
|
/*@=boundswrite@*/
|
|
u = urlFree(u, "urlSplit (davInit)");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* =============================================================== */
|
|
enum fetch_rtype_e {
|
|
resr_normal = 0,
|
|
resr_collection,
|
|
resr_reference,
|
|
resr_error
|
|
};
|
|
|
|
struct fetch_resource_s {
|
|
struct fetch_resource_s *next;
|
|
char *uri;
|
|
/*@unused@*/
|
|
char *displayname;
|
|
enum fetch_rtype_e type;
|
|
size_t size;
|
|
time_t modtime;
|
|
int is_executable;
|
|
int is_vcr; /* Is version resource. 0: no vcr, 1 checkin 2 checkout */
|
|
char *error_reason; /* error string returned for this resource */
|
|
int error_status; /* error status returned for this resource */
|
|
};
|
|
|
|
/*@null@*/
|
|
static void *fetch_destroy_item(/*@only@*/ struct fetch_resource_s *res)
|
|
/*@modifies res @*/
|
|
{
|
|
NE_FREE(res->uri);
|
|
NE_FREE(res->error_reason);
|
|
res = _free(res);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef UNUSED
|
|
/*@null@*/
|
|
static void *fetch_destroy_list(/*@only@*/ struct fetch_resource_s *res)
|
|
/*@modifies res @*/
|
|
{
|
|
struct fetch_resource_s *next;
|
|
/*@-branchstate@*/
|
|
for (; res != NULL; res = next) {
|
|
next = res->next;
|
|
res = fetch_destroy_item(res);
|
|
}
|
|
/*@=branchstate@*/
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static void *fetch_create_item(/*@unused@*/ void *userdata, /*@unused@*/ const char *uri)
|
|
/*@*/
|
|
{
|
|
struct fetch_resource_s * res = ne_calloc(sizeof(*res));
|
|
return res;
|
|
}
|
|
|
|
/* =============================================================== */
|
|
struct fetch_context_s {
|
|
/*@relnull@*/
|
|
struct fetch_resource_s **resrock;
|
|
const char *uri;
|
|
unsigned int include_target; /* Include resource at href */
|
|
/*@refcounted@*/
|
|
urlinfo u;
|
|
int ac;
|
|
int nalloced;
|
|
ARGV_t av;
|
|
mode_t * modes;
|
|
size_t * sizes;
|
|
time_t * mtimes;
|
|
};
|
|
|
|
/*@null@*/
|
|
static void *fetch_destroy_context(/*@only@*/ /*@null@*/ struct fetch_context_s *ctx)
|
|
/*@globals internalState @*/
|
|
/*@modifies ctx, internalState @*/
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;
|
|
if (ctx->av != NULL)
|
|
ctx->av = argvFree(ctx->av);
|
|
ctx->modes = _free(ctx->modes);
|
|
ctx->sizes = _free(ctx->sizes);
|
|
ctx->mtimes = _free(ctx->mtimes);
|
|
ctx->u = urlFree(ctx->u, __FUNCTION__);
|
|
ctx->uri = _free(ctx->uri);
|
|
/*@-boundswrite@*/
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
/*@=boundswrite@*/
|
|
ctx = _free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
/*@null@*/
|
|
static void *fetch_create_context(const char *uri)
|
|
/*@globals internalState @*/
|
|
/*@modifies internalState @*/
|
|
{
|
|
struct fetch_context_s * ctx;
|
|
urlinfo u;
|
|
|
|
/*@-globs@*/ /* FIX: h_errno annoyance. */
|
|
if (urlSplit(uri, &u))
|
|
return NULL;
|
|
/*@=globs@*/
|
|
|
|
ctx = ne_calloc(sizeof(*ctx));
|
|
ctx->uri = xstrdup(uri);
|
|
ctx->u = urlLink(u, __FUNCTION__);
|
|
return ctx;
|
|
}
|
|
|
|
/*@unchecked@*/ /*@observer@*/
|
|
static const ne_propname fetch_props[] = {
|
|
{ "DAV:", "getcontentlength" },
|
|
{ "DAV:", "getlastmodified" },
|
|
{ "http://apache.org/dav/props/", "executable" },
|
|
{ "DAV:", "resourcetype" },
|
|
{ "DAV:", "checked-in" },
|
|
{ "DAV:", "checked-out" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
#define ELM_resourcetype (NE_PROPS_STATE_TOP + 1)
|
|
#define ELM_collection (NE_PROPS_STATE_TOP + 2)
|
|
|
|
/*@unchecked@*/ /*@observer@*/
|
|
static const struct ne_xml_idmap fetch_idmap[] = {
|
|
{ "DAV:", "resourcetype", ELM_resourcetype },
|
|
{ "DAV:", "collection", ELM_collection }
|
|
};
|
|
|
|
static int fetch_startelm(void *userdata, int parent,
|
|
const char *nspace, const char *name,
|
|
/*@unused@*/ const char **atts)
|
|
/*@*/
|
|
{
|
|
ne_propfind_handler *pfh = userdata;
|
|
struct fetch_resource_s *r = ne_propfind_current_private(pfh);
|
|
int state = ne_xml_mapid(fetch_idmap, NE_XML_MAPLEN(fetch_idmap),
|
|
nspace, name);
|
|
|
|
if (r == NULL ||
|
|
!((parent == NE_207_STATE_PROP && state == ELM_resourcetype) ||
|
|
(parent == ELM_resourcetype && state == ELM_collection)))
|
|
return NE_XML_DECLINE;
|
|
|
|
if (state == ELM_collection) {
|
|
r->type = resr_collection;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
static int fetch_compare(const struct fetch_resource_s *r1,
|
|
const struct fetch_resource_s *r2)
|
|
/*@*/
|
|
{
|
|
/* Sort errors first, then collections, then alphabetically */
|
|
if (r1->type == resr_error) {
|
|
return -1;
|
|
} else if (r2->type == resr_error) {
|
|
return 1;
|
|
} else if (r1->type == resr_collection) {
|
|
if (r2->type != resr_collection) {
|
|
return -1;
|
|
} else {
|
|
return strcmp(r1->uri, r2->uri);
|
|
}
|
|
} else {
|
|
if (r2->type != resr_collection) {
|
|
return strcmp(r1->uri, r2->uri);
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fetch_results(void *userdata, const char *uri,
|
|
const ne_prop_result_set *set)
|
|
/*@*/
|
|
{
|
|
struct fetch_context_s *ctx = userdata;
|
|
struct fetch_resource_s *current, *previous, *newres;
|
|
const char *clength, *modtime, *isexec;
|
|
const char *checkin, *checkout;
|
|
const ne_status *status = NULL;
|
|
const char * path = NULL;
|
|
|
|
(void) urlPath(uri, &path);
|
|
if (path == NULL)
|
|
return;
|
|
|
|
newres = ne_propset_private(set);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "==> %s in uri %s\n", path, ctx->uri);
|
|
|
|
if (ne_path_compare(ctx->uri, path) == 0 && !ctx->include_target) {
|
|
/* This is the target URI */
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "==> %s skipping target resource.\n", path);
|
|
/* Free the private structure. */
|
|
free(newres);
|
|
return;
|
|
}
|
|
|
|
newres->uri = ne_strdup(path);
|
|
|
|
/*@-boundsread@*/
|
|
clength = ne_propset_value(set, &fetch_props[0]);
|
|
modtime = ne_propset_value(set, &fetch_props[1]);
|
|
isexec = ne_propset_value(set, &fetch_props[2]);
|
|
checkin = ne_propset_value(set, &fetch_props[4]);
|
|
checkout = ne_propset_value(set, &fetch_props[5]);
|
|
/*@=boundsread@*/
|
|
|
|
/*@-branchstate@*/
|
|
if (clength == NULL)
|
|
status = ne_propset_status(set, &fetch_props[0]);
|
|
if (modtime == NULL)
|
|
status = ne_propset_status(set, &fetch_props[1]);
|
|
/*@=branchstate@*/
|
|
|
|
if (newres->type == resr_normal && status != NULL) {
|
|
/* It's an error! */
|
|
newres->error_status = status->code;
|
|
|
|
/* Special hack for Apache 1.3/mod_dav */
|
|
if (strcmp(status->reason_phrase, "status text goes here") == 0) {
|
|
const char *desc;
|
|
if (status->code == 401) {
|
|
desc = _("Authorization Required");
|
|
} else if (status->klass == 3) {
|
|
desc = _("Redirect");
|
|
} else if (status->klass == 5) {
|
|
desc = _("Server Error");
|
|
} else {
|
|
desc = _("Unknown Error");
|
|
}
|
|
newres->error_reason = ne_strdup(desc);
|
|
} else {
|
|
newres->error_reason = ne_strdup(status->reason_phrase);
|
|
}
|
|
newres->type = resr_error;
|
|
}
|
|
|
|
if (isexec && strcasecmp(isexec, "T") == 0) {
|
|
newres->is_executable = 1;
|
|
} else {
|
|
newres->is_executable = 0;
|
|
}
|
|
|
|
if (modtime)
|
|
newres->modtime = ne_httpdate_parse(modtime);
|
|
|
|
if (clength)
|
|
newres->size = atoi(clength);
|
|
|
|
/* is vcr */
|
|
if (checkin) {
|
|
newres->is_vcr = 1;
|
|
} else if (checkout) {
|
|
newres->is_vcr = 2;
|
|
} else {
|
|
newres->is_vcr = 0;
|
|
}
|
|
|
|
for (current = *ctx->resrock, previous = NULL; current != NULL;
|
|
previous = current, current = current->next)
|
|
{
|
|
if (fetch_compare(current, newres) >= 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (previous) {
|
|
previous->next = newres;
|
|
} else {
|
|
/*@-boundswrite@*/
|
|
*ctx->resrock = newres;
|
|
/*@=boundswrite@*/
|
|
}
|
|
newres->next = current;
|
|
}
|
|
|
|
static int davFetch(const urlinfo u, struct fetch_context_s * ctx)
|
|
/*@globals internalState @*/
|
|
/*@modifies ctx, internalState @*/
|
|
{
|
|
const char * path = NULL;
|
|
int depth = 1; /* XXX passed arg? */
|
|
unsigned int include_target = 0; /* XXX passed arg? */
|
|
struct fetch_resource_s * resitem = NULL;
|
|
struct fetch_resource_s ** resrock = &resitem; /* XXX passed arg? */
|
|
ne_propfind_handler *pfh;
|
|
struct fetch_resource_s *current, *next;
|
|
mode_t st_mode;
|
|
int rc = 0;
|
|
int xx;
|
|
|
|
(void) urlPath(u->url, &path);
|
|
pfh = ne_propfind_create(u->sess, ctx->uri, depth);
|
|
|
|
/* HACK: need to set u->httpHasRange here. */
|
|
|
|
ctx->resrock = resrock;
|
|
ctx->include_target = include_target;
|
|
|
|
ne_xml_push_handler(ne_propfind_get_parser(pfh),
|
|
fetch_startelm, NULL, NULL, pfh);
|
|
|
|
ne_propfind_set_private(pfh, fetch_create_item, NULL);
|
|
|
|
rc = ne_propfind_named(pfh, fetch_props, fetch_results, ctx);
|
|
|
|
ne_propfind_destroy(pfh);
|
|
|
|
for (current = resitem; current != NULL; current = next) {
|
|
const char *s, *se;
|
|
char * val;
|
|
|
|
next = current->next;
|
|
|
|
/* Collections have trailing '/' that needs trim. */
|
|
/* The top level collection is returned as well. */
|
|
se = current->uri + strlen(current->uri);
|
|
if (se[-1] == '/') {
|
|
if (strlen(current->uri) <= strlen(path)) {
|
|
current = fetch_destroy_item(current);
|
|
continue;
|
|
}
|
|
se--;
|
|
}
|
|
s = se;
|
|
while (s > current->uri && s[-1] != '/')
|
|
s--;
|
|
|
|
val = ne_strndup(s, (se - s));
|
|
|
|
/*@-nullpass@*/
|
|
val = ne_path_unescape(val);
|
|
/*@=nullpass@*/
|
|
|
|
xx = argvAdd(&ctx->av, val);
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** argvAdd(%p,\"%s\")\n", &ctx->av, val);
|
|
NE_FREE(val);
|
|
|
|
while (ctx->ac >= ctx->nalloced) {
|
|
if (ctx->nalloced <= 0)
|
|
ctx->nalloced = 1;
|
|
ctx->nalloced *= 2;
|
|
ctx->modes = xrealloc(ctx->modes,
|
|
(sizeof(*ctx->modes) * ctx->nalloced));
|
|
ctx->sizes = xrealloc(ctx->sizes,
|
|
(sizeof(*ctx->sizes) * ctx->nalloced));
|
|
ctx->mtimes = xrealloc(ctx->mtimes,
|
|
(sizeof(*ctx->mtimes) * ctx->nalloced));
|
|
}
|
|
|
|
switch (current->type) {
|
|
case resr_normal:
|
|
st_mode = S_IFREG;
|
|
/*@switchbreak@*/ break;
|
|
case resr_collection:
|
|
st_mode = S_IFDIR;
|
|
/*@switchbreak@*/ break;
|
|
case resr_reference:
|
|
case resr_error:
|
|
default:
|
|
st_mode = 0;
|
|
/*@switchbreak@*/ break;
|
|
}
|
|
/*@-boundswrite@*/
|
|
ctx->modes[ctx->ac] = st_mode;
|
|
ctx->sizes[ctx->ac] = current->size;
|
|
ctx->mtimes[ctx->ac] = current->modtime;
|
|
/*@=boundswrite@*/
|
|
ctx->ac++;
|
|
|
|
current = fetch_destroy_item(current);
|
|
}
|
|
ctx->resrock = NULL; /* HACK: avoid leaving stack reference. */
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int davNLST(struct fetch_context_s * ctx)
|
|
/*@globals internalState @*/
|
|
/*@modifies ctx, internalState @*/
|
|
{
|
|
urlinfo u = NULL;
|
|
int rc;
|
|
int xx;
|
|
|
|
rc = davInit(ctx->uri, &u);
|
|
if (rc || u == NULL)
|
|
goto exit;
|
|
|
|
rc = davFetch(u, ctx);
|
|
switch (rc) {
|
|
case NE_OK:
|
|
break;
|
|
case NE_ERROR:
|
|
/* HACK: "301 Moved Permanently" on empty subdir. */
|
|
if (!strncmp("301 ", ne_get_error(u->sess), sizeof("301 ")-1))
|
|
break;
|
|
/*@fallthrough@*/
|
|
default:
|
|
if (_dav_debug)
|
|
fprintf(stderr, "*** Fetch from %s:%d failed:\n\t%s\n",
|
|
u->host, u->port, ne_get_error(u->sess));
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
if (rc)
|
|
xx = davFree(u);
|
|
return rc;
|
|
}
|
|
|
|
/* =============================================================== */
|
|
static int my_result(const char * msg, int ret, /*@null@*/ FILE * fp)
|
|
/*@modifies *fp @*/
|
|
{
|
|
/* HACK: don't print unless debugging. */
|
|
if (_dav_debug >= 0)
|
|
return ret;
|
|
if (fp == NULL)
|
|
fp = stderr;
|
|
if (msg != NULL)
|
|
fprintf(fp, "*** %s: ", msg);
|
|
|
|
/* HACK FTPERR_NE_FOO == -NE_FOO error impedance match */
|
|
#ifdef HACK
|
|
fprintf(fp, "%s: %s\n", ftpStrerror(-ret), ne_get_error(sess));
|
|
#else
|
|
fprintf(fp, "%s\n", ftpStrerror(-ret));
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
#ifdef DYING
|
|
static void hexdump(const unsigned char * buf, ssize_t len)
|
|
/*@*/
|
|
{
|
|
int i;
|
|
if (len <= 0)
|
|
return;
|
|
for (i = 0; i < len; i++) {
|
|
if (i != 0 && (i%16) == 0)
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, " %02X", buf[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
#endif
|
|
|
|
static void davAcceptRanges(void * userdata, /*@null@*/ const char * value)
|
|
/*@modifies userdata @*/
|
|
{
|
|
urlinfo u = userdata;
|
|
|
|
if (!(u && value)) return;
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** u %p Accept-Ranges: %s\n", u, value);
|
|
if (!strcmp(value, "bytes"))
|
|
u->httpHasRange = 1;
|
|
if (!strcmp(value, "none"))
|
|
u->httpHasRange = 0;
|
|
}
|
|
|
|
#if !defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
|
|
static void davAllHeaders(void * userdata, const char * value)
|
|
{
|
|
FD_t ctrl = userdata;
|
|
|
|
if (!(ctrl && value)) return;
|
|
if (_dav_debug)
|
|
fprintf(stderr, "<- %s\n", value);
|
|
}
|
|
#endif
|
|
|
|
static void davContentLength(void * userdata, /*@null@*/ const char * value)
|
|
/*@modifies userdata @*/
|
|
{
|
|
FD_t ctrl = userdata;
|
|
|
|
if (!(ctrl && value)) return;
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** fd %p Content-Length: %s\n", ctrl, value);
|
|
/*@-unrecog@*/
|
|
ctrl->contentLength = strtoll(value, NULL, 10);
|
|
/*@=unrecog@*/
|
|
}
|
|
|
|
static void davConnection(void * userdata, /*@null@*/ const char * value)
|
|
/*@modifies userdata @*/
|
|
{
|
|
FD_t ctrl = userdata;
|
|
|
|
if (!(ctrl && value)) return;
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** fd %p Connection: %s\n", ctrl, value);
|
|
if (!strcasecmp(value, "close"))
|
|
ctrl->persist = 0;
|
|
else if (!strcasecmp(value, "Keep-Alive"))
|
|
ctrl->persist = 1;
|
|
}
|
|
|
|
/*@-mustmod@*/ /* HACK: stash error in *str. */
|
|
int davResp(urlinfo u, FD_t ctrl, /*@unused@*/ char *const * str)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = ne_begin_request(ctrl->req);
|
|
rc = my_result("ne_begin_req(ctrl->req)", rc, NULL);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davResp(%p,%p,%p) sess %p req %p rc %d\n", u, ctrl, str, u->sess, ctrl->req, rc);
|
|
|
|
/* HACK FTPERR_NE_FOO == -NE_FOO error impedance match */
|
|
/*@-observertrans@*/
|
|
if (rc)
|
|
fdSetSyserrno(ctrl, errno, ftpStrerror(-rc));
|
|
/*@=observertrans@*/
|
|
|
|
return rc;
|
|
}
|
|
/*@=mustmod@*/
|
|
|
|
int davReq(FD_t ctrl, const char * httpCmd, const char * httpArg)
|
|
{
|
|
urlinfo u;
|
|
int rc = 0;
|
|
|
|
assert(ctrl != NULL);
|
|
u = ctrl->url;
|
|
URLSANE(u);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davReq(%p,%s,\"%s\") entry sess %p req %p\n", ctrl, httpCmd, (httpArg ? httpArg : ""), u->sess, ctrl->req);
|
|
|
|
ctrl->persist = (u->httpVersion > 0 ? 1 : 0);
|
|
ctrl = fdLink(ctrl, "open ctrl (davReq)");
|
|
|
|
assert(u->sess != NULL);
|
|
assert(ctrl->req == NULL);
|
|
/*@-nullpass@*/
|
|
ctrl->req = ne_request_create(u->sess, httpCmd, httpArg);
|
|
/*@=nullpass@*/
|
|
assert(ctrl->req != NULL);
|
|
|
|
ne_set_request_private(ctrl->req, "fd", ctrl);
|
|
|
|
#if !defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
|
|
ne_add_response_header_catcher(ctrl->req, davAllHeaders, ctrl);
|
|
|
|
ne_add_response_header_handler(ctrl->req, "Content-Length",
|
|
davContentLength, ctrl);
|
|
ne_add_response_header_handler(ctrl->req, "Connection",
|
|
davConnection, ctrl);
|
|
#endif
|
|
|
|
if (!strcmp(httpCmd, "PUT")) {
|
|
#ifdef NOTYET /* XXX HACK no wr_chunked until libneon supports */
|
|
ctrl->wr_chunked = 1;
|
|
ne_add_request_header(ctrl->req, "Transfer-Encoding", "chunked");
|
|
ne_set_request_chunked(ctrl->req, 1);
|
|
/* HACK: no retries if/when chunking. */
|
|
rc = davResp(u, ctrl, NULL);
|
|
#else
|
|
rc = FTPERR_SERVER_IO_ERROR;
|
|
#endif
|
|
} else {
|
|
/* HACK: possible Last-Modified: Tue, 02 Nov 2004 14:29:36 GMT */
|
|
/* HACK: possible ETag: "inode-size-mtime" */
|
|
#if !defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
|
|
ne_add_response_header_handler(ctrl->req, "Accept-Ranges",
|
|
davAcceptRanges, u);
|
|
#endif
|
|
/* HACK: possible Transfer-Encoding: on GET. */
|
|
|
|
/* HACK: other errors may need retry too. */
|
|
/* HACK: neon retries once, gud enuf. */
|
|
/* HACK: retry counter? */
|
|
do {
|
|
rc = davResp(u, ctrl, NULL);
|
|
} while (rc == NE_RETRY);
|
|
}
|
|
if (rc)
|
|
goto errxit;
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davReq(%p,%s,\"%s\") exit sess %p req %p rc %d\n", ctrl, httpCmd, (httpArg ? httpArg : ""), u->sess, ctrl->req, rc);
|
|
|
|
#if defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
|
|
davContentLength(ctrl,
|
|
ne_get_response_header(ctrl->req, "Content-Length"));
|
|
davConnection(ctrl,
|
|
ne_get_response_header(ctrl->req, "Connection"));
|
|
if (strcmp(httpCmd, "PUT"))
|
|
davAcceptRanges(u,
|
|
ne_get_response_header(ctrl->req, "Accept-Ranges"));
|
|
#endif
|
|
|
|
ctrl = fdLink(ctrl, "open data (davReq)");
|
|
return 0;
|
|
|
|
errxit:
|
|
/*@-observertrans@*/
|
|
fdSetSyserrno(ctrl, errno, ftpStrerror(rc));
|
|
/*@=observertrans@*/
|
|
|
|
/* HACK balance fd refs. ne_session_destroy to tear down non-keepalive? */
|
|
ctrl = fdLink(ctrl, "error data (davReq)");
|
|
|
|
return rc;
|
|
}
|
|
|
|
FD_t davOpen(const char * url, /*@unused@*/ int flags,
|
|
/*@unused@*/ mode_t mode, /*@out@*/ urlinfo * uret)
|
|
{
|
|
const char * path = NULL;
|
|
urltype urlType = urlPath(url, &path);
|
|
urlinfo u = NULL;
|
|
FD_t fd = NULL;
|
|
int rc;
|
|
|
|
#if 0 /* XXX makeTempFile() heartburn */
|
|
assert(!(flags & O_RDWR));
|
|
#endif
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davOpen(%s,0x%x,0%o,%p)\n", url, flags, mode, uret);
|
|
rc = davInit(url, &u);
|
|
if (rc || u == NULL || u->sess == NULL)
|
|
goto exit;
|
|
|
|
if (u->ctrl == NULL)
|
|
u->ctrl = fdNew("persist ctrl (davOpen)");
|
|
if (u->ctrl->nrefs > 2 && u->data == NULL)
|
|
u->data = fdNew("persist data (davOpen)");
|
|
|
|
if (u->ctrl->url == NULL)
|
|
fd = fdLink(u->ctrl, "grab ctrl (davOpen persist ctrl)");
|
|
else if (u->data->url == NULL)
|
|
fd = fdLink(u->data, "grab ctrl (davOpen persist data)");
|
|
else
|
|
fd = fdNew("grab ctrl (davOpen)");
|
|
|
|
if (fd) {
|
|
fdSetIo(fd, ufdio);
|
|
fd->ftpFileDoneNeeded = 0;
|
|
fd->rd_timeoutsecs = httpTimeoutSecs;
|
|
fd->contentLength = fd->bytesRemain = -1;
|
|
fd->url = urlLink(u, "url (davOpen)");
|
|
fd = fdLink(fd, "grab data (davOpen)");
|
|
assert(urlType == URL_IS_HTTPS || urlType == URL_IS_HTTP || urlType == URL_IS_HKP);
|
|
fd->urlType = urlType;
|
|
}
|
|
|
|
exit:
|
|
/*@-boundswrite@*/
|
|
if (uret)
|
|
*uret = u;
|
|
/*@=boundswrite@*/
|
|
/*@-refcounttrans@*/
|
|
return fd;
|
|
/*@=refcounttrans@*/
|
|
}
|
|
|
|
ssize_t davRead(void * cookie, /*@out@*/ char * buf, size_t count)
|
|
{
|
|
FD_t fd = cookie;
|
|
ssize_t rc;
|
|
|
|
#if 0
|
|
assert(count >= 128); /* HACK: see ne_request.h comment */
|
|
#endif
|
|
rc = ne_read_response_block(fd->req, buf, count);
|
|
|
|
if (_dav_debug < 0) {
|
|
fprintf(stderr, "*** davRead(%p,%p,0x%x) rc 0x%x\n", cookie, buf, count, (unsigned)rc);
|
|
#ifdef DYING
|
|
hexdump(buf, rc);
|
|
#endif
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
ssize_t davWrite(void * cookie, const char * buf, size_t count)
|
|
{
|
|
#ifdef NOTYET /* XXX HACK no wr_chunked until libneon supports */
|
|
FD_t fd = cookie;
|
|
ssize_t rc;
|
|
int xx;
|
|
|
|
assert(fd->req != NULL);
|
|
xx = ne_send_request_chunk(fd->req, buf, count);
|
|
|
|
/* HACK: stupid error impedence matching. */
|
|
rc = (xx == 0 ? count : -1);
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davWrite(%p,%p,0x%x) rc 0x%x\n", cookie, buf, count, rc);
|
|
#ifdef DYING
|
|
if (count > 0)
|
|
hexdump(buf, count);
|
|
#endif
|
|
|
|
return rc;
|
|
#else
|
|
errno = EIO; /* HACK */
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int davSeek(void * cookie, /*@unused@*/ _libio_pos_t pos, int whence)
|
|
{
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davSeek(%p,pos,%d)\n", cookie, whence);
|
|
return -1;
|
|
}
|
|
|
|
/*@-mustmod@*/ /* HACK: fd->req is modified. */
|
|
int davClose(void * cookie)
|
|
{
|
|
/*@-onlytrans@*/
|
|
FD_t fd = cookie;
|
|
/*@=onlytrans@*/
|
|
int rc;
|
|
|
|
assert(fd->req != NULL);
|
|
rc = ne_end_request(fd->req);
|
|
rc = my_result("ne_end_request(req)", rc, NULL);
|
|
|
|
ne_request_destroy(fd->req);
|
|
fd->req = NULL;
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davClose(%p) rc %d\n", fd, rc);
|
|
return rc;
|
|
}
|
|
/*@=mustmod@*/
|
|
|
|
/* =============================================================== */
|
|
int davMkdir(const char * path, mode_t mode)
|
|
{
|
|
urlinfo u = NULL;
|
|
const char * src = NULL;
|
|
int rc;
|
|
|
|
rc = davInit(path, &u);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
(void) urlPath(path, &src);
|
|
|
|
rc = ne_mkcol(u->sess, path);
|
|
|
|
if (rc) rc = -1; /* XXX HACK: errno impedance match */
|
|
|
|
/* XXX HACK: verify getrestype(remote) == resr_collection */
|
|
|
|
exit:
|
|
if (_dav_debug)
|
|
fprintf(stderr, "*** davMkdir(%s,0%o) rc %d\n", path, mode, rc);
|
|
return rc;
|
|
}
|
|
|
|
int davRmdir(const char * path)
|
|
{
|
|
urlinfo u = NULL;
|
|
const char * src = NULL;
|
|
int rc;
|
|
|
|
rc = davInit(path, &u);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
(void) urlPath(path, &src);
|
|
|
|
/* XXX HACK: only getrestype(remote) == resr_collection */
|
|
|
|
rc = ne_delete(u->sess, path);
|
|
|
|
if (rc) rc = -1; /* XXX HACK: errno impedance match */
|
|
|
|
exit:
|
|
if (_dav_debug)
|
|
fprintf(stderr, "*** davRmdir(%s) rc %d\n", path, rc);
|
|
return rc;
|
|
}
|
|
|
|
int davRename(const char * oldpath, const char * newpath)
|
|
{
|
|
urlinfo u = NULL;
|
|
const char * src = NULL;
|
|
const char * dst = NULL;
|
|
int overwrite = 1; /* HACK: set this correctly. */
|
|
int rc;
|
|
|
|
rc = davInit(oldpath, &u);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
(void) urlPath(oldpath, &src);
|
|
(void) urlPath(newpath, &dst);
|
|
|
|
/* XXX HACK: only getrestype(remote) != resr_collection */
|
|
|
|
rc = ne_move(u->sess, overwrite, src, dst);
|
|
|
|
if (rc) rc = -1; /* XXX HACK: errno impedance match */
|
|
|
|
exit:
|
|
if (_dav_debug)
|
|
fprintf(stderr, "*** davRename(%s,%s) rc %d\n", oldpath, newpath, rc);
|
|
return rc;
|
|
}
|
|
|
|
int davUnlink(const char * path)
|
|
{
|
|
urlinfo u = NULL;
|
|
const char * src = NULL;
|
|
int rc;
|
|
|
|
rc = davInit(path, &u);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
(void) urlPath(path, &src);
|
|
|
|
/* XXX HACK: only getrestype(remote) != resr_collection */
|
|
|
|
rc = ne_delete(u->sess, src);
|
|
|
|
exit:
|
|
if (rc) rc = -1; /* XXX HACK: errno impedance match */
|
|
|
|
if (_dav_debug)
|
|
fprintf(stderr, "*** davUnlink(%s) rc %d\n", path, rc);
|
|
return rc;
|
|
}
|
|
|
|
#ifdef NOTYET
|
|
static int davChdir(const char * path)
|
|
/*@globals h_errno, fileSystem, internalState @*/
|
|
/*@modifies fileSystem, internalState @*/
|
|
{
|
|
return davCommand("CWD", path, NULL);
|
|
}
|
|
#endif /* NOTYET */
|
|
|
|
/* =============================================================== */
|
|
|
|
static const char * statstr(const struct stat * st,
|
|
/*@returned@*/ /*@out@*/ char * buf)
|
|
/*@modifies *buf @*/
|
|
{
|
|
sprintf(buf,
|
|
"*** dev %x ino %x mode %0o nlink %d uid %d gid %d rdev %x size %x\n",
|
|
(unsigned)st->st_dev,
|
|
(unsigned)st->st_ino,
|
|
st->st_mode,
|
|
(unsigned)st->st_nlink,
|
|
st->st_uid,
|
|
st->st_gid,
|
|
(unsigned)st->st_rdev,
|
|
(unsigned)st->st_size);
|
|
return buf;
|
|
}
|
|
|
|
/*@unchecked@*/
|
|
static int dav_st_ino = 0xdead0000;
|
|
|
|
/*@-boundswrite@*/
|
|
int davStat(const char * path, /*@out@*/ struct stat *st)
|
|
/*@globals dav_st_ino, fileSystem, internalState @*/
|
|
/*@modifies *st, dav_st_ino, fileSystem, internalState @*/
|
|
{
|
|
struct fetch_context_s * ctx = NULL;
|
|
char buf[1024];
|
|
int rc = -1;
|
|
|
|
/* HACK: neon really wants collections with trailing '/' */
|
|
ctx = fetch_create_context(path);
|
|
if (ctx == NULL) {
|
|
/* HACK: errno = ??? */
|
|
goto exit;
|
|
}
|
|
rc = davNLST(ctx);
|
|
if (rc) {
|
|
/* HACK: errno = ??? */
|
|
goto exit;
|
|
}
|
|
|
|
memset(st, 0, sizeof(*st));
|
|
st->st_mode = ctx->modes[0];
|
|
st->st_size = ctx->sizes[0];
|
|
st->st_mtime = ctx->mtimes[0];
|
|
if (S_ISDIR(st->st_mode)) {
|
|
st->st_nlink = 2;
|
|
st->st_mode |= 0755;
|
|
} else
|
|
if (S_ISREG(st->st_mode)) {
|
|
st->st_nlink = 1;
|
|
st->st_mode |= 0644;
|
|
}
|
|
|
|
/* XXX fts(3) needs/uses st_ino, make something up for now. */
|
|
if (st->st_ino == 0)
|
|
st->st_ino = dav_st_ino++;
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davStat(%s) rc %d\n%s", path, rc, statstr(st, buf));
|
|
exit:
|
|
ctx = fetch_destroy_context(ctx);
|
|
return rc;
|
|
}
|
|
/*@=boundswrite@*/
|
|
|
|
/*@-boundswrite@*/
|
|
int davLstat(const char * path, /*@out@*/ struct stat *st)
|
|
/*@globals dav_st_ino, fileSystem, internalState @*/
|
|
/*@modifies *st, dav_st_ino, fileSystem, internalState @*/
|
|
{
|
|
struct fetch_context_s * ctx = NULL;
|
|
char buf[1024];
|
|
int rc = -1;
|
|
|
|
/* HACK: neon really wants collections with trailing '/' */
|
|
ctx = fetch_create_context(path);
|
|
if (ctx == NULL) {
|
|
/* HACK: errno = ??? */
|
|
goto exit;
|
|
}
|
|
rc = davNLST(ctx);
|
|
if (rc) {
|
|
/* HACK: errno = ??? */
|
|
goto exit;
|
|
}
|
|
|
|
memset(st, 0, sizeof(*st));
|
|
st->st_mode = ctx->modes[0];
|
|
st->st_size = ctx->sizes[0];
|
|
st->st_mtime = ctx->mtimes[0];
|
|
if (S_ISDIR(st->st_mode)) {
|
|
st->st_nlink = 2;
|
|
st->st_mode |= 0755;
|
|
} else
|
|
if (S_ISREG(st->st_mode)) {
|
|
st->st_nlink = 1;
|
|
st->st_mode |= 0644;
|
|
}
|
|
|
|
/* XXX fts(3) needs/uses st_ino, make something up for now. */
|
|
if (st->st_ino == 0)
|
|
st->st_ino = dav_st_ino++;
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davLstat(%s) rc %d\n%s\n", path, rc, statstr(st, buf));
|
|
exit:
|
|
ctx = fetch_destroy_context(ctx);
|
|
return rc;
|
|
}
|
|
/*@=boundswrite@*/
|
|
|
|
#ifdef NOTYET
|
|
static int davReadlink(const char * path, /*@out@*/ char * buf, size_t bufsiz)
|
|
/*@globals h_errno, fileSystem, internalState @*/
|
|
/*@modifies *buf, fileSystem, internalState @*/
|
|
{
|
|
int rc;
|
|
rc = davNLST(path, DO_FTP_READLINK, NULL, buf, bufsiz);
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davReadlink(%s) rc %d\n", path, rc);
|
|
return rc;
|
|
}
|
|
#endif /* NOTYET */
|
|
|
|
/* =============================================================== */
|
|
/*@unchecked@*/
|
|
int avmagicdir = 0x3607113;
|
|
|
|
int avClosedir(/*@only@*/ DIR * dir)
|
|
{
|
|
AVDIR avdir = (AVDIR)dir;
|
|
|
|
if (_av_debug)
|
|
fprintf(stderr, "*** avClosedir(%p)\n", avdir);
|
|
|
|
#if defined(HAVE_PTHREAD_H)
|
|
/*@-moduncon -noeffectuncon @*/
|
|
(void) pthread_mutex_destroy(&avdir->lock);
|
|
/*@=moduncon =noeffectuncon @*/
|
|
#endif
|
|
|
|
avdir = _free(avdir);
|
|
return 0;
|
|
}
|
|
|
|
struct dirent * avReaddir(DIR * dir)
|
|
{
|
|
AVDIR avdir = (AVDIR)dir;
|
|
struct dirent * dp;
|
|
const char ** av;
|
|
unsigned char * dt;
|
|
int ac;
|
|
int i;
|
|
|
|
if (avdir == NULL || !ISAVMAGIC(avdir) || avdir->data == NULL) {
|
|
/* XXX TODO: EBADF errno. */
|
|
return NULL;
|
|
}
|
|
|
|
dp = (struct dirent *) avdir->data;
|
|
av = (const char **) (dp + 1);
|
|
ac = avdir->size;
|
|
dt = (char *) (av + (ac + 1));
|
|
i = avdir->offset + 1;
|
|
|
|
/*@-boundsread@*/
|
|
if (i < 0 || i >= ac || av[i] == NULL)
|
|
return NULL;
|
|
/*@=boundsread@*/
|
|
|
|
avdir->offset = i;
|
|
|
|
/* XXX glob(3) uses REAL_DIR_ENTRY(dp) test on d_ino */
|
|
/*@-type@*/
|
|
dp->d_ino = i + 1; /* W2DO? */
|
|
dp->d_reclen = 0; /* W2DO? */
|
|
|
|
#if !defined(hpux) && !defined(sun)
|
|
#if !defined(__APPLE__)
|
|
dp->d_off = 0; /* W2DO? */
|
|
#endif
|
|
/*@-boundsread@*/
|
|
dp->d_type = dt[i];
|
|
/*@=boundsread@*/
|
|
#endif
|
|
/*@=type@*/
|
|
|
|
strncpy(dp->d_name, av[i], sizeof(dp->d_name));
|
|
if (_av_debug)
|
|
fprintf(stderr, "*** avReaddir(%p) %p \"%s\"\n", (void *)avdir, dp, dp->d_name);
|
|
|
|
return dp;
|
|
}
|
|
|
|
/*@-boundswrite@*/
|
|
DIR * avOpendir(const char * path)
|
|
{
|
|
AVDIR avdir;
|
|
struct dirent * dp;
|
|
size_t nb = 0;
|
|
const char ** av;
|
|
unsigned char * dt;
|
|
char * t;
|
|
int ac;
|
|
|
|
if (_av_debug)
|
|
fprintf(stderr, "*** avOpendir(%s)\n", path);
|
|
nb = sizeof(".") + sizeof("..");
|
|
ac = 2;
|
|
|
|
nb += sizeof(*avdir) + sizeof(*dp) + ((ac + 1) * sizeof(*av)) + (ac + 1);
|
|
avdir = xcalloc(1, nb);
|
|
/*@-abstract@*/
|
|
dp = (struct dirent *) (avdir + 1);
|
|
av = (const char **) (dp + 1);
|
|
dt = (char *) (av + (ac + 1));
|
|
t = (char *) (dt + ac + 1);
|
|
/*@=abstract@*/
|
|
|
|
avdir->fd = avmagicdir;
|
|
/*@-usereleased@*/
|
|
avdir->data = (char *) dp;
|
|
/*@=usereleased@*/
|
|
avdir->allocation = nb;
|
|
avdir->size = ac;
|
|
avdir->offset = -1;
|
|
avdir->filepos = 0;
|
|
|
|
#if defined(HAVE_PTHREAD_H)
|
|
/*@-moduncon -noeffectuncon -nullpass @*/
|
|
(void) pthread_mutex_init(&avdir->lock, NULL);
|
|
/*@=moduncon =noeffectuncon =nullpass @*/
|
|
#endif
|
|
|
|
ac = 0;
|
|
/*@-dependenttrans -unrecog@*/
|
|
dt[ac] = DT_DIR; av[ac++] = t; t = stpcpy(t, "."); t++;
|
|
dt[ac] = DT_DIR; av[ac++] = t; t = stpcpy(t, ".."); t++;
|
|
/*@=dependenttrans =unrecog@*/
|
|
|
|
av[ac] = NULL;
|
|
|
|
/*@-kepttrans@*/
|
|
return (DIR *) avdir;
|
|
/*@=kepttrans@*/
|
|
}
|
|
/*@=boundswrite@*/
|
|
|
|
/* =============================================================== */
|
|
/*@unchecked@*/
|
|
int davmagicdir = 0x8440291;
|
|
|
|
int davClosedir(/*@only@*/ DIR * dir)
|
|
{
|
|
DAVDIR avdir = (DAVDIR)dir;
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davClosedir(%p)\n", avdir);
|
|
|
|
#if defined(HAVE_PTHREAD_H)
|
|
/*@-moduncon -noeffectuncon @*/
|
|
(void) pthread_mutex_destroy(&avdir->lock);
|
|
/*@=moduncon =noeffectuncon @*/
|
|
#endif
|
|
|
|
avdir = _free(avdir);
|
|
return 0;
|
|
}
|
|
|
|
struct dirent * davReaddir(DIR * dir)
|
|
{
|
|
DAVDIR avdir = (DAVDIR)dir;
|
|
struct dirent * dp;
|
|
const char ** av;
|
|
unsigned char * dt;
|
|
int ac;
|
|
int i;
|
|
|
|
if (avdir == NULL || !ISDAVMAGIC(avdir) || avdir->data == NULL) {
|
|
/* XXX TODO: EBADF errno. */
|
|
return NULL;
|
|
}
|
|
|
|
dp = (struct dirent *) avdir->data;
|
|
av = (const char **) (dp + 1);
|
|
ac = avdir->size;
|
|
dt = (char *) (av + (ac + 1));
|
|
i = avdir->offset + 1;
|
|
|
|
/*@-boundsread@*/
|
|
if (i < 0 || i >= ac || av[i] == NULL)
|
|
return NULL;
|
|
/*@=boundsread@*/
|
|
|
|
avdir->offset = i;
|
|
|
|
/* XXX glob(3) uses REAL_DIR_ENTRY(dp) test on d_ino */
|
|
/*@-type@*/
|
|
dp->d_ino = i + 1; /* W2DO? */
|
|
dp->d_reclen = 0; /* W2DO? */
|
|
|
|
#if !defined(hpux) && !defined(sun)
|
|
#if !defined(__APPLE__)
|
|
dp->d_off = 0; /* W2DO? */
|
|
#endif
|
|
/*@-boundsread@*/
|
|
dp->d_type = dt[i];
|
|
/*@=boundsread@*/
|
|
#endif
|
|
/*@=type@*/
|
|
|
|
strncpy(dp->d_name, av[i], sizeof(dp->d_name));
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davReaddir(%p) %p \"%s\"\n", (void *)avdir, dp, dp->d_name);
|
|
|
|
return dp;
|
|
}
|
|
|
|
/*@-boundswrite@*/
|
|
DIR * davOpendir(const char * path)
|
|
{
|
|
struct fetch_context_s * ctx;
|
|
DAVDIR avdir;
|
|
struct dirent * dp;
|
|
size_t nb;
|
|
const char ** av, ** nav;
|
|
unsigned char * dt;
|
|
char * t;
|
|
int ac, nac;
|
|
int rc;
|
|
|
|
/* HACK: glob does not pass dirs with trailing '/' */
|
|
nb = strlen(path)+1;
|
|
/*@-branchstate@*/
|
|
if (path[nb-1] != '/') {
|
|
char * npath = alloca(nb+1);
|
|
*npath = '\0';
|
|
(void) stpcpy( stpcpy(npath, path), "/");
|
|
path = npath;
|
|
}
|
|
/*@=branchstate@*/
|
|
|
|
if (_dav_debug < 0)
|
|
fprintf(stderr, "*** davOpendir(%s)\n", path);
|
|
|
|
/* Load DAV collection into argv. */
|
|
ctx = fetch_create_context(path);
|
|
if (ctx == NULL) {
|
|
/* HACK: errno = ??? */
|
|
return NULL;
|
|
}
|
|
rc = davNLST(ctx);
|
|
if (rc) {
|
|
/* HACK: errno = ??? */
|
|
return NULL;
|
|
}
|
|
|
|
nb = 0;
|
|
ac = 0;
|
|
av = ctx->av;
|
|
if (av != NULL)
|
|
while (av[ac] != NULL)
|
|
nb += strlen(av[ac++]) + 1;
|
|
ac += 2; /* for "." and ".." */
|
|
nb += sizeof(".") + sizeof("..");
|
|
|
|
nb += sizeof(*avdir) + sizeof(*dp) + ((ac + 1) * sizeof(*av)) + (ac + 1);
|
|
avdir = xcalloc(1, nb);
|
|
/*@-abstract@*/
|
|
dp = (struct dirent *) (avdir + 1);
|
|
nav = (const char **) (dp + 1);
|
|
dt = (char *) (nav + (ac + 1));
|
|
t = (char *) (dt + ac + 1);
|
|
/*@=abstract@*/
|
|
|
|
avdir->fd = davmagicdir;
|
|
/*@-usereleased@*/
|
|
avdir->data = (char *) dp;
|
|
/*@=usereleased@*/
|
|
avdir->allocation = nb;
|
|
avdir->size = ac;
|
|
avdir->offset = -1;
|
|
avdir->filepos = 0;
|
|
|
|
#if defined(HAVE_PTHREAD_H)
|
|
/*@-moduncon -noeffectuncon -nullpass @*/
|
|
(void) pthread_mutex_init(&avdir->lock, NULL);
|
|
/*@=moduncon =noeffectuncon =nullpass @*/
|
|
#endif
|
|
|
|
nac = 0;
|
|
/*@-dependenttrans -unrecog@*/
|
|
dt[nac] = DT_DIR; nav[nac++] = t; t = stpcpy(t, "."); t++;
|
|
dt[nac] = DT_DIR; nav[nac++] = t; t = stpcpy(t, ".."); t++;
|
|
/*@=dependenttrans =unrecog@*/
|
|
|
|
/* Copy DAV items into DIR elments. */
|
|
ac = 0;
|
|
if (av != NULL)
|
|
while (av[ac] != NULL) {
|
|
nav[nac] = t;
|
|
dt[nac] = (S_ISDIR(ctx->modes[ac]) ? DT_DIR : DT_REG);
|
|
t = stpcpy(t, av[ac]);
|
|
ac++;
|
|
t++;
|
|
nac++;
|
|
}
|
|
nav[nac] = NULL;
|
|
|
|
ctx = fetch_destroy_context(ctx);
|
|
|
|
/*@-kepttrans@*/
|
|
return (DIR *) avdir;
|
|
/*@=kepttrans@*/
|
|
}
|
|
/*@=modfilesys@*/
|