Class: SpoolPool::Pool

Inherits:
Object
  • Object
show all
Defined in:
lib/spool_pool/pool.rb

Overview

This is a container class used to manage the interaction with the individual Spool instances. Spool directories are created using the name given in the put/get methods on demand as subdirectories of the spool_dir passed to the initializer..

Security Note

Some naive tests are in place to catch the most blatant directory traversal attempts. But for real security you should never blindly pass any user-supplied or computed queue name to these methods. Always validate user input!

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(spool_path) ⇒ Pool

Sets up a spooling pool in the spool_path given. If the directory does not exist, it will try to create it for you.

Will throw an exception if it can’t create the directoy, or if the directory exists and is not read- and writeable by the effective user id of the process.



66
67
68
69
70
71
72
73
74
75
# File 'lib/spool_pool/pool.rb', line 66

def initialize( spool_path )
  @spool_dir = Pathname.new spool_path
  @spools = {}

  self.class.validate_pool_dir( spool_path )

  setup_spooldir unless @spool_dir.exist?
  assert_readable @spool_dir
  assert_writeable @spool_dir
end

Instance Attribute Details

#spool_dirObject (readonly)

Returns the value of attribute spool_dir.



19
20
21
# File 'lib/spool_pool/pool.rb', line 19

def spool_dir
  @spool_dir
end

#spoolsObject (readonly)

Returns the value of attribute spools.



20
21
22
# File 'lib/spool_pool/pool.rb', line 20

def spools
  @spools
end

Class Method Details

.validate_pool_dir(directory) ⇒ Object

Sanity checking of the given pool directory and it’s children (and parent,

if the +directory+ itself doesn't exist yet). 

Will throw an exception if anything permission-wise looks fishy.


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/spool_pool/pool.rb', line 28

def self.validate_pool_dir( directory )
  pool_dir = Pathname.new( directory )
 
  begin
    if !pool_dir.exist?
      raise Errno::EACCES unless pool_dir.parent.writable? and
                                 pool_dir.parent.executable?
      return
    end

    raise Errno::EACCES unless pool_dir.readable? and
                               pool_dir.writable? and
                               pool_dir.executable?
    
    return if pool_dir.children.empty?

    pool_dir.children.select{ |d| d.dir? }.each do |spool_dir|
      raise Errno::EACCES unless spool_dir.readable? and
                                 spool_dir.writable? and
                                 spool_dir.executable?

      spool_dir.children.select{ |f| f.file? }.each do |spool_file|
        raise Errno::EACCES unless spool_file.readable?
      end
    end
  rescue Errno::EACCES
    raise Errno::EACCES.new( "Something doesn't look right permission wise. Consider running 'chmod -R 0755 #{directory}' or the equivalent. If the #{directory} itself doesn't exist, check to make sure it's parent exists, and is write- and executable for the current process owner." )
  end
end

Instance Method Details

#flush(spool, &block) ⇒ Object

Retrieves and deserializes all data in the given spool, yielding each deserialized data to the supplied block. Ordering is oldest data first.

Note that while data is retrieved oldest first, the order is non-strict, i.e. different data written during the same second to the storage will be retrieved in a random order. Or to put it another way: Ordering is exact down to the second, but sub-second ordering is random.

This method performs a naive check on the spool name for directory traversal attempts. *DO NOT* rely on this for security relevant systems, always validate user supplied queue names yourself before handing them off to this method!



135
136
137
138
139
140
141
# File 'lib/spool_pool/pool.rb', line 135

def flush( spool, &block )
  validate_spool_path spool

  missing_spool_on_read_handler( spool ) unless @spools.has_key?( spool )

  @spools[spool].flush( &block ) if @spools[spool]
end

#get(spool, &block) ⇒ Object

Retrieves and deserializes oldest data in the given spool, yielding it to an optional block as well. The spool file is deleted just before the method returns. If a block was given, and an exception was raised within the block, the spool file is not deleted and another try at processing can be attempted in the future.

Note that while data is retrieved oldest first, the order is non-strict, i.e. different data written during the same second to the storage will be retrieved in a random order. Or to put it another way: Ordering is exact down to the second, but sub-second ordering is random.

This method performs a naive check on the spool name for directory traversal attempts. *DO NOT* rely on this for security relevant systems, always validate user supplied queue names yourself before handing them off to this method!



111
112
113
114
115
116
117
118
119
# File 'lib/spool_pool/pool.rb', line 111

def get( spool, &block )
  validate_spool_path spool

  missing_spool_on_read_handler( spool ) unless @spools.has_key?( spool )

  data = nil
  data = @spools[spool].get( &block ) if @spools[spool] 
  data
end

#put(spool, data) ⇒ Object

Serializes and stores the data in the given spool. If the spool doesn’t exist yet, it will try to create a new spool and directory.

Returns the path of the file storing the data.

This method performs a naive check on the spool name for directory traversal attempts. *DO NOT* rely on this for security relevant systems, always validate user supplied queue names yourself before handing them off to this method!



88
89
90
91
92
# File 'lib/spool_pool/pool.rb', line 88

def put( spool, data )
  validate_spool_path spool
  @spools[spool] ||= SpoolPool::Spool.new( @spool_dir + spool.to_s )
  @spools[spool].put( data )
end