mirror of https://gitee.com/openkylin/gvfs.git
1261 lines
27 KiB
C
1261 lines
27 KiB
C
#include "config.h"
|
|
#include "metabuilder.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#if HAVE_SYS_STATFS_H
|
|
#include <sys/statfs.h>
|
|
#endif
|
|
#if HAVE_SYS_STATVFS_H
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
#if HAVE_SYS_VFS_H
|
|
#include <sys/vfs.h>
|
|
#elif HAVE_SYS_MOUNT_H
|
|
#if HAVE_SYS_PARAM_H
|
|
#include <sys/param.h>
|
|
#endif
|
|
#include <sys/mount.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_STATFS) && defined(HAVE_STATVFS)
|
|
/* Some systems have both statfs and statvfs, pick the
|
|
most "native" for these */
|
|
# if !defined(HAVE_STRUCT_STATFS_F_BAVAIL)
|
|
/* on solaris and irix, statfs doesn't even have the
|
|
f_bavail field */
|
|
# define USE_STATVFS
|
|
# else
|
|
/* at least on linux, statfs is the actual syscall */
|
|
# define USE_STATFS
|
|
# endif
|
|
|
|
#elif defined(HAVE_STATFS)
|
|
|
|
# define USE_STATFS
|
|
|
|
#elif defined(HAVE_STATVFS)
|
|
|
|
# define USE_STATVFS
|
|
|
|
#endif
|
|
|
|
|
|
#define MAJOR_VERSION 1
|
|
#define MINOR_VERSION 0
|
|
#define MAJOR_JOURNAL_VERSION 1
|
|
#define MINOR_JOURNAL_VERSION 0
|
|
#define NEW_JOURNAL_SIZE (32*1024)
|
|
|
|
#define RANDOM_TAG_OFFSET 12
|
|
#define ROTATED_OFFSET 8
|
|
|
|
#define KEY_IS_LIST_MASK (1<<31)
|
|
|
|
MetaBuilder *
|
|
meta_builder_new (void)
|
|
{
|
|
MetaBuilder *builder;
|
|
|
|
builder = g_new0 (MetaBuilder, 1);
|
|
builder->root = metafile_new ("/", NULL);
|
|
|
|
return builder;
|
|
}
|
|
|
|
void
|
|
meta_builder_free (MetaBuilder *builder)
|
|
{
|
|
if (builder->root)
|
|
metafile_free (builder->root);
|
|
g_free (builder);
|
|
}
|
|
|
|
static gint
|
|
compare_metafile (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
const MetaFile *aa, *bb;
|
|
|
|
aa = a;
|
|
bb = b;
|
|
return strcmp (aa->name, bb->name);
|
|
}
|
|
|
|
static gint
|
|
compare_metadata (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
const MetaData *aa, *bb;
|
|
|
|
aa = a;
|
|
bb = b;
|
|
return strcmp (aa->key, bb->key);
|
|
}
|
|
|
|
static void
|
|
metadata_free (MetaData *data)
|
|
{
|
|
g_free (data->key);
|
|
if (data->is_list)
|
|
g_list_free_full (data->values, g_free);
|
|
else
|
|
g_free (data->value);
|
|
|
|
g_free (data);
|
|
}
|
|
|
|
MetaFile *
|
|
metafile_new (const char *name,
|
|
MetaFile *parent)
|
|
{
|
|
MetaFile *f;
|
|
|
|
f = g_new0 (MetaFile, 1);
|
|
f->name = g_strdup (name);
|
|
f->children = g_sequence_new ((GDestroyNotify)metafile_free);
|
|
f->data = g_sequence_new ((GDestroyNotify)metadata_free);
|
|
if (parent)
|
|
g_sequence_insert_sorted (parent->children, f, compare_metafile, NULL);
|
|
|
|
return f;
|
|
}
|
|
|
|
static MetaData *
|
|
metadata_new (const char *key,
|
|
MetaFile *file)
|
|
{
|
|
MetaData *data;
|
|
|
|
data = g_new0 (MetaData, 1);
|
|
data->key = g_strdup (key);
|
|
|
|
if (file)
|
|
g_sequence_insert_sorted (file->data, data, compare_metadata, NULL);
|
|
|
|
return data;
|
|
}
|
|
|
|
static MetaData *
|
|
metadata_dup (MetaFile *file,
|
|
MetaData *data)
|
|
{
|
|
MetaData *new_data;
|
|
GList *l;
|
|
|
|
new_data = metadata_new (data->key, file);
|
|
|
|
new_data->is_list = data->is_list;
|
|
if (data->is_list)
|
|
{
|
|
for (l = data->values; l != NULL; l = l->next)
|
|
new_data->values =
|
|
g_list_prepend (new_data->values, g_strdup (l->data));
|
|
new_data->values = g_list_reverse (new_data->values);
|
|
}
|
|
else
|
|
new_data->value = g_strdup (data->value);
|
|
|
|
return new_data;
|
|
}
|
|
|
|
void
|
|
metafile_free (MetaFile *file)
|
|
{
|
|
g_free (file->name);
|
|
g_sequence_free (file->children);
|
|
g_sequence_free (file->data);
|
|
g_free (file);
|
|
}
|
|
|
|
MetaFile *
|
|
metafile_lookup_child (MetaFile *metafile,
|
|
const char *name,
|
|
gboolean create)
|
|
{
|
|
MetaFile *child;
|
|
MetaFile lookup_file;
|
|
GSequenceIter *lookup_file_iter;
|
|
|
|
lookup_file.name = (char *)name;
|
|
|
|
lookup_file_iter = g_sequence_lookup (metafile->children,
|
|
&lookup_file,
|
|
compare_metafile,
|
|
NULL);
|
|
|
|
if (lookup_file_iter)
|
|
return g_sequence_get (lookup_file_iter);
|
|
|
|
child = NULL;
|
|
if (create)
|
|
child = metafile_new (name, metafile);
|
|
return child;
|
|
}
|
|
|
|
static MetaFile *
|
|
meta_builder_lookup_with_parent (MetaBuilder *builder,
|
|
const char *path,
|
|
gboolean create,
|
|
MetaFile **parent)
|
|
{
|
|
MetaFile *f, *last;
|
|
const char *element_start;
|
|
char *element;
|
|
|
|
last = NULL;
|
|
f = builder->root;
|
|
while (f)
|
|
{
|
|
while (*path == '/')
|
|
path++;
|
|
|
|
if (*path == 0)
|
|
break; /* Found it! */
|
|
|
|
element_start = path;
|
|
while (*path != 0 && *path != '/')
|
|
path++;
|
|
element = g_strndup (element_start, path - element_start);
|
|
|
|
last = f;
|
|
f = metafile_lookup_child (f, element, create);
|
|
g_free (element);
|
|
}
|
|
|
|
if (parent)
|
|
*parent = last;
|
|
|
|
return f;
|
|
}
|
|
|
|
|
|
MetaFile *
|
|
meta_builder_lookup (MetaBuilder *builder,
|
|
const char *path,
|
|
gboolean create)
|
|
{
|
|
return meta_builder_lookup_with_parent (builder, path, create, NULL);
|
|
}
|
|
|
|
void
|
|
meta_builder_remove (MetaBuilder *builder,
|
|
const char *path,
|
|
guint64 mtime)
|
|
{
|
|
MetaFile *f, *parent;
|
|
|
|
f = meta_builder_lookup_with_parent (builder, path, FALSE, &parent);
|
|
|
|
if (f == NULL)
|
|
return;
|
|
|
|
if (parent != NULL)
|
|
{
|
|
GSequenceIter *iter;
|
|
|
|
iter = g_sequence_lookup (parent->children,
|
|
f,
|
|
compare_metafile,
|
|
NULL);
|
|
g_sequence_remove (iter);
|
|
|
|
if (mtime)
|
|
parent->last_changed = mtime;
|
|
}
|
|
else
|
|
{
|
|
/* Removing root not allowed, just remove children */
|
|
g_sequence_remove_range (g_sequence_get_begin_iter (f->children),
|
|
g_sequence_get_end_iter (f->children));
|
|
if (mtime)
|
|
f->last_changed = mtime;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
meta_file_copy_into (MetaFile *src,
|
|
MetaFile *dest,
|
|
guint64 mtime)
|
|
{
|
|
MetaFile *src_child, *dest_child;
|
|
GSequenceIter *iter;
|
|
|
|
if (mtime)
|
|
dest->last_changed = mtime;
|
|
else
|
|
dest->last_changed = src->last_changed;
|
|
|
|
for (iter = g_sequence_get_begin_iter (src->data);
|
|
iter != g_sequence_get_end_iter (src->data);
|
|
iter = g_sequence_iter_next (iter))
|
|
metadata_dup (dest, g_sequence_get (iter));
|
|
|
|
for (iter = g_sequence_get_begin_iter (src->children);
|
|
iter != g_sequence_get_end_iter (src->children);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
src_child = g_sequence_get (iter);
|
|
dest_child = metafile_new (src_child->name, dest);
|
|
meta_file_copy_into (src_child, dest_child, mtime);
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_builder_copy (MetaBuilder *builder,
|
|
const char *source_path,
|
|
const char *dest_path,
|
|
guint64 mtime)
|
|
{
|
|
MetaFile *src, *dest, *temp;
|
|
|
|
meta_builder_remove (builder, dest_path, mtime);
|
|
|
|
src = meta_builder_lookup (builder, source_path, FALSE);
|
|
if (src == NULL)
|
|
return;
|
|
|
|
temp = metafile_new (NULL, NULL);
|
|
meta_file_copy_into (src, temp, mtime);
|
|
|
|
dest = meta_builder_lookup (builder, dest_path, TRUE);
|
|
g_sequence_free (dest->data);
|
|
g_sequence_free (dest->children);
|
|
dest->data = temp->data;
|
|
dest->children = temp->children;
|
|
dest->last_changed = temp->last_changed;
|
|
|
|
g_free (temp);
|
|
}
|
|
|
|
void
|
|
metafile_set_mtime (MetaFile *file,
|
|
guint64 mtime)
|
|
{
|
|
file->last_changed = mtime;
|
|
}
|
|
|
|
static GSequenceIter *
|
|
metafile_key_lookup_iter (MetaFile *file,
|
|
const char *key)
|
|
{
|
|
MetaData lookup_data;
|
|
|
|
lookup_data.key = (char *)key;
|
|
|
|
return g_sequence_lookup (file->data,
|
|
&lookup_data,
|
|
compare_metadata,
|
|
NULL);
|
|
}
|
|
|
|
MetaData *
|
|
metafile_key_lookup (MetaFile *file,
|
|
const char *key,
|
|
gboolean create)
|
|
{
|
|
MetaData *data;
|
|
GSequenceIter *iter;
|
|
|
|
iter = metafile_key_lookup_iter (file, key);
|
|
if (iter)
|
|
return g_sequence_get (iter);
|
|
|
|
data = NULL;
|
|
if (create)
|
|
data = metadata_new (key, file);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
metadata_clear (MetaData *data)
|
|
{
|
|
if (data->is_list)
|
|
{
|
|
g_list_free_full (data->values, g_free);
|
|
data->values = NULL;
|
|
}
|
|
else
|
|
{
|
|
g_free (data->value);
|
|
}
|
|
}
|
|
|
|
void
|
|
metafile_key_unset (MetaFile *metafile,
|
|
const char *key)
|
|
{
|
|
GSequenceIter *iter;
|
|
|
|
iter = metafile_key_lookup_iter (metafile, key);
|
|
if (iter)
|
|
g_sequence_remove (iter);
|
|
}
|
|
|
|
void
|
|
metafile_key_set_value (MetaFile *metafile,
|
|
const char *key,
|
|
const char *value)
|
|
{
|
|
MetaData *data;
|
|
|
|
data = metafile_key_lookup (metafile, key, TRUE);
|
|
metadata_clear (data);
|
|
data->is_list = FALSE;
|
|
data->value = g_strdup (value);
|
|
}
|
|
|
|
void
|
|
metafile_key_list_set (MetaFile *metafile,
|
|
const char *key)
|
|
{
|
|
MetaData *data;
|
|
|
|
data = metafile_key_lookup (metafile, key, TRUE);
|
|
if (!data->is_list)
|
|
{
|
|
metadata_clear (data);
|
|
data->is_list = TRUE;
|
|
}
|
|
g_list_free_full (data->values, g_free);
|
|
data->values = NULL;
|
|
}
|
|
|
|
void
|
|
metafile_key_list_add (MetaFile *metafile,
|
|
const char *key,
|
|
const char *value)
|
|
{
|
|
MetaData *data;
|
|
|
|
data = metafile_key_lookup (metafile, key, TRUE);
|
|
if (!data->is_list)
|
|
{
|
|
metadata_clear (data);
|
|
data->is_list = TRUE;
|
|
}
|
|
|
|
data->values = g_list_append (data->values, g_strdup (value));
|
|
}
|
|
|
|
static void
|
|
metafile_print (MetaFile *file, int indent, char *parent)
|
|
{
|
|
GSequenceIter *iter;
|
|
GList *v;
|
|
MetaData *data;
|
|
char *dir;
|
|
|
|
if (parent)
|
|
dir = g_strconcat (parent, "/", file->name, NULL);
|
|
else
|
|
dir = g_strdup ("");
|
|
|
|
if (parent)
|
|
{
|
|
g_print ("%*s%s\n", indent, "", dir);
|
|
indent += 3;
|
|
}
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->data);
|
|
iter != g_sequence_get_end_iter (file->data);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
data = g_sequence_get (iter);
|
|
g_print ("%*s%s=", indent, "", data->key);
|
|
if (data->is_list)
|
|
{
|
|
for (v = data->values; v != NULL; v = v->next)
|
|
{
|
|
g_print ("%s", (char *)v->data);
|
|
if (v->next != NULL)
|
|
g_print (", ");
|
|
}
|
|
}
|
|
else
|
|
g_print ("%s", data->value);
|
|
g_print ("\n");
|
|
}
|
|
for (iter = g_sequence_get_begin_iter (file->children);
|
|
iter != g_sequence_get_end_iter (file->children);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
metafile_print (g_sequence_get (iter), indent, dir);
|
|
}
|
|
|
|
g_free (dir);
|
|
}
|
|
|
|
void
|
|
meta_builder_print (MetaBuilder *builder)
|
|
{
|
|
metafile_print (builder->root, 0, NULL);
|
|
}
|
|
|
|
static void
|
|
set_uint32 (GString *s, guint32 offset, guint32 val)
|
|
{
|
|
union {
|
|
guint32 as_int;
|
|
char as_bytes[4];
|
|
} u;
|
|
|
|
u.as_int = GUINT32_TO_BE (val);
|
|
memcpy (s->str + offset, u.as_bytes, 4);
|
|
}
|
|
|
|
static GString *
|
|
append_uint32 (GString *s, guint32 val, guint32 *offset)
|
|
{
|
|
union {
|
|
guint32 as_int;
|
|
char as_bytes[4];
|
|
} u;
|
|
|
|
if (offset)
|
|
*offset = s->len;
|
|
|
|
u.as_int = GUINT32_TO_BE (val);
|
|
|
|
g_string_append_len (s, u.as_bytes, 4);
|
|
|
|
return s;
|
|
}
|
|
|
|
static GString *
|
|
append_time_t (GString *s, gint64 val, MetaBuilder *builder)
|
|
{
|
|
guint32 offset;
|
|
|
|
if (val == 0)
|
|
offset = 0;
|
|
else if (val <= builder->time_t_base)
|
|
offset = 1;
|
|
else
|
|
offset = val - builder->time_t_base;
|
|
|
|
return append_uint32 (s, offset, NULL);
|
|
}
|
|
|
|
static GString *
|
|
append_int64 (GString *s, gint64 val)
|
|
{
|
|
union {
|
|
gint64 as_int;
|
|
char as_bytes[8];
|
|
} u;
|
|
|
|
u.as_int = GINT64_TO_BE (val);
|
|
|
|
g_string_append_len (s, u.as_bytes, 8);
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
metafile_collect_times (MetaFile *file,
|
|
gint64 *time_t_min,
|
|
gint64 *time_t_max)
|
|
{
|
|
GSequenceIter *iter;
|
|
MetaFile *child;
|
|
|
|
if (*time_t_min == 0)
|
|
*time_t_min = file->last_changed;
|
|
else if (file->last_changed != 0 && file->last_changed < *time_t_min)
|
|
*time_t_min = file->last_changed;
|
|
|
|
if (file->last_changed > *time_t_max)
|
|
*time_t_max = file->last_changed;
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->children);
|
|
iter != g_sequence_get_end_iter (file->children);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
child = g_sequence_get (iter);
|
|
metafile_collect_times (child, time_t_min, time_t_max);
|
|
}
|
|
}
|
|
|
|
static void
|
|
metafile_collect_keywords (MetaFile *file,
|
|
GHashTable *hash)
|
|
{
|
|
GSequenceIter *iter;
|
|
MetaData *data;
|
|
MetaFile *child;
|
|
|
|
file->metadata_pointer = 0;
|
|
file->children_pointer = 0;
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->data);
|
|
iter != g_sequence_get_end_iter (file->data);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
data = g_sequence_get (iter);
|
|
g_hash_table_insert (hash, data->key, GINT_TO_POINTER (1));
|
|
}
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->children);
|
|
iter != g_sequence_get_end_iter (file->children);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
child = g_sequence_get (iter);
|
|
metafile_collect_keywords (child, hash);
|
|
}
|
|
}
|
|
|
|
static GHashTable *
|
|
string_block_begin (void)
|
|
{
|
|
return g_hash_table_new (g_str_hash, g_str_equal);
|
|
}
|
|
|
|
static void
|
|
append_string (GString *out,
|
|
const char *string,
|
|
GHashTable *string_block)
|
|
{
|
|
guint32 offset;
|
|
GQueue *offsets;
|
|
|
|
append_uint32 (out, 0xdeaddead, &offset);
|
|
|
|
if (!g_hash_table_lookup_extended (string_block,
|
|
string, NULL,
|
|
(gpointer *)&offsets))
|
|
{
|
|
offsets = g_queue_new ();
|
|
|
|
g_hash_table_insert (string_block,
|
|
(char *)string,
|
|
offsets);
|
|
}
|
|
|
|
g_queue_push_tail (offsets, GUINT_TO_POINTER (offset));
|
|
}
|
|
|
|
static void
|
|
string_block_end (GString *out,
|
|
GHashTable *string_block)
|
|
{
|
|
char *string;
|
|
GQueue *offsets;
|
|
GList *l;
|
|
guint32 string_offset, offset;
|
|
GHashTableIter iter;
|
|
|
|
g_hash_table_iter_init (&iter, string_block);
|
|
while (g_hash_table_iter_next (&iter,
|
|
(gpointer *)&string,
|
|
(gpointer *)&offsets))
|
|
{
|
|
string_offset = out->len;
|
|
g_string_append_len (out, string, strlen (string) + 1);
|
|
for (l = g_queue_peek_head_link (offsets); l != NULL; l = l->next)
|
|
{
|
|
offset = GPOINTER_TO_UINT (l->data);
|
|
set_uint32 (out, offset, string_offset);
|
|
}
|
|
g_queue_free (offsets);
|
|
}
|
|
|
|
g_hash_table_destroy (string_block);
|
|
|
|
/* Pad to 32bit */
|
|
while (out->len % 4 != 0)
|
|
g_string_append_c (out, 0);
|
|
}
|
|
|
|
|
|
static GList *
|
|
stringv_block_begin (void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
guint32 offset;
|
|
GList *strings;
|
|
} StringvInfo;
|
|
|
|
static void
|
|
append_stringv (GString *out,
|
|
GList *strings,
|
|
GList **stringv_block)
|
|
{
|
|
guint32 offset;
|
|
StringvInfo *info;
|
|
|
|
append_uint32 (out, 0xdeaddead, &offset);
|
|
|
|
info = g_new (StringvInfo, 1);
|
|
info->offset = offset;
|
|
info->strings = strings;
|
|
|
|
*stringv_block = g_list_prepend (*stringv_block, info);
|
|
}
|
|
|
|
static void
|
|
stringv_block_end (GString *out,
|
|
GHashTable *string_block,
|
|
GList *stringv_block)
|
|
{
|
|
guint32 table_offset;
|
|
StringvInfo *info;
|
|
GList *l, *s;
|
|
|
|
|
|
for (l = stringv_block; l != NULL; l = l->next)
|
|
{
|
|
info = l->data;
|
|
|
|
table_offset = out->len;
|
|
|
|
append_uint32 (out, g_list_length (info->strings), NULL);
|
|
for (s = info->strings; s != NULL; s = s->next)
|
|
append_string (out, s->data, string_block);
|
|
|
|
set_uint32 (out, info->offset, table_offset);
|
|
|
|
g_free (info);
|
|
}
|
|
|
|
g_list_free (stringv_block);
|
|
|
|
/* Pad to 32bit */
|
|
while (out->len % 4 != 0)
|
|
g_string_append_c (out, 0);
|
|
}
|
|
|
|
static void
|
|
write_children (GString *out,
|
|
MetaBuilder *builder)
|
|
{
|
|
GHashTable *strings;
|
|
MetaFile *child, *file;
|
|
GSequenceIter *iter;
|
|
GQueue *files;
|
|
|
|
files = g_queue_new ();
|
|
|
|
g_queue_push_tail (files, builder->root);
|
|
|
|
while (!g_queue_is_empty (files))
|
|
{
|
|
file = g_queue_pop_head (files);
|
|
|
|
if (file->children == NULL)
|
|
continue; /* No children, skip file */
|
|
|
|
strings = string_block_begin ();
|
|
|
|
if (file->children_pointer != 0)
|
|
set_uint32 (out, file->children_pointer, out->len);
|
|
|
|
append_uint32 (out, g_sequence_get_length (file->children), NULL);
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->children);
|
|
iter != g_sequence_get_end_iter (file->children);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
child = g_sequence_get (iter);
|
|
|
|
/* No mtime, children or metadata, no need for this
|
|
to be in the file */
|
|
if (child->last_changed == 0 &&
|
|
child->children == NULL &&
|
|
child->data == NULL)
|
|
continue;
|
|
|
|
append_string (out, child->name, strings);
|
|
append_uint32 (out, 0, &child->children_pointer);
|
|
append_uint32 (out, 0, &child->metadata_pointer);
|
|
append_time_t (out, child->last_changed, builder);
|
|
|
|
if (child->children)
|
|
g_queue_push_tail (files, child);
|
|
}
|
|
|
|
string_block_end (out, strings);
|
|
}
|
|
|
|
g_queue_free (files);
|
|
}
|
|
|
|
static void
|
|
write_metadata_for_file (GString *out,
|
|
MetaFile *file,
|
|
GList **stringvs,
|
|
GHashTable *strings,
|
|
GHashTable *key_hash)
|
|
{
|
|
GSequenceIter *iter;
|
|
MetaData *data;
|
|
guint32 key;
|
|
|
|
g_assert (file->metadata_pointer != 0);
|
|
set_uint32 (out, file->metadata_pointer, out->len);
|
|
|
|
append_uint32 (out, g_sequence_get_length (file->data), NULL);
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->data);
|
|
iter != g_sequence_get_end_iter (file->data);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
data = g_sequence_get (iter);
|
|
|
|
key = GPOINTER_TO_UINT (g_hash_table_lookup (key_hash, data->key));
|
|
if (data->is_list)
|
|
key |= KEY_IS_LIST_MASK;
|
|
append_uint32 (out, key, NULL);
|
|
if (data->is_list)
|
|
append_stringv (out, data->values, stringvs);
|
|
else
|
|
append_string (out, data->value, strings);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_metadata (GString *out,
|
|
MetaBuilder *builder,
|
|
GHashTable *key_hash)
|
|
{
|
|
GHashTable *strings;
|
|
GList *stringvs;
|
|
MetaFile *child, *file;
|
|
GSequenceIter *iter;
|
|
GQueue *files;
|
|
|
|
/* Root metadata */
|
|
if (builder->root->data != NULL)
|
|
{
|
|
strings = string_block_begin ();
|
|
stringvs = stringv_block_begin ();
|
|
write_metadata_for_file (out, builder->root,
|
|
&stringvs, strings, key_hash);
|
|
stringv_block_end (out, strings, stringvs);
|
|
string_block_end (out, strings);
|
|
}
|
|
|
|
/* the rest, breadth first with all files in one
|
|
dir sharing string block */
|
|
files = g_queue_new ();
|
|
|
|
g_queue_push_tail (files, builder->root);
|
|
|
|
while (!g_queue_is_empty (files))
|
|
{
|
|
file = g_queue_pop_head (files);
|
|
|
|
if (file->children == NULL)
|
|
continue; /* No children, skip file */
|
|
|
|
strings = string_block_begin ();
|
|
stringvs = stringv_block_begin ();
|
|
|
|
for (iter = g_sequence_get_begin_iter (file->children);
|
|
iter != g_sequence_get_end_iter (file->children);
|
|
iter = g_sequence_iter_next (iter))
|
|
{
|
|
child = g_sequence_get (iter);
|
|
|
|
if (child->data != NULL)
|
|
write_metadata_for_file (out, child,
|
|
&stringvs, strings, key_hash);
|
|
|
|
if (child->children != NULL)
|
|
g_queue_push_tail (files, child);
|
|
}
|
|
|
|
stringv_block_end (out, strings, stringvs);
|
|
string_block_end (out, strings);
|
|
}
|
|
|
|
g_queue_free (files);
|
|
}
|
|
|
|
static gboolean
|
|
write_all_data_and_close (int fd, char *data, gsize len)
|
|
{
|
|
gssize written;
|
|
gboolean res;
|
|
|
|
res = FALSE;
|
|
|
|
while (len > 0)
|
|
{
|
|
written = write (fd, data, len);
|
|
|
|
if (written < 0)
|
|
{
|
|
if (errno == EAGAIN)
|
|
continue;
|
|
goto out;
|
|
}
|
|
else if (written == 0)
|
|
goto out; /* WTH? Don't loop forever*/
|
|
|
|
len -= written;
|
|
data += written;
|
|
}
|
|
|
|
if (fsync (fd) == -1)
|
|
goto out;
|
|
|
|
res = TRUE; /* Succeeded! */
|
|
|
|
out:
|
|
if (close (fd) == -1)
|
|
res = FALSE;
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
meta_builder_is_on_nfs (const char *filename)
|
|
{
|
|
#ifdef USE_STATFS
|
|
struct statfs statfs_buffer;
|
|
int statfs_result;
|
|
#elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
|
|
struct statvfs statfs_buffer;
|
|
int statfs_result;
|
|
#endif
|
|
char *dirname;
|
|
gboolean res;
|
|
|
|
dirname = g_path_get_dirname (filename);
|
|
|
|
res = FALSE;
|
|
|
|
#ifdef USE_STATFS
|
|
|
|
# if STATFS_ARGS == 2
|
|
statfs_result = statfs (dirname, &statfs_buffer);
|
|
# elif STATFS_ARGS == 4
|
|
statfs_result = statfs (dirname, &statfs_buffer,
|
|
sizeof (statfs_buffer), 0);
|
|
# endif
|
|
if (statfs_result == 0)
|
|
#ifdef __OpenBSD__
|
|
res = strcmp(statfs_buffer.f_fstypename, MOUNT_NFS) == 0;
|
|
#else
|
|
res = statfs_buffer.f_type == 0x6969;
|
|
#endif
|
|
|
|
#elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
|
|
statfs_result = statvfs (dirname, &statfs_buffer);
|
|
|
|
if (statfs_result == 0)
|
|
res = strcmp (statfs_buffer.f_basetype, "nfs") == 0;
|
|
#endif
|
|
|
|
g_free (dirname);
|
|
|
|
return res;
|
|
}
|
|
|
|
static char *
|
|
get_runtime_journal_dir (const char *tree_filename)
|
|
{
|
|
const char *rd;
|
|
char *dbname;
|
|
char *real_path;
|
|
char *ret;
|
|
|
|
rd = g_get_user_runtime_dir ();
|
|
if (! rd || *rd == '\0')
|
|
return NULL;
|
|
|
|
real_path = g_build_filename (rd, "gvfs-metadata", NULL);
|
|
if (! g_file_test (real_path, G_FILE_TEST_EXISTS))
|
|
{
|
|
if (g_mkdir_with_parents (real_path, 0700) != 0)
|
|
{
|
|
g_free (real_path);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
dbname = g_path_get_basename (tree_filename);
|
|
ret = g_build_filename (real_path, dbname, NULL);
|
|
|
|
g_free (dbname);
|
|
g_free (real_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
meta_builder_get_journal_filename (const char *tree_filename, guint32 random_tag)
|
|
{
|
|
const char *hexdigits = "0123456789abcdef";
|
|
char tag[9];
|
|
int i;
|
|
char *ret;
|
|
char *real_filename = NULL;
|
|
|
|
for (i = 7; i >= 0; i--)
|
|
{
|
|
tag[i] = hexdigits[random_tag % 0x10];
|
|
random_tag >>= 4;
|
|
}
|
|
|
|
tag[8] = 0;
|
|
|
|
if (meta_builder_is_on_nfs (tree_filename))
|
|
{
|
|
/* Put the journal in $XDG_RUNTIME_DIR to avoid file usage from concurrent clients */
|
|
real_filename = get_runtime_journal_dir (tree_filename);
|
|
}
|
|
|
|
if (! real_filename)
|
|
return g_strconcat (tree_filename, "-", tag, ".log", NULL);
|
|
|
|
ret = g_strconcat (real_filename, "-", tag, ".log", NULL);
|
|
g_free (real_filename);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
meta_builder_create_new_journal (const char *filename, guint32 random_tag)
|
|
{
|
|
char *journal_name;
|
|
guint32 size_offset;
|
|
GString *out;
|
|
gsize pos;
|
|
gboolean res;
|
|
|
|
journal_name = meta_builder_get_journal_filename (filename, random_tag);
|
|
|
|
out = g_string_new (NULL);
|
|
|
|
/* HEADER */
|
|
g_string_append_c (out, 0xda);
|
|
g_string_append_c (out, 0x1a);
|
|
g_string_append_c (out, 'j');
|
|
g_string_append_c (out, 'o');
|
|
g_string_append_c (out, 'u');
|
|
g_string_append_c (out, 'r');
|
|
|
|
/* VERSION */
|
|
g_string_append_c (out, MAJOR_JOURNAL_VERSION);
|
|
g_string_append_c (out, MINOR_JOURNAL_VERSION);
|
|
|
|
append_uint32 (out, random_tag, NULL);
|
|
append_uint32 (out, 0, &size_offset);
|
|
append_uint32 (out, 0, NULL); /* Num entries, none so far */
|
|
|
|
pos = out->len;
|
|
|
|
g_string_set_size (out, NEW_JOURNAL_SIZE);
|
|
memset (out->str + pos, 0, out->len - pos);
|
|
|
|
set_uint32 (out, size_offset, out->len);
|
|
|
|
res = g_file_set_contents (journal_name,
|
|
out->str, out->len,
|
|
NULL);
|
|
|
|
g_free (journal_name);
|
|
g_string_free (out, TRUE);
|
|
|
|
return res;
|
|
}
|
|
|
|
static GString *
|
|
metadata_create_static (MetaBuilder *builder,
|
|
guint32 *random_tag_out)
|
|
{
|
|
GString *out;
|
|
GHashTable *hash, *key_hash;
|
|
GHashTableIter iter;
|
|
char *key;
|
|
GList *keys, *l;
|
|
GHashTable *strings;
|
|
guint32 index;
|
|
guint32 attributes_pointer;
|
|
gint64 time_t_min;
|
|
gint64 time_t_max;
|
|
guint32 random_tag, root_name;
|
|
|
|
out = g_string_new (NULL);
|
|
|
|
/* HEADER */
|
|
g_string_append_c (out, 0xda);
|
|
g_string_append_c (out, 0x1a);
|
|
g_string_append_c (out, 'm');
|
|
g_string_append_c (out, 'e');
|
|
g_string_append_c (out, 't');
|
|
g_string_append_c (out, 'a');
|
|
|
|
/* VERSION */
|
|
g_string_append_c (out, MAJOR_VERSION);
|
|
g_string_append_c (out, MINOR_VERSION);
|
|
|
|
append_uint32 (out, 0, NULL); /* Rotated */
|
|
random_tag = g_random_int ();
|
|
*random_tag_out = random_tag;
|
|
append_uint32 (out, random_tag, NULL);
|
|
append_uint32 (out, 0, &builder->root_pointer);
|
|
append_uint32 (out, 0, &attributes_pointer);
|
|
|
|
time_t_min = 0;
|
|
time_t_max = 0;
|
|
metafile_collect_times (builder->root, &time_t_min, &time_t_max);
|
|
|
|
/* Store the base as the min value in use minus one so that
|
|
0 is free to mean "not defined" */
|
|
if (time_t_min != 0)
|
|
time_t_min = time_t_min - 1;
|
|
|
|
/* Pick the base as the minimum, unless that leads to
|
|
a 32bit overflow */
|
|
if (time_t_max - time_t_min > G_MAXUINT32)
|
|
time_t_min = time_t_max - G_MAXUINT32;
|
|
builder->time_t_base = time_t_min;
|
|
append_int64 (out, builder->time_t_base);
|
|
|
|
/* Collect and sort all used keys */
|
|
hash = g_hash_table_new (g_str_hash, g_str_equal);
|
|
metafile_collect_keywords (builder->root, hash);
|
|
g_hash_table_iter_init (&iter, hash);
|
|
keys = NULL;
|
|
while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL))
|
|
keys = g_list_prepend (keys, key);
|
|
g_hash_table_destroy (hash);
|
|
keys = g_list_sort (keys, (GCompareFunc)strcmp);
|
|
|
|
/* Write keys to file and collect mapping for keys */
|
|
set_uint32 (out, attributes_pointer, out->len);
|
|
key_hash = g_hash_table_new (g_str_hash, g_str_equal);
|
|
strings = string_block_begin ();
|
|
append_uint32 (out, g_list_length (keys), NULL);
|
|
for (l = keys, index = 0; l != NULL; l = l->next, index++)
|
|
{
|
|
key = l->data;
|
|
append_string (out, key, strings);
|
|
g_hash_table_insert (key_hash, key, GUINT_TO_POINTER (index));
|
|
}
|
|
string_block_end (out, strings);
|
|
|
|
/* update root pointer */
|
|
set_uint32 (out, builder->root_pointer, out->len);
|
|
|
|
/* Root name */
|
|
append_uint32 (out, 0, &root_name);
|
|
|
|
/* Root child pointer */
|
|
append_uint32 (out, 0, &builder->root->children_pointer);
|
|
|
|
/* Root metadata pointer */
|
|
append_uint32 (out, 0, &builder->root->metadata_pointer);
|
|
|
|
/* Root last changed */
|
|
append_uint32 (out, builder->root->last_changed, NULL);
|
|
|
|
/* Root name */
|
|
set_uint32 (out, root_name, out->len);
|
|
g_string_append_len (out, "/", 2);
|
|
|
|
/* Pad to 32bit */
|
|
while (out->len % 4 != 0)
|
|
g_string_append_c (out, 0);
|
|
|
|
write_children (out, builder);
|
|
write_metadata (out, builder, key_hash);
|
|
|
|
g_hash_table_destroy (key_hash);
|
|
g_list_free (keys);
|
|
|
|
return out;
|
|
}
|
|
|
|
gboolean
|
|
meta_builder_write (MetaBuilder *builder,
|
|
const char *filename)
|
|
{
|
|
GString *out;
|
|
guint32 random_tag;
|
|
int fd, fd2, fd_dir;
|
|
char *tmp_name, *dirname;
|
|
|
|
out = metadata_create_static (builder, &random_tag);
|
|
|
|
tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
|
|
fd = g_mkstemp (tmp_name);
|
|
if (fd == -1)
|
|
goto out;
|
|
|
|
if (!write_all_data_and_close (fd, out->str, out->len))
|
|
goto out;
|
|
|
|
if (!meta_builder_create_new_journal (filename, random_tag))
|
|
goto out;
|
|
|
|
/* Open old file so we can set it rotated */
|
|
fd2 = open (filename, O_RDWR);
|
|
if (g_rename (tmp_name, filename) == -1)
|
|
{
|
|
if (fd2 != -1)
|
|
close (fd2);
|
|
goto out;
|
|
}
|
|
|
|
/* Sync the directory to make sure that the entry in the directory containing
|
|
the new medata file has also reached disk. */
|
|
dirname = g_path_get_dirname (filename);
|
|
fd_dir = open (dirname, O_RDONLY);
|
|
if (fd_dir > -1)
|
|
{
|
|
fsync (fd_dir);
|
|
close (fd_dir);
|
|
}
|
|
g_free (dirname);
|
|
|
|
/* Mark old file (if any) as rotated) */
|
|
if (fd2 != -1)
|
|
{
|
|
guint32 old_tag;
|
|
char *old_log;
|
|
char *data;
|
|
|
|
data = mmap (NULL, RANDOM_TAG_OFFSET + 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd2, 0);
|
|
|
|
if (data != MAP_FAILED)
|
|
{
|
|
old_tag = GUINT32_FROM_BE (*(guint32 *)(data + RANDOM_TAG_OFFSET));
|
|
*(guint32 *)(data + ROTATED_OFFSET) = 0xffffffff;
|
|
munmap (data, RANDOM_TAG_OFFSET + 4);
|
|
close (fd2);
|
|
|
|
old_log = meta_builder_get_journal_filename (filename, old_tag);
|
|
g_unlink (old_log);
|
|
g_free (old_log);
|
|
}
|
|
}
|
|
|
|
g_string_free (out, TRUE);
|
|
g_free (tmp_name);
|
|
return TRUE;
|
|
|
|
out:
|
|
if (fd != -1)
|
|
g_unlink (tmp_name);
|
|
g_string_free (out, TRUE);
|
|
g_free (tmp_name);
|
|
return FALSE;
|
|
}
|