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 <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 <[email protected]>
> ---
>  .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 &&


Reply via email to