add PUT and SHA1 support
authorTom Clegg <tom@clinicalfuture.com>
Thu, 25 Apr 2013 00:00:18 +0000 (17:00 -0700)
committerTom Clegg <tom@clinicalfuture.com>
Thu, 25 Apr 2013 00:00:18 +0000 (17:00 -0700)
services/keep/INSTALL
services/keep/keep.rb

index 6280cd94759987b327d10e6094d1af520e2bf90a..cf6ec62a1443807d1a480cf076d83af393cff6f1 100644 (file)
@@ -11,10 +11,10 @@ Set up Keep backing store directories
 
 Start server
 
- IP=0.0.0.0 PORT=25107 ./keep.rb
RACK_ENV=production IP=0.0.0.0 PORT=25107 ./keep.rb
 
 Start server With SSL support
 
  export SSL_CERT=/etc/ssl/certs/keep.crt
  export SSL_KEY=/etc/ssl/private/keep.pem
- IP=... PORT=... ./keep.rb
RACK_ENV=... IP=... PORT=... ./keep.rb
index 26c31c60ed552f38825b9815a011ae452c84d854..f38ceeddde4ae0d3f2fbbd919683131a86ed298b 100755 (executable)
@@ -1,6 +1,8 @@
 #!/usr/bin/env ruby
 
 require 'sinatra/base'
+require 'digest/md5'
+require 'digest/sha1'
 
 class Keep < Sinatra::Base
   configure do
@@ -10,6 +12,16 @@ class Keep < Sinatra::Base
     set :bind, (ENV['IP'] || '0.0.0.0')
   end
 
+  def verify_hash(data, hash)
+    if hash.length == 32
+      Digest::MD5.hexdigest(data) == hash && hash
+    elsif hash.length == 40
+      Digest::SHA1.hexdigest(data) == hash && hash
+    else
+      false
+    end
+  end
+
   def self.debuglevel
     if ENV['DEBUG'] and ENV['DEBUG'].match /^-?\d+/
       ENV['DEBUG'].to_i
@@ -57,35 +69,92 @@ class Keep < Sinatra::Base
   end
   self.keepdirs
 
+  def find_backfile(hash, opts)
+    subdir = hash[0..2]
+    keepdirs.each do |keepdir|
+      backfile = "#{keepdir[:root]}/#{subdir}/#{hash}"
+      if File.exists? backfile
+        data = nil
+        File.open("#{keepdir[:root]}/lock", "a+") do |f|
+          if f.flock File::LOCK_EX
+            data = File.read backfile
+          end
+        end
+        if data and (!opts[:verify_hash] or verify_hash data, hash)
+          return [backfile, data]
+        end
+      end
+    end
+    nil
+  end
+
   get '/:locator' do |locator|
     regs = locator.match /^([0-9a-f]{32,})/
     if regs
       hash = regs[1]
+      backfile, data = find_backfile hash, :verify_hash => false
+      if data
+        content_type :binary
+        body data
+      else
+        status 404
+        body 'not found'
+      end
+    else
+      pass
+    end
+  end
+
+  put '/:locator' do |locator|
+    data = request.body.read
+    hash = verify_hash(data, locator)
+    if not hash
+      status 422
+      body "Checksum mismatch"
+      return
+    end
+    backfile, havedata = find_backfile hash, :verify_hash => true
+    if havedata
+      status 200
+      body 'OK'
+    else
+      wrote = nil
       subdir = hash[0..2]
-      found = false
       keepdirs.each do |keepdir|
+        next if keepdir[:readonly]
+        backdir = "#{keepdir[:root]}/#{subdir}"
+        if !File.exists? backdir
+          begin
+            Dir.mkdir backdir
+          rescue
+          end
+        end
         backfile = "#{keepdir[:root]}/#{subdir}/#{hash}"
-        if File.exists? backfile
-          data = nil
-          File.open("#{keepdir[:root]}/lock", "a+") do |f|
-            if f.flock File::LOCK_EX
-              data = File.read backfile
+        File.open("#{keepdir[:root]}/lock", "a+") do |lf|
+          if lf.flock File::LOCK_EX
+            File.open(backfile + ".tmp", "a+") do |wf|
+              if wf.flock File::LOCK_EX
+                wf.seek 0, File::SEEK_SET
+                wf.truncate 0
+                wrote = wf.write data
+              end
+              if wrote == data.length
+                File.rename backfile+".tmp", backfile
+                break
+              else
+                File.unlink backfile+".tmp"
+              end
             end
           end
-          if data
-            found = true
-            content_type :binary
-            body data
-            break
-          end
         end
       end
-      if not found
-        status 404
-        body 'not found'
+      if wrote == data.length
+        status 200
+        body 'OK'
+      else
+        status 500
+        body 'Fail'
       end
-    else
-      pass
     end
   end