From mboxrd@z Thu Jan 1 00:00:00 1970 From: Steven Whitehouse Date: Mon, 15 Oct 2012 10:48:50 +0100 Subject: [Cluster-devel] [PATCH] libgfs2: Add a gfs2 block query language In-Reply-To: <1350067881-1458-1-git-send-email-anprice@redhat.com> References: <1350067881-1458-1-git-send-email-anprice@redhat.com> Message-ID: <1350294530.2733.38.camel@menhir> List-Id: To: cluster-devel.redhat.com MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Hi, Looks good. Thats a big step forward for us I think, Steve. On Fri, 2012-10-12 at 19:51 +0100, Andrew Price wrote: > 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 [state] > set {:, :, ... } > > 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 > --- > .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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 > +#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++; > + } > +<> { > + 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 > +#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 &&