Index: /trunk/Nebulous-Server/Changes
===================================================================
--- /trunk/Nebulous-Server/Changes	(revision 24275)
+++ /trunk/Nebulous-Server/Changes	(revision 24276)
@@ -2,4 +2,5 @@
 
 0.17
+    - add chmod_object() method
     - retry database transactions when a deadlock is detected
     - add log4perl logging to nebdiskd
Index: /trunk/Nebulous-Server/MANIFEST
===================================================================
--- /trunk/Nebulous-Server/MANIFEST	(revision 24275)
+++ /trunk/Nebulous-Server/MANIFEST	(revision 24276)
@@ -48,3 +48,4 @@
 t/15_mounts.t
 t/16_server_swap_objects.t
+t/17_server_chmod_object.t
 t/75_parse_neb_key.t
Index: /trunk/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- /trunk/Nebulous-Server/lib/Nebulous/Server.pm	(revision 24275)
+++ /trunk/Nebulous-Server/lib/Nebulous/Server.pm	(revision 24276)
@@ -15,4 +15,5 @@
 use DBI;
 use Digest::SHA1 qw( sha1_hex );
+use Fcntl ':mode';
 use File::Basename qw( basename dirname fileparse );
 use File::ExtAttr qw( setfattr );
@@ -1576,4 +1577,61 @@
 }
 
+sub chmod_object
+{
+    my $self = shift;
+
+    my ($key, $mode) = validate_pos( @_, 
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+            regex       => qr/\d{3,4}/,
+            callbacks   => {
+                'is allowable mode' => sub {
+                    $_[0] == (S_IRUSR | S_IRGRP)
+                },
+            },
+        },
+    );
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  = $self->db($key);
+
+    $log->debug("entered - @_");
+
+    # find all instances of this object
+    my $locations;
+    eval {
+        $locations = $self->find_instances("$key");
+    };
+    $log->logdie("error: $@") if $@;
+
+    # update each instances
+    foreach my $inst (@$locations) {
+        chmod $mode, URI->new($inst)->path 
+            or $log->logdie("chmod() failed: $!");
+    }
+
+    # stick an xattr on this object with the mode
+    # XXX this would probably be better as a field in the storage_object_attr
+    # table but since we're not planning to use this for very many objects (as
+    # a %) it may not be worth adding the extra field at this time.
+    eval {
+        $self->setxattr_object("$key", 'user.mode', $mode, 'replace');
+    };
+    $log->logdie("error: $@") if $@;
+
+    $log->debug("leaving");
+
+    return $mode;
+}
 
 sub _get_storage_volume
Index: /trunk/Nebulous-Server/t/17_server_chmod_object.t
===================================================================
--- /trunk/Nebulous-Server/t/17_server_chmod_object.t	(revision 24276)
+++ /trunk/Nebulous-Server/t/17_server_chmod_object.t	(revision 24276)
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2009  Joshua Hoblitt
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::URI;
+use Test::Nebulous;
+use URI;
+use File::stat;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+# object with one instance
+{
+    my $key = "foo";
+    my $uri = $neb->create_object($key);
+
+    my $path = URI->new($uri)->path;
+    my $st1 = stat($path);
+
+    my $mode = $neb->chmod_object($key, 0440);
+    my $st2 = stat($path);
+
+    is($st2->mode &07777, 0440, "chmod() single instance");
+    is($mode, 0440, "returned mode");
+    is($neb->getxattr_object($key, 'user.mode'), 0440, "xattr user.mode");
+}
+
+Test::Nebulous->setup;
+
+# object with two instances
+{
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+    my $uri2 = $neb->replicate_object($key);
+
+    my $mode = $neb->chmod_object($key, 0440);
+
+    my $st1 = stat(URI->new($uri1)->path);
+    my $st2 = stat(URI->new($uri2)->path);
+
+    is($st1->mode &07777, 0440, "chmod() first instance");
+    is($st2->mode &07777, 0440, "chmod() second instance");
+    is($mode, 0440, "returned mode");
+    is($neb->getxattr_object($key, 'user.mode'), 0440, "xattr user.mode");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->chmod_object("foo", 0644);
+};
+like($@, qr/valid object/, "object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo", 0640);
+};
+like($@, qr/allowable mode/, "mode is not 0400");
+
+Test::Nebulous->setup;
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo", 999);
+};
+like($@, qr/allowable mode/, "bad mode");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->chmod_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo");
+};
+like($@, qr/2 were expected/, "one param");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo", 0440, 2);
+};
+like($@, qr/2 were expected/, "three params");
+
+Test::Nebulous->cleanup;
