blob: bee74f14c20c72a6cfa16870cb681d6b03bfbc26 [file] [log] [blame]
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <glib.h>
#include <db.h>
#include "dbfs.h"
struct dbfs_lookup_info {
const char *name;
size_t namelen;
guint64 *ino;
};
struct dbfs_dirscan_info {
const char *name;
size_t namelen;
void *start_ent;
void *end_ent;
};
static int dbmeta_del(const char *key_str)
{
DBT key;
int rc;
key.data = (void *) key_str;
key.size = strlen(key_str);
/* delete key 'key_str' from metadata database */
rc = gfs->meta->del(gfs->meta, NULL, &key, 0);
if (rc == DB_NOTFOUND)
return -ENOENT;
if (rc)
return -EIO;
return 0;
}
static int dbfs_mode_type(guint32 mode, enum dbfs_inode_type *itype)
{
if (S_ISDIR(mode))
*itype = IT_DIR;
else if (S_ISCHR(mode) || S_ISBLK(mode))
*itype = IT_DEV;
else if (S_ISFIFO(mode))
*itype = IT_FIFO;
else if (S_ISLNK(mode))
*itype = IT_SYMLINK;
else if (S_ISSOCK(mode))
*itype = IT_SOCKET;
else if (S_ISREG(mode))
*itype = IT_REG;
else
return -EINVAL;
return 0;
}
int dbfs_inode_del(struct dbfs_inode *ino)
{
guint64 ino_n = GUINT64_FROM_LE(ino->raw_inode->ino);
char key[32];
int rc, rrc;
sprintf(key, "/inode/%Lu", (unsigned long long) ino_n);
rrc = dbmeta_del(key);
switch (ino->type) {
case IT_REG:
/* FIXME */
break;
case IT_DIR:
sprintf(key, "/dir/%Lu", (unsigned long long) ino_n);
rc = dbmeta_del(key);
if (rc && !rrc)
rrc = rc;
break;
case IT_SYMLINK:
sprintf(key, "/symlink/%Lu", (unsigned long long) ino_n);
rc = dbmeta_del(key);
if (rc && !rrc)
rrc = rc;
break;
case IT_DEV:
case IT_FIFO:
case IT_SOCKET:
/* nothing additional to delete */
break;
}
return rrc;
}
int dbfs_inode_read(guint64 ino_n, struct dbfs_inode **ino_out)
{
int rc;
DBT key, val;
char key_str[32];
struct dbfs_inode *ino;
size_t ex_sz;
guint32 mode;
memset(&key, 0, sizeof(key));
memset(&val, 0, sizeof(val));
sprintf(key_str, "/inode/%Lu", (unsigned long long) ino_n);
key.data = key_str;
key.size = strlen(key_str);
val.flags = DB_DBT_MALLOC;
rc = gfs->meta->get(gfs->meta, NULL, &key, &val, 0);
if (rc == DB_NOTFOUND)
return -ENOENT;
if (rc)
return rc;
/* calculate size of struct dbfs_extent area */
ex_sz = val.size - sizeof(struct dbfs_raw_inode);
/* initialize runtime information about the inode */
ino = g_new(struct dbfs_inode, 1);
ino->n_extents = ex_sz / sizeof(struct dbfs_extent);
ino->raw_ino_size = val.size;
ino->raw_inode = val.data;
/* deduce inode type */
mode = GUINT32_FROM_LE(ino->raw_inode->mode);
rc = dbfs_mode_type(mode, &ino->type);
g_assert(rc == 0);
*ino_out = ino;
return 0;
}
static int dbfs_inode_next(struct dbfs_inode **ino_out)
{
struct dbfs_inode *ino;
int rc;
guint64 curtime, start = gfs->next_inode;
*ino_out = NULL;
/* loop through inode numbers starting at gfs->next_inode,
* and stop on first error. Error ENOENT means the
* inode number was not found, and can therefore be used
* as the next free inode number.
*/
while (1) {
rc = dbfs_inode_read(gfs->next_inode, &ino);
if (rc)
break;
dbfs_inode_free(ino);
gfs->next_inode++;
if (gfs->next_inode < 2)
gfs->next_inode = 2;
if (gfs->next_inode == start) { /* loop */
rc = -EBUSY;
break;
}
}
if (rc != -ENOENT)
return rc;
/* allocate an empty inode */
ino = g_new0(struct dbfs_inode, 1);
ino->raw_ino_size = sizeof(struct dbfs_raw_inode);
ino->raw_inode = malloc(ino->raw_ino_size);
memset(ino->raw_inode, 0, ino->raw_ino_size);
ino->raw_inode->ino = GUINT64_TO_LE(gfs->next_inode++);
curtime = GUINT64_TO_LE(time(NULL));
ino->raw_inode->ctime = curtime;
ino->raw_inode->atime = curtime;
ino->raw_inode->mtime = curtime;
*ino_out = ino;
return 0;
}
int dbfs_dir_read(guint64 ino, DBT *val)
{
DBT key;
char key_str[32];
int rc;
memset(&key, 0, sizeof(key));
memset(val, 0, sizeof(*val));
sprintf(key_str, "/dir/%Lu", (unsigned long long) ino);
key.data = key_str;
key.size = strlen(key_str);
val->flags = DB_DBT_MALLOC;
rc = gfs->meta->get(gfs->meta, NULL, &key, val, 0);
if (rc == DB_NOTFOUND)
return -ENOTDIR;
return rc ? -EIO : 0;
}
int dbfs_dir_foreach(void *dir, dbfs_dir_actor_t func, void *userdata)
{
struct dbfs_dirent *de;
void *p;
int rc = 0;
unsigned int namelen;
p = dir;
while (1) {
de = p;
g_assert (de->magic == DBFS_DE_MAGIC);
if (!de->namelen)
break;
/* send dirent to callback function */
rc = func(de, userdata);
if (rc)
break;
/* align things so that compiler structures
* do not wind up misaligned.
*/
namelen = GUINT16_FROM_LE(de->namelen);
p += sizeof(struct dbfs_dirent) + namelen +
(4 - (namelen & 0x3));
}
return rc;
}
static int dbfs_dir_scan1(struct dbfs_dirent *de, void *userdata)
{
struct dbfs_dirscan_info *di = userdata;
if (!di->start_ent) {
if ((GUINT16_FROM_LE(de->namelen) == di->namelen) &&
(!memcmp(de->name, di->name, di->namelen)))
di->start_ent = de;
}
else if (!di->end_ent) {
di->end_ent = de;
return 1;
}
return 0;
}
int dbfs_dir_lookup(guint64 parent, const char *name, guint64 *ino)
{
struct dbfs_dirscan_info di;
struct dbfs_dirent *de;
DBT val;
int rc;
*ino = 0;
/* read directory from database */
rc = dbfs_dir_read(parent, &val);
if (rc)
return rc;
memset(&di, 0, sizeof(di));
di.name = name;
di.namelen = strlen(name);
/* query pointer to start of matching dirent */
rc = dbfs_dir_foreach(val.data, dbfs_dir_scan1, &di);
if (!rc || !di.start_ent) {
rc = -ENOENT;
goto out;
}
if (rc != 1)
goto out;
/* if match found, return inode number */
de = di.start_ent;
*ino = de->ino;
out:
free(val.data);
return rc;
}
static int dbfs_dirent_del(guint64 parent, const char *name)
{
struct dbfs_dirscan_info ui;
DBT dir_val;
int rc, del_len, tail_len;
rc = dbfs_dir_read(parent, &dir_val);
if (rc)
return rc;
memset(&ui, 0, sizeof(ui));
ui.name = name;
ui.namelen = strlen(name);
/* query pointer to start of matching dirent */
rc = dbfs_dir_foreach(dir_val.data, dbfs_dir_scan1, &ui);
if (rc != 1) {
free(dir_val.data);
return -ENOENT;
}
del_len = ui.end_ent - ui.start_ent;
tail_len = (dir_val.data + dir_val.size) - ui.end_ent;
memmove(ui.start_ent, ui.end_ent, tail_len);
dir_val.size -= del_len;
rc = dbfs_dir_write(parent, &dir_val);
free(dir_val.data);
return rc;
}
static int dbfs_dir_find_last(struct dbfs_dirent *de, void *userdata)
{
struct dbfs_dirscan_info *di = userdata;
di->end_ent = de;
return 0;
}
static int dbfs_dir_append(guint64 parent, guint64 ino_n, const char *name)
{
struct dbfs_dirscan_info di;
struct dbfs_dirent *de;
DBT val;
int rc;
unsigned int dir_size, namelen;
void *p;
/* read parent directory from database */
rc = dbfs_dir_read(parent, &val);
if (rc)
return rc;
memset(&di, 0, sizeof(di));
di.name = name;
di.namelen = strlen(name);
/* scan for name in directory, abort if found */
rc = dbfs_dir_foreach(val.data, dbfs_dir_scan1, &di);
if (rc != -ENOENT) {
if (rc == 0)
rc = -EEXIST;
goto out;
}
/* get pointer to last entry */
rc = dbfs_dir_foreach(val.data, dbfs_dir_find_last, &di);
if (rc)
goto out;
/* adjust pointer 'p' to point to terminator entry */
de = p = di.end_ent;
namelen = GUINT16_FROM_LE(de->namelen);
p += sizeof(struct dbfs_dirent) + namelen +
(4 - (namelen & 0x3));
/* increase directory data area size */
dir_size = p - val.data;
val.size = dir_size + (3 * sizeof(struct dbfs_dirent)) + di.namelen;
val.data = realloc(val.data, val.size);
p = val.data + dir_size;
/* append directory entry */
de = p;
de->magic = GUINT32_TO_LE(DBFS_DE_MAGIC);
de->namelen = GUINT16_TO_LE(di.namelen);
de->ino = GUINT64_TO_LE(ino_n);
memcpy(de->name, name, di.namelen);
namelen = di.namelen;
p += sizeof(struct dbfs_dirent) + namelen +
(4 - (namelen & 0x3));
/* append terminator entry */
de = p;
memset(de, 0, sizeof(*de));
de->magic = GUINT32_TO_LE(DBFS_DE_MAGIC);
namelen = 0;
p += sizeof(struct dbfs_dirent) + namelen +
(4 - (namelen & 0x3));
val.size = p - val.data;
rc = dbfs_dir_write(parent, &val);
out:
free(val.data);
return rc;
}
int dbfs_symlink_read(guint64 ino, DBT *val)
{
DBT key;
char key_str[32];
int rc;
memset(&key, 0, sizeof(key));
memset(val, 0, sizeof(*val));
sprintf(key_str, "/symlink/%Lu", (unsigned long long) ino);
key.data = key_str;
key.size = strlen(key_str);
val->flags = DB_DBT_MALLOC;
rc = gfs->meta->get(gfs->meta, NULL, &key, val, 0);
if (rc == DB_NOTFOUND)
return -EINVAL;
return rc ? -EIO : 0;
}
int dbfs_symlink_write(guint64 ino, const char *link)
{
DBT key, val;
char key_str[32];
memset(&key, 0, sizeof(key));
memset(&val, 0, sizeof(val));
sprintf(key_str, "/symlink/%Lu", (unsigned long long) ino);
key.data = key_str;
key.size = strlen(key_str);
val.data = (void *) link;
val.size = strlen(link);
return gfs->meta->put(gfs->meta, NULL, &key, &val, 0) ? -EIO : 0;
}
int dbfs_unlink(guint64 parent, const char *name, unsigned long flags)
{
struct dbfs_inode *ino;
guint64 ino_n;
int rc, is_dir;
guint32 nlink;
rc = dbfs_dir_lookup(parent, name, &ino_n);
if (rc)
goto out;
if (ino_n == DBFS_ROOT_INO) {
rc = -EINVAL;
goto out;
}
rc = dbfs_inode_read(ino_n, &ino);
if (rc)
goto out;
is_dir = S_ISDIR(GUINT32_FROM_LE(ino->raw_inode->mode));
if (is_dir && (!(flags & DBFS_UNLINK_DIR))) {
rc = -EISDIR;
goto out_ino;
}
rc = dbfs_dirent_del(parent, name);
if (rc)
goto out_ino;
nlink = GUINT32_FROM_LE(ino->raw_inode->nlink);
if (is_dir && (nlink <= 2))
nlink = 0;
else
nlink--;
ino->raw_inode->nlink = GUINT32_TO_LE(nlink);
if (!nlink)
rc = dbfs_inode_del(ino);
else
rc = dbfs_inode_write(ino);
out_ino:
dbfs_inode_free(ino);
out:
return rc;
}
static int dbfs_name_validate(const char *name)
{
if (strchr(name, '/'))
return -EINVAL;
if (!g_utf8_validate(name, -1, NULL))
return -EINVAL;
return 0;
}
int dbfs_mknod(guint64 parent, const char *name, guint32 mode, guint64 rdev,
struct dbfs_inode **ino_out)
{
struct dbfs_inode *ino;
int rc, is_dir;
unsigned int nlink = 1;
guint64 ino_n;
*ino_out = NULL;
rc = dbfs_name_validate(name);
if (rc)
return rc;
rc = dbfs_inode_next(&ino);
if (rc)
return rc;
is_dir = S_ISDIR(mode);
if (is_dir)
nlink++;
ino_n = GUINT64_FROM_LE(ino->raw_inode->ino);
ino->raw_inode->mode = GUINT32_TO_LE(mode);
ino->raw_inode->nlink = GUINT32_TO_LE(nlink);
ino->raw_inode->rdev = GUINT64_TO_LE(rdev);
rc = dbfs_inode_write(ino);
if (rc)
goto err_out;
if (is_dir) {
rc = dbfs_dir_new(parent, ino_n, ino);
if (rc)
goto err_out_del;
}
rc = dbfs_dir_append(parent, ino_n, name);
if (rc)
goto err_out_del;
*ino_out = ino;
return 0;
err_out_del:
dbfs_inode_del(ino);
err_out:
dbfs_inode_free(ino);
return rc;
}