/* Implementation of file-system folder for GNU Mailutils
Copyright (C) 1999-2019 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
. */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* File-system folder is shared between UNIX mbox, maildir and MH
mailboxes. It implements all usual folder methods, excepting
for _delete, which is implemented on the mailbox level. See
comment to mu_folder_delete in folder.c */
struct _mu_fsfolder
{
char *dirname;
mu_property_t subscription;
};
static int
open_subscription (struct _mu_fsfolder *folder)
{
int rc;
mu_property_t prop;
mu_stream_t str;
char *filename = mu_make_file_name (folder->dirname, ".mu-subscr");
rc = mu_file_stream_create (&str, filename, MU_STREAM_RDWR|MU_STREAM_CREAT);
if (rc)
return rc;
rc = mu_property_create_init (&prop, mu_assoc_property_init, str);
free (filename);
if (rc == 0)
folder->subscription = prop;
return rc;
}
static char *
get_pathname (const char *dirname, const char *basename)
{
char *pathname = NULL, *p;
/* Skip eventual protocol designator. */
p = strchr (dirname, ':');
if (p && p[1] == '/' && p[2] == '/')
dirname = p + 3;
/* null basename gives dirname. */
if (basename == NULL)
pathname = strdup (dirname ? dirname : ".");
/* Absolute. */
else if (basename[0] == '/')
pathname = strdup (basename);
/* Relative. */
else
{
size_t baselen = strlen (basename);
size_t dirlen = strlen (dirname);
while (dirlen > 0 && dirname[dirlen-1] == '/')
dirlen--;
pathname = calloc (dirlen + baselen + 2, sizeof (char));
if (pathname)
{
memcpy (pathname, dirname, dirlen);
pathname[dirlen] = '/';
strcpy (pathname + dirlen + 1, basename);
}
}
return pathname;
}
static void
_fsfolder_destroy (mu_folder_t folder)
{
if (folder->data)
{
struct _mu_fsfolder *fsfolder = folder->data;
free (fsfolder->dirname);
mu_property_destroy (&fsfolder->subscription);
free (folder->data);
folder->data = NULL;
}
}
/* Noop. */
static int
_fsfolder_open (mu_folder_t folder, int flags MU_ARG_UNUSED)
{
struct _mu_fsfolder *fsfolder = folder->data;
if (flags & MU_STREAM_CREAT)
{
return (mkdir (fsfolder->dirname, S_IRWXU) == 0) ? 0 : errno;
}
return 0;
}
/* Noop. */
static int
_fsfolder_close (mu_folder_t folder MU_ARG_UNUSED)
{
int rc = 0;
struct _mu_fsfolder *fsfolder = folder->data;
if (fsfolder->subscription)
rc = mu_property_save (fsfolder->subscription);
return rc;
}
static int
_fsfolder_rename (mu_folder_t folder, const char *oldpath,
const char *newpath)
{
struct _mu_fsfolder *fsfolder = folder->data;
if (oldpath && newpath)
{
int status = 0;
char *pathold = get_pathname (fsfolder->dirname, oldpath);
if (pathold)
{
char *pathnew = get_pathname (fsfolder->dirname, newpath);
if (pathnew)
{
if (access (pathnew, F_OK) == 0)
status = EEXIST;
else if (rename (pathold, pathnew) != 0)
status = errno;
free (pathnew);
}
else
status = ENOMEM;
free (pathold);
}
else
status = ENOMEM;
return status;
}
return EINVAL;
}
struct inode_list /* Inode/dev number list used to cut off
recursion */
{
struct inode_list *next;
ino_t inode;
dev_t dev;
};
struct folder_scan_data
{
mu_folder_t folder;
char *dirname;
size_t dirlen;
size_t errcnt;
};
static int
inode_list_lookup (struct inode_list *list, struct stat *st)
{
for (; list; list = list->next)
if (list->inode == st->st_ino && list->dev == st->st_dev)
return 1;
return 0;
}
static int
fold_record_match (void *item, void *data, void *prev, void **ret)
{
struct mu_record_match *cur_match = item;
struct mu_record_match *prev_match = prev;
if (prev == NULL || cur_match->flags >= prev_match->flags)
*ret = cur_match;
else
*ret = prev_match;
return 0;
}
/* List item comparator for computing an intersection between a list
of mu_record_t objects and a list of struct mi_record_match pointers.
*/
static int
mcomp (const void *a, const void *b)
{
struct _mu_record const * r = a;
struct mu_record_match const *m = b;
return !(m->record == r);
}
/* Find a record from RECORDS that is the best match for mailbox REFNAME.
Return the record found in PREC and mailbox attribute flags (the
MU_FOLDER_ATTRIBUTE_* bitmask) in PFLAGS.
Return 0 on success, MU_ERR_NOENT if no match was found and mailutils
error code on error.
*/
static int
best_match (mu_list_t records, char const *refname,
mu_record_t *prec, int *pflags)
{
int rc;
mu_list_t mlist, isect;
mu_list_comparator_t prev;
struct mu_record_match *m;
rc = mu_registrar_match_records (refname,
MU_FOLDER_ATTRIBUTE_ALL,
&mlist);
if (rc)
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s():%s: %s",
__func__,
"mu_registrar_match_records",
mu_strerror (rc)));
return rc;
}
prev = mu_list_set_comparator (records, mcomp);
rc = mu_list_intersect (&isect, mlist, records);
mu_list_set_comparator (records, prev);
if (rc)
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s():%s: %s",
__func__,
"mu_list_intersect",
mu_strerror (rc)));
mu_list_destroy (&mlist);
return rc;
}
rc = mu_list_fold (isect, fold_record_match, NULL, NULL, &m);
if (rc == 0)
{
if (m == NULL)
rc = MU_ERR_NOENT;
else
{
*prec = m->record;
*pflags = m->flags;
}
}
else
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s():%s: %s",
__func__,
"mu_list_fold",
mu_strerror (rc)));
}
mu_list_destroy (&mlist);
mu_list_destroy (&isect);
return rc;
}
static int
list_helper (struct mu_folder_scanner *scn,
struct folder_scan_data *data,
struct inode_list *ilist,
const char *dirname, size_t depth)
{
DIR *dirp;
struct dirent *dp;
int stop = 0;
if (scn->max_depth && depth >= scn->max_depth)
return 0;
dirp = opendir (dirname);
if (dirp == NULL)
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s: %s(%s): %s",
__func__, "opendir", dirname, mu_strerror (errno)));
data->errcnt++;
return 1;
}
while ((dp = readdir (dirp)))
{
char const *ename = dp->d_name;
char *fname;
struct stat st;
if (ename[ename[0] != '.' ? 0 : ename[1] != '.' ? 1 : 2] == 0)
continue;
if (strncmp (ename, ".mu-", 4) == 0)
continue;
fname = get_pathname (dirname, ename);
if (lstat (fname, &st) == 0)
{
int f;
if (S_ISDIR (st.st_mode))
f = MU_FOLDER_ATTRIBUTE_DIRECTORY;
else if (S_ISREG (st.st_mode))
f = MU_FOLDER_ATTRIBUTE_FILE;
else if (S_ISLNK (st.st_mode))
f = MU_FOLDER_ATTRIBUTE_LINK;
else
f = 0;
if (mu_registrar_list_p (scn->records, ename, f))
{
if (scn->pattern == NULL
|| data->folder->_match == NULL
|| data->folder->_match (fname + data->dirlen +
((data->dirlen > 1
&& data->dirname[data->dirlen-1] != '/') ?
1 : 0),
scn->pattern,
scn->match_flags) == 0)
{
char *refname = fname;
int type = 0;
struct mu_list_response *resp;
mu_record_t rec = NULL;
int rc;
resp = malloc (sizeof (*resp));
if (resp == NULL)
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s: %s", __func__, mu_strerror (ENOMEM)));
data->errcnt++;
free (fname);
continue;
}
if (scn->records)
rc = best_match (scn->records, refname, &rec, &type);
else
rc = mu_registrar_lookup (refname, MU_FOLDER_ATTRIBUTE_ALL,
&rec, &type);
if (rc || type == 0)
{
free (resp);
if (f == MU_FOLDER_ATTRIBUTE_DIRECTORY)
type = f;
}
else
{
resp->name = fname;
resp->depth = depth;
resp->separator = '/';
resp->type = type;
resp->format = rec;
if (scn->enumfun)
{
if (scn->enumfun (data->folder, resp, scn->enumdata))
{
free (resp->name);
free (resp);
stop = 1;
break;
}
}
if (scn->result)
{
int rc;
rc = mu_list_append (scn->result, resp);
if (rc)
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s(%s):%s: %s",
__func__, dirname, "mu_list_append",
mu_strerror (rc)));
/* Prevent fname from being freed at the end of the
loop
*/
fname = NULL;
}
else
free (resp);
}
if ((type & MU_FOLDER_ATTRIBUTE_DIRECTORY)
&& !inode_list_lookup (ilist, &st))
{
struct inode_list idata;
idata.inode = st.st_ino;
idata.dev = st.st_dev;
idata.next = ilist;
stop = list_helper (scn, data, &idata, refname,
depth + 1);
}
}
else if (S_ISDIR (st.st_mode))
{
struct inode_list idata;
idata.inode = st.st_ino;
idata.dev = st.st_dev;
idata.next = ilist;
stop = list_helper (scn, data, &idata, fname, depth + 1);
}
}
}
else
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
("%s: lstat(%s): %s",
__func__, fname, mu_strerror (errno)));
}
free (fname);
}
closedir (dirp);
return stop;
}
static int
_fsfolder_list (mu_folder_t folder, struct mu_folder_scanner *scn)
{
struct _mu_fsfolder *fsfolder = folder->data;
struct inode_list iroot;
struct folder_scan_data sdata;
memset (&iroot, 0, sizeof iroot);
sdata.folder = folder;
sdata.dirname = get_pathname (fsfolder->dirname, scn->refname);
sdata.dirlen = strlen (sdata.dirname);
sdata.errcnt = 0;
list_helper (scn, &sdata, &iroot, sdata.dirname, 0);
free (sdata.dirname);
/* FIXME: error code */
return 0;
}
static int
_fsfolder_lsub (mu_folder_t folder, const char *ref, const char *name,
mu_list_t flist)
{
struct _mu_fsfolder *fsfolder = folder->data;
int rc;
char *pattern;
mu_iterator_t itr;
if (name == NULL || *name == '\0')
name = "*";
if (!fsfolder->subscription && (rc = open_subscription (fsfolder)))
return rc;
pattern = mu_make_file_name (ref, name);
rc = mu_property_get_iterator (fsfolder->subscription, &itr);
if (rc == 0)
{
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
const char *key, *val;
mu_iterator_current_kv (itr, (const void **)&key, (void**)&val);
if (fnmatch (pattern, key, 0) == 0)
{
struct mu_list_response *resp;
resp = malloc (sizeof (*resp));
if (resp == NULL)
{
rc = ENOMEM;
break;
}
else if ((resp->name = strdup (key)) == NULL)
{
free (resp);
rc = ENOMEM;
break;
}
resp->type = MU_FOLDER_ATTRIBUTE_FILE;
resp->depth = 0;
resp->separator = '/';
rc = mu_list_append (flist, resp);
if (rc)
{
free (resp);
break;
}
}
}
mu_iterator_destroy (&itr);
}
free (pattern);
return rc;
}
static int
_fsfolder_subscribe (mu_folder_t folder, const char *name)
{
struct _mu_fsfolder *fsfolder = folder->data;
int rc;
if (!fsfolder->subscription && (rc = open_subscription (fsfolder)))
return rc;
return mu_property_set_value (fsfolder->subscription, name, "", 1);
}
static int
_fsfolder_unsubscribe (mu_folder_t folder, const char *name)
{
struct _mu_fsfolder *fsfolder = folder->data;
int rc;
if (!fsfolder->subscription && (rc = open_subscription (fsfolder)))
return rc;
return mu_property_unset (fsfolder->subscription, name);
}
static int
_fsfolder_get_authority (mu_folder_t folder, mu_authority_t *pauth)
{
int status = 0;
if (folder->authority == NULL)
status = mu_authority_create_null (&folder->authority, folder);
if (!status && pauth)
*pauth = folder->authority;
return status;
}
int
_mu_fsfolder_init (mu_folder_t folder)
{
struct _mu_fsfolder *dfolder;
int status = 0;
/* We create an authority so the API is uniform across the mailbox
types. */
status = _fsfolder_get_authority (folder, NULL);
if (status != 0)
return status;
dfolder = folder->data = calloc (1, sizeof (*dfolder));
if (dfolder == NULL)
return ENOMEM;
status = mu_url_aget_path (folder->url, &dfolder->dirname);
if (status == MU_ERR_NOENT)
{
dfolder->dirname = malloc (2);
if (dfolder->dirname == NULL)
status = ENOMEM;
else
{
strcpy (dfolder->dirname, ".");
status = 0;
}
}
if (status)
{
free (dfolder);
folder->data = NULL;
return status;
}
folder->_destroy = _fsfolder_destroy;
folder->_open = _fsfolder_open;
folder->_close = _fsfolder_close;
folder->_list = _fsfolder_list;
folder->_lsub = _fsfolder_lsub;
folder->_subscribe = _fsfolder_subscribe;
folder->_unsubscribe = _fsfolder_unsubscribe;
folder->_delete = NULL;
folder->_rename = _fsfolder_rename;
return 0;
}