message_uuid_master.c [plain text]
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <time.h>
#include "master.h"
#include "message_uuid_master.h"
#include "xmalloc.h"
struct uuid_info {
unsigned short schema;
unsigned short machine;
unsigned short timestamp_generation;
unsigned long master_start_time;
unsigned long child_counter;
unsigned long count;
};
static struct uuid_info uuid_private;
static void
uuid_info_clear(struct uuid_info *uuid_info)
{
memset(uuid_info, 0, sizeof(struct uuid_info));
}
static int
uuid_info_compare(struct uuid_info *u1, struct uuid_info *u2)
{
return(((u1->schema == u2->schema) &&
(u1->machine == u2->machine) &&
(u1->timestamp_generation == u2->timestamp_generation) &&
(u1->master_start_time==u2->master_start_time) &&
(u1->child_counter == u2->child_counter) &&
(u1->count == u2->count)) ? 1 : 0);
}
#define UUID_SCHEMA_MAX (255)
#define UUID_MACHINE_MAX (255)
#define UUID_TIMESTAMP_GENERATION_MAX (255)
#define UUID_CHILD_COUNTER_MAX (65535)
#define UUID_COUNT_MAX ((256*256*256)-1)
static int
message_uuid_extract(struct message_uuid *uuid)
{
unsigned char *s = &uuid->value[0];
if (uuid_private.schema == 0) {
message_uuid_set_null(uuid);
return(1);
}
if ((uuid_private.schema != 1) ||
(uuid_private.machine > UUID_MACHINE_MAX) ||
(uuid_private.timestamp_generation > UUID_TIMESTAMP_GENERATION_MAX) ||
(uuid_private.count > UUID_COUNT_MAX)) {
message_uuid_set_null(uuid);
return(0);
}
s[0] = (uuid_private.schema & 0xff);
s[1] = (uuid_private.machine & 0xff);
s[2] = (uuid_private.timestamp_generation & 0x00ff);
s[3] = (uuid_private.master_start_time & 0xff000000) >> 24;
s[4] = (uuid_private.master_start_time & 0x00ff0000) >> 16;
s[5] = (uuid_private.master_start_time & 0x0000ff00) >> 8;
s[6] = (uuid_private.master_start_time & 0x000000ff);
s[7] = (uuid_private.child_counter & 0xff00) >> 8;
s[8] = (uuid_private.child_counter & 0x00ff);
s[9] = (uuid_private.count & 0xff0000) >> 24;
s[10] = (uuid_private.count & 0x00ff00) >> 8;
s[11] = (uuid_private.count & 0x0000ff);
return(1);
}
static int
master_value_isnumeric(char *s)
{
while (*s) {
if (!isdigit((unsigned char)*s)) return(0);
s++;
}
return(1);
}
static void
master_chomp(char *s)
{
if (!(s && *s))
return;
while (s[1])
s++;
if (*s == '\n') *s = '\0';
}
#define MASTER_UUID_MAX_LINE (512)
static int
master_uuid_read_worker(struct uuid_info *uuid_info, char *line)
{
char *s;
unsigned long value;
int keylen;
if ((s=strchr(line, '=')) == NULL) return(0);
if (!master_value_isnumeric(s+1)) return(0);
keylen = s-line;
value = strtoul(s+1, NULL, 10);
if (!strncmp(line, "schema", keylen)) {
if (value > UUID_SCHEMA_MAX) return(0);
uuid_info->schema = (unsigned char)value;
return(1);
}
if (!strncmp(line, "machine", keylen)) {
if (value > UUID_MACHINE_MAX) return(0);
uuid_info->machine = (unsigned char)value;
return(1);
}
if (!strncmp(line, "timestamp_generation=", keylen)) {
if (value > UUID_TIMESTAMP_GENERATION_MAX) return(0);
uuid_info->timestamp_generation = value;
return(1);
}
if (!strncmp(line, "master_start_time", keylen)) {
uuid_info->master_start_time = value;
return(1);
}
return(0);
}
static int
master_uuid_read(struct uuid_info *uuid_info, char *filename)
{
FILE *file;
char buf[MASTER_UUID_MAX_LINE];
int error=0;
uuid_info_clear(uuid_info);
if ((file=fopen(filename, "r")) == NULL) {
syslog(LOG_ERR, "Failed to open %s: %m", filename);
return(0);
}
while (fgets(buf, MASTER_UUID_MAX_LINE, file)) {
master_chomp(buf);
if (!master_uuid_read_worker(uuid_info, buf)) {
error = 1;
break;
}
}
fclose(file);
if (error) {
uuid_info_clear(uuid_info);
syslog(LOG_ERR, "Invalid line in %s: %s", filename, buf);
fclose(file);
return(0);
}
if (uuid_info->schema != 1) {
uuid_info_clear(uuid_info);
syslog(LOG_ERR, "Invalid schema in %s", filename);
return(0);
}
uuid_info->child_counter = 0;
uuid_info->count = 0;
return(1);
}
static int
master_uuid_write(struct uuid_info *uuid_info, char *filename)
{
FILE *file;
if ((file=fopen(filename, "w")) == NULL)
return(0);
fprintf(file, "schema=%lu\n",
(unsigned long)uuid_info->schema);
fprintf(file, "machine=%lu\n",
(unsigned long)uuid_info->machine);
fprintf(file, "timestamp_generation=%lu\n",
(unsigned long)uuid_info->timestamp_generation);
fprintf(file, "master_start_time=%lu\n",
(unsigned long)uuid_info->master_start_time);
if (fflush(file) || fsync(fileno(file))) {
fclose(file);
return(0);
}
fclose(file);
return(1);
}
static int
master_uuid_write_and_test(struct uuid_info *uuid_info)
{
struct uuid_info uuid_tmp;
int code = 1;
char *nfname = (char *)xmalloc(strlen(config_dir) + strlen(MASTER_UUID_FILE)+6);
char *fname = (char *)xmalloc(strlen(config_dir) + strlen(MASTER_UUID_FILE)+2);
sprintf(fname, "%s/%s", config_dir, MASTER_UUID_FILE);
sprintf(nfname, "%s/%s.NEW", config_dir, MASTER_UUID_FILE);
uuid_info_clear(&uuid_tmp);
if (!master_uuid_write(uuid_info, nfname)) {
uuid_info_clear(uuid_info);
code = 0;
goto uuidout;
}
if (!master_uuid_read(&uuid_tmp, nfname)) {
syslog(LOG_ERR, "Failed to read in %s: %m",
nfname);
code = 0;
goto uuidout;
}
if (!uuid_info_compare(&uuid_private, &uuid_tmp)) {
syslog(LOG_ERR, "Sanity check failed on %s",
nfname);
code = 0;
goto uuidout;
}
if (rename(nfname, fname) < 0) {
syslog(LOG_ERR, "Failed to commit: %s -> %s: %m",
nfname, fname);
code = 0;
goto uuidout;
}
uuidout:
free(fname);
free(nfname);
return(code);
}
int
message_uuid_master_init()
{
unsigned long machine = config_getint(IMAPOPT_SYNC_MACHINEID);
char *ufname;
struct stat sbuf;
int r;
if (machine < 0) return (0);
uuid_info_clear(&uuid_private);
ufname = (char *) xmalloc(strlen(config_dir) + strlen(MASTER_UUID_FILE)+2);
sprintf(ufname, "%s/%s", config_dir, MASTER_UUID_FILE);
r = stat(ufname, &sbuf);
if (r == 0) {
r = master_uuid_read(&uuid_private, ufname);
free(ufname);
if (r == 0) return(0);
if (uuid_private.machine != machine) {
syslog(LOG_ERR, "Machine mismatch: %lu != %lu",
(unsigned long)machine,
(unsigned long)uuid_private.machine);
return(0);
}
if (uuid_private.master_start_time >= time(NULL))
return(0);
}
else if (errno == ENOENT) {
uuid_private.schema = 1;
uuid_private.machine = machine;
free(ufname);
}
else {
syslog(LOG_ERR, "Failed to stat %s: %m", ufname);
free(ufname);
return(0);
}
uuid_private.master_start_time = time(NULL);
uuid_private.child_counter = 0;
uuid_private.count = 0;
if (!master_uuid_write_and_test(&uuid_private)) {
uuid_info_clear(&uuid_private);
return(0);
}
return(1);
}
int
message_uuid_master_next_child(struct message_uuid *uuid)
{
if (uuid_private.schema != 1)
return(0);
uuid_private.child_counter++;
if (uuid_private.child_counter > UUID_CHILD_COUNTER_MAX) {
while (uuid_private.master_start_time >= time(NULL))
sleep(1);
uuid_private.master_start_time++;
uuid_private.child_counter = 0;
if (!master_uuid_write_and_test(&uuid_private)) {
uuid_info_clear(&uuid_private);
return(0);
}
}
if (!message_uuid_extract(uuid)) {
uuid_info_clear(&uuid_private);
return(0);
}
return(1);
}
int
message_uuid_master_checksum(struct message_uuid *uuid)
{
unsigned char *s = &uuid->value[0];
unsigned long count = 0;
count += (s[0] << 16) + (s[1] << 8) + s[2];
count += (s[3] << 16) + (s[4] << 8) + s[5];
count += (s[6] << 16) + (s[7] << 8) + s[8];
count &= 0x00ffffff;
s[9] = (count & 0xff0000) >> 16;
s[10] = (count & 0x00ff00) >> 8;
s[11] = (count & 0x0000ff);
return(1);
}
int
message_uuid_set_null(struct message_uuid *dst)
{
memset(dst, 0, MESSAGE_UUID_SIZE);
return(1);
}
char *
message_uuid_text(struct message_uuid *uuid)
{
static char buf[MESSAGE_UUID_TEXT_SIZE+1];
static char *hex = "0123456789abcdef";
unsigned char *value = &uuid->value[0];
char *p = buf;
int i;
for (i = 0 ; i < MESSAGE_UUID_SIZE ; i++) {
*p++ = hex[(value[i] & 0xf0) >> 4];
*p++ = hex[value[i] & 0x0f];
}
*p = '\0';
return(buf);
}