[Cluster-devel] [PATCH] libgfs2: Add a gfs2 block query language

Andrew Price anprice at redhat.com
Fri Oct 12 18:51:21 UTC 2012


This patch adds a small language which we can use to write libgfs2 and
gfs2-utils tests (among other things). It provides 'get' and 'set'
statements which look up and modify gfs2 blocks. The language API is
defined as:

   struct lgfs2_lang_state;

   struct lgfs2_lang_result {
           uint64_t lr_blocknr;
           struct gfs2_buffer_head *lr_bh;
           const struct lgfs2_metadata *lr_mtype;
           int lr_state; // GFS2_BLKST_*
   };

   struct lgfs2_lang_state *lgfs2_lang_init(void);
   void lgfs2_lang_free(struct lgfs2_lang_state **state);

   int lgfs2_lang_parsef(struct lgfs2_lang_state *state, FILE *script);
   int lgfs2_lang_parses(struct lgfs2_lang_state *state, const char *script);

   struct lgfs2_lang_result *lgfs2_lang_result_next(struct lgfs2_lang_state *state, struct gfs2_sbd *sbd);
   int lgfs2_lang_result_print(struct lgfs2_lang_result *result);
   void lgfs2_lang_result_free(struct lgfs2_lang_result **result);

The lgfs2_lang_parse{s,f} functions allow you to parse a string or a
file respectively. Using the same state object you can then run the same
script multiple times, without parsing it again, using
lgfs2_lang_interpret(). The intended usage of these functions can be
shown with a simple example (error checking omitted):

   struct lgfs2_lang_state *state = lgfs2_lang_init();
   lgfs2_lang_parses(state, "get sb; get 1234 state; set '/foo/bar' {di_entries: 3}");
   for (result = lgfs2_lang_result_next(state, &sbd);
        result != NULL;
        result = lgfs2_lang_result_next(state, &sbd)) {
           lgfs2_lang_result_print(result);
           lgfs2_lang_result_free(&result);
   }
   lgfs2_lang_free(&state);

The language has a simple syntax:

   get <block_lookup> [state]
   set <block_lookup> {<field0>:<value0>, <field1>:<value1>, ... }

A block lookup can be a file system block address (1234 or 0x1234), a
resource group subscript (rgrp[0], rgrp[0x5]), a keyword (sb, master,
root, rindex) or an offset from any of the above (1234+5, rgrp[1]+23,
rindex+68, ...).

After the block lookup, the 'get' statement takes an optional keyword
'state' which allows you to query the bitmap state of the block.

Examples of 'get':

   get rgrp[1]+23 state
   --> result.lr_state == GFS2_BLKST_FREE

   get rindex
   --> result.lr_bh is a buffer containing the rindex dinode.
   --> result.lr_mtype is the block's metatype as defined in meta.c.

The 'set' statement requires a list of field-value pairs which are to be
modified in the block. The field names must match the names shown when
running the 'get' statement with the same block lookup.

Examples of 'set':

   set '/foo/bar/baz' { di_entries: 3 }
   --> result.lr_bh contains the dinode block at path /foo/bar/baz, with
       the di_entries field modified.
   --> result.lr_mtype is the block's metatype as defined in meta.c.

   set sb { sb_lockproto: 'lock_dlm', sb_bsize: 0x1000 }
   --> result.lr_bh contains the superblock with the lockproto changed to
       lock_dlm and the block size changed to 4096.
   --> result.lr_mtype is the block's metatype as defined in meta.c.

Whitespace is insignificant in the language and semicolons can be used
to separate multiple statements. When writing longer scripts spanning
multiple lines, C-like // syntax can be used to insert comments.

Signed-off-by: Andrew Price <anprice at redhat.com>
---
 .gitignore               |   7 +
 configure.ac             |   2 +
 gfs2/libgfs2/Makefile.am |  13 +-
 gfs2/libgfs2/buf.c       |  10 +
 gfs2/libgfs2/fs_ops.c    |   6 +-
 gfs2/libgfs2/lang.c      | 603 +++++++++++++++++++++++++++++++++++++++++++++++
 gfs2/libgfs2/lang.h      |  61 +++++
 gfs2/libgfs2/lexer.l     | 101 ++++++++
 gfs2/libgfs2/libgfs2.h   |  24 ++
 gfs2/libgfs2/meta.c      |  50 ++++
 gfs2/libgfs2/misc.c      |   2 -
 gfs2/libgfs2/parser.y    | 186 +++++++++++++++
 gfs2/libgfs2/super.c     |   2 +-
 13 files changed, 1057 insertions(+), 10 deletions(-)
 create mode 100644 gfs2/libgfs2/lang.c
 create mode 100644 gfs2/libgfs2/lang.h
 create mode 100644 gfs2/libgfs2/lexer.l
 create mode 100644 gfs2/libgfs2/parser.y

diff --git a/.gitignore b/.gitignore
index e8b7ea1..4e3071a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,9 @@ make/stamp-h1
 m4
 make/clusterautoconfig.h*
 missing
+ylwrap
+cscope.out
+.gdb_history
 *.pc
 .deps
 .libs
@@ -29,6 +32,10 @@ missing
 *.lo
 gfs2/convert/gfs2_convert
 gfs2/edit/gfs2_edit
+gfs2/libgfs2/parser.c
+gfs2/libgfs2/parser.h
+gfs2/libgfs2/lexer.c
+gfs2/libgfs2/lexer.h
 gfs2/fsck/fsck.gfs2
 gfs2/mkfs/mkfs.gfs2
 gfs2/mount/mount.gfs2
diff --git a/configure.ac b/configure.ac
index d56cfac..ef09569 100644
--- a/configure.ac
+++ b/configure.ac
@@ -56,6 +56,8 @@ AM_PROG_CC_C_O
 AC_PROG_LN_S
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
+AC_PROG_LEX
+AC_PROG_YACC
 
 ## local helper functions
 
diff --git a/gfs2/libgfs2/Makefile.am b/gfs2/libgfs2/Makefile.am
index 4e60b0a..7103e09 100644
--- a/gfs2/libgfs2/Makefile.am
+++ b/gfs2/libgfs2/Makefile.am
@@ -1,15 +1,24 @@
 MAINTAINERCLEANFILES	= Makefile.in
 
-noinst_HEADERS		= libgfs2.h
+CLEANFILES		= parser.h parser.c lexer.c lexer.h
+BUILT_SOURCES		= parser.h lexer.h
+AM_LFLAGS		= --header-file=lexer.h
+AM_YFLAGS		= -d
+
+noinst_HEADERS		= libgfs2.h lang.h
 
 noinst_LTLIBRARIES	= libgfs2.la
 
 libgfs2_la_SOURCES	= block_list.c fs_bits.c gfs1.c misc.c rgrp.c super.c \
 			  buf.c fs_geometry.c gfs2_disk_hash.c ondisk.c \
 			  device_geometry.c fs_ops.c gfs2_log.c recovery.c \
-			  structures.c meta.c
+			  structures.c meta.c lang.c parser.y lexer.l
 
 libgfs2_la_CPPFLAGS	= -D_FILE_OFFSET_BITS=64 \
 			  -D_LARGEFILE64_SOURCE \
 			  -D_GNU_SOURCE \
 			  -I$(top_srcdir)/gfs2/include
+
+# Autotools can't handle header files output by flex so we have to generate it manually
+lexer.h: lexer.l
+	$(LEX) -o lexer.c $(AM_LFLAGS) $^
diff --git a/gfs2/libgfs2/buf.c b/gfs2/libgfs2/buf.c
index 956dd8b..5bc1a4e 100644
--- a/gfs2/libgfs2/buf.c
+++ b/gfs2/libgfs2/buf.c
@@ -83,3 +83,13 @@ int brelse(struct gfs2_buffer_head *bh)
 	free(bh);
 	return error;
 }
+
+uint32_t lgfs2_get_block_type(const struct gfs2_buffer_head *lbh)
+{
+	const struct gfs2_meta_header *mh = lbh->iov.iov_base;
+
+	if (be32_to_cpu(mh->mh_magic) == GFS2_MAGIC)
+		return be32_to_cpu(mh->mh_type);
+
+	return 0;
+}
diff --git a/gfs2/libgfs2/fs_ops.c b/gfs2/libgfs2/fs_ops.c
index ec150e8..3d027e8 100644
--- a/gfs2/libgfs2/fs_ops.c
+++ b/gfs2/libgfs2/fs_ops.c
@@ -1759,11 +1759,7 @@ int gfs2_lookupi(struct gfs2_inode *dip, const char *filename, int len,
 		return 0;
 	}
 	error = dir_search(dip, filename, len, NULL, &inum);
-	if (error) {
-		if (error == -ENOENT)
-			return 0;
-	}
-	else
+	if (!error)
 		*ipp = lgfs2_inode_read(sdp, inum.no_addr);
 
 	return error;
diff --git a/gfs2/libgfs2/lang.c b/gfs2/libgfs2/lang.c
new file mode 100644
index 0000000..12ca7bd
--- /dev/null
+++ b/gfs2/libgfs2/lang.c
@@ -0,0 +1,603 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "parser.h"
+#include "lang.h"
+
+const char* ast_type_string[] = {
+	[AST_NONE] = "NONE",
+	// Statements
+	[AST_ST_SET] = "SET",
+	[AST_ST_GET] = "GET",
+
+	// Expressions
+	[AST_EX_ID] = "IDENTIFIER",
+	[AST_EX_NUMBER] = "NUMBER",
+	[AST_EX_STRING] = "STRING",
+	[AST_EX_ADDRESS] = "ADDRESS",
+	[AST_EX_PATH] = "PATH",
+	[AST_EX_SUBSCRIPT] = "SUBSCRIPT",
+	[AST_EX_OFFSET] = "OFFSET",
+	[AST_EX_BLOCKSPEC] = "BLOCKSPEC",
+	[AST_EX_STRUCTSPEC] = "STRUCTSPEC",
+	[AST_EX_FIELDSPEC] = "FIELDSPEC",
+
+	// Keywords
+	[AST_KW_STATE] = "STATE",
+};
+
+/**
+ * Initialize an expression node of the given type from a source string.
+ * Currently just converts numerical values and string values where
+ * appropriate. String values are duplicted into newly allocated buffers as the
+ * text from the parser will go away.
+ * Returns 0 on success or non-zero with errno set on failure
+ */
+static int ast_expr_init(struct ast_node *expr, ast_node_t type, const char *str)
+{
+	int ret = 0;
+	switch (type) {
+	case AST_EX_OFFSET:
+		str++; // Cut off the +
+	case AST_EX_NUMBER:
+		ret = sscanf(str, "%"SCNi64, &expr->ast_num);
+		if (ret != 1) {
+			return 1;
+		}
+		break;
+	case AST_EX_ID:
+	case AST_EX_PATH:
+	case AST_EX_STRING:
+		expr->ast_str = strdup(str);
+		if (expr->ast_str == NULL) {
+			return 1;
+		}
+		break;
+	case AST_EX_ADDRESS:
+	case AST_EX_SUBSCRIPT:
+	case AST_EX_BLOCKSPEC:
+	case AST_EX_STRUCTSPEC:
+	case AST_EX_FIELDSPEC:
+	case AST_KW_STATE:
+		break;
+	default:
+		errno = EINVAL;
+		return 1;
+	}
+	return 0;
+}
+
+/**
+ * Create a new AST node of a given type from a source string.
+ * Returns a pointer to the new node or NULL on failure with errno set.
+ */
+struct ast_node *ast_new(ast_node_t type, const char *text)
+{
+	struct ast_node *node;
+	node = (struct ast_node *)calloc(1, sizeof(struct ast_node));
+	if (node == NULL) {
+		goto return_fail;
+	}
+
+	if (type > _AST_EX_START && ast_expr_init(node, type, text)) {
+		goto return_free;
+	}
+
+	node->ast_text = strdup(text);
+	if (node->ast_text == NULL) {
+		goto return_free;
+	}
+	node->ast_type = type;
+
+	return node;
+
+return_free:
+	if (node->ast_text) {
+		free(node->ast_text);
+	}
+	if (node->ast_str) {
+		free(node->ast_str);
+	}
+	free(node);
+return_fail:
+	fprintf(stderr, "Failed to create new value from %s: %s\n", text, strerror(errno));
+	return NULL;
+}
+
+/**
+ * Free the memory allocated for an AST node and set its pointer to NULL
+ */
+void ast_destroy(struct ast_node **node)
+{
+	if (*node == NULL) {
+		return;
+	}
+	ast_destroy(&(*node)->ast_left);
+	ast_destroy(&(*node)->ast_right);
+	switch((*node)->ast_type) {
+	case AST_EX_ID:
+	case AST_EX_PATH:
+	case AST_EX_STRING:
+		free((*node)->ast_str);
+		break;
+	default:
+		break;
+	}
+	free((*node)->ast_text);
+	free(*node);
+	*node = NULL;
+}
+
+static void ast_string_unescape(char *str)
+{
+	int head, tail;
+	for (head = tail = 0; str[head] != '\0'; head++, tail++) {
+		if (str[head] == '\\' && str[head+1] != '\0')
+			head++;
+		str[tail] = str[head];
+	}
+	str[tail] = '\0';
+}
+
+static uint64_t ast_lookup_path(char *path, struct gfs2_sbd *sbd)
+{
+	int err = 0;
+	char *c;
+	struct gfs2_inode *ip, *iptmp;
+	char *segment;
+	uint64_t bn = 0;
+
+	segment = strtok_r(path, "/", &c);
+	ip = lgfs2_inode_read(sbd, sbd->sd_sb.sb_root_dir.no_addr);
+
+	while (ip != NULL) {
+		if (segment == NULL) { // No more segments
+			bn = ip->i_di.di_num.no_addr;
+			inode_put(&ip);
+			return bn;
+		}
+		ast_string_unescape(segment);
+		err = gfs2_lookupi(ip, segment, strlen(segment), &iptmp);
+		inode_put(&ip);
+		if (err != 0) {
+			errno = -err;
+			break;
+		}
+		ip = iptmp;
+		segment = strtok_r(NULL, "/", &c);
+	}
+
+	perror("Path lookup");
+	return 0;
+}
+
+enum block_id {
+	ID_SB	= 0,
+	ID_MASTER,
+	ID_ROOT,
+	ID_RINDEX,
+
+	ID_END
+};
+
+/**
+ * Names of blocks which can be uniquely identified in the fs
+ */
+static const char *block_ids[] = {
+	[ID_SB]		= "sb",
+	[ID_MASTER]	= "master",
+	[ID_ROOT]	= "root",
+	[ID_RINDEX]	= "rindex",
+
+	[ID_END]	= NULL
+};
+
+static uint64_t ast_lookup_id(const char *id, struct gfs2_sbd *sbd)
+{
+	uint64_t bn = 0;
+	int i;
+	for (i = 0; i < ID_END; i++) {
+		if (!strcmp(id, block_ids[i])) {
+			break;
+		}
+	}
+	switch (i) {
+	case ID_SB:
+		bn = sbd->sb_addr;
+		break;
+	case ID_MASTER:
+		bn = sbd->sd_sb.sb_master_dir.no_addr;
+		break;
+	case ID_ROOT:
+		bn = sbd->sd_sb.sb_root_dir.no_addr;
+		break;
+	case ID_RINDEX:
+		bn = sbd->md.riinode->i_di.di_num.no_addr;
+		break;
+	default:
+		return 0;
+	}
+	return bn;
+}
+
+static uint64_t ast_lookup_rgrp(uint64_t rgnum, struct gfs2_sbd *sbd)
+{
+	uint64_t i = rgnum;
+	struct osi_node *n;
+
+	for (n = osi_first(&sbd->rgtree); n != NULL && i > 0; n = osi_next(n), i--);
+	if (n != NULL && i == 0)
+		return ((struct rgrp_tree *)n)->ri.ri_addr;
+	fprintf(stderr, "Resource group number out of range: %"PRIu64"\n", rgnum);
+	return 0;
+}
+
+static uint64_t ast_lookup_subscript(struct ast_node *id, struct ast_node *index,
+                                     struct gfs2_sbd *sbd)
+{
+	uint64_t bn = 0;
+	const char *name = id->ast_str;
+	if (!strcmp(name, "rgrp")) {
+		bn = ast_lookup_rgrp(index->ast_num, sbd);
+	} else {
+		fprintf(stderr, "Unrecognized identifier %s\n", name);
+	}
+	return bn;
+}
+
+/**
+ * Look up a block and return its number. The kind of lookup depends on the
+ * type of the ast node.
+ */
+static uint64_t ast_lookup_block_num(struct ast_node *ast, struct gfs2_sbd *sbd)
+{
+	uint64_t bn = 0;
+	switch (ast->ast_type) {
+	case AST_EX_OFFSET:
+		bn = ast_lookup_block_num(ast->ast_left, sbd) + ast->ast_num;
+		break;
+	case AST_EX_ADDRESS:
+		bn = ast->ast_num;
+		break;
+	case AST_EX_PATH:
+		bn = ast_lookup_path(ast->ast_str, sbd);
+		break;
+	case AST_EX_ID:
+		bn = ast_lookup_id(ast->ast_str, sbd);
+		break;
+	case AST_EX_SUBSCRIPT:
+		bn = ast_lookup_subscript(ast->ast_left, ast->ast_left->ast_left, sbd);
+		break;
+	default:
+		break;
+	}
+	return bn;
+}
+
+static struct gfs2_buffer_head *ast_lookup_block(struct ast_node *node, struct gfs2_sbd *sbd)
+{
+	uint64_t bn = ast_lookup_block_num(node, sbd);
+	if (bn == 0) {
+		return NULL;
+	}
+
+	return bread(sbd, bn);
+}
+
+static const char *bitstate_strings[] = {
+	[GFS2_BLKST_FREE] = "Free",
+	[GFS2_BLKST_USED] = "Used",
+	[GFS2_BLKST_UNLINKED] = "Unlinked",
+	[GFS2_BLKST_DINODE] = "Dinode"
+};
+
+/**
+ * Print a representation of an arbitrary GFS2 block to stdout
+ */
+int lgfs2_lang_result_print(struct lgfs2_lang_result *result)
+{
+	int i;
+	if (result->lr_mtype != NULL) {
+		for (i = 0; i < result->lr_mtype->nfields; i++) {
+			lgfs2_field_print(result->lr_bh, result->lr_mtype, &result->lr_mtype->fields[i]);
+		}
+	} else {
+		printf("%"PRIu64": %s\n", result->lr_blocknr, bitstate_strings[result->lr_state]);
+	}
+	return 0;
+}
+
+static int ast_get_bitstate(uint64_t bn, struct gfs2_sbd *sbd)
+{
+	int ret = 0;
+	int state = 0;
+	struct rgrp_tree *rgd = gfs2_blk2rgrpd(sbd, bn);
+	if (rgd == NULL) {
+		fprintf(stderr, "Could not find resource group for block %"PRIu64"\n", bn);
+		return -1;
+	}
+
+	ret = gfs2_rgrp_read(sbd, rgd);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to read resource group for block %"PRIu64": %d\n", bn, ret);
+		return -1;
+	}
+
+	state = gfs2_get_bitmap(sbd, bn, rgd);
+	if (state == -1) {
+		fprintf(stderr, "Failed to acquire bitmap state for block %"PRIu64"\n", bn);
+		return -1;
+	}
+
+	gfs2_rgrp_relse(rgd);
+	return state;
+}
+
+static const struct lgfs2_metadata *ast_lookup_mtype(const struct gfs2_buffer_head *bh)
+{
+	const struct lgfs2_metadata *mtype;
+	const uint32_t mh_type = lgfs2_get_block_type(bh);
+	if (mh_type == 0) {
+		fprintf(stderr, "Could not determine type for block %"PRIu64"\n", bh->b_blocknr);
+		return NULL;
+	}
+
+	mtype = lgfs2_find_mtype(mh_type, bh->sdp->gfs1 ? LGFS2_MD_GFS1 : LGFS2_MD_GFS2);
+	if (mtype == NULL) {
+		fprintf(stderr, "Could not determine meta type for block %"PRIu64"\n", bh->b_blocknr);
+		return NULL;
+	}
+	return mtype;
+}
+
+/**
+ * Interpret the get statement.
+ */
+static struct lgfs2_lang_result *ast_interp_get(struct lgfs2_lang_state *state,
+                                     struct ast_node *ast, struct gfs2_sbd *sbd)
+{
+	struct lgfs2_lang_result *result = calloc(1, sizeof(struct lgfs2_lang_result));
+	if (result == NULL) {
+		fprintf(stderr, "Failed to allocate memory for result\n");
+		return NULL;
+	}
+
+	if (ast->ast_right->ast_right == NULL) {
+		result->lr_bh = ast_lookup_block(ast->ast_right, sbd);
+		if (result->lr_bh == NULL) {
+			free(result);
+			return NULL;
+		}
+		result->lr_blocknr = result->lr_bh->b_blocknr;
+		result->lr_mtype = ast_lookup_mtype(result->lr_bh);
+
+	} else if (ast->ast_right->ast_right->ast_type == AST_KW_STATE) {
+		result->lr_blocknr = ast_lookup_block_num(ast->ast_right, sbd);
+		if (result->lr_blocknr == 0) {
+			return NULL;
+		}
+		result->lr_state = ast_get_bitstate(result->lr_blocknr, sbd);
+	}
+
+	return result;
+}
+
+/**
+ * Interpret a UUID string by removing hyphens from the string and then
+ * interprets 16 pairs of hex digits as octets.
+ */
+static int ast_str_to_uuid(const char *str, uint8_t *uuid)
+{
+	char s[33];
+	int head, tail, tmp;
+
+	for (head = tail = 0; head < strlen(str) && tail < 33; head++) {
+		if (str[head] == '-')
+			continue;
+		s[tail] = tolower(str[head]);
+		if (!((s[tail] >= 'a' && s[tail] <= 'f') ||
+		      (s[tail] >= '0' && s[tail] <= '9')))
+			goto invalid;
+		tail++;
+	}
+	if (tail != 32) {
+		goto invalid;
+	}
+	s[tail] = '\0';
+	for (head = 0; head < 16; head++) {
+		if (sscanf(s+(head*2), "%02x", &tmp) != 1) {
+			goto invalid;
+		}
+		*(uuid + head) = tmp;
+	}
+	return AST_INTERP_SUCCESS;
+invalid:
+	fprintf(stderr, "Invalid UUID\n");
+	return AST_INTERP_INVAL;
+}
+
+/**
+ * Set a field of a gfs2 block of a given type to a given value.
+ * Returns AST_INTERP_* to signal success, an invalid field/value or an error.
+ */
+static int ast_field_set(struct gfs2_buffer_head *bh, const struct lgfs2_metafield *field,
+                                                                        struct ast_node *val)
+{
+	char *fieldp = (char *)bh->iov.iov_base + field->offset;
+
+	if (field->flags & LGFS2_MFF_UUID) {
+		uint8_t uuid[16];
+		int ret = ast_str_to_uuid(val->ast_str, uuid);
+
+		if (ret != AST_INTERP_SUCCESS)
+			return ret;
+
+		memcpy(fieldp, uuid, 16);
+		bmodified(bh);
+		return AST_INTERP_SUCCESS;
+	}
+
+	if ((field->flags & LGFS2_MFF_STRING) && strlen(val->ast_str) > field->length) {
+		fprintf(stderr, "String '%s' is too long for field '%s'\n", val->ast_str, field->name);
+		return AST_INTERP_INVAL;
+	}
+
+	if (field->flags & (LGFS2_MFF_STRING|LGFS2_MFF_UUID)) {
+		strncpy(fieldp, val->ast_str, field->length - 1);
+		fieldp[field->length - 1] = '\0';
+		bmodified(bh);
+		return AST_INTERP_SUCCESS;
+	} else {
+		// Numeric fields
+		switch(field->length) {
+		case 1:
+			if (val->ast_num > UINT8_MAX)
+				break;
+			*fieldp = (uint8_t)val->ast_num;
+			bmodified(bh);
+			return AST_INTERP_SUCCESS;
+		case 2:
+			if (val->ast_num > UINT16_MAX)
+				break;
+			*(uint16_t *)fieldp = cpu_to_be16((uint16_t)val->ast_num);
+			bmodified(bh);
+			return AST_INTERP_SUCCESS;
+		case 4:
+			if (val->ast_num > UINT32_MAX)
+				break;
+			*(uint32_t *)fieldp = cpu_to_be32((uint32_t)val->ast_num);
+			bmodified(bh);
+			return AST_INTERP_SUCCESS;
+		case 8:
+			*(uint64_t *)fieldp = cpu_to_be64((uint64_t)val->ast_num);
+			bmodified(bh);
+			return AST_INTERP_SUCCESS;
+		default:
+			// This should never happen
+			return AST_INTERP_ERR;
+		}
+	}
+
+	fprintf(stderr, "Invalid field assignment: %s (size %d) = %s\n",
+	                     field->name, field->length, val->ast_text);
+	return AST_INTERP_INVAL;
+}
+
+/**
+ * Interpret an assignment (set)
+ */
+static struct lgfs2_lang_result *ast_interp_set(struct lgfs2_lang_state *state,
+                                    struct ast_node *ast, struct gfs2_sbd *sbd)
+{
+	struct ast_node *lookup = ast->ast_right;
+	struct ast_node *fieldspec;
+	struct ast_node *fieldname;
+	struct ast_node *fieldval;
+	uint32_t mh_type = 0;
+	int i = 0;
+	int ret = 0;
+
+	struct lgfs2_lang_result *result = calloc(1, sizeof(struct lgfs2_lang_result));
+	if (result == NULL) {
+		fprintf(stderr, "Failed to allocate memory for result\n");
+		return NULL;
+	}
+
+	result->lr_bh = ast_lookup_block(lookup, sbd);
+	if (result->lr_bh == NULL) {
+		goto out_err;
+	}
+
+	mh_type = lgfs2_get_block_type(result->lr_bh);
+	if (mh_type == 0) {
+		goto out_err;
+	}
+
+	result->lr_mtype = lgfs2_find_mtype(mh_type, sbd->gfs1 ? LGFS2_MD_GFS1 : LGFS2_MD_GFS2);
+	if (result->lr_mtype == NULL) {
+		goto out_err;
+	}
+
+	for (fieldspec = lookup->ast_right;
+	     fieldspec != NULL && fieldspec->ast_type == AST_EX_FIELDSPEC;
+	     fieldspec = fieldspec->ast_left) {
+
+		fieldname = fieldspec->ast_right;
+		fieldval = fieldname->ast_right;
+		for (i = 0; i < result->lr_mtype->nfields; i++) {
+			if (!strcmp(result->lr_mtype->fields[i].name, fieldname->ast_str)) {
+				ret = ast_field_set(result->lr_bh, &result->lr_mtype->fields[i], fieldval);
+				if (ret != AST_INTERP_SUCCESS) {
+					goto out_err;
+				}
+				break;
+			}
+		}
+	}
+
+	ret = bwrite(result->lr_bh);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to write modified block %"PRIu64": %s\n",
+		                        result->lr_bh->b_blocknr, strerror(errno));
+		goto out_err;
+	}
+
+	return result;
+
+out_err:
+	lgfs2_lang_result_free(&result);
+	return NULL;
+}
+
+static struct lgfs2_lang_result *ast_interpret_node(struct lgfs2_lang_state *state,
+                                        struct ast_node *ast, struct gfs2_sbd *sbd)
+{
+	struct lgfs2_lang_result *result = NULL;
+
+	if (ast->ast_type == AST_ST_SET) {
+		result = ast_interp_set(state, ast, sbd);
+	} else if (ast->ast_type == AST_ST_GET) {
+		result = ast_interp_get(state, ast, sbd);
+	} else {
+		fprintf(stderr, "Invalid AST node type: %d\n", ast->ast_type);
+	}
+	return result;
+}
+
+struct lgfs2_lang_result *lgfs2_lang_result_next(struct lgfs2_lang_state *state,
+                                                           struct gfs2_sbd *sbd)
+{
+	struct lgfs2_lang_result *result;
+	if (state->ls_interp_curr == NULL) {
+		return NULL;
+	}
+	result = ast_interpret_node(state, state->ls_interp_curr, sbd);
+	if (result == NULL) {
+		return NULL;
+	}
+	state->ls_interp_curr = state->ls_interp_curr->ast_left;
+	return result;
+}
+
+void lgfs2_lang_result_free(struct lgfs2_lang_result **result)
+{
+	if (*result == NULL) {
+		fprintf(stderr, "Warning: attempted to free a null result\n");
+		return;
+	}
+
+	if ((*result)->lr_mtype != NULL) {
+		(*result)->lr_bh->b_modified = 0;
+		brelse((*result)->lr_bh);
+		(*result)->lr_bh = NULL;
+	}
+
+	free(*result);
+	*result = NULL;
+}
diff --git a/gfs2/libgfs2/lang.h b/gfs2/libgfs2/lang.h
new file mode 100644
index 0000000..955e52e
--- /dev/null
+++ b/gfs2/libgfs2/lang.h
@@ -0,0 +1,61 @@
+#ifndef LANG_H
+#define LANG_H
+#include <stdint.h>
+#include "libgfs2.h"
+
+struct lgfs2_lang_state {
+	int ls_colnum;
+	int ls_linenum;
+	int ls_errnum;
+	struct ast_node *ls_ast_root;
+	struct ast_node *ls_ast_tail;
+	struct ast_node *ls_interp_curr;
+};
+
+typedef enum {
+	AST_NONE,
+	// Statements
+	AST_ST_SET,
+	AST_ST_GET,
+
+	_AST_EX_START,
+	// Expressions
+	AST_EX_ID,
+	AST_EX_NUMBER,
+	AST_EX_STRING,
+	AST_EX_ADDRESS,
+	AST_EX_PATH,
+	AST_EX_SUBSCRIPT,
+	AST_EX_OFFSET,
+	AST_EX_BLOCKSPEC,
+	AST_EX_STRUCTSPEC,
+	AST_EX_FIELDSPEC,
+
+	// Keywords
+	AST_KW_STATE,
+} ast_node_t;
+
+enum {
+	AST_INTERP_SUCCESS	= 0, // Success
+	AST_INTERP_FAIL		= 1, // Failure
+	AST_INTERP_INVAL	= 2, // Invalid field/type mismatch
+	AST_INTERP_ERR		= 3, // Something went wrong, see errno
+};
+
+extern const char* ast_type_string[];
+
+struct ast_node {
+	ast_node_t ast_type;
+	struct ast_node *ast_left;
+	struct ast_node *ast_right;
+	char *ast_text;
+	char *ast_str;
+	uint64_t ast_num;
+};
+
+extern struct ast_node *ast_new(ast_node_t type, const char *text);
+extern void ast_destroy(struct ast_node **val);
+
+#define YYSTYPE struct ast_node *
+
+#endif /* LANG_H */
diff --git a/gfs2/libgfs2/lexer.l b/gfs2/libgfs2/lexer.l
new file mode 100644
index 0000000..36e1c2d
--- /dev/null
+++ b/gfs2/libgfs2/lexer.l
@@ -0,0 +1,101 @@
+%{
+#include "lang.h"
+#include "parser.h"
+
+#define EXTRA ((struct lgfs2_lang_state *)yyextra)
+
+#define P(token, type, text) do {\
+	*(yylval) = ast_new(type, text);\
+	if (*(yylval) == NULL) {\
+		EXTRA->ls_errnum = errno;\
+		return 1;\
+	}\
+	return (TOK_##token);\
+} while(0)
+
+#define COLNUM EXTRA->ls_colnum
+#define YY_USER_ACTION COLNUM += yyleng;
+
+%}
+%option bison-bridge reentrant
+%option warn debug
+%option nounput noinput
+%option noyywrap
+%option extra-type="struct lgfs2_lang_state *"
+
+letter			[a-zA-Z_]
+decdigit		[0-9]
+decnumber		-?{decdigit}+
+hexdigit		[0-9a-fA-F]
+hexnumber		-?0x{hexdigit}+
+number			({decnumber}|{hexnumber})
+offset			\+{number}
+id			{letter}({letter}|{decdigit}|\.)*
+string			\'([^\']|\\\')*\'
+comment			\/\/.*\n
+whitespace		[ \t\r]+
+
+%%
+
+\{			{
+			return TOK_LBRACE;
+			}
+\}			{
+			return TOK_RBRACE;
+			}
+\[			{
+			return TOK_LBRACKET;
+			}
+\]			{
+			P(RBRACKET, AST_EX_SUBSCRIPT, "[ ]");
+			}
+\,			{
+			return TOK_COMMA;
+			}
+\:			{
+			P(COLON, AST_EX_FIELDSPEC, yytext);
+			}
+\;			{
+			return TOK_SEMI;
+			}
+set			{
+			P(SET, AST_ST_SET, yytext);
+			}
+get			{
+			P(GET, AST_ST_GET, yytext);
+			}
+state			{
+			P(STATE, AST_KW_STATE, yytext);
+			}
+{string}		{
+			yytext[yyleng-1] = '\0';
+			P(STRING, AST_EX_STRING, yytext + 1);
+			}
+{offset}		{
+			P(OFFSET, AST_EX_OFFSET, yytext);
+			}
+{number}		{
+			P(NUMBER, AST_EX_NUMBER, yytext);
+			}
+{id}			{
+			P(ID, AST_EX_ID, yytext);
+			}
+{comment}		{
+			COLNUM = 0;
+			EXTRA->ls_linenum++;
+			}
+<<EOF>>			{
+			return 0;
+			}
+\n			{
+			COLNUM = 0;
+			EXTRA->ls_linenum++;
+			}
+{whitespace}		;
+.			{
+			printf("Unexpected character '%s' on line %d column %d\n",
+			       yytext, yylineno, COLNUM);
+			return 1;
+			}
+
+%%
diff --git a/gfs2/libgfs2/libgfs2.h b/gfs2/libgfs2/libgfs2.h
index 74ee2d0..3045337 100644
--- a/gfs2/libgfs2/libgfs2.h
+++ b/gfs2/libgfs2/libgfs2.h
@@ -356,6 +356,10 @@ extern const unsigned lgfs2_ld_type_size;
 extern const struct lgfs2_symbolic lgfs2_ld1_types[];
 extern const unsigned lgfs2_ld1_type_size;
 extern int lgfs2_selfcheck(void);
+extern const struct lgfs2_metadata *lgfs2_find_mtype(uint32_t mh_type, const unsigned versions);
+extern int lgfs2_field_print(const struct gfs2_buffer_head *bh,
+                             const struct lgfs2_metadata *mtype,
+                             const struct lgfs2_metafield *field);
 
 /* bitmap.c */
 struct gfs2_bmap {
@@ -379,6 +383,7 @@ extern struct gfs2_buffer_head *__bread(struct gfs2_sbd *sdp, uint64_t num,
 					int line, const char *caller);
 extern int bwrite(struct gfs2_buffer_head *bh);
 extern int brelse(struct gfs2_buffer_head *bh);
+extern uint32_t lgfs2_get_block_type(const struct gfs2_buffer_head *lbh);
 
 #define bmodified(bh) do { bh->b_modified = 1; } while(0)
 
@@ -828,6 +833,25 @@ extern void gfs2_log_descriptor_print(struct gfs2_log_descriptor *ld);
 extern void gfs2_statfs_change_print(struct gfs2_statfs_change *sc);
 extern void gfs2_quota_change_print(struct gfs2_quota_change *qc);
 
+/* Language functions */
+
+struct lgfs2_lang_state;
+
+struct lgfs2_lang_result {
+	uint64_t lr_blocknr;
+	struct gfs2_buffer_head *lr_bh;
+	const struct lgfs2_metadata *lr_mtype;
+	int lr_state; // GFS2_BLKST_*
+};
+
+extern struct lgfs2_lang_state *lgfs2_lang_init(void);
+extern int lgfs2_lang_parsef(struct lgfs2_lang_state *state, FILE *script);
+extern int lgfs2_lang_parses(struct lgfs2_lang_state *state, const char *script);
+extern struct lgfs2_lang_result *lgfs2_lang_result_next(struct lgfs2_lang_state *state, struct gfs2_sbd *sbd);
+extern int lgfs2_lang_result_print(struct lgfs2_lang_result *result);
+extern void lgfs2_lang_result_free(struct lgfs2_lang_result **result);
+extern void lgfs2_lang_free(struct lgfs2_lang_state **state);
+
 __END_DECLS
 
 #endif /* __LIBGFS2_DOT_H__ */
diff --git a/gfs2/libgfs2/meta.c b/gfs2/libgfs2/meta.c
index a677cdc..29bf239 100644
--- a/gfs2/libgfs2/meta.c
+++ b/gfs2/libgfs2/meta.c
@@ -808,3 +808,53 @@ int lgfs2_selfcheck(void)
 	return ret;
 }
 
+const struct lgfs2_metadata *lgfs2_find_mtype(uint32_t mh_type, const unsigned versions)
+{
+	const struct lgfs2_metadata *m = lgfs2_metadata;
+	unsigned n = 0;
+
+	do {
+		if ((m[n].versions & versions) && m[n].mh_type == mh_type)
+			return &m[n];
+		n++;
+	} while (n < lgfs2_metadata_size);
+
+	return NULL;
+}
+
+/**
+ * Print a representation of an arbitrary field of an arbitrary GFS2 block to stdout
+ * Returns 0 if successful, 1 otherwise
+ */
+int lgfs2_field_print(const struct gfs2_buffer_head *bh, const struct lgfs2_metadata *mtype,
+                      const struct lgfs2_metafield *field)
+{
+	const char *fieldp = (char *)bh->iov.iov_base + field->offset;
+
+	printf("%s\t%"PRIu64"\t%u\t%u\t%s\t", mtype->name, bh->b_blocknr, field->offset, field->length, field->name);
+	if (field->flags & LGFS2_MFF_UUID) {
+		printf("'%s'\n", str_uuid((const unsigned char *)fieldp));
+	} else if (field->flags & LGFS2_MFF_STRING) {
+		printf("'%s'\n", fieldp);
+	} else {
+		switch(field->length) {
+		case 1:
+			printf("%"PRIu8"\n", *(uint8_t *)fieldp);
+			break;
+		case 2:
+			printf("%"PRIu16"\n", be16_to_cpu(*(uint16_t *)fieldp));
+			break;
+		case 4:
+			printf("%"PRIu32"\n", be32_to_cpu(*(uint32_t *)fieldp));
+			break;
+		case 8:
+			printf("%"PRIu64"\n", be64_to_cpu(*(uint64_t *)fieldp));
+			break;
+		default:
+			// "Reserved" field so just print 0
+			printf("0\n");
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/gfs2/libgfs2/misc.c b/gfs2/libgfs2/misc.c
index a68da4a..c2eb245 100644
--- a/gfs2/libgfs2/misc.c
+++ b/gfs2/libgfs2/misc.c
@@ -26,8 +26,6 @@
 #define SYS_BASE "/sys/fs/gfs2" /* FIXME: Look in /proc/mounts to find this */
 #define DIV_RU(x, y) (((x) + (y) - 1) / (y))
 
-static char sysfs_buf[PAGE_SIZE];
-
 int compute_heightsize(struct gfs2_sbd *sdp, uint64_t *heightsize,
 	uint32_t *maxheight, uint32_t bsize1, int diptrs, int inptrs)
 {
diff --git a/gfs2/libgfs2/parser.y b/gfs2/libgfs2/parser.y
new file mode 100644
index 0000000..084d15e
--- /dev/null
+++ b/gfs2/libgfs2/parser.y
@@ -0,0 +1,186 @@
+%code top {
+#include <errno.h>
+#include "lang.h"
+#include "lexer.h"
+
+static int yyerror(struct lgfs2_lang_state *state, yyscan_t lexer, const char *errorstr)
+{
+	fprintf(stderr, "%d:%d: %s\n", state->ls_linenum, state->ls_colnum, errorstr);
+	return 1;
+}
+
+}
+%defines
+%debug
+%define api.pure
+%parse-param { struct lgfs2_lang_state *state }
+%parse-param { yyscan_t lexer }
+%lex-param { yyscan_t lexer }
+%start script
+%token TOK_COLON
+%token TOK_COMMA
+%token TOK_ID
+%token TOK_LBRACE
+%token TOK_LBRACKET
+%token TOK_NUMBER
+%token TOK_OFFSET
+%token TOK_RBRACE
+%token TOK_RBRACKET
+%token TOK_SEMI
+%token TOK_SET
+%token TOK_GET
+%token TOK_STATE
+%token TOK_STRING
+%%
+script:		statements		{
+			state->ls_ast_root = $1;
+			state->ls_interp_curr = $1;
+		}
+		| statements TOK_SEMI	{
+			state->ls_ast_root = $1;
+			state->ls_interp_curr = $1;
+		}
+;
+statements:	statements TOK_SEMI statement	{
+			state->ls_ast_tail->ast_left = $3;
+			state->ls_ast_tail = $3;
+			$$ = $1;
+		}
+		| statement		{
+			if (state->ls_ast_tail == NULL)
+				state->ls_ast_tail = $1;
+			$$ = $1;
+		}
+;
+
+statement:	set_stmt		{ $$ = $1; }
+		| get_stmt		{ $$ = $1; }
+;
+set_stmt:	TOK_SET blockspec structspec {
+			$1->ast_right = $2;
+			$2->ast_right = $3;
+			$$ = $1;
+		}
+;
+get_stmt:	TOK_GET blockspec { $1->ast_right = $2; $$ = $1; }
+		| TOK_GET blockspec TOK_STATE {
+			$1->ast_right = $2;
+			$2->ast_right = $3;
+			$$ = $1;
+		}
+;
+blockspec:	offset			{ $$ = $1; }
+		| address		{ $$ = $1; }
+		| path			{ $$ = $1; }
+		| block_literal		{ $$ = $1; }
+		| subscript		{ $$ = $1; }
+;
+offset:		blockspec TOK_OFFSET {
+			$2->ast_left = $1;
+			$$ = $2;
+		}
+;
+block_literal:	identifier		{ $$ = $1; }
+;
+subscript:	block_literal TOK_LBRACKET index TOK_RBRACKET {
+			$4->ast_left = $1;
+			$1->ast_left = $3;
+			$$ = $4;
+		}
+;
+index:		number			{ $$ = $1; }
+		| identifier		{ $$ = $1; }
+;
+address:	number			{ $1->ast_type = AST_EX_ADDRESS; $$ = $1; }
+;
+path:		string			{
+			if (*($1->ast_str) != '/') {
+				fprintf(stderr, "Path doesn't begin with '/': %s\n", $1->ast_str);
+				YYABORT;
+			}
+			$1->ast_type = AST_EX_PATH;
+			$$ = $1;
+		}
+;
+structspec:	TOK_LBRACE fieldspecs TOK_RBRACE { $$ = $2; }
+;
+fieldspecs:	fieldspecs TOK_COMMA fieldspec	{ $1->ast_left = $3; $$ = $1; }
+		| fieldspec			{ $$ = $1; }
+;
+fieldspec:	identifier TOK_COLON fieldvalue {
+			$2->ast_right = $1;
+			$1->ast_right = $3;
+			$$ = $2;
+		}
+;
+fieldvalue:	number			{ $$ = $1; }
+		| string		{ $$ = $1; }
+;
+number:		TOK_NUMBER		{ $$ = $1; }
+string:		TOK_STRING		{ $$ = $1; }
+identifier:	TOK_ID			{ $$ = $1; }
+%%
+
+/**
+ * Allocate and initialize a new parse state structure. The caller must free the
+ * memory returned by this function.
+ */
+struct lgfs2_lang_state *lgfs2_lang_init(void)
+{
+	struct lgfs2_lang_state *state;
+	state = calloc(1, sizeof(struct lgfs2_lang_state));
+	if (state == NULL) {
+		return NULL;
+	}
+	state->ls_linenum = 1;
+	return state;
+}
+
+void lgfs2_lang_free(struct lgfs2_lang_state **state)
+{
+	ast_destroy(&(*state)->ls_ast_root);
+	free(*state);
+	*state = NULL;
+}
+
+int lgfs2_lang_parsef(struct lgfs2_lang_state *state, FILE *src)
+{
+	int ret = 0;
+	yyscan_t lexer;
+
+	ret = yylex_init_extra(state, &lexer);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to initialize lexer.\n");
+		return ret;
+	}
+
+	yyset_in(src, lexer);
+	ret = yyparse(state, lexer);
+	yylex_destroy(lexer);
+	return ret;
+}
+
+int lgfs2_lang_parses(struct lgfs2_lang_state *state, const char *cstr)
+{
+	int ret;
+	FILE *src;
+	char *str = strdup(cstr);
+
+	if (str == NULL) {
+		perror("Failed to duplicate source string");
+		return 1;
+	}
+	src = fmemopen(str, strlen(str), "r");
+	if (src == NULL) {
+		perror("Failed to open string as source file");
+		free(str);
+		return 1;
+	}
+	ret = lgfs2_lang_parsef(state, src);
+	fclose(src);
+	free(str);
+	if (ret != 0 || state->ls_errnum != 0) {
+		return 1;
+	}
+	return 0;
+}
diff --git a/gfs2/libgfs2/super.c b/gfs2/libgfs2/super.c
index a3c1964..fdf0e60 100644
--- a/gfs2/libgfs2/super.c
+++ b/gfs2/libgfs2/super.c
@@ -25,7 +25,7 @@ int check_sb(struct gfs2_sb *sb)
 {
 	if (sb->sb_header.mh_magic != GFS2_MAGIC ||
 	    sb->sb_header.mh_type != GFS2_METATYPE_SB) {
-		errno = -EIO;
+		errno = EIO;
 		return -1;
 	}
 	if (sb->sb_fs_format == GFS_FORMAT_FS &&
-- 
1.7.11.4




More information about the Cluster-devel mailing list