/*++
/* NAME
/* dict_memcache 3
/* SUMMARY
/* dictionary interface to memcaches
/* SYNOPSIS
/* #include <dict_memcache.h>
/*
/* DICT *dict_memcache_open(name, open_flags, dict_flags)
/* const char *name;
/* int open_flags;
/* int dict_flags;
/* DESCRIPTION
/* dict_memcache_open() opens a memcache, providing
/* a dictionary interface for Postfix key->value mappings.
/* The result is a pointer to the installed dictionary.
/*
/* Arguments:
/* .IP name
/* The path to the Postfix memcache configuration file.
/* .IP open_flags
/* O_RDONLY or O_RDWR. This function ignores flags that don't
/* specify a read, write or append mode.
/* .IP dict_flags
/* See dict_open(3).
/* SEE ALSO
/* dict(3) generic dictionary manager
/* memcache_table(5) memcache client configuration
/* HISTORY
/* .ad
/* .fi
/* The first memcache client for Postfix was written by Omar
/* Kilani, and was based on libmemcache. The current
/* implementation implements the memcache protocol directly,
/* and bears no resemblance to earlier work.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h> /* XXX sscanf() */
/* Utility library. */
#include <hex_code.h>
#include <msg.h>
#include <mymalloc.h>
#include <dict.h>
#include <ossl_digest.h>
#include <vstring.h>
#include <stringops.h>
#include <auto_clnt.h>
#include <vstream.h>
/* Global library. */
#include <cfg_parser.h>
#include <db_common.h>
#include <memcache_proto.h>
/* Application-specific. */
#include <dict_memcache.h>
/*
* Structure of one memcache dictionary handle .
*/
typedef struct {
DICT dict; /* parent class */
CFG_PARSER *parser; /* common parameter parser */
void *dbc_ctxt; /* db_common context */
char *key_digest; /* digest the query key */
#ifdef USE_TLS
OSSL_DGST *key_dgst_eng; /* digest engine */
VSTRING *key_dgst_out; /* digest result */
#endif
char *key_format; /* query key translation */
int timeout; /* client timeout */
int mc_ttl; /* memcache update expiration */
int mc_flags; /* memcache update flags */
int err_pause; /* delay between errors */
int max_tries; /* number of tries */
int max_line; /* reply line limit */
int max_data; /* reply data limit */
char *memcache; /* memcache server spec */
AUTO_CLNT *clnt; /* memcache client stream */
VSTRING *clnt_buf; /* memcache client buffer */
VSTRING *key_buf; /* lookup key */
VSTRING *res_buf; /* lookup result */
int error; /* memcache dict_errno */
DICT *backup; /* persistent backup */
} DICT_MC;
/*
* Memcache option defaults and names .
*/
#define DICT_MC_DEF_HOST "localhost"
#define DICT_MC_DEF_PORT "11211"
#define DICT_MC_DEF_MEMCACHE "inet:" DICT_MC_DEF_HOST ":" DICT_MC_DEF_PORT
#define DICT_MC_DEF_KEY_FMT "%s"
#define DICT_MC_DEF_KEY_DGST ""
#define DICT_MC_DEF_MC_TTL 3600
#define DICT_MC_DEF_MC_TIMEOUT 2
#define DICT_MC_DEF_MC_FLAGS 0
#define DICT_MC_DEF_MAX_TRY 2
#define DICT_MC_DEF_MAX_LINE 1024
#define DICT_MC_DEF_MAX_DATA 10240
#define DICT_MC_DEF_ERR_PAUSE 1
#define DICT_MC_NAME_MEMCACHE "memcache"
#define DICT_MC_NAME_BACKUP "backup"
#define DICT_MC_NAME_KEY_DGST "key_digest"
#define DICT_MC_NAME_KEY_FMT "key_format"
#define DICT_MC_NAME_MC_TTL "ttl"
#define DICT_MC_NAME_MC_TIMEOUT "timeout"
#define DICT_MC_NAME_MC_FLAGS "flags"
#define DICT_MC_NAME_MAX_TRY "max_try"
#define DICT_MC_NAME_MAX_LINE "line_size_limit"
#define DICT_MC_NAME_MAX_DATA "data_size_limit"
#define DICT_MC_NAME_ERR_PAUSE "retry_pause"
/*
* SLMs .
*/
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
/*#define msg_verbose 1*/
/* dict_memcache_set - set memcache key/value */
static int dict_memcache_set(DICT_MC *dict_mc, const char *value, int ttl)
{
VSTREAM *fp;
int count;
size_t data_len = strlen(value);
/*
* Return a permanent error if we can ' t store this data . This results in
* loss of information .
*/
if (data_len > dict_mc->max_data) {
msg_warn("database %s:%s: data for key %s is too long (%s=%d) "
"-- not stored" , DICT_TYPE_MEMCACHE, dict_mc->dict.name,
STR(dict_mc->key_buf), DICT_MC_NAME_MAX_DATA,
dict_mc->max_data);
/* Not stored! */
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL);
}
for (count = 0 ; count < dict_mc->max_tries; count++) {
if (count > 0 )
sleep(dict_mc->err_pause);
if ((fp = auto_clnt_access(dict_mc->clnt)) == 0 ) {
break ;
} else if (memcache_printf(fp, "set %s %d %d %ld" ,
STR(dict_mc->key_buf), dict_mc->mc_flags,
ttl, (long ) data_len) < 0
|| memcache_fwrite(fp, value, strlen(value)) < 0
|| memcache_get(fp, dict_mc->clnt_buf,
dict_mc->max_line) < 0 ) {
if (count > 0 )
msg_warn(errno ? "database %s:%s: I/O error: %m" :
"database %s:%s: I/O error" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name);
} else if (strcmp(STR(dict_mc->clnt_buf), "STORED" ) != 0 ) {
if (count > 0 )
msg_warn("database %s:%s: update failed: %.100s" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name,
STR(dict_mc->clnt_buf));
} else {
/* Victory! */
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS);
}
auto_clnt_recover(dict_mc->clnt);
}
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR);
}
/* dict_memcache_get - get memcache key/value */
static const char *dict_memcache_get(DICT_MC *dict_mc)
{
VSTREAM *fp;
long todo;
int count;
for (count = 0 ; count < dict_mc->max_tries; count++) {
if (count > 0 )
sleep(dict_mc->err_pause);
if ((fp = auto_clnt_access(dict_mc->clnt)) == 0 ) {
break ;
} else if (memcache_printf(fp, "get %s" , STR(dict_mc->key_buf)) < 0
|| memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0 ) {
if (count > 0 )
msg_warn(errno ? "database %s:%s: I/O error: %m" :
"database %s:%s: I/O error" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name);
} else if (strcmp(STR(dict_mc->clnt_buf), "END" ) == 0 ) {
/* Not found. */
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, (char *) 0 );
} else if (sscanf(STR(dict_mc->clnt_buf),
"VALUE %*s %*s %ld" , &todo) != 1
|| todo < 0 || todo > dict_mc->max_data) {
if (count > 0 )
msg_warn("%s: unexpected memcache server reply: %.30s" ,
dict_mc->dict.name, STR(dict_mc->clnt_buf));
} else if (memcache_fread(fp, dict_mc->res_buf, todo) < 0 ) {
if (count > 0 )
msg_warn("%s: EOF receiving memcache server reply" ,
dict_mc->dict.name);
} else {
/* Victory! */
if (memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0
|| strcmp(STR(dict_mc->clnt_buf), "END" ) != 0 )
auto_clnt_recover(dict_mc->clnt);
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, STR(dict_mc->res_buf));
}
auto_clnt_recover(dict_mc->clnt);
}
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, (char *) 0 );
}
/* dict_memcache_del - delete memcache key/value */
static int dict_memcache_del(DICT_MC *dict_mc)
{
VSTREAM *fp;
int count;
for (count = 0 ; count < dict_mc->max_tries; count++) {
if (count > 0 )
sleep(dict_mc->err_pause);
if ((fp = auto_clnt_access(dict_mc->clnt)) == 0 ) {
break ;
} else if (memcache_printf(fp, "delete %s" , STR(dict_mc->key_buf)) < 0
|| memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0 ) {
if (count > 0 )
msg_warn(errno ? "database %s:%s: I/O error: %m" :
"database %s:%s: I/O error" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name);
} else if (strcmp(STR(dict_mc->clnt_buf), "DELETED" ) == 0 ) {
/* Victory! */
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS);
} else if (strcmp(STR(dict_mc->clnt_buf), "NOT_FOUND" ) == 0 ) {
/* Not found! */
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL);
} else {
if (count > 0 )
msg_warn("database %s:%s: delete failed: %.30s" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name,
STR(dict_mc->clnt_buf));
}
auto_clnt_recover(dict_mc->clnt);
}
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR);
}
/* dict_memcache_prepare_key - prepare lookup key */
static ssize_t dict_memcache_prepare_key(DICT_MC *dict_mc, const char *name)
{
/*
* Optionally case - fold the search string .
*/
if (dict_mc->dict.flags & DICT_FLAG_FOLD_FIX) {
if (dict_mc->dict.fold_buf == 0 )
dict_mc->dict.fold_buf = vstring_alloc(10 );
vstring_strcpy(dict_mc->dict.fold_buf, name);
name = lowercase(STR(dict_mc->dict.fold_buf));
}
/*
* Optionally expand the query key format .
*/
#define DICT_MC_NO_KEY (0 )
#define DICT_MC_NO_QUOTING ((void (*)(DICT *, const char *, VSTRING *)) 0 )
if (dict_mc->key_format != 0
&& strcmp(dict_mc->key_format, DICT_MC_DEF_KEY_FMT) != 0 ) {
VSTRING_RESET(dict_mc->key_buf);
if (db_common_expand(dict_mc->dbc_ctxt, dict_mc->key_format,
name, DICT_MC_NO_KEY, dict_mc->key_buf,
DICT_MC_NO_QUOTING) == 0 )
return (0 );
} else {
vstring_strcpy(dict_mc->key_buf, name);
}
#ifdef USE_TLS
if (dict_mc->key_dgst_eng) {
if (ossl_digest_data(dict_mc->key_dgst_eng, STR(dict_mc->key_buf),
LEN(dict_mc->key_buf), dict_mc->key_dgst_out) < 0 ) {
ossl_digest_log_errors(msg_warn);
msg_warn("%s:%s: %s message digest failed" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name,
dict_mc->key_digest);
return (-1 );
}
hex_encode_opt(dict_mc->key_buf, STR(dict_mc->key_dgst_out),
LEN(dict_mc->key_dgst_out), HEX_ENCODE_FLAG_LOWERCASE);
}
#endif
/*
* The length indicates whether the expansion is empty or not .
*/
return (LEN(dict_mc->key_buf));
}
/* dict_memcache_valid_key - validate key */
static int dict_memcache_valid_key(DICT_MC *dict_mc,
const char *name,
const char *operation,
void (*log_func) (const char *,...))
{
unsigned char *cp;
int rc;
#define DICT_MC_SKIP(why) do { \
if (msg_verbose || log_func != msg_info) \
log_func("%s: skipping %s for name \" %s\": %s" , \
dict_mc->dict.name, operation, name, (why)); \
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 0 ); \
} while (0 )
if (*name == 0 )
DICT_MC_SKIP("empty lookup key" );
if ((rc = db_common_check_domain(dict_mc->dbc_ctxt, name)) == 0 )
DICT_MC_SKIP("domain mismatch" );
if (rc < 0 )
DICT_ERR_VAL_RETURN(dict_mc, rc, 0 );
if ((rc = dict_memcache_prepare_key(dict_mc, name)) == 0 )
DICT_MC_SKIP("empty lookup key expansion" );
if (rc < 0 )
DICT_ERR_VAL_RETURN(dict_mc, rc, 0 );
for (cp = (unsigned char *) STR(dict_mc->key_buf); *cp; cp++)
if (isascii(*cp) && isspace(*cp))
DICT_MC_SKIP("name contains space" );
DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 1 );
}
/* dict_memcache_update - update memcache */
static int dict_memcache_update(DICT *dict, const char *name,
const char *value)
{
const char *myname = "dict_memcache_update" ;
DICT_MC *dict_mc = (DICT_MC *) dict;
DICT *backup = dict_mc->backup;
int upd_res;
/*
* Skip updates with an inapplicable key , noisily . This results in loss
* of information .
*/
if (dict_memcache_valid_key(dict_mc, name, "update" , msg_warn) == 0 )
DICT_ERR_VAL_RETURN(dict, dict_mc->error, DICT_STAT_FAIL);
/*
* Update the memcache first .
*/
upd_res = dict_memcache_set(dict_mc, value, dict_mc->mc_ttl);
dict->error = dict_mc->error;
/*
* Update the backup database last .
*/
if (backup) {
upd_res = backup->update(backup, name, value);
dict->error = backup->error;
}
if (msg_verbose)
msg_info("%s: %s: update key \" %s\"(%s) => \" %s\" %s" ,
myname, dict_mc->dict.name, name, STR(dict_mc->key_buf),
value, dict_mc->error ? "(memcache error)" : (backup
&& backup->error) ? "(backup error)" : "(no error)" );
return (upd_res);
}
/* dict_memcache_lookup - lookup memcache */
static const char *dict_memcache_lookup(DICT *dict, const char *name)
{
const char *myname = "dict_memcache_lookup" ;
DICT_MC *dict_mc = (DICT_MC *) dict;
DICT *backup = dict_mc->backup;
const char *retval;
/*
* Skip lookups with an inapplicable key , silently . This is just asking
* for information that cannot exist .
*/
if (dict_memcache_valid_key(dict_mc, name, "lookup" , msg_info) == 0 )
DICT_ERR_VAL_RETURN(dict, dict_mc->error, (char *) 0 );
/*
* Search the memcache first .
*/
retval = dict_memcache_get(dict_mc);
dict->error = dict_mc->error;
/*
* Search the backup database last . Update the memcache if the data is
* found .
*/
if (backup) {
backup->error = 0 ;
if (retval == 0 ) {
retval = backup->lookup(backup, name);
dict->error = backup->error;
/* Update the cache. */
if (retval != 0 )
dict_memcache_set(dict_mc, retval, dict_mc->mc_ttl);
}
}
if (msg_verbose)
msg_info("%s: %s: key \" %s\"(%s) => %s" ,
myname, dict_mc->dict.name, name, STR(dict_mc->key_buf),
retval ? retval : dict_mc->error ? "(memcache error)" :
(backup && backup->error) ? "(backup error)" : "(not found)" );
return (retval);
}
/* dict_memcache_delete - delete memcache entry */
static int dict_memcache_delete(DICT *dict, const char *name)
{
const char *myname = "dict_memcache_delete" ;
DICT_MC *dict_mc = (DICT_MC *) dict;
DICT *backup = dict_mc->backup;
int del_res;
/*
* Skip lookups with an inapplicable key , noisily . This is just deleting
* information that cannot exist .
*/
if (dict_memcache_valid_key(dict_mc, name, "delete" , msg_info) == 0 )
DICT_ERR_VAL_RETURN(dict, dict_mc->error, dict_mc->error ?
DICT_STAT_ERROR : DICT_STAT_FAIL);
/*
* Update the memcache first .
*/
del_res = dict_memcache_del(dict_mc);
dict->error = dict_mc->error;
/*
* Update the persistent database last .
*/
if (backup) {
del_res = backup->delete (backup, name);
dict->error = backup->error;
}
if (msg_verbose)
msg_info("%s: %s: delete key \" %s\"(%s) => %s" ,
myname, dict_mc->dict.name, name, STR(dict_mc->key_buf),
dict_mc->error ? "(memcache error)" : (backup
&& backup->error) ? "(backup error)" : "(no error)" );
return (del_res);
}
/* dict_memcache_sequence - first/next lookup */
static int dict_memcache_sequence(DICT *dict, int function, const char **key,
const char **value)
{
const char *myname = "dict_memcache_sequence" ;
DICT_MC *dict_mc = (DICT_MC *) dict;
DICT *backup = dict_mc->backup;
int seq_res;
if (backup == 0 ) {
msg_warn("database %s:%s: first/next support requires backup database" ,
DICT_TYPE_MEMCACHE, dict_mc->dict.name);
DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
} else {
seq_res = backup->sequence(backup, function, key, value);
if (msg_verbose)
msg_info("%s: %s: key \" %s\" => %s" ,
myname, dict_mc->dict.name, *key ? *key : "(not found)" ,
*value ? *value : backup->error ? "(backup error)" :
"(not found)" );
DICT_ERR_VAL_RETURN(dict, backup->error, seq_res);
}
}
/* dict_memcache_close - close memcache */
static void dict_memcache_close(DICT *dict)
{
DICT_MC *dict_mc = (DICT_MC *) dict;
cfg_parser_free(dict_mc->parser);
db_common_free_ctx(dict_mc->dbc_ctxt);
if (dict_mc->key_digest)
myfree(dict_mc->key_digest);
#ifdef USE_TLS
if (dict_mc->key_dgst_eng)
ossl_digest_free(dict_mc->key_dgst_eng);
if (dict_mc->key_dgst_out)
vstring_free(dict_mc->key_dgst_out);
#endif
if (dict_mc->key_format)
myfree(dict_mc->key_format);
myfree(dict_mc->memcache);
auto_clnt_free(dict_mc->clnt);
vstring_free(dict_mc->clnt_buf);
vstring_free(dict_mc->key_buf);
vstring_free(dict_mc->res_buf);
if (dict->fold_buf)
vstring_free(dict->fold_buf);
if (dict_mc->backup)
dict_close(dict_mc->backup);
dict_free(dict);
}
/* dict_memcache_open - open memcache */
DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags)
{
DICT_MC *dict_mc;
char *backup;
CFG_PARSER *parser;
/*
* Sanity checks .
*/
if (dict_flags & DICT_FLAG_NO_UNAUTH)
return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags,
"%s:%s map is not allowed for security-sensitive data" ,
DICT_TYPE_MEMCACHE, name));
open_flags &= (O_RDONLY | O_RDWR | O_WRONLY | O_APPEND);
if (open_flags != O_RDONLY && open_flags != O_RDWR)
return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags,
"%s:%s map requires O_RDONLY or O_RDWR access mode" ,
DICT_TYPE_MEMCACHE, name));
/*
* Open the configuration file .
*/
if ((parser = cfg_parser_alloc(name)) == 0 )
return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags,
"open %s: %m" , name));
/*
* Create the dictionary object .
*/
dict_mc = (DICT_MC *) dict_alloc(DICT_TYPE_MEMCACHE, name,
sizeof (*dict_mc));
dict_mc->dict.lookup = dict_memcache_lookup;
if (open_flags == O_RDWR) {
dict_mc->dict.update = dict_memcache_update;
dict_mc->dict.delete = dict_memcache_delete;
}
dict_mc->dict.sequence = dict_memcache_sequence;
dict_mc->dict.close = dict_memcache_close;
dict_mc->dict.flags = dict_flags;
dict_mc->key_buf = vstring_alloc(10 );
dict_mc->res_buf = vstring_alloc(10 );
/*
* Parse the configuration file .
*/
dict_mc->parser = parser;
dict_mc->key_digest = cfg_get_str(dict_mc->parser, DICT_MC_NAME_KEY_DGST,
DICT_MC_DEF_KEY_DGST, 0 , 0 );
#ifdef USE_TLS
if (*dict_mc->key_digest) {
if ((dict_mc->key_dgst_eng = ossl_digest_new(dict_mc->key_digest)) == 0 )
/* See below for dict_surrogate() error propagation. */
ossl_digest_log_errors(msg_warn);
dict_mc->key_dgst_out = vstring_alloc(1 );
} else {
dict_mc->key_dgst_eng = 0 ;
dict_mc->key_dgst_out = 0 ;
}
#endif
dict_mc->key_format = cfg_get_str(dict_mc->parser, DICT_MC_NAME_KEY_FMT,
DICT_MC_DEF_KEY_FMT, 0 , 0 );
dict_mc->timeout = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TIMEOUT,
DICT_MC_DEF_MC_TIMEOUT, 0 , 0 );
dict_mc->mc_ttl = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TTL,
DICT_MC_DEF_MC_TTL, 0 , 0 );
dict_mc->mc_flags = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_FLAGS,
DICT_MC_DEF_MC_FLAGS, 0 , 0 );
dict_mc->err_pause = cfg_get_int(dict_mc->parser, DICT_MC_NAME_ERR_PAUSE,
DICT_MC_DEF_ERR_PAUSE, 1 , 0 );
dict_mc->max_tries = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_TRY,
DICT_MC_DEF_MAX_TRY, 1 , 0 );
dict_mc->max_line = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_LINE,
DICT_MC_DEF_MAX_LINE, 1 , 0 );
dict_mc->max_data = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_DATA,
DICT_MC_DEF_MAX_DATA, 1 , 0 );
dict_mc->memcache = cfg_get_str(dict_mc->parser, DICT_MC_NAME_MEMCACHE,
DICT_MC_DEF_MEMCACHE, 0 , 0 );
/*
* Initialize the memcache client .
*/
dict_mc->clnt = auto_clnt_create(dict_mc->memcache, dict_mc->timeout, 0 , 0 );
dict_mc->clnt_buf = vstring_alloc(100 );
/*
* Open the optional backup database .
*/
backup = cfg_get_str(dict_mc->parser, DICT_MC_NAME_BACKUP,
(char *) 0 , 0 , 0 );
if (backup) {
dict_mc->backup = dict_open(backup, open_flags, dict_flags);
myfree(backup);
} else
dict_mc->backup = 0 ;
/*
* Parse templates and common database parameters . Maps that use
* substring keys should only be used with the full input key .
*/
dict_mc->dbc_ctxt = 0 ;
db_common_parse(&dict_mc->dict, &dict_mc->dbc_ctxt,
dict_mc->key_format, 1 );
db_common_parse_domain(dict_mc->parser, dict_mc->dbc_ctxt);
if (db_common_dict_partial(dict_mc->dbc_ctxt))
/* Breaks recipient delimiters */
dict_mc->dict.flags |= DICT_FLAG_PATTERN;
else
dict_mc->dict.flags |= DICT_FLAG_FIXED;
dict_mc->dict.flags |= DICT_FLAG_MULTI_WRITER;
#ifdef USE_TLS
if (*dict_mc->key_digest && dict_mc->key_dgst_eng == 0 ) {
/* See above for ossl_digest_new() error detection. */
DICT *d = dict_surrogate(DICT_TYPE_MEMCACHE, name,
open_flags, dict_flags,
"open %s: key digest %s is not available" ,
name, dict_mc->key_digest);
dict_memcache_close(&dict_mc->dict);
return (d);
}
#else
if (*dict_mc->key_digest) {
DICT *d = dict_surrogate(DICT_TYPE_MEMCACHE, name,
open_flags, dict_flags, "%s support "
"requires build with -DUSE_TLS" ,
DICT_MC_NAME_KEY_DGST);
dict_memcache_close(&dict_mc->dict);
return (d);
}
#endif
return (&dict_mc->dict);
}
Messung V0.5 in Prozent C=88 H=98 G=93
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland