Index: /trunk/Nebulous-Server/Changes
===================================================================
--- /trunk/Nebulous-Server/Changes	(revision 13086)
+++ /trunk/Nebulous-Server/Changes	(revision 13087)
@@ -2,4 +2,6 @@
 
 0.05
+    - add Nebulous::Server->{setxattr_object, listxattr_object,
+      getxattr_object, removexattr_object}()
     - add neb-cat
     - add neb-mv
Index: /trunk/Nebulous-Server/MANIFEST
===================================================================
--- /trunk/Nebulous-Server/MANIFEST	(revision 13086)
+++ /trunk/Nebulous-Server/MANIFEST	(revision 13087)
@@ -91,4 +91,5 @@
 t/12_server_find_objects.t
 t/13_server_rename_object.t
+t/14_server_xattr.t
 t/50_client_new.t
 t/51_client_create.t
Index: /trunk/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- /trunk/Nebulous-Server/lib/Nebulous/Server.pm	(revision 13086)
+++ /trunk/Nebulous-Server/lib/Nebulous/Server.pm	(revision 13087)
@@ -1,5 +1,5 @@
 # Copyright (c) 2004  Joshua Hoblitt
 #
-# $Id: Server.pm,v 1.26 2007-04-28 00:44:38 jhoblitt Exp $
+# $Id: Server.pm,v 1.27 2007-05-01 02:00:07 jhoblitt Exp $
 
 package Nebulous::Server;
@@ -614,4 +614,207 @@
 
 
+sub setxattr_object
+{
+    my $self = shift;
+
+    my ($key, $name, $value, $flags) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:create|replace)$/i },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    eval {
+        my $query;
+
+        if ($flags eq 'create') {
+            $query = $db->prepare_cached( $sql->new_object_xattr );
+        } else {
+            # replace
+            $query = $db->prepare_cached( $sql->replace_object_xattr );
+        }
+
+        # name, value, ext_id
+        my $rows = $query->execute($name, $value, $key);
+
+        # if we affected more then one row something very bad has happened.
+        if ($flags eq 'create') {
+            unless ($rows == 1) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        } else {
+            unless ($rows == 2) {
+                $log->logdie( "affected row count is $rows instead of 2" );
+            }
+        }
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    eval { 
+        $db->commit;
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
+sub getxattr_object
+{
+    my $self = shift;
+
+    my ($key, $name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $query;
+    eval {
+        $query = $db->prepare_cached( $sql->get_object_xattr );
+        # ext_id, name
+        my $rows = $query->execute($key, $name);
+
+        # if we affected more then one row something very bad has happened.
+        unless ($rows == 1) {
+            $log->logdie( "affected row count is $rows instead of 1" );
+        }
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    my $row = $query->fetchrow_hashref;
+    my $value = $row->{ 'value' };
+    $query->finish;
+
+    $log->debug("leaving");
+
+    return $value;
+}
+
+
+sub listxattr_object
+{
+    my $self = shift;
+
+    my ($key) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $query;
+    eval {
+        $query = $db->prepare_cached( $sql->list_object_xattr );
+        # ext_id
+        my $rows = $query->execute($key);
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    my @xattrs;
+    while (my $row = $query->fetchrow_hashref) {
+        push @xattrs, $row->{ 'name' };
+    }
+
+    $log->debug("leaving");
+
+    return @xattrs;
+}
+
+
+sub removexattr_object
+{
+    my $self = shift;
+
+    my ($key, $name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $query;
+    eval {
+        $query = $db->prepare_cached( $sql->remove_object_xattr );
+        # ext_id, name
+        my $rows = $query->execute($key, $name);
+
+        # if we affected more then one row something very bad has happened.
+        unless ($rows == 1) {
+            $log->logdie( "affected row count is $rows instead of 1" );
+        }
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    eval { 
+        $db->commit;
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
 sub find_objects {
     my $self = shift;
Index: /trunk/Nebulous-Server/lib/Nebulous/Server/SQL.pm
===================================================================
--- /trunk/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 13086)
+++ /trunk/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 13087)
@@ -1,5 +1,5 @@
 # Copyright (c) 2004  Joshua Hoblitt
 #
-# $Id: SQL.pm,v 1.28 2007-04-28 00:44:38 jhoblitt Exp $
+# $Id: SQL.pm,v 1.29 2007-05-01 02:00:07 jhoblitt Exp $
 
 package Nebulous::Server::SQL;
@@ -63,4 +63,42 @@
         WHERE ext_id = ?
         FOR UPDATE
+    },
+    new_object_xattr  => qq{
+        INSERT INTO storage_object_xattr
+            SELECT
+                so_id,
+                ?,
+                ?        
+            FROM storage_object
+            WHERE ext_id = ?
+    },
+    replace_object_xattr  => qq{
+        REPLACE INTO storage_object_xattr
+            SELECT
+                so_id,
+                ?,
+                ?        
+            FROM storage_object
+            WHERE ext_id = ?
+    },
+    list_object_xattr    => qq{
+        SELECT storage_object_xattr.name
+        FROM storage_object
+        JOIN storage_object_xattr
+        USING (so_id)
+        WHERE ext_id = ?
+    },
+    get_object_xattr    => qq{
+        SELECT storage_object_xattr.value
+        FROM storage_object
+        JOIN storage_object_xattr
+        USING (so_id)
+        WHERE ext_id = ?
+            AND name = ?
+    },
+    remove_object_xattr    => qq{
+        DELETE FROM storage_object_xattr
+        WHERE so_id = (SELECT so_id from storage_object where ext_id = ?)
+            AND name = ?
     },
     set_write_lock      => qq{
@@ -179,4 +217,5 @@
 DROP TABLE IF EXISTS storage_object;
 DROP TABLE IF EXISTS storage_object_attr;
+DROP TABLE IF EXISTS storage_object_xattr;
 DROP TABLE IF EXISTS instance;
 DROP TABLE IF EXISTS lock_record;
@@ -226,4 +265,13 @@
     PRIMARY KEY(so_id),
     KEY(class_id)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE storage_object_xattr (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    name VARCHAR(255),
+    value BLOB,
+    PRIMARY KEY(so_id, name)
 ) ENGINE=innodb;
 
Index: /trunk/Nebulous-Server/t/14_server_xattr.t
===================================================================
--- /trunk/Nebulous-Server/t/14_server_xattr.t	(revision 13087)
+++ /trunk/Nebulous-Server/t/14_server_xattr.t	(revision 13087)
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 14_server_xattr.t,v 1.1 2007-05-01 02:00:07 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 32;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Nebulous::Util qw( :standard );
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => "DBI:mysql:database=test:host=localhost",
+    dbuser      => "test",
+    dbpasswd    => "",
+);
+
+# 1 key / xattr
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 1, 'number of xattrs');
+        is($xattrs[0], 'bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'baz', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 0, 'number of xattrs');
+    }
+}
+
+# multiple xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'bonk', 'quix', 'create'), 'set object xattr');
+    
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 2, 'number of xattrs');
+        is($xattrs[0], 'bar', 'xattr name');
+        is($xattrs[1], 'bonk', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'baz', 'xattr value');
+    $value = $neb->getxattr_object('foo', 'bonk');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    ok($neb->removexattr_object('foo', 'bonk'), "remove object xattr");
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 0, 'number of xattrs');
+    }
+}
+
+# replace xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'bar', 'quix', 'replace'), 're-set object xattr');
+    
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 1, 'number of xattrs');
+        is($xattrs[0], 'bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 0, 'number of xattrs');
+    }
+}
+
+# setxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object();
+};
+like($@, qr/4 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'bar');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'bar', 'baz');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'bar', 'baz', 'create', 'quix');
+};
+like($@, qr/4 were expected/, "too many params");
+
+# listxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->listxattr_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->listxattr_object('foo', 'bar');
+};
+like($@, qr/1 was expected/, "too many params");
+
+# getxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object('foo');
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object('foo', 'bar', 'baz');
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /trunk/Nebulous/Changes
===================================================================
--- /trunk/Nebulous/Changes	(revision 13086)
+++ /trunk/Nebulous/Changes	(revision 13087)
@@ -2,4 +2,6 @@
 
 0.05
+    - add Nebulous::Server->{setxattr_object, listxattr_object,
+      getxattr_object, removexattr_object}()
     - add neb-cat
     - add neb-mv
Index: /trunk/Nebulous/MANIFEST
===================================================================
--- /trunk/Nebulous/MANIFEST	(revision 13086)
+++ /trunk/Nebulous/MANIFEST	(revision 13087)
@@ -91,4 +91,5 @@
 t/12_server_find_objects.t
 t/13_server_rename_object.t
+t/14_server_xattr.t
 t/50_client_new.t
 t/51_client_create.t
Index: /trunk/Nebulous/lib/Nebulous/Server.pm
===================================================================
--- /trunk/Nebulous/lib/Nebulous/Server.pm	(revision 13086)
+++ /trunk/Nebulous/lib/Nebulous/Server.pm	(revision 13087)
@@ -1,5 +1,5 @@
 # Copyright (c) 2004  Joshua Hoblitt
 #
-# $Id: Server.pm,v 1.26 2007-04-28 00:44:38 jhoblitt Exp $
+# $Id: Server.pm,v 1.27 2007-05-01 02:00:07 jhoblitt Exp $
 
 package Nebulous::Server;
@@ -614,4 +614,207 @@
 
 
+sub setxattr_object
+{
+    my $self = shift;
+
+    my ($key, $name, $value, $flags) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:create|replace)$/i },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    eval {
+        my $query;
+
+        if ($flags eq 'create') {
+            $query = $db->prepare_cached( $sql->new_object_xattr );
+        } else {
+            # replace
+            $query = $db->prepare_cached( $sql->replace_object_xattr );
+        }
+
+        # name, value, ext_id
+        my $rows = $query->execute($name, $value, $key);
+
+        # if we affected more then one row something very bad has happened.
+        if ($flags eq 'create') {
+            unless ($rows == 1) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        } else {
+            unless ($rows == 2) {
+                $log->logdie( "affected row count is $rows instead of 2" );
+            }
+        }
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    eval { 
+        $db->commit;
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
+sub getxattr_object
+{
+    my $self = shift;
+
+    my ($key, $name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $query;
+    eval {
+        $query = $db->prepare_cached( $sql->get_object_xattr );
+        # ext_id, name
+        my $rows = $query->execute($key, $name);
+
+        # if we affected more then one row something very bad has happened.
+        unless ($rows == 1) {
+            $log->logdie( "affected row count is $rows instead of 1" );
+        }
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    my $row = $query->fetchrow_hashref;
+    my $value = $row->{ 'value' };
+    $query->finish;
+
+    $log->debug("leaving");
+
+    return $value;
+}
+
+
+sub listxattr_object
+{
+    my $self = shift;
+
+    my ($key) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $query;
+    eval {
+        $query = $db->prepare_cached( $sql->list_object_xattr );
+        # ext_id
+        my $rows = $query->execute($key);
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    my @xattrs;
+    while (my $row = $query->fetchrow_hashref) {
+        push @xattrs, $row->{ 'name' };
+    }
+
+    $log->debug("leaving");
+
+    return @xattrs;
+}
+
+
+sub removexattr_object
+{
+    my $self = shift;
+
+    my ($key, $name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $query;
+    eval {
+        $query = $db->prepare_cached( $sql->remove_object_xattr );
+        # ext_id, name
+        my $rows = $query->execute($key, $name);
+
+        # if we affected more then one row something very bad has happened.
+        unless ($rows == 1) {
+            $log->logdie( "affected row count is $rows instead of 1" );
+        }
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    eval { 
+        $db->commit;
+    };
+    if ($@) {
+        $db->rollback;
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
 sub find_objects {
     my $self = shift;
Index: /trunk/Nebulous/lib/Nebulous/Server/SQL.pm
===================================================================
--- /trunk/Nebulous/lib/Nebulous/Server/SQL.pm	(revision 13086)
+++ /trunk/Nebulous/lib/Nebulous/Server/SQL.pm	(revision 13087)
@@ -1,5 +1,5 @@
 # Copyright (c) 2004  Joshua Hoblitt
 #
-# $Id: SQL.pm,v 1.28 2007-04-28 00:44:38 jhoblitt Exp $
+# $Id: SQL.pm,v 1.29 2007-05-01 02:00:07 jhoblitt Exp $
 
 package Nebulous::Server::SQL;
@@ -63,4 +63,42 @@
         WHERE ext_id = ?
         FOR UPDATE
+    },
+    new_object_xattr  => qq{
+        INSERT INTO storage_object_xattr
+            SELECT
+                so_id,
+                ?,
+                ?        
+            FROM storage_object
+            WHERE ext_id = ?
+    },
+    replace_object_xattr  => qq{
+        REPLACE INTO storage_object_xattr
+            SELECT
+                so_id,
+                ?,
+                ?        
+            FROM storage_object
+            WHERE ext_id = ?
+    },
+    list_object_xattr    => qq{
+        SELECT storage_object_xattr.name
+        FROM storage_object
+        JOIN storage_object_xattr
+        USING (so_id)
+        WHERE ext_id = ?
+    },
+    get_object_xattr    => qq{
+        SELECT storage_object_xattr.value
+        FROM storage_object
+        JOIN storage_object_xattr
+        USING (so_id)
+        WHERE ext_id = ?
+            AND name = ?
+    },
+    remove_object_xattr    => qq{
+        DELETE FROM storage_object_xattr
+        WHERE so_id = (SELECT so_id from storage_object where ext_id = ?)
+            AND name = ?
     },
     set_write_lock      => qq{
@@ -179,4 +217,5 @@
 DROP TABLE IF EXISTS storage_object;
 DROP TABLE IF EXISTS storage_object_attr;
+DROP TABLE IF EXISTS storage_object_xattr;
 DROP TABLE IF EXISTS instance;
 DROP TABLE IF EXISTS lock_record;
@@ -226,4 +265,13 @@
     PRIMARY KEY(so_id),
     KEY(class_id)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE storage_object_xattr (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    name VARCHAR(255),
+    value BLOB,
+    PRIMARY KEY(so_id, name)
 ) ENGINE=innodb;
 
Index: /trunk/Nebulous/t/14_server_xattr.t
===================================================================
--- /trunk/Nebulous/t/14_server_xattr.t	(revision 13087)
+++ /trunk/Nebulous/t/14_server_xattr.t	(revision 13087)
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 14_server_xattr.t,v 1.1 2007-05-01 02:00:07 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 32;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Nebulous::Util qw( :standard );
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => "DBI:mysql:database=test:host=localhost",
+    dbuser      => "test",
+    dbpasswd    => "",
+);
+
+# 1 key / xattr
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 1, 'number of xattrs');
+        is($xattrs[0], 'bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'baz', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 0, 'number of xattrs');
+    }
+}
+
+# multiple xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'bonk', 'quix', 'create'), 'set object xattr');
+    
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 2, 'number of xattrs');
+        is($xattrs[0], 'bar', 'xattr name');
+        is($xattrs[1], 'bonk', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'baz', 'xattr value');
+    $value = $neb->getxattr_object('foo', 'bonk');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    ok($neb->removexattr_object('foo', 'bonk'), "remove object xattr");
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 0, 'number of xattrs');
+    }
+}
+
+# replace xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'bar', 'quix', 'replace'), 're-set object xattr');
+    
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 1, 'number of xattrs');
+        is($xattrs[0], 'bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    {
+        my @xattrs = $neb->listxattr_object('foo');
+        is(scalar @xattrs, 0, 'number of xattrs');
+    }
+}
+
+# setxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object();
+};
+like($@, qr/4 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'bar');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'bar', 'baz');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'bar', 'baz', 'create', 'quix');
+};
+like($@, qr/4 were expected/, "too many params");
+
+# listxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->listxattr_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->listxattr_object('foo', 'bar');
+};
+like($@, qr/1 was expected/, "too many params");
+
+# getxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object('foo');
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object('foo', 'bar', 'baz');
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
