#include "lib.h"
#include "str.h"
#include "array.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "unichar.h"
#include "strescape.h"
#include "imap-client.h"
#include "imap-commands.h"
#include "mail-namespace.h"
#include "mail-storage-private.h"
#include "mail-search-build.h"
#include "nfs-workarounds.h"
#include "safe-mkstemp.h"
#include "mkdir-parents.h"
#include "file-lock.h"
#include "file-dotlock.h"
#include "write-full.h"
#include "seq-range-array.h"
#include "fd-set-nonblock.h"
#include "randgen.h"
#include "hex-binary.h"
#include "imap-search.h"
#include "fts-api.h"
#include "fts-storage.h"
#include "fts-sk-plugin.h"
#include <stdlib.h>
#include <sysexits.h>
#include <dlfcn.h>
#ifdef DEBUG
# undef DEBUG
# define DEBUG 1
#endif
#define dec2str Carbon_conflicting_dec2str
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <CoreFoundation/CFPreferences_Private.h>
#undef dec2str
#define __APPLE_API_PRIVATE
#include <sandbox.h>
#undef __APPLE_API_PRIVATE
#define SK_FILE_NAME "dovecot.skindex"
#define SK_LOCK_FILE_NAME "dovecot.sklock"
#define SK_LOCK_TIMEOUT_SECS 20
#define SK_DOTLOCK_STALE_TIMEOUT_SECS (15 * 60)
#define SK_DEFAULT_MIN_TERM_LENGTH 1
#define SK_SEARCH_SECS 5
#define SK_DEFAULT_SPILL_DIR "/tmp"
#define SK_COMPACT_AGE (2 * 24 * 60 * 60)
#define SK_COMPACT_EXPUNGES 100
#define SK_COMPACT_BATCH_SIZE 8
#define SK_FRAGMENT_THRESHOLD_SIZE ((off_t) 1024 * 1024 * 1024)
#define SK_TEXT_BUFFER (128 * 1024)
#define SK_INDEX_NAME "fts-sk"
#define SK_SCHEME "fts-sk"
#define SK_META_URL SK_SCHEME"://meta"
#define SK_META_VERSION 1
#define SK_VERSION_NAME "version"
#define SK_LAST_UID_NAME "last_uid"
#define SK_UIDV_NAME "uidvalidity"
#define SK_FRAGS_NAME "fragments"
#define SK_DOCUMENTS_NAME "documents"
#define SK_SHADOW_NAME "shadow"
#define SK_EXPUNGED_UIDS_NAME "expunged_uids"
#define SK_DEFERRED_SINCE_NAME "deferred_since"
#define SK_INDEXER "skindexer"
#define SK_INDEXER_EXECUTABLE PKG_LIBEXECDIR"/"SK_INDEXER
#define SK_INDEXER_TIMEOUT_SECS (2 * 60)
#define SK_TERM_CHARS "@.-+#$%_&'"
#define CFRelease_and_null(x) STMT_START { \
CFRelease(x); \
(x) = NULL; \
} STMT_END
enum sk_where {
SK_BODY,
SK_HEADERS,
SK_SHADOW,
SK_WHERE_LAST
};
static const char *sk_wheres[SK_WHERE_LAST] = {
"body",
"hdr",
"shadow"
};
static size_t sk_where_lengths[SK_WHERE_LAST];
struct sk_fts_index {
char *path;
SKIndexRef skiref;
SKDocumentRef meta_doc;
};
struct sk_fts_backend {
struct fts_backend backend;
char *index_path;
char *spill_dir;
char *lock_path;
enum file_lock_method lock_method;
int lock_fd;
struct dotlock_settings dotlock_set;
struct file_lock *file_lock;
struct dotlock *dotlock;
int lock_type;
uint32_t uidvalidity;
mode_t create_mode;
gid_t create_gid;
CFMutableArrayRef pendingExpunges;
unsigned int default_min_term_length;
unsigned int lock_timeout_secs;
unsigned int search_secs;
int indexer_timeout_secs;
long compact_age;
int compact_expunges;
ARRAY_DEFINE(indexes, struct sk_fts_index);
unsigned int debug:1;
};
struct sk_meta_values {
uint32_t last_uid;
CFArrayRef expungedUids;
time_t deferred_since;
};
struct sk_fts_backend_build_context {
struct fts_backend_build_context ctx;
unsigned int fragment;
uint32_t next_uid, cur_uid, cur_seq;
enum sk_where next_where, cur_where;
buffer_t *cur_text;
char *next_content_type, *cur_content_type;
CFMutableArrayRef cur_shadow_ids;
struct sk_meta_values *values;
string_t *spill_path;
int spill_fd;
pid_t indexer_pid;
struct ostream *to_indexer;
struct istream *from_indexer;
int from_indexer_fd;
char *indexer_tag;
struct ioloop *indexer_ioloop;
struct io *indexer_io;
struct timeout *indexer_timeout;
const char *indexer_reply;
unsigned int use_indexer_forever:1;
unsigned int indexer_shook:1;
unsigned int indexer_synchronous:1;
unsigned int indexer_shadowing:1;
unsigned int indexer_timed_out:1;
};
struct sk_fts_search_context {
const struct sk_fts_index *skindex;
SKSearchRef search;
unsigned int done:1;
};
struct sk_fts_expunge_context {
const struct sk_fts_index *skindex;
SKSearchRef search;
CFMutableArrayRef targets;
unsigned int done:1;
};
static bool sk_plugin_initialized = FALSE;
static bool (*sk_cmd_search_more)(struct client_command_context *);
static void (*sk_cmd_search_more_callback)(struct client_command_context *);
int sk_assert_build_cancel = 1;
static const char sk_sandbox_profile[] = "\
(version 1)\n\
\n\
; set mail_debug to log to system log\n\
(deny default%s)\n\
(debug %s)\n\
\n\
(allow file-ioctl\n\
(literal \"/dev/dtracehelper\"))\n\
\n\
(allow file-read*\n\
(literal \"/\")\n\
(regex #\"/(Library|Applications)($|/)\") ; explicitly not ^\n\
(literal \"/private\")\n\
(regex #\"^/(private/)?(tmp|var)($|/)\")\n\
(literal \"/dev/autofs_nowait\")\n\
(literal \"/dev/dtracehelper\")\n\
(literal \"/dev/null\")\n\
(regex #\"^/dev/u?random$\")\n\
(subpath \"/System\")\n\
(regex #\"^/usr/(lib|share)($|/)\")\n\
(literal \""PKG_LIBEXECDIR"\")\n\
(subpath \""SK_INDEXER_EXECUTABLE"\") ; subpath for resource fork\n\
%s) ; spill dir\n\
\n\
(allow file-read-metadata\n\
(regex #\"^/(private/)?etc(/localtime)?$)\")\n\
(literal \"/usr\"))\n\
\n\
(allow file-write-data\n\
(literal \"/dev/dtracehelper\")\n\
(literal \"/dev/null\")\n\
(regex #\"^/(private/)?(var/)?tmp($|/)\"))\n\
\n\
(allow ipc-posix-shm\n\
(ipc-posix-name-regex #\"^/tmp/com\\.apple\\.csseed\\.\")\n\
(ipc-posix-name \"apple.shm.notification_center\"))\n\
\n\
(allow mach-lookup\n\
(global-name \"com.apple.CoreServices.coreservicesd\")\n\
(global-name-regex #\"^com\\.apple\\.distributed_notifications\\.\")\n\
(global-name-regex #\"^com\\.apple\\.system\\.DirectoryService\\.\")\n\
(global-name \"com.apple.system.logger\")\n\
(global-name \"com.apple.system.notification_center\"))\n\
\n\
(allow process-exec\n\
(literal \""SK_INDEXER_EXECUTABLE"\"))\n\
\n\
(allow signal (target self))\n\
\n\
(allow sysctl-read)\n\
\n\
(allow file-read* file-write-data file-write-mode\n\
%s) ; skindex path\n\
";
static void sk_plugin_init(void)
{
unsigned int i;
if (sk_plugin_initialized)
return;
_CFPreferencesAlwaysUseVolatileUserDomains();
for (i = 0; i < SK_WHERE_LAST; i++)
sk_where_lengths[i] = strlen(sk_wheres[i]);
sk_cmd_search_more = dlsym(RTLD_MAIN_ONLY, "cmd_search_more");
sk_cmd_search_more_callback = dlsym(RTLD_MAIN_ONLY,
"cmd_search_more_callback");
if (sk_cmd_search_more != NULL && sk_cmd_search_more_callback == NULL)
i_fatal("fts_sk: found cmd_search_more but not cmd_search_more_callback");
sk_plugin_initialized = TRUE;
}
static void sk_nonnull(const void *ptr, const char *tag)
{
if (ptr == NULL)
i_fatal_status(FATAL_OUTOFMEM,
"fts_sk: %s failed: Out of memory", tag);
}
static void sk_set_options(struct sk_fts_backend *backend, const char *str)
{
const char *const *tmp;
for (tmp = t_strsplit_spaces(str, " "); *tmp != NULL; tmp++) {
if (strncasecmp(*tmp, "compact_age_days=", 17) == 0) {
const char *p = *tmp + 17;
long val = atoi(p) * 24 * 60 * 60;
if (val <= 0)
i_fatal("fts_sk: Invalid compact_age_days: %s",
p);
backend->compact_age = val;
} else if (strncasecmp(*tmp, "compact_expunges=", 17) == 0) {
const char *p = *tmp + 17;
int val = atoi(p);
if (val <= 0)
i_fatal("fts_sk: Invalid compact_expunges: %s",
p);
backend->compact_expunges = val;
} else if (strncasecmp(*tmp, "lock_timeout_secs=", 18) == 0) {
const char *p = *tmp + 18;
int val = atoi(p);
if (val < 0)
i_fatal("fts_sk: Invalid lock_timeout_secs: %s",
p);
backend->lock_timeout_secs = val;
} else if (strncasecmp(*tmp, "min_term_length=", 16) == 0) {
const char *p = *tmp + 16;
int val = atoi(p);
if (val <= 0)
i_fatal("fts_sk: Invalid min_term_length: %s",
p);
backend->default_min_term_length = val;
} else if (strncasecmp(*tmp, "indexer_timeout_secs=", 21) == 0) {
const char *p = *tmp + 21;
int val = atoi(p);
if (val <= 0)
i_fatal("fts_sk: Invalid indexer_timeout_secs: %s",
p);
backend->indexer_timeout_secs = val;
} else if (strncasecmp(*tmp, "search_secs=", 12) == 0) {
const char *p = *tmp + 12;
int val = atoi(p);
if (val < 0)
i_fatal("fts_sk: Invalid search_secs: %s", p);
backend->search_secs = val;
} else if (strncasecmp(*tmp, "spill_dir=", 10) == 0) {
const char *p = *tmp + 10;
if (strcasecmp(p, "index") == 0) {
i_free(backend->spill_dir);
backend->spill_dir =
i_strdup(backend->index_path);
} else if (*p == '/') {
i_free(backend->spill_dir);
backend->spill_dir = i_strdup(p);
} else
i_fatal("fts_sk: Invalid spill_dir: %s "
"(need \"index\" or an absolute path)",
p);
} else
i_fatal("fts_sk: Invalid setting: %s", *tmp);
}
}
static CFDictionaryRef sk_index_properties(struct sk_fts_backend *backend)
{
CFMutableDictionaryRef properties;
CFNumberRef minTermLength;
int max_terms;
CFNumberRef maxTerms;
properties =
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
sk_nonnull(properties, "CFDictionaryCreateMutable(index properties)");
minTermLength = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&backend->default_min_term_length);
sk_nonnull(minTermLength, "CFNumberCreate(min_term_length)");
CFDictionaryAddValue(properties, kSKMinTermLength, minTermLength);
CFRelease_and_null(minTermLength);
max_terms = 0;
maxTerms = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&max_terms);
sk_nonnull(maxTerms, "CFNumberCreate(max_terms)");
CFDictionaryAddValue(properties, kSKMaximumTerms, maxTerms);
CFRelease_and_null(maxTerms);
CFDictionaryAddValue(properties, kSKProximityIndexing, kCFBooleanTrue);
CFDictionaryAddValue(properties, kSKTermChars, CFSTR(SK_TERM_CHARS));
return properties;
}
static CFDictionaryRef sk_meta_properties(struct sk_fts_backend *backend,
const struct sk_meta_values *values)
{
CFMutableDictionaryRef metaprops;
unsigned int version, fragments;
CFNumberRef versionNum, lastUid, uidValidity, fragmentCount,
deferredSince;
metaprops =
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
sk_nonnull(metaprops, "CFDictionaryCreateMutable(metaprops)");
version = SK_META_VERSION;
versionNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&version);
sk_nonnull(versionNum, "CFNumberCreate(version)");
CFDictionaryAddValue(metaprops, CFSTR(SK_VERSION_NAME), versionNum);
CFRelease_and_null(versionNum);
uidValidity = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&backend->uidvalidity);
sk_nonnull(uidValidity, "CFNumberCreate(uidvalidity)");
CFDictionaryAddValue(metaprops, CFSTR(SK_UIDV_NAME), uidValidity);
CFRelease_and_null(uidValidity);
fragments = array_count(&backend->indexes);
fragmentCount = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&fragments);
sk_nonnull(fragmentCount, "CFNumberCreate(fragments)");
CFDictionaryAddValue(metaprops, CFSTR(SK_FRAGS_NAME), fragmentCount);
CFRelease_and_null(fragmentCount);
lastUid = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&values->last_uid);
sk_nonnull(lastUid, "CFNumberCreate(last_uid)");
CFDictionaryAddValue(metaprops, CFSTR(SK_LAST_UID_NAME), lastUid);
CFRelease_and_null(lastUid);
if (values->expungedUids != NULL)
CFDictionaryAddValue(metaprops, CFSTR(SK_EXPUNGED_UIDS_NAME),
values->expungedUids);
else {
CFArrayRef empty = CFArrayCreate(kCFAllocatorDefault, NULL, 0,
&kCFTypeArrayCallBacks);
CFDictionaryAddValue(metaprops, CFSTR(SK_EXPUNGED_UIDS_NAME),
empty);
CFRelease_and_null(empty);
}
i_assert(sizeof values->deferred_since == sizeof (long));
deferredSince = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType,
&values->deferred_since);
sk_nonnull(deferredSince, "CFNumberCreate(deferred_since)");
CFDictionaryAddValue(metaprops, CFSTR(SK_DEFERRED_SINCE_NAME),
deferredSince);
CFRelease_and_null(deferredSince);
return metaprops;
}
static CFDictionaryRef sk_get_metaprops(struct sk_fts_backend *backend)
{
const struct sk_fts_index *skindex = array_idx(&backend->indexes, 0);
CFDictionaryRef metaprops =
SKIndexCopyDocumentProperties(skindex->skiref,
skindex->meta_doc);
if (metaprops == NULL)
i_error("fts_sk: meta properties missing from %s",
skindex->path);
return metaprops;
}
static struct sk_meta_values *sk_meta_values_init(void)
{
struct sk_meta_values *values;
values = i_new(struct sk_meta_values, 1);
values->last_uid = (uint32_t) -1;
values->expungedUids = NULL;
values->deferred_since = 0;
return values;
}
static void sk_meta_values_deinit(struct sk_meta_values **_values)
{
struct sk_meta_values *values = *_values;
*_values = NULL;
if (values->expungedUids != NULL)
CFRelease_and_null(values->expungedUids);
i_free(values);
}
static int sk_set_meta_values(struct sk_fts_backend *backend,
const struct sk_meta_values *values)
{
const struct sk_fts_index *skindex = array_idx(&backend->indexes, 0);
if (values->last_uid == 0) {
if (!SKIndexAddDocumentWithText(skindex->skiref,
skindex->meta_doc,
NULL, TRUE)) {
i_error("fts_sk: initializing meta props in %s failed",
skindex->path);
return -1;
}
}
CFDictionaryRef metaprops = sk_meta_properties(backend, values);
SKIndexSetDocumentProperties(skindex->skiref, skindex->meta_doc,
metaprops);
CFRelease_and_null(metaprops);
return 1;
}
static int sk_get_meta_values(struct sk_fts_backend *backend,
struct sk_meta_values **values_r)
{
CFDictionaryRef metaprops;
struct sk_meta_values *values;
CFNumberRef lastUid, deferredSince;
int ret = 0;
metaprops = sk_get_metaprops(backend);
if (metaprops == NULL)
return -1;
values = sk_meta_values_init();
lastUid = (CFNumberRef) CFDictionaryGetValue(metaprops,
CFSTR(SK_LAST_UID_NAME));
if (lastUid == NULL ||
!CFNumberGetValue(lastUid, kCFNumberSInt32Type, &values->last_uid))
ret = -1;
values->expungedUids = (CFArrayRef) CFDictionaryGetValue(metaprops,
CFSTR(SK_EXPUNGED_UIDS_NAME));
if (values->expungedUids != NULL)
CFRetain(values->expungedUids);
deferredSince = (CFNumberRef) CFDictionaryGetValue(metaprops,
CFSTR(SK_DEFERRED_SINCE_NAME));
i_assert(sizeof values->deferred_since == sizeof (long));
if (deferredSince == NULL ||
!CFNumberGetValue(deferredSince, kCFNumberLongType,
&values->deferred_since))
ret = -1;
CFRelease_and_null(metaprops);
if (ret < 0)
sk_meta_values_deinit(&values);
else
*values_r = values;
return ret;
}
static int sk_delete(struct sk_fts_backend *backend)
{
struct sk_fts_index *skindex;
unsigned int count, i;
int ret = 0;
skindex = array_get_modifiable(&backend->indexes, &count);
for (i = 0; i < count; i++) {
if (unlink(skindex[i].path) == 0) {
i_info("fts_sk: deleted %s", skindex[i].path);
if (ret == 0)
ret = 1;
} else if (errno != ENOENT) {
i_error("fts_sk: unlink(%s) failed: %m",
skindex[i].path);
ret = -1;
}
i_assert(skindex[i].skiref == NULL);
if (i > 0) {
i_free(skindex[i].path);
i_assert(skindex[i].meta_doc == NULL);
}
}
array_delete(&backend->indexes, 1, count - 1);
return ret;
}
static void sk_close(struct sk_fts_backend *backend)
{
struct sk_fts_index *skindex;
array_foreach_modifiable(&backend->indexes, skindex) {
if (skindex->skiref != NULL) {
SKIndexClose(skindex->skiref);
skindex->skiref = NULL;
}
}
}
static int sk_flush(struct sk_fts_backend *backend)
{
const struct sk_fts_index *skindex;
int ret = 0;
array_foreach(&backend->indexes, skindex) {
if (!SKIndexFlush(skindex->skiref)) {
i_error("fts_sk: SKIndexFlush(%s) failed",
skindex->path);
ret = -1;
}
}
return ret;
}
static int sk_open(struct sk_fts_backend *backend, bool writable,
bool meta_only)
{
struct sk_fts_index *skindex;
size_t path_len;
CFURLRef url;
CFDictionaryRef metaprops;
int version = -1;
uint32_t uidvalidity = (uint32_t) -1;
int error_ret = -1;
unsigned int fragments = 0, i;
string_t *fragment_path;
skindex = array_idx_modifiable(&backend->indexes, 0);
if (skindex->skiref != NULL)
return 1;
path_len = strlen(skindex->path);
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
(const UInt8 *) skindex->path, path_len, FALSE);
sk_nonnull(url, "CFURLCreateFromFileSystemRepresentation(path)");
skindex->skiref = SKIndexOpenWithURL(url, CFSTR(SK_INDEX_NAME),
writable);
CFRelease_and_null(url);
if (skindex->skiref == NULL)
return -1;
metaprops = sk_get_metaprops(backend);
if (metaprops != NULL) {
CFNumberRef versionNum, uidValidity, fragmentCount;
versionNum = (CFNumberRef) CFDictionaryGetValue(metaprops,
CFSTR(SK_VERSION_NAME));
if (versionNum == NULL ||
!CFNumberGetValue(versionNum, kCFNumberSInt32Type,
&version))
version = -1;
uidValidity = (CFNumberRef) CFDictionaryGetValue(metaprops,
CFSTR(SK_UIDV_NAME));
if (uidValidity == NULL ||
!CFNumberGetValue(uidValidity, kCFNumberSInt32Type,
&uidvalidity))
uidvalidity = (uint32_t) -1;
fragmentCount = (CFNumberRef) CFDictionaryGetValue(metaprops,
CFSTR(SK_FRAGS_NAME));
if (fragmentCount == NULL ||
!CFNumberGetValue(fragmentCount, kCFNumberSInt32Type,
&fragments))
fragments = 0;
CFRelease_and_null(metaprops);
}
fragment_path = t_str_new(path_len + 5);
str_append(fragment_path, skindex->path);
for (i = 1; fragments == 0 || i < fragments; i++) {
str_truncate(fragment_path, path_len);
str_printfa(fragment_path, "-%u", i);
if (fragments == 0) {
struct stat stbuf;
if (stat(str_c(fragment_path), &stbuf) < 0) {
if (errno != ENOENT)
i_error("fts_sk: stat(%s): %m",
str_c(fragment_path));
break;
}
}
if (i < array_count(&backend->indexes))
i_assert((array_idx(&backend->indexes, i))->path !=
NULL);
else {
skindex = array_append_space(&backend->indexes);
skindex->path = i_strdup(str_c(fragment_path));
}
}
skindex = array_get_modifiable(&backend->indexes, &fragments);
if (version != SK_META_VERSION || uidvalidity != backend->uidvalidity) {
if (version > SK_META_VERSION) {
i_warning("fts_sk: %s is version %d which is "
"newer than version %d; ignoring",
skindex->path, version, SK_META_VERSION);
error_ret = 0;
sk_close(backend);
} else {
sk_close(backend);
sk_delete(backend);
}
}
skindex = array_get_modifiable(&backend->indexes, &fragments);
i_assert(fragments > 0);
if (skindex->skiref == NULL)
return error_ret;
if (meta_only)
return 1;
for (i = 1; i < fragments; i++) {
url = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, (const UInt8 *) skindex[i].path,
strlen(skindex[i].path), FALSE);
sk_nonnull(url, "CFURLCreateFromFileSystemRepresentation(fragment_path)");
skindex[i].skiref = SKIndexOpenWithURL(url,
CFSTR(SK_INDEX_NAME),
writable);
CFRelease_and_null(url);
if (skindex[i].skiref == NULL) {
i_error("fts_sk: failed to open index fragment %s",
skindex[i].path);
break;
}
}
if (i < fragments) {
sk_close(backend);
return -1;
}
return 1;
}
static int sk_create(struct sk_fts_backend *backend, unsigned int fragno)
{
struct sk_fts_index *skindex;
CFURLRef url;
skindex = array_idx_modifiable(&backend->indexes, fragno);
i_assert(skindex->skiref == NULL);
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
(const UInt8 *) skindex->path, strlen(skindex->path), FALSE);
sk_nonnull(url, "CFURLCreateFromFileSystemRepresentation(path)");
CFDictionaryRef properties = sk_index_properties(backend);
skindex->skiref = SKIndexCreateWithURL(url,
CFSTR(SK_INDEX_NAME),
kSKIndexInverted,
properties);
CFRelease_and_null(properties);
CFRelease_and_null(url);
if (skindex->skiref != NULL) {
struct sk_meta_values *values;
if (chmod(skindex->path, backend->create_mode) < 0)
i_warning("fts_sk: chmod(%s, 0%o) failed: %m",
skindex->path, backend->create_mode);
if (backend->create_gid != (gid_t) -1 &&
chown(skindex->path, (uid_t) -1,
backend->create_gid) < 0)
i_warning("fts_sk: chown(%s, -1, %d) failed: %m",
skindex->path, backend->create_gid);
values = sk_meta_values_init();
values->last_uid = 0;
if ((fragno == 0 && sk_set_meta_values(backend, values) < 0) ||
sk_flush(backend) < 0) {
sk_close(backend);
sk_delete(backend);
}
sk_meta_values_deinit(&values);
}
return skindex->skiref == NULL ? -1 : 1;
}
static int sk_open_or_create(struct sk_fts_backend *backend, bool meta_only)
{
int ret;
ret = sk_open(backend, TRUE, meta_only);
if (ret < 0)
ret = sk_create(backend, 0);
if (ret <= 0) {
if (sk_delete(backend) > 0)
ret = sk_create(backend, 0);
}
if (ret <= 0)
i_error("fts_sk: Could not open or create %s",
(array_idx(&backend->indexes, 0))->path);
return ret;
}
static void sk_doc_mkname(string_t *name, uint32_t uid, enum sk_where where,
uint32_t seq)
{
str_printfa(name, "uid=%u&where=%s&seq=%u", uid, sk_wheres[where], seq);
}
static SKDocumentRef sk_create_document_immediate(uint32_t uid,
enum sk_where where,
uint32_t seq)
{
string_t *url;
CFURLRef docurl;
SKDocumentRef doc;
url = t_str_new(64);
str_append(url, "/"SK_DOCUMENTS_NAME"/");
sk_doc_mkname(url, uid, where, seq);
docurl = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
str_data(url), str_len(url), FALSE);
if (docurl == NULL) {
i_error("fts_sk: CFURLCreateWithString(%s) failed", str_c(url));
return NULL;
}
doc = SKDocumentCreateWithURL(docurl);
if (doc == NULL)
i_error("fts_sk: SKDocumentCreateWithURL(%s) failed",
str_c(url));
CFRelease_and_null(docurl);
return doc;
}
static const char *sk_clean_term(const char *term, bool *quote)
{
string_t *clean;
if (strcmp(term, "NOT") == 0 || strcmp(term, "AND") == 0 ||
strcmp(term, "OR") == 0) {
*quote = TRUE;
return term;
}
clean = t_str_new(64);
while (*term) {
char c = *term;
unsigned int len = uni_utf8_char_bytes(c);
if (len == 1) {
if ((unsigned char) c <= '\"' ||
strchr("&|*()", c) != NULL) {
str_append_c(clean, '\\');
}
} else if (len == 0)
len = 1;
str_append_n(clean, term, len);
term += len;
}
return str_c(clean);
}
static bool sk_build_query(CFMutableStringRef query, const char *term)
{
bool definite = TRUE;
const char **words;
unsigned int count, i;
words = t_strsplit_spaces(term, " \t");
for (count = 0; words[count] != NULL; ++count)
;
if (count > 1)
CFStringAppend(query, CFSTR("("));
for (i = 0; i < count; i++) {
bool quote = FALSE;
const char *word = sk_clean_term(words[i], "e);
if (i > 0)
CFStringAppend(query, CFSTR(" "));
if (quote) {
CFStringAppend(query, CFSTR("\""));
CFStringAppendCString(query, word, kCFStringEncodingUTF8);
CFStringAppend(query, CFSTR("\""));
definite = FALSE;
} else {
CFStringAppend(query, CFSTR("*"));
CFStringAppendCString(query, word, kCFStringEncodingUTF8);
CFStringAppend(query, CFSTR("*"));
}
}
if (count > 1)
CFStringAppend(query, CFSTR(")"));
return definite;
}
static int sk_score_cmp(const struct fts_score_map *s1,
const struct fts_score_map *s2)
{
return s1->uid < s2->uid ? -1 :
(s1->uid > s2->uid ? 1 : 0);
}
static void sk_search_do(struct sk_fts_backend *backend,
struct sk_fts_search_context *sctx,
enum fts_lookup_flags flags,
const ARRAY_TYPE(seq_range) *expunged,
ARRAY_TYPE(seq_range) *uids,
ARRAY_TYPE(fts_score_map) *score_map)
{
enum { SK_MAXDOCS = 50 };
SKDocumentID results[SK_MAXDOCS];
float scores[SK_MAXDOCS];
CFStringRef names[SK_MAXDOCS];
CFIndex found = 0, i;
memset(results, 0, sizeof results);
if (score_map != NULL)
memset(scores, 0, sizeof scores);
memset(names, 0, sizeof names);
sctx->done = !SKSearchFindMatches(sctx->search, SK_MAXDOCS, results,
score_map != NULL ? scores : NULL,
backend->search_secs, &found);
SKIndexCopyInfoForDocumentIDs(sctx->skindex->skiref, found, results,
names, NULL);
for (i = 0; i < found; i++) {
char name[128], where[128];
uint32_t uid = 0;
if (names[i] == NULL)
continue;
if (!CFStringGetCString(names[i], name, sizeof name,
kCFStringEncodingUTF8)) {
CFRelease_and_null(names[i]);
continue;
}
CFRelease_and_null(names[i]);
where[0] = '\0';
if (sscanf(name, "uid=%u&where=%s", &uid, where) != 2 ||
uid == 0)
continue;
if ((flags & FTS_LOOKUP_FLAG_HEADER) == 0 &&
strncmp(where, sk_wheres[SK_HEADERS],
sk_where_lengths[SK_HEADERS]) == 0)
continue;
if ((flags & FTS_LOOKUP_FLAG_BODY) == 0 &&
strncmp(where, sk_wheres[SK_BODY],
sk_where_lengths[SK_BODY]) == 0)
continue;
if (strncmp(where, sk_wheres[SK_SHADOW],
sk_where_lengths[SK_SHADOW]) == 0)
continue;
if (seq_range_exists(expunged, uid))
continue;
seq_range_array_add(uids, found, uid);
if (score_map != NULL) {
struct fts_score_map *score =
array_append_space(score_map);
score->uid = uid;
score->score = scores[i];
}
}
}
static int sk_search(struct sk_fts_backend *backend, CFStringRef query,
enum fts_lookup_flags flags,
ARRAY_TYPE(seq_range) *uids,
ARRAY_TYPE(fts_score_map) *score_map)
{
struct sk_meta_values *values = NULL;
ARRAY_TYPE(seq_range) expunged;
unsigned int count, i;
const struct sk_fts_index *skindex;
ARRAY_DEFINE(searches, struct sk_fts_search_context);
struct sk_fts_search_context *sctx;
bool more;
if (sk_get_meta_values(backend, &values) < 0)
return -1;
count = values->expungedUids != NULL ?
CFArrayGetCount(values->expungedUids) : 0;
t_array_init(&expunged, count);
for (i = 0; i < count; i++) {
CFNumberRef num = (CFNumberRef)
CFArrayGetValueAtIndex(values->expungedUids, i);
uint32_t uid;
if (num == NULL ||
!CFNumberGetValue(num, kCFNumberSInt32Type, &uid))
uid = (uint32_t) -1;
if (uid != (uint32_t) -1)
seq_range_array_add(&expunged, count, uid);
}
sk_meta_values_deinit(&values);
skindex = array_get(&backend->indexes, &count);
t_array_init(&searches, count);
for (i = 0; i < count; i++) {
SKSearchRef search;
if (!SKIndexFlush(skindex[i].skiref)) {
i_error("fts_sk: SKIndexFlush(%s) failed",
skindex[i].path);
return -1;
}
search = SKSearchCreate(skindex[i].skiref, query,
score_map != NULL ?
kSKSearchOptionDefault :
kSKSearchOptionNoRelevanceScores);
if (search == NULL) {
char qbuf[1024];
if (!CFStringGetCString(query, qbuf, sizeof qbuf,
kCFStringEncodingUTF8))
strcpy(qbuf, "(unknown)");
i_error("fts_sk: SKSearchCreate(%s, %s) failed",
skindex[i].path, qbuf);
break;
}
sctx = array_append_space(&searches);
sctx->skindex = &skindex[i];
sctx->search = search;
}
if (i < count) {
array_foreach_modifiable(&searches, sctx)
CFRelease_and_null(sctx->search);
return -1;
}
sctx = array_get_modifiable(&searches, &count);
do {
more = FALSE;
for (i = 0; i < count; i++) {
if (sctx[i].done)
continue;
sk_search_do(backend, &sctx[i], flags, &expunged,
uids, score_map);
if (sctx[i].done)
CFRelease_and_null(sctx[i].search);
else
more = TRUE;
}
} while (more);
if (score_map != NULL)
array_sort(score_map, sk_score_cmp);
return 1;
}
static int sk_spill_init(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
if (ctx->spill_path == NULL) {
ctx->spill_path = str_new(default_pool, 128);
str_printfa(ctx->spill_path, "%s/", backend->spill_dir);
sk_doc_mkname(ctx->spill_path, ctx->cur_uid, ctx->cur_where,
ctx->cur_seq);
str_append(ctx->spill_path, "&rand=");
}
ctx->spill_fd = safe_mkstemp(ctx->spill_path, 0600, (uid_t) -1,
backend->create_gid);
if (ctx->spill_fd < 0) {
i_error("fts_sk: safe_mkstemp(%s) failed: %m",
str_c(ctx->spill_path));
return -1;
}
return 1;
}
static int sk_spill(struct sk_fts_backend_build_context *ctx)
{
const void *cur_data;
size_t cur_size;
cur_data = buffer_get_data(ctx->cur_text, &cur_size);
if (cur_size == 0)
return 1;
if (ctx->spill_fd < 0) {
if (sk_spill_init(ctx) < 0)
return -1;
}
if (write_full(ctx->spill_fd, cur_data, cur_size) < 0) {
i_error("fts_sk: write_full(%s, %"PRIuSIZE_T") failed: %m",
str_c(ctx->spill_path), cur_size);
return -1;
}
buffer_set_used_size(ctx->cur_text, 0);
return 1;
}
static void sk_indexer_end_clean(struct sk_fts_backend_build_context *ctx)
{
int status;
io_loop_stop(ctx->indexer_ioloop);
if (waitpid(ctx->indexer_pid, &status, 0) < 0)
i_error("fts_sk: waitpid(%d) failed: %m", ctx->indexer_pid);
else if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0)
i_error("fts_sk: indexer process %d exited with code %d",
ctx->indexer_pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status))
i_error("fts_sk: indexer process %d terminated abnormally, "
"signal %d", ctx->indexer_pid, WTERMSIG(status));
else if (WIFSTOPPED(status))
i_error("fts_sk: indexer process %d stopped, signal %d",
ctx->indexer_pid, WSTOPSIG(status));
else
i_error("fts_sk: indexer process %d terminated abnormally, "
"exit status %d", ctx->indexer_pid, status);
}
static void sk_indexer_end_dirty(struct sk_fts_backend_build_context *ctx)
{
if (ctx->indexer_ioloop != NULL)
io_loop_stop(ctx->indexer_ioloop);
if (kill(ctx->indexer_pid, SIGKILL) < 0) {
if (errno == ESRCH)
return;
i_error("fts_sk: kill(%d, %d) failed: %m", ctx->indexer_pid,
SIGKILL);
}
i_warning("fts_sk: killed unresponsive indexer process %d",
ctx->indexer_pid);
(void) waitpid(ctx->indexer_pid, NULL, 0);
}
static void sk_indexer_end(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
struct io *io;
struct timeout *timeout;
if (ctx->indexer_pid < 0)
return;
ctx->indexer_synchronous = TRUE;
if (ctx->indexer_io != NULL)
io_remove(&ctx->indexer_io);
if (ctx->indexer_timeout != NULL)
timeout_remove(&ctx->indexer_timeout);
if (ctx->indexer_ioloop != NULL)
io_loop_destroy(&ctx->indexer_ioloop);
o_stream_close(ctx->to_indexer);
if (ctx->indexer_timed_out) {
ctx->indexer_ioloop = NULL;
sk_indexer_end_dirty(ctx);
} else {
ctx->indexer_ioloop = io_loop_create();
io = io_add(ctx->from_indexer_fd, IO_READ, sk_indexer_end_clean,
ctx);
timeout = timeout_add(backend->indexer_timeout_secs * 1000,
sk_indexer_end_dirty, ctx);
io_loop_run(ctx->indexer_ioloop);
timeout_remove(&timeout);
io_remove(&io);
io_loop_destroy(&ctx->indexer_ioloop);
}
ctx->indexer_pid = -1;
o_stream_destroy(&ctx->to_indexer);
i_stream_destroy(&ctx->from_indexer);
ctx->from_indexer_fd = -1;
if (ctx->indexer_tag != NULL)
i_free_and_null(ctx->indexer_tag);
ctx->use_indexer_forever = FALSE;
ctx->indexer_shook = FALSE;
ctx->indexer_synchronous = FALSE;
ctx->indexer_shadowing = FALSE;
}
static void sk_indexer_drop_privileges(void)
{
int euid, ruid;
const char *error;
euid = geteuid();
ruid = getuid();
i_assert(euid != 0);
if (seteuid(0) == 0) {
if (setuid(euid) < 0)
i_fatal("fts_sk: setuid(%d) failed: %m "
"(euid=%d ruid=%d)",
euid, geteuid(), getuid());
} else
(void) setuid(euid);
if (seteuid(0) == 0)
error = "seteuid(0) succeeded";
else if (setuid(0) == 0)
error = "setuid(0) succeeded";
else if (geteuid() == 0)
error = "euid=0";
else if (getuid() == 0)
error = "ruid=0";
else
error = NULL;
if (error != NULL)
i_fatal("fts_sk: failed to drop root privileges: %s "
"(euid=%d ruid=%d)", error, geteuid(), getuid());
}
static bool sk_indexer_sandbox(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
unsigned int conversions;
const char *profile, *spill_dir_clean, *index_path, *index_path_clean,
*spill_allow, *index_allow;
char *spill_dir_real, *index_path_real, *errorbuf;
conversions = 0;
for (profile = sk_sandbox_profile; *profile; profile++)
if (*profile == '%')
++conversions;
i_assert(conversions == 4);
spill_dir_clean = str_escape(backend->spill_dir);
spill_dir_real = realpath(backend->spill_dir, NULL);
if (spill_dir_real != NULL) {
const char *spill_dir_real_clean = str_escape(spill_dir_real);
free(spill_dir_real);
spill_allow = t_strdup_printf("(subpath \"%s\")\n"
"(subpath \"%s\")",
spill_dir_clean,
spill_dir_real_clean);
} else
spill_allow = t_strdup_printf("(subpath \"%s\")",
spill_dir_clean);
index_path = (array_idx(&backend->indexes, ctx->fragment))->path;
index_path_clean = str_escape(index_path);
index_path_real = realpath(index_path, NULL);
if (index_path_real != NULL) {
const char *index_path_real_clean = str_escape(index_path_real);
free(index_path_real);
index_allow = t_strdup_printf("(literal \"%s\")\n"
"(literal \"%s\")",
index_path_clean,
index_path_real_clean);
} else
index_allow = t_strdup_printf("(literal \"%s\")",
index_path_clean);
profile = t_strdup_printf(sk_sandbox_profile,
backend->debug ? "" : " (with no-log)",
backend->debug ? "deny" : "none",
spill_allow,
index_allow);
errorbuf = NULL;
if (sandbox_init(profile, 0, &errorbuf) != 0) {
i_error("fts_sk: sandbox_init failed: %s", errorbuf);
sandbox_free_error(errorbuf);
return FALSE;
}
return TRUE;
}
static void sk_indexer_child_exec(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
char *args[5];
int fd;
i_set_fatal_handler(NULL);
i_set_error_handler(NULL);
i_set_info_handler(NULL);
sk_indexer_drop_privileges();
(void) sk_indexer_sandbox(ctx);
for (fd = getdtablesize(); --fd >= 3; )
(void) close(fd);
args[0] = SK_INDEXER;
args[1] = backend->spill_dir;
args[2] = (array_idx(&backend->indexes, ctx->fragment))->path;
args[3] = SK_INDEX_NAME;
args[4] = NULL;
execv(SK_INDEXER_EXECUTABLE, args);
i_fatal_status(FATAL_EXEC, "fts_sk: execv(%s) failed: %m",
SK_INDEXER_EXECUTABLE);
}
static int sk_indexer_sync(struct sk_fts_backend_build_context *ctx);
static int sk_indexer_start(struct sk_fts_backend_build_context *ctx)
{
pid_t pid;
int parent_to_child[2], child_to_parent[2];
if (ctx->indexer_pid > 0)
return 1;
if (pipe(parent_to_child) < 0) {
i_error("fts_sk: pipe() failed: %m");
return -1;
}
if (pipe(child_to_parent) < 0) {
i_error("fts_sk: pipe() failed: %m");
(void) close(parent_to_child[0]);
(void) close(parent_to_child[1]);
return -1;
}
pid = fork();
if (pid < 0) {
i_error("fts_sk: fork() failed: %m");
(void) close(parent_to_child[0]);
(void) close(parent_to_child[1]);
(void) close(child_to_parent[0]);
(void) close(child_to_parent[1]);
return -1;
}
if (pid == 0) {
(void) close(parent_to_child[1]);
(void) close(child_to_parent[0]);
if (dup2(parent_to_child[0], 0) < 0)
i_fatal("fts_sk: dup2(stdin) failed: %m");
if (dup2(child_to_parent[1], 1) < 0)
i_fatal("fts_sk: dup2(stdout) failed: %m");
if (dup2(child_to_parent[1], 2) < 0)
i_fatal("fts_sk: dup2(stderr) failed: %m");
sk_indexer_child_exec(ctx);
}
(void) close(parent_to_child[0]);
fd_set_nonblock(parent_to_child[1], TRUE);
fd_set_nonblock(child_to_parent[0], TRUE);
(void) close(child_to_parent[1]);
ctx->use_indexer_forever = TRUE;
ctx->indexer_pid = pid;
ctx->to_indexer = o_stream_create_fd(parent_to_child[1], 8192, TRUE);
ctx->from_indexer = i_stream_create_fd(child_to_parent[0], 8192, TRUE);
ctx->from_indexer_fd = child_to_parent[0];
if (o_stream_send_str(ctx->to_indexer,
"version\t"PACKAGE_VERSION"\n") <= 0) {
i_error("fts_sk: sending handshake to indexer process %d failed",
pid);
sk_indexer_end(ctx);
return -1;
}
return sk_indexer_sync(ctx);
}
static int sk_indexer_handle_reply(struct sk_fts_backend_build_context *ctx)
{
size_t taglen;
if (strstr(ctx->indexer_reply, "CFPreferences") != NULL)
return 0;
if (strstr(ctx->indexer_reply, "bootstrap_look_up") != NULL)
return 0;
#ifdef DEBUG
if (strstr(ctx->indexer_reply, "DEBUG") != NULL) {
i_debug("%s", ctx->indexer_reply);
return 0;
}
#endif
if (strncmp(ctx->indexer_reply, "version\t", 8) == 0) {
bool already_shook = ctx->indexer_shook;
ctx->indexer_shook = TRUE;
if (strcmp(ctx->indexer_reply + 8, PACKAGE_VERSION) == 0) {
return !already_shook;
}
i_error("fts_sk: indexer version mismatch: "
"process %d ("SK_INDEXER_EXECUTABLE") is v%s, "
"imap is v"PACKAGE_VERSION,
ctx->indexer_pid, ctx->indexer_reply + 8);
return -1;
} else if (!ctx->indexer_shook) {
i_error("fts_sk: indexer process %d failed to identify itself"
" (greeted with \"%s\")",
ctx->indexer_pid, ctx->indexer_reply);
return -1;
}
taglen = strlen(ctx->indexer_tag);
if (strncmp(ctx->indexer_reply, ctx->indexer_tag, taglen) == 0 &&
ctx->indexer_reply[taglen] == '\t')
return 1;
i_info("fts_sk: indexer process %d: %s", ctx->indexer_pid,
ctx->indexer_reply);
return 0;
}
static bool sk_build_cancel(struct client_command_context *cmd)
{
i_assert(cmd->cancel);
i_assert(cmd->func == sk_build_cancel);
cmd->func = sk_cmd_search_more;
(void) sk_cmd_search_more(cmd);
cmd->client->ignore = FALSE;
return TRUE;
}
static bool sk_build_cancelling(struct sk_fts_backend_build_context *ctx)
{
struct mail_search_context *mail_ctx;
struct imap_search_context *imap_ctx;
struct client_command_context *cmd;
if (ctx->ctx.fts_ctx == NULL)
return FALSE;
mail_ctx = fts_build_get_mail_context(ctx->ctx.fts_ctx);
if (mail_ctx == NULL)
return FALSE;
imap_ctx = mail_ctx->imap_ctx;
if (imap_ctx == NULL)
return FALSE;
cmd = imap_ctx->cmd;
if (cmd == NULL)
return FALSE;
return cmd->client->ignore;
}
static void sk_indexer_resume_build(struct sk_fts_backend_build_context *ctx)
{
struct mail_search_context *mail_ctx;
struct imap_search_context *imap_ctx;
struct client_command_context *cmd;
i_assert(ctx->ctx.fts_ctx != NULL);
mail_ctx = fts_build_get_mail_context(ctx->ctx.fts_ctx);
i_assert(mail_ctx != NULL);
imap_ctx = mail_ctx->imap_ctx;
i_assert(imap_ctx != NULL);
i_assert(imap_ctx->to == NULL);
cmd = imap_ctx->cmd;
i_assert(cmd != NULL);
imap_ctx->to = timeout_add(0, sk_cmd_search_more_callback, cmd);
i_assert(!sk_assert_build_cancel || cmd->func == sk_build_cancel);
cmd->func = sk_cmd_search_more;
cmd->client->ignore = FALSE;
if (ctx->indexer_io != NULL)
io_remove(&ctx->indexer_io);
if (ctx->indexer_timeout != NULL)
timeout_remove(&ctx->indexer_timeout);
}
static void sk_indexer_input(struct sk_fts_backend_build_context *ctx)
{
int ret;
while ((ret = i_stream_read(ctx->from_indexer)) > 0) {
while ((ctx->indexer_reply =
i_stream_next_line(ctx->from_indexer)) != NULL) {
if (sk_indexer_handle_reply(ctx) != 0)
break;
}
if (ctx->indexer_reply != NULL)
break;
}
if (ret < 0 || ctx->indexer_reply != NULL) {
if (ctx->indexer_reply == NULL)
ctx->indexer_reply = "[EOF]";
if (ctx->indexer_synchronous)
io_loop_stop(ctx->indexer_ioloop);
else
sk_indexer_resume_build(ctx);
}
}
static void sk_indexer_timeout(struct sk_fts_backend_build_context *ctx)
{
ctx->indexer_timed_out = TRUE;
if (ctx->indexer_synchronous)
io_loop_stop(ctx->indexer_ioloop);
else
sk_indexer_resume_build(ctx);
}
static int sk_indexer_sync(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
struct mail_search_context *mail_ctx = NULL;
struct imap_search_context *imap_ctx = NULL;
struct client_command_context *cmd = NULL;
if (ctx->ctx.fts_ctx != NULL)
mail_ctx = fts_build_get_mail_context(ctx->ctx.fts_ctx);
if (mail_ctx != NULL)
imap_ctx = mail_ctx->imap_ctx;
if (imap_ctx != NULL)
cmd = imap_ctx->cmd;
if (cmd == NULL || sk_cmd_search_more == NULL ||
cmd->func != sk_cmd_search_more)
ctx->indexer_synchronous = TRUE;
if (ctx->indexer_synchronous) {
struct io *io;
struct timeout *timeout;
ctx->indexer_ioloop = io_loop_create();
io = io_add(ctx->from_indexer_fd, IO_READ,
sk_indexer_input, ctx);
timeout =
timeout_add(backend->indexer_timeout_secs * 1000,
sk_indexer_timeout, ctx);
io_loop_run(ctx->indexer_ioloop);
timeout_remove(&timeout);
io_remove(&io);
io_loop_destroy(&ctx->indexer_ioloop);
return 1;
} else {
if (cmd->client->io != NULL)
io_remove(&cmd->client->io);
cmd->client->ignore = TRUE;
if (imap_ctx->to != NULL)
timeout_remove(&imap_ctx->to);
ctx->indexer_io = io_add(ctx->from_indexer_fd, IO_READ,
sk_indexer_input, ctx);
ctx->indexer_timeout =
timeout_add(backend->indexer_timeout_secs * 1000,
sk_indexer_timeout, ctx);
cmd->func = sk_build_cancel;
i_assert(cmd->context != NULL);
return 0;
}
}
static void sk_save_id(struct sk_fts_backend_build_context *ctx,
SKDocumentID id)
{
CFNumberRef num;
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &id);
sk_nonnull(num, "CFNumberCreate(id)");
CFArrayAppendValue(ctx->cur_shadow_ids, num);
CFRelease_and_null(num);
}
static void sk_indexer_save_id(struct sk_fts_backend_build_context *ctx,
const char *idstr)
{
char *endstr;
SKDocumentID id;
id = strtol(idstr, &endstr, 10);
if (*idstr == '\0' || *endstr != '\0')
i_error("fts_sk: bad document ID from indexer process %d: %s",
ctx->indexer_pid, idstr);
else
sk_save_id(ctx, id);
}
typedef void sk_indexer_reply_callback_t(struct sk_fts_backend_build_context *,
const char *reply);
static int sk_indexer_do(struct sk_fts_backend_build_context *ctx,
const char *cmd,
sk_indexer_reply_callback_t reply_callback)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
int ret;
if (ctx->indexer_tag == NULL) {
unsigned char randbuf[8];
const char *line;
ret = sk_indexer_start(ctx);
if (ret <= 0)
return ret;
random_fill_weak(randbuf, sizeof randbuf);
ctx->indexer_tag = i_strdup(binary_to_hex(randbuf,
sizeof randbuf));
line = t_strdup_printf("%s\t%s\n", ctx->indexer_tag, cmd);
if (o_stream_send_str(ctx->to_indexer, line) <= 0) {
i_error("fts_sk: sending command to "
"indexer process %d failed", ctx->indexer_pid);
sk_indexer_end(ctx);
return -1;
}
ret = sk_indexer_sync(ctx);
if (ret == 0)
return 0;
}
ret = -1;
if (ctx->indexer_reply != NULL) {
size_t randlen = strlen(ctx->indexer_tag);
if (strncmp(ctx->indexer_reply, ctx->indexer_tag,
randlen) == 0 &&
ctx->indexer_reply[randlen] == '\t')
ret = 1;
if (ret == 1 &&
strncmp(&ctx->indexer_reply[randlen + 1], "OK\t", 3) == 0) {
if (reply_callback != NULL)
reply_callback(ctx,
&ctx->indexer_reply[randlen + 4]);
} else if (backend->debug)
i_error("fts_sk: indexer process %d "
"failed command \"%s\": %s",
ctx->indexer_pid, cmd, ctx->indexer_reply);
} else
i_error("fts_sk: timed out waiting for indexer process %d "
"to execute command \"%s\"", ctx->indexer_pid, cmd);
ctx->indexer_reply = NULL;
i_free_and_null(ctx->indexer_tag);
if (ctx->indexer_io != NULL)
io_remove(&ctx->indexer_io);
if (ctx->indexer_timeout != NULL)
timeout_remove(&ctx->indexer_timeout);
if (ret < 0)
sk_indexer_end(ctx);
return ret;
}
static int sk_index_path_indexer(struct sk_fts_backend_build_context *ctx,
const char *path)
{
const char *cmd;
cmd = t_strdup_printf("index\t%s\t%s", path, ctx->cur_content_type);
return sk_indexer_do(ctx, cmd, sk_indexer_save_id);
}
static int sk_shadow_indexer(struct sk_fts_backend_build_context *ctx,
const char *path)
{
string_t *cmd;
CFIndex count, i;
bool first = TRUE;
int ret;
cmd = t_str_new(128);
str_printfa(cmd, "shadow\t%s\tids=", path);
count = CFArrayGetCount(ctx->cur_shadow_ids);
for (i = 0; i < count; i++) {
CFNumberRef num;
SKDocumentID id;
num = CFArrayGetValueAtIndex(ctx->cur_shadow_ids, i);
if (CFNumberGetValue(num, kCFNumberCFIndexType, &id)) {
if (!first)
str_append_c(cmd, ',');
str_printfa(cmd, "%ld", id);
first = FALSE;
}
}
ret = sk_indexer_do(ctx, str_c(cmd), NULL);
if (ret == 0)
return 0;
CFArrayRemoveAllValues(ctx->cur_shadow_ids);
return ret;
}
static int sk_spill_flush(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
int ret = 1;
if (ctx->indexer_tag == NULL) {
ret = sk_spill(ctx);
if (ret < 0 || ctx->spill_path == NULL)
return ret;
sk_close(backend);
}
if (!ctx->indexer_shadowing) {
ret = sk_index_path_indexer(ctx, str_c(ctx->spill_path));
if (ret == 0)
return 0;
if (ret > 0)
ctx->values->last_uid = ctx->cur_uid;
}
if (ret > 0 && ctx->cur_where == SK_SHADOW) {
ret = sk_shadow_indexer(ctx, str_c(ctx->spill_path));
if (ret == 0) {
ctx->indexer_shadowing = TRUE;
return 0;
}
ctx->indexer_shadowing = FALSE;
}
if (close(ctx->spill_fd) < 0)
i_error("fts_sk: close(%s) failed: %m", str_c(ctx->spill_path));
ctx->spill_fd = -1;
if (unlink(str_c(ctx->spill_path)) < 0)
i_error("fts_sk: unlink(%s) failed: %m",
str_c(ctx->spill_path));
str_free(&ctx->spill_path);
return ret;
}
static int sk_build_flush(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
const void *cur_data;
size_t cur_size;
SKDocumentRef doc;
CFStringRef text;
const struct sk_fts_index *skindex;
int ret;
if (ctx->spill_fd >= 0 || ctx->use_indexer_forever ||
(strncasecmp(ctx->cur_content_type, "text/", 5) != 0 &&
strcasecmp(ctx->cur_content_type, "text") != 0))
return sk_spill_flush(ctx);
cur_data = buffer_get_data(ctx->cur_text, &cur_size);
if (cur_size == 0)
return 1;
if (sk_open_or_create(backend, FALSE) <= 0)
ret = -1;
doc = sk_create_document_immediate(ctx->cur_uid, ctx->cur_where,
ctx->cur_seq);
if (doc == NULL)
return -1;
text = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
cur_data, cur_size,
kCFStringEncodingUTF8,
FALSE, kCFAllocatorNull);
if (text == NULL) {
CFRelease_and_null(doc);
sk_close(backend);
return sk_spill_flush(ctx);
}
skindex = array_idx(&backend->indexes, ctx->fragment);
if (SKIndexAddDocumentWithText(skindex->skiref, doc, text, TRUE)) {
ctx->values->last_uid = ctx->cur_uid;
buffer_set_used_size(ctx->cur_text, 0);
ret = 1;
sk_save_id(ctx, SKIndexGetDocumentID(skindex->skiref, doc));
} else {
i_error("fts_sk: SKIndexAddDocumentWithText(path=%s, size=%lu) failed",
skindex->path, cur_size);
ret = -1;
}
CFRelease_and_null(text);
if (ret > 0 && ctx->cur_where == SK_SHADOW) {
CFMutableDictionaryRef properties;
properties =
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
sk_nonnull(properties, "CFDictionaryCreateMutable(shadow properties)");
CFDictionaryAddValue(properties, CFSTR(SK_SHADOW_NAME),
ctx->cur_shadow_ids);
CFRelease_and_null(ctx->cur_shadow_ids);
ctx->cur_shadow_ids = CFArrayCreateMutable(kCFAllocatorDefault,
0, &kCFTypeArrayCallBacks);
sk_nonnull(ctx->cur_shadow_ids,
"CFArrayCreateMutable(shadow_ids)");
SKIndexSetDocumentProperties(skindex->skiref, doc, properties);
CFRelease_and_null(properties);
}
CFRelease_and_null(doc);
return ret;
}
static int sk_assign_fragment(struct sk_fts_backend_build_context *ctx)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->ctx.backend;
struct sk_fts_index *skindex;
unsigned int count, i;
struct stat stbuf;
struct sk_meta_values *values = NULL;
skindex = array_get_modifiable(&backend->indexes, &count);
if (ctx->fragment != (unsigned int) -1) {
if (ctx->fragment >= count)
i_panic("fts_sk: ctx->fragment=%u >= count=%u",
ctx->fragment, count);
if (stat(skindex[ctx->fragment].path, &stbuf) < 0) {
i_error("fts_sk: stat(%s): %m",
skindex[ctx->fragment].path);
return -1;
}
if (stbuf.st_size < SK_FRAGMENT_THRESHOLD_SIZE)
return 1;
}
for (i = 0; i < count; i++) {
if (i == ctx->fragment)
continue;
if (stat(skindex[i].path, &stbuf) < 0) {
i_error("fts_sk: stat(%s): %m", skindex[i].path);
return -1;
}
if (stbuf.st_size < SK_FRAGMENT_THRESHOLD_SIZE) {
ctx->fragment = i;
return 1;
}
}
i_assert(buffer_get_used_size(ctx->cur_text) == 0);
sk_indexer_end(ctx);
if (sk_open(backend, TRUE, FALSE) <= 0)
return -1;
if (sk_get_meta_values(backend, &values) < 0) {
sk_close(backend);
return -1;
}
skindex = array_append_space(&backend->indexes);
skindex->path = i_strdup_printf("%s-%u",
(array_idx(&backend->indexes, 0))->path,
count);
if (sk_create(backend, count) < 0) {
i_error("fts_sk: could not create new index fragment %s",
skindex->path);
sk_meta_values_deinit(&values);
sk_close(backend);
return -1;
}
ctx->fragment = count;
if (sk_set_meta_values(backend, values) < 0) {
sk_meta_values_deinit(&values);
sk_close(backend);
(void) unlink(skindex->path);
return -1;
}
sk_meta_values_deinit(&values);
return 1;
}
static int sk_build_shadow(struct sk_fts_backend_build_context *ctx);
static int sk_build_common(struct sk_fts_backend_build_context *ctx,
uint32_t uid, const unsigned char *data, size_t size,
enum sk_where where, const char *content_type)
{
int ret = 1;
i_assert(uid >= ctx->values->last_uid);
if (uid != ctx->cur_uid || where != ctx->cur_where ||
strcasecmp(content_type, ctx->cur_content_type) != 0) {
if (uid != ctx->cur_uid && ctx->cur_where != SK_SHADOW) {
ret = sk_build_shadow(ctx);
if (ret <= 0)
return ret;
}
ret = sk_build_flush(ctx);
if (ret <= 0)
return ret;
if (uid == ctx->cur_uid)
++ctx->cur_seq;
else {
ctx->cur_seq = 0;
if (sk_assign_fragment(ctx) < 0)
return -1;
}
ctx->cur_uid = uid;
ctx->cur_where = where;
i_free(ctx->cur_content_type);
ctx->cur_content_type = i_strdup(content_type);
}
if (buffer_get_used_size(ctx->cur_text) + size >
buffer_get_size(ctx->cur_text)) {
ret = sk_spill(ctx);
if (ret < 0)
return ret;
}
buffer_append(ctx->cur_text, data, size);
i_assert(ret != 0);
return ret;
}
static int sk_build_shadow(struct sk_fts_backend_build_context *ctx)
{
string_t *data;
if (ctx->cur_uid == 0)
return 1;
data = t_str_new(32);
str_printfa(data, "FTS%uSK", ctx->cur_uid);
return sk_build_common(ctx, ctx->cur_uid, str_data(data), str_len(data),
SK_SHADOW, "text/plain");
}
static void sk_shadow_expunge(const struct sk_fts_index *skindex,
SKDocumentRef shadow, CFMutableArrayRef targets)
{
CFDictionaryRef properties;
CFArrayRef ids;
CFIndex count, i;
properties = SKIndexCopyDocumentProperties(skindex->skiref, shadow);
if (properties == NULL)
return;
ids = (CFArrayRef) CFDictionaryGetValue(properties,
CFSTR(SK_SHADOW_NAME));
if (ids == NULL) {
CFRelease_and_null(properties);
return;
}
count = CFArrayGetCount(ids);
for (i = 0; i < count; i++) {
CFNumberRef num;
SKDocumentID id;
SKDocumentRef doc;
num = CFArrayGetValueAtIndex(ids, i);
if (!CFNumberGetValue(num, kCFNumberCFIndexType, &id))
continue;
doc = SKIndexCopyDocumentForDocumentID(skindex->skiref, id);
if (doc == NULL)
continue;
CFArrayAppendValue(targets, doc);
CFRelease_and_null(doc);
}
CFRelease_and_null(properties);
}
static void sk_expunge_do(struct sk_fts_backend *backend,
struct sk_fts_expunge_context *ectx)
{
enum { SK_MAXDOCS = 5 };
SKDocumentID results[SK_MAXDOCS];
CFIndex found = 0, i;
memset(results, 0, sizeof results);
ectx->done = !SKSearchFindMatches(ectx->search, SK_MAXDOCS, results,
NULL, backend->search_secs, &found);
for (i = 0; i < found; i++) {
SKDocumentRef doc;
CFIndex count;
doc = SKIndexCopyDocumentForDocumentID(ectx->skindex->skiref,
results[i]);
if (doc == NULL)
continue;
sk_shadow_expunge(ectx->skindex, doc, ectx->targets);
count = CFArrayGetCount(ectx->targets);
if (count > 0 && results[i] !=
SKIndexGetDocumentID(ectx->skindex->skiref,
CFArrayGetValueAtIndex(ectx->targets, count - 1)))
CFArrayAppendValue(ectx->targets, doc);
CFRelease_and_null(doc);
}
}
static int sk_expunge_defer(struct sk_fts_backend *backend, uint32_t uid)
{
CFNumberRef num;
if (backend->pendingExpunges == NULL) {
backend->pendingExpunges =
CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
sk_nonnull(backend->pendingExpunges,
"CFArrayCreateMutable(pendingExpunges)");
}
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &uid);
sk_nonnull(num, "CFNumberCreate(uid)");
CFArrayAppendValue(backend->pendingExpunges, num);
CFRelease_and_null(num);
return 0;
}
static int sk_expunge_commit(struct sk_fts_backend *backend)
{
struct sk_meta_values *values = NULL;
int ret = 0;
if (backend->pendingExpunges == NULL ||
CFArrayGetCount(backend->pendingExpunges) == 0)
return 0;
if (sk_get_meta_values(backend, &values) < 0)
return -1;
if (values->expungedUids != NULL) {
CFIndex count = CFArrayGetCount(values->expungedUids);
if (count > 0) {
CFArrayAppendArray(backend->pendingExpunges,
values->expungedUids,
CFRangeMake(0, count));
} else {
values->deferred_since = ioloop_time;
}
CFRelease_and_null(values->expungedUids);
} else {
values->deferred_since = ioloop_time;
}
values->expungedUids = backend->pendingExpunges;
backend->pendingExpunges = NULL;
if (sk_set_meta_values(backend, values) < 0)
ret = -1;
sk_meta_values_deinit(&values);
return ret;
}
static void sk_expunge_cancel(struct sk_fts_backend *backend)
{
if (backend->pendingExpunges != NULL)
CFRelease_and_null(backend->pendingExpunges);
}
static int sk_compact_batch(struct sk_fts_backend *backend, uint32_t *uids,
unsigned int uids_count)
{
const struct sk_fts_index *skindex;
unsigned int count, i;
CFMutableStringRef query;
ARRAY_DEFINE(expunges, struct sk_fts_expunge_context);
struct sk_fts_expunge_context *ectx;
bool more;
int ret = 0;
query = CFStringCreateMutable(kCFAllocatorDefault, 0);
sk_nonnull(query, "CFStringCreateMutable(query)");
for (i = 0; i < uids_count; i++) {
if (uids[i] == (uint32_t) -1)
continue;
CFStringAppendFormat(query, NULL, CFSTR("FTS%uSK "), uids[i]);
}
if (CFStringGetLength(query) == 0) {
CFRelease_and_null(query);
return 0;
}
skindex = array_get(&backend->indexes, &count);
t_array_init(&expunges, count);
for (i = 0; i < count; i++) {
SKSearchRef search;
search = SKSearchCreate(skindex[i].skiref, query,
kSKSearchOptionNoRelevanceScores |
kSKSearchOptionSpaceMeansOR);
if (search == NULL) {
char qbuf[1024];
if (!CFStringGetCString(query, qbuf, sizeof qbuf,
kCFStringEncodingUTF8))
strcpy(qbuf, "(unknown)");
i_error("fts_sk: SKSearchCreate(%s, %s) failed",
skindex[i].path, qbuf);
break;
}
ectx = array_append_space(&expunges);
ectx->skindex = &skindex[i];
ectx->search = search;
ectx->targets = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
sk_nonnull(ectx->targets, "CFArrayCreateMutable(targets)");
}
CFRelease_and_null(query);
if (i < count) {
array_foreach_modifiable(&expunges, ectx) {
CFRelease_and_null(ectx->search);
CFRelease_and_null(ectx->targets);
}
return -1;
}
ectx = array_get_modifiable(&expunges, &count);
do {
more = FALSE;
for (i = 0; i < count; i++) {
if (ectx[i].done)
continue;
sk_expunge_do(backend, &ectx[i]);
if (ectx[i].done)
CFRelease_and_null(ectx[i].search);
else
more = TRUE;
}
} while (more);
for (i = 0; i < count; i++) {
CFMutableArrayRef targets = ectx[i].targets;
CFIndex tcount = CFArrayGetCount(targets);
CFIndex t;
for (t = 0; t < tcount; t++) {
SKDocumentRef doc = (SKDocumentRef)
CFArrayGetValueAtIndex(targets, t);
(void) SKIndexRemoveDocument(skindex[i].skiref, doc);
}
CFRelease_and_null(ectx[i].targets);
}
return ret;
}
static int sk_compact(struct sk_fts_backend *backend)
{
struct sk_meta_values *values = NULL;
CFIndex num_expunged, num_compacted;
time_t deferred_age;
const struct sk_fts_index *skindex;
int ret = 0;
if (sk_get_meta_values(backend, &values) < 0)
return -1;
if (values->expungedUids == NULL) {
sk_meta_values_deinit(&values);
return 0;
}
num_expunged = CFArrayGetCount(values->expungedUids);
deferred_age = ioloop_time - values->deferred_since;
if (num_expunged == 0 ||
(num_expunged < backend->compact_expunges &&
deferred_age < backend->compact_age)) {
sk_meta_values_deinit(&values);
return 0;
}
array_foreach(&backend->indexes, skindex) {
if (!SKIndexFlush(skindex->skiref)) {
i_error("fts_sk: SKIndexFlush(%s) failed",
skindex->path);
return -1;
}
}
num_compacted = 0;
do {
CFIndex to_compact, u;
uint32_t uids[SK_COMPACT_BATCH_SIZE];
to_compact = num_expunged - num_compacted;
if (to_compact > SK_COMPACT_BATCH_SIZE)
to_compact = SK_COMPACT_BATCH_SIZE;
for (u = 0; u < to_compact; u++) {
CFNumberRef num = (CFNumberRef)
CFArrayGetValueAtIndex(values->expungedUids,
num_compacted + u);
if (num == NULL ||
!CFNumberGetValue(num, kCFNumberSInt32Type,
&uids[u]))
uids[u] = (uint32_t) -1;
}
if (sk_compact_batch(backend, uids, to_compact) < 0)
ret = -1;
num_compacted += to_compact;
} while (num_compacted < num_expunged);
if (ret >= 0) {
CFRelease_and_null(values->expungedUids);
ret = sk_set_meta_values(backend, values);
}
sk_meta_values_deinit(&values);
array_foreach(&backend->indexes, skindex) {
if (!SKIndexCompact(skindex->skiref)) {
i_error("fts_sk: SKIndexCompact(%s) failed",
skindex->path);
ret = -1;
}
}
return ret;
}
static bool sk_is_locked(struct sk_fts_backend *backend, int lock_type)
{
i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
if (backend->lock_type == F_UNLCK) {
i_assert(backend->file_lock == NULL &&
backend->dotlock == NULL);
return FALSE;
}
i_assert(backend->file_lock != NULL || backend->dotlock != NULL);
if (lock_type == F_WRLCK && backend->lock_type != F_WRLCK)
return FALSE;
return TRUE;
}
static int sk_lock(struct sk_fts_backend *backend, int lock_type)
{
int ret;
const char *lock_path;
i_assert(!sk_is_locked(backend, F_RDLCK));
if (backend->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
lock_path = backend->lock_path;
if (backend->lock_fd == -1) {
backend->lock_fd = open(lock_path, O_RDWR | O_CREAT,
0600);
if (backend->lock_fd == -1) {
i_error("fts_sk: creat(%s) failed: %m",
lock_path);
return -1;
}
}
ret = file_wait_lock_multiclient(backend->lock_fd, lock_path,
lock_type,
backend->lock_method,
backend->lock_timeout_secs,
&backend->file_lock,
POINTER_CAST_TO(backend, uintmax_t));
} else {
lock_path = (array_idx(&backend->indexes, 0))->path;
ret = file_dotlock_create_multiclient(&backend->dotlock_set,
lock_path, 0,
&backend->dotlock,
POINTER_CAST_TO(backend, uintmax_t));
}
if (ret == 0) {
i_warning("fts_sk: %s: Locking timed out", lock_path);
return 0;
}
if (ret < 0)
return -1;
backend->lock_type = lock_type;
return 1;
}
static void sk_unlock(struct sk_fts_backend *backend)
{
if (backend->file_lock != NULL)
file_unlock_multiclient(&backend->file_lock,
POINTER_CAST_TO(backend, uintmax_t));
if (backend->dotlock != NULL)
(void) file_dotlock_delete_multiclient(&backend->dotlock,
POINTER_CAST_TO(backend, uintmax_t));
backend->lock_type = F_UNLCK;
}
static int sk_lock_and_open(struct sk_fts_backend *backend, int lock_type,
bool meta_only)
{
int ret = -1;
i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
if (lock_type == F_RDLCK) {
if (sk_lock(backend, F_RDLCK) <= 0)
return -1;
ret = sk_open(backend, FALSE, meta_only);
if (ret <= 0)
sk_unlock(backend);
if (ret == 0)
return -1;
}
if (ret < 0) {
if (sk_lock(backend, F_WRLCK) <= 0)
return -1;
ret = sk_open_or_create(backend, meta_only);
}
if (ret <= 0) {
sk_unlock(backend);
return -1;
}
return 1;
}
static struct fts_backend *fts_backend_sk_init(struct mailbox *box)
{
struct mail_storage *storage;
const char *path, *env;
struct mailbox_status status;
struct sk_fts_backend *backend;
struct sk_fts_index *skindex;
CFURLRef meta_url;
sk_plugin_init();
storage = mailbox_get_storage(box);
path = mailbox_list_get_path(box->list, box->name,
MAILBOX_LIST_PATH_TYPE_INDEX);
if (*path == '\0') {
if (storage->set->mail_debug)
i_debug("fts_sk: Disabled with in-memory indexes");
return NULL;
}
backend = i_new(struct sk_fts_backend, 1);
backend->backend = fts_backend_sk;
backend->index_path = i_strdup(path);
backend->spill_dir = i_strdup(SK_DEFAULT_SPILL_DIR);
backend->lock_path = i_strconcat(path, "/"SK_LOCK_FILE_NAME, NULL);
backend->lock_method = storage->set->parsed_lock_method;
if (backend->lock_method == FILE_LOCK_METHOD_FCNTL) {
backend->lock_method = FILE_LOCK_METHOD_FLOCK;
}
backend->lock_fd = -1;
backend->dotlock_set.stale_timeout = SK_DOTLOCK_STALE_TIMEOUT_SECS;
backend->dotlock_set.use_excl_lock = storage->set->dotlock_use_excl;
backend->dotlock_set.nfs_flush = storage->set->mail_nfs_index;
backend->lock_type = F_UNLCK;
mailbox_get_status(box, STATUS_UIDVALIDITY, &status);
backend->uidvalidity = status.uidvalidity;
backend->create_mode = box->file_create_mode;
backend->create_gid = box->file_create_gid;
backend->default_min_term_length = SK_DEFAULT_MIN_TERM_LENGTH;
backend->lock_timeout_secs = SK_LOCK_TIMEOUT_SECS;
backend->search_secs = SK_SEARCH_SECS;
backend->indexer_timeout_secs = SK_INDEXER_TIMEOUT_SECS;
backend->compact_age = SK_COMPACT_AGE;
backend->compact_expunges = SK_COMPACT_EXPUNGES;
backend->debug = storage->set->mail_debug;
i_array_init(&backend->indexes, 1);
skindex = array_append_space(&backend->indexes);
skindex->path = i_strconcat(path, "/"SK_FILE_NAME, NULL);
meta_url = CFURLCreateWithString(kCFAllocatorDefault,
CFSTR(SK_META_URL), NULL);
sk_nonnull(meta_url, "CFURLCreateWithString(meta_url)");
skindex->meta_doc = SKDocumentCreateWithURL(meta_url);
sk_nonnull(skindex->meta_doc, "SKDocumentCreateWithURL(meta_url)");
CFRelease_and_null(meta_url);
env = mail_user_plugin_getenv(box->storage->user, "fts_sk");
if (env != NULL)
sk_set_options(backend, env);
backend->dotlock_set.timeout = backend->lock_timeout_secs;
return &backend->backend;
}
static void fts_backend_sk_deinit(struct fts_backend *_backend)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *) _backend;
struct sk_fts_index *skindex;
sk_close(backend);
sk_unlock(backend);
array_foreach_modifiable(&backend->indexes, skindex) {
i_free(skindex->path);
if (skindex->meta_doc != NULL)
CFRelease_and_null(skindex->meta_doc);
}
array_free(&backend->indexes);
if (backend->pendingExpunges != NULL)
CFRelease_and_null(backend->pendingExpunges);
if (backend->lock_fd != -1) {
if (close(backend->lock_fd) < 0)
i_error("fts_sk: close(%s) failed: %m",
backend->lock_path);
backend->lock_fd = -1;
}
i_free(backend->lock_path);
i_free(backend->spill_dir);
i_free(backend->index_path);
i_free(backend);
}
static int fts_backend_sk_get_last_uid(struct fts_backend *_backend,
uint32_t *last_uid_r)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *) _backend;
struct sk_meta_values *values = NULL;
int ret = 0;
if (sk_lock_and_open(backend, F_RDLCK, TRUE) < 0)
return -1;
if (sk_get_meta_values(backend, &values) < 0)
ret = -1;
else {
*last_uid_r = values->last_uid;
sk_meta_values_deinit(&values);
}
sk_close(backend);
if (ret < 0)
sk_delete(backend);
sk_unlock(backend);
return ret;
}
static int fts_backend_sk_build_init(struct fts_backend *_backend,
uint32_t *last_uid_r,
struct fts_backend_build_context **ctx_r)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *) _backend;
struct sk_fts_backend_build_context *ctx;
struct sk_meta_values *values = NULL;
if (sk_lock_and_open(backend, F_WRLCK, FALSE) < 0)
return -1;
if (sk_get_meta_values(backend, &values) < 0) {
sk_close(backend);
sk_delete(backend);
sk_unlock(backend);
return -1;
}
*last_uid_r = values->last_uid;
ctx = i_new(struct sk_fts_backend_build_context, 1);
ctx->ctx.backend = _backend;
ctx->fragment = (unsigned int) -1;
ctx->values = values;
ctx->cur_text = buffer_create_dynamic(default_pool, SK_TEXT_BUFFER);
ctx->cur_content_type = i_strdup("text/plain");
ctx->cur_shadow_ids = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
sk_nonnull(ctx->cur_shadow_ids, "CFArrayCreateMutable(shadow_ids)");
ctx->spill_fd = -1;
ctx->indexer_pid = -1;
ctx->from_indexer_fd = -1;
*ctx_r = &ctx->ctx;
return 0;
}
static void fts_backend_sk_build_hdr(struct fts_backend_build_context *_ctx,
uint32_t uid)
{
struct sk_fts_backend_build_context *ctx =
(struct sk_fts_backend_build_context *) _ctx;
ctx->next_uid = uid;
ctx->next_where = SK_HEADERS;
if (ctx->next_content_type != NULL)
i_free(ctx->next_content_type);
ctx->next_content_type = i_strdup("text/plain");
}
static bool
fts_backend_sk_build_body_begin(struct fts_backend_build_context *_ctx,
uint32_t uid, const char *content_type,
const char *content_disposition ATTR_UNUSED)
{
struct sk_fts_backend_build_context *ctx =
(struct sk_fts_backend_build_context *) _ctx;
if (strncasecmp(content_type, "multipart/", 10) == 0)
return FALSE;
ctx->next_uid = uid;
ctx->next_where = SK_BODY;
if (ctx->next_content_type != NULL)
i_free(ctx->next_content_type);
ctx->next_content_type = i_strdup(content_type);
return TRUE;
}
static int fts_backend_sk_build_more(struct fts_backend_build_context *_ctx,
const unsigned char *data, size_t size)
{
struct sk_fts_backend_build_context *ctx =
(struct sk_fts_backend_build_context *) _ctx;
struct sk_fts_backend *backend =
(struct sk_fts_backend *) _ctx->backend;
i_assert(sk_is_locked(backend, F_WRLCK));
return sk_build_common(ctx, ctx->next_uid, data, size,
ctx->next_where, ctx->next_content_type);
}
static int fts_backend_sk_build_deinit(struct fts_backend_build_context *_ctx)
{
struct sk_fts_backend_build_context *ctx =
(struct sk_fts_backend_build_context *)_ctx;
struct sk_fts_backend *backend =
(struct sk_fts_backend *) _ctx->backend;
int ret = 0;
i_assert(sk_is_locked(backend, F_WRLCK));
ctx->indexer_synchronous = TRUE;
if (!sk_build_cancelling(ctx)) {
if (sk_build_shadow(ctx) < 0)
ret = -1;
if (sk_build_flush(ctx) < 0)
ret = -1;
}
sk_indexer_end(ctx);
if (sk_open(backend, TRUE, FALSE) <= 0)
ret = -1;
else {
if (sk_set_meta_values(backend, ctx->values) < 0)
ret = -1;
if (sk_flush(backend) < 0) {
sk_close(backend);
sk_delete(backend);
ret = -1;
}
}
sk_meta_values_deinit(&ctx->values);
sk_close(backend);
sk_unlock(backend);
if (ctx->next_content_type != NULL)
i_free(ctx->next_content_type);
i_free(ctx->cur_content_type);
buffer_free(&ctx->cur_text);
CFRelease_and_null(ctx->cur_shadow_ids);
if (ctx->spill_fd >= 0) {
if (close(ctx->spill_fd) < 0)
i_error("fts_sk: close(%s) failed: %m",
str_c(ctx->spill_path));
if (unlink(str_c(ctx->spill_path)) < 0 && errno != ENOENT)
i_error("fts_sk: unlink(%s) failed: %m",
str_c(ctx->spill_path));
ctx->spill_fd = -1;
}
if (ctx->spill_path != NULL)
str_free(&ctx->spill_path);
i_free(ctx);
return ret;
}
static void fts_backend_sk_expunge(struct fts_backend *_backend,
struct mail *mail)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *)_backend;
if (!sk_is_locked(backend, F_WRLCK)) {
if (sk_lock_and_open(backend, F_WRLCK, FALSE) < 0)
return;
}
if (sk_expunge_defer(backend, mail->uid) < 0) {
sk_close(backend);
sk_delete(backend);
sk_unlock(backend);
}
}
static void fts_backend_sk_expunge_finish(struct fts_backend *_backend,
struct mailbox *box ATTR_UNUSED,
bool committed)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *)_backend;
if (!sk_is_locked(backend, F_WRLCK)) {
i_assert((array_idx(&backend->indexes, 0))->skiref == NULL);
return;
}
if (committed) {
if (sk_expunge_commit(backend) < 0 || sk_flush(backend) < 0) {
sk_close(backend);
sk_delete(backend);
} else
sk_close(backend);
} else {
sk_expunge_cancel(backend);
sk_close(backend);
}
sk_unlock(backend);
}
static void fts_backend_sk_compact(struct fts_backend *_backend)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *)_backend;
int ret;
if (sk_lock_and_open(backend, F_WRLCK, FALSE) < 0)
return;
ret = sk_compact(backend);
sk_close(backend);
if (ret < 0)
sk_delete(backend);
sk_unlock(backend);
}
static int fts_backend_sk_lock(struct fts_backend *_backend)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *)_backend;
return sk_lock_and_open(backend, F_RDLCK, FALSE);
}
static void fts_backend_sk_unlock(struct fts_backend *_backend)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *)_backend;
i_assert(sk_is_locked(backend, F_RDLCK));
sk_close(backend);
sk_unlock(backend);
}
static int fts_backend_sk_lookup_one(struct fts_backend *_backend,
const char *key,
enum fts_lookup_flags flags,
ARRAY_TYPE(seq_range) *definite_uids,
ARRAY_TYPE(seq_range) *maybe_uids)
{
struct sk_fts_backend *backend = (struct sk_fts_backend *) _backend;
CFMutableStringRef query;
ARRAY_TYPE(seq_range) *uids = definite_uids;
int ret;
i_assert((flags & FTS_LOOKUP_FLAG_INVERT) == 0);
i_assert(sk_is_locked(backend, F_RDLCK));
query = CFStringCreateMutable(kCFAllocatorDefault, 0);
sk_nonnull(query, "CFStringCreateMutable(query)");
if (!sk_build_query(query, key))
uids = maybe_uids;
array_clear(definite_uids);
array_clear(maybe_uids);
ret = sk_search(backend, query, flags, uids, NULL);
CFRelease_and_null(query);
return ret;
}
static int fts_backend_sk_lookup_all(struct fts_backend_lookup_context *ctx,
ARRAY_TYPE(seq_range) *definite_uids,
ARRAY_TYPE(seq_range) *maybe_uids,
ARRAY_TYPE(fts_score_map) *score_map)
{
struct sk_fts_backend *backend =
(struct sk_fts_backend *) ctx->backend;
unsigned int i, count;
const struct fts_backend_lookup_field *fields;
enum fts_lookup_flags flags;
CFMutableStringRef query;
ARRAY_TYPE(seq_range) *uids = definite_uids;
int ret;
i_assert(sk_is_locked(backend, F_RDLCK));
fields = array_get(&ctx->fields, &count);
for (i = 0; i < count; i++)
if ((fields[i].flags & FTS_LOOKUP_FLAG_INVERT) == 0)
break;
if (i >= count)
return -1;
flags = fields[0].flags & (FTS_LOOKUP_FLAG_HEADER |
FTS_LOOKUP_FLAG_BODY);
i_assert(flags != 0);
query = CFStringCreateMutable(kCFAllocatorDefault, 0);
sk_nonnull(query, "CFStringCreateMutable(query)");
for (i = 0; i < count; i++) {
if (i > 0) {
if ((fields[i].flags & (FTS_LOOKUP_FLAG_HEADER |
FTS_LOOKUP_FLAG_BODY)) !=
flags) {
CFRelease_and_null(query);
return -1;
}
CFStringAppend(query, CFSTR(" "));
}
if (fields[i].flags & FTS_LOOKUP_FLAG_INVERT)
CFStringAppend(query, CFSTR("NOT "));
if (!sk_build_query(query, fields[i].key)) {
uids = maybe_uids;
}
}
array_clear(definite_uids);
array_clear(maybe_uids);
ret = sk_search(backend, query, flags, uids, score_map);
CFRelease_and_null(query);
return ret;
}
struct fts_backend fts_backend_sk = {
.name = "sk",
.flags = FTS_BACKEND_FLAG_SUBSTRING_LOOKUPS |
FTS_BACKEND_FLAG_BINARY_MIME_PARTS,
{
fts_backend_sk_init,
fts_backend_sk_deinit,
fts_backend_sk_get_last_uid,
NULL,
fts_backend_sk_build_init,
fts_backend_sk_build_hdr,
fts_backend_sk_build_body_begin,
NULL,
fts_backend_sk_build_more,
fts_backend_sk_build_deinit,
fts_backend_sk_expunge,
fts_backend_sk_expunge_finish,
fts_backend_sk_compact,
fts_backend_sk_lock,
fts_backend_sk_unlock,
fts_backend_sk_lookup_one,
NULL,
fts_backend_sk_lookup_all
}
};