higgs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. data/ChangeLog +208 -0
  2. data/LICENSE +26 -0
  3. data/README +2 -0
  4. data/Rakefile +75 -0
  5. data/bin/higgs_backup +67 -0
  6. data/bin/higgs_dump_index +43 -0
  7. data/bin/higgs_dump_jlog +42 -0
  8. data/bin/higgs_verify +37 -0
  9. data/lib/cgi/session/higgs.rb +72 -0
  10. data/lib/higgs/block.rb +192 -0
  11. data/lib/higgs/cache.rb +117 -0
  12. data/lib/higgs/dbm.rb +55 -0
  13. data/lib/higgs/exceptions.rb +31 -0
  14. data/lib/higgs/flock.rb +77 -0
  15. data/lib/higgs/index.rb +164 -0
  16. data/lib/higgs/jlog.rb +159 -0
  17. data/lib/higgs/lock.rb +189 -0
  18. data/lib/higgs/storage.rb +1086 -0
  19. data/lib/higgs/store.rb +228 -0
  20. data/lib/higgs/tar.rb +390 -0
  21. data/lib/higgs/thread.rb +370 -0
  22. data/lib/higgs/tman.rb +513 -0
  23. data/lib/higgs/utils/bman.rb +285 -0
  24. data/lib/higgs/utils.rb +22 -0
  25. data/lib/higgs/version.rb +21 -0
  26. data/lib/higgs.rb +59 -0
  27. data/misc/cache_bench/cache_bench.rb +43 -0
  28. data/misc/dbm_bench/.strc +8 -0
  29. data/misc/dbm_bench/Rakefile +78 -0
  30. data/misc/dbm_bench/dbm_multi_thread.rb +199 -0
  31. data/misc/dbm_bench/dbm_rnd_delete.rb +43 -0
  32. data/misc/dbm_bench/dbm_rnd_read.rb +44 -0
  33. data/misc/dbm_bench/dbm_rnd_update.rb +44 -0
  34. data/misc/dbm_bench/dbm_seq_read.rb +45 -0
  35. data/misc/dbm_bench/dbm_seq_write.rb +44 -0
  36. data/misc/dbm_bench/st_verify.rb +28 -0
  37. data/misc/io_bench/cksum_bench.rb +48 -0
  38. data/misc/io_bench/jlog_bench.rb +71 -0
  39. data/misc/io_bench/write_bench.rb +128 -0
  40. data/misc/thread_bench/lock_bench.rb +132 -0
  41. data/mkrdoc.rb +8 -0
  42. data/rdoc.yml +13 -0
  43. data/sample/count.rb +60 -0
  44. data/sample/dbmtest.rb +38 -0
  45. data/test/Rakefile +45 -0
  46. data/test/run.rb +32 -0
  47. data/test/test_block.rb +163 -0
  48. data/test/test_cache.rb +214 -0
  49. data/test/test_cgi_session.rb +142 -0
  50. data/test/test_flock.rb +162 -0
  51. data/test/test_index.rb +258 -0
  52. data/test/test_jlog.rb +180 -0
  53. data/test/test_lock.rb +320 -0
  54. data/test/test_online_backup.rb +169 -0
  55. data/test/test_storage.rb +439 -0
  56. data/test/test_storage_conf.rb +202 -0
  57. data/test/test_storage_init_opts.rb +89 -0
  58. data/test/test_store.rb +211 -0
  59. data/test/test_tar.rb +432 -0
  60. data/test/test_thread.rb +541 -0
  61. data/test/test_tman.rb +875 -0
  62. data/test/test_tman_init_opts.rb +56 -0
  63. data/test/test_utils_bman.rb +234 -0
  64. metadata +115 -0
@@ -0,0 +1,192 @@
1
+ # = block read/write
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'higgs/exceptions'
12
+
13
+ module Higgs
14
+ # block header format
15
+ #
16
+ # 0..15 : Z16 : magic symbol
17
+ # 16..19 : V : body length
18
+ # 20..31 : x12 : (reserved)
19
+ # 32..33 : v : format version
20
+ # 34..35 : x2 : (reserved)
21
+ # 36..37 : v : head cksum
22
+ # 38..39 : x2 : (reserved)
23
+ # 40..55 : Z16 : body hash type
24
+ # 56..57 : v : body hash length
25
+ # 58..71 : x14 : (reserved)
26
+ # 73..511 : a440 : body hash binary
27
+ #
28
+ module Block
29
+ # for ident(1)
30
+ CVS_ID = '$Id: block.rb 559 2007-09-25 15:20:20Z toki $'
31
+
32
+ include Exceptions
33
+
34
+ class BrokenError < HiggsError
35
+ end
36
+
37
+ BLOCK_SIZE = 512
38
+
39
+ FMT_VERSION = 0x00_00
40
+ HEAD_CKSUM_BITS = 16
41
+ HEAD_CKSUM_POS = 36..37
42
+ HEAD_CKSUM_FMT = 'v'
43
+
44
+ BODY_HASH = {}
45
+ [ [ :SUM16, proc{|s| s.sum(16).to_s }, nil ],
46
+ [ :MD5, proc{|s| Digest::MD5.digest(s) }, 'digest/md5' ],
47
+ [ :RMD160, proc{|s| Digest::RMD160.digest(s) }, 'digest/rmd160' ],
48
+ [ :SHA1, proc{|s| Digest::SHA1.digest(s) }, 'digest/sha1' ],
49
+ [ :SHA256, proc{|s| Digest::SHA256.digest(s) }, 'digest/sha2' ],
50
+ [ :SHA384, proc{|s| Digest::SHA384.digest(s) }, 'digest/sha2' ],
51
+ [ :SHA512, proc{|s| Digest::SHA512.digest(s) }, 'digest/sha2' ]
52
+ ].each do |hash_symbol, hash_proc, hash_lib|
53
+ if (hash_lib) then
54
+ begin
55
+ require(hash_lib)
56
+ rescue LoadError
57
+ next
58
+ end
59
+ end
60
+ BODY_HASH[hash_symbol] = hash_proc
61
+ end
62
+
63
+ BODY_HASH_BIN = {}
64
+ BODY_HASH.each do |hash_symbol, hash_proc|
65
+ BODY_HASH_BIN[hash_symbol.to_s] = hash_proc
66
+ end
67
+
68
+ HEAD_FMT = [
69
+ 'Z16', # magic symbol
70
+ 'V', # body length
71
+ 'x12', # (reserved)
72
+ 'v', # format version
73
+ 'x2', # (reserved)
74
+ HEAD_CKSUM_FMT, # head cksum
75
+ 'x2', # (reserved)
76
+ 'Z16', # body hash type
77
+ 'v', # body hash length
78
+ 'x14', # (reserved)
79
+ 'a440' # body hash binary
80
+ ].join('')
81
+
82
+ def padding_size(bytes)
83
+ r = bytes % BLOCK_SIZE
84
+ (r > 0) ? BLOCK_SIZE - r : 0
85
+ end
86
+ module_function :padding_size
87
+
88
+ def head_read(io, magic_symbol)
89
+ head_block = io.read(BLOCK_SIZE) or return
90
+ if (head_block.size != BLOCK_SIZE) then
91
+ raise BrokenError, 'short read'
92
+ end
93
+
94
+ _magic_symbol, body_len, fmt_version, head_cksum,
95
+ body_hash_type, body_hash_len, body_hash_bucket = head_block.unpack(HEAD_FMT)
96
+
97
+ head_block[HEAD_CKSUM_POS] = "\000\000"
98
+ if (head_block.sum(HEAD_CKSUM_BITS) != head_cksum) then
99
+ raise BrokenError, 'broken head block'
100
+ end
101
+
102
+ if (_magic_symbol != magic_symbol) then
103
+ raise BrokenError, "unknown magic symbol: #{_magic_symbol}"
104
+ end
105
+
106
+ if (fmt_version != FMT_VERSION) then
107
+ raise BrokenError, format('unknown format version: 0x%04F', fmt_version)
108
+ end
109
+
110
+ body_hash_bin = body_hash_bucket[0, body_hash_len]
111
+
112
+ return body_len, body_hash_type, body_hash_bin
113
+ end
114
+ module_function :head_read
115
+
116
+ def head_write(io, magic_symbol, body_len, body_hash_type, body_hash_bin)
117
+ head_block = [
118
+ magic_symbol,
119
+ body_len,
120
+ FMT_VERSION,
121
+ 0,
122
+ body_hash_type,
123
+ body_hash_bin.length,
124
+ body_hash_bin
125
+ ].pack(HEAD_FMT)
126
+
127
+ head_cksum = head_block.sum(HEAD_CKSUM_BITS)
128
+ head_block[HEAD_CKSUM_POS] = [ head_cksum ].pack(HEAD_CKSUM_FMT)
129
+
130
+ bytes = io.write(head_block)
131
+ if (bytes != head_block.size) then
132
+ raise BrokenError, 'short write'
133
+ end
134
+ bytes
135
+ end
136
+ module_function :head_write
137
+
138
+ def block_read(io, magic_symbol)
139
+ body_len, body_hash_type, body_hash_bin = head_read(io, magic_symbol)
140
+ unless (body_len) then
141
+ return
142
+ end
143
+
144
+ body = io.read(body_len) or raise BrokenError, 'unexpected EOF'
145
+ if (body.length != body_len) then
146
+ raise BrokenError, 'short read'
147
+ end
148
+
149
+ unless (hash_proc = BODY_HASH_BIN[body_hash_type]) then
150
+ raise BrokenError, "unknown body hash type: #{body_hash_type}"
151
+ end
152
+
153
+ if (hash_proc.call(body) != body_hash_bin) then
154
+ raise BrokenError, 'body hash error'
155
+ end
156
+
157
+ padding_size = padding_size(body_len)
158
+ padding = io.read(padding_size) or raise BrokenError, 'unexpected EOF'
159
+ if (padding.size != padding_size) then
160
+ raise BrokenError, 'short read'
161
+ end
162
+
163
+ body
164
+ end
165
+ module_function :block_read
166
+
167
+ def block_write(io, magic_symbol, body, body_hash_type=:MD5)
168
+ hash_proc = BODY_HASH[body_hash_type.to_sym] or "unknown body hash type: #{body_hash_type}"
169
+ body_hash = hash_proc.call(body)
170
+ head_write(io, magic_symbol, body.length, body_hash_type.to_s, body_hash)
171
+
172
+ bytes = io.write(body)
173
+ if (bytes != body.size) then
174
+ raise BrokenError, 'short write'
175
+ end
176
+
177
+ padding_size = padding_size(body.length)
178
+ bytes = io.write("\0" * padding_size)
179
+ if (bytes != padding_size) then
180
+ raise BrokenError, 'short write'
181
+ end
182
+
183
+ body.size + padding_size
184
+ end
185
+ module_function :block_write
186
+ end
187
+ end
188
+
189
+ # Local Variables:
190
+ # mode: Ruby
191
+ # indent-tabs-mode: nil
192
+ # End:
@@ -0,0 +1,117 @@
1
+ # = cache utilities
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'forwardable'
12
+ require 'higgs/thread'
13
+ require 'thread'
14
+
15
+ module Higgs
16
+ # = cache by Least Recently Used strategy
17
+ class LRUCache
18
+ # for ident(1)
19
+ CVS_ID = '$Id: cache.rb 559 2007-09-25 15:20:20Z toki $'
20
+
21
+ extend Forwardable
22
+
23
+ def initialize(limit_size=1000)
24
+ @limit_size = limit_size
25
+ @count = 0
26
+ @cache = {}
27
+ end
28
+
29
+ attr_reader :limit_size
30
+ def_delegator :@cache, :keys
31
+ def_delegator :@cache, :key?
32
+ alias has_key? key?
33
+ alias include? key?
34
+
35
+ def [](key)
36
+ if (cached_pair = @cache[key]) then
37
+ cached_pair[1] = @count
38
+ @count = @count.succ
39
+ return cached_pair[0]
40
+ end
41
+ nil
42
+ end
43
+
44
+ def []=(key, value)
45
+ if (cached_pair = @cache[key]) then
46
+ cached_pair[1] = @count
47
+ else
48
+ @cache[key] = [ value, @count ]
49
+ end
50
+ @count = @count.succ
51
+ if (@cache.size > @limit_size) then
52
+ purge_old_cache
53
+ end
54
+ value
55
+ end
56
+
57
+ def purge_old_cache
58
+ c_list = @cache.map{|key, (value, c)| c }
59
+ c_list.sort!
60
+ threshold = c_list[c_list.size / 2]
61
+ @cache.delete_if{|key, (value, c)| c < threshold }
62
+ end
63
+ private :purge_old_cache
64
+
65
+ def delete(key)
66
+ if (cached_pair = @cache.delete(key)) then
67
+ return cached_pair[0]
68
+ end
69
+ nil
70
+ end
71
+ end
72
+
73
+ # = multi-thread safe cache
74
+ class SharedWorkCache
75
+ # for ident(1)
76
+ CVS_ID = '$Id: cache.rb 559 2007-09-25 15:20:20Z toki $'
77
+
78
+ def initialize(cache={}, &work)
79
+ unless (work) then
80
+ raise 'required work block'
81
+ end
82
+ @work = work
83
+ @lock = Mutex.new
84
+ @cache = cache
85
+ end
86
+
87
+ def fetch_work(key)
88
+ @lock.synchronize{
89
+ if (@cache.key? key) then
90
+ @cache[key]
91
+ else
92
+ @cache[key] = SharedWork.new{ @work.call(key) }
93
+ end
94
+ }
95
+ end
96
+ private :fetch_work
97
+
98
+ def [](key)
99
+ fetch_work(key).result
100
+ end
101
+
102
+ def []=(key, value)
103
+ work = fetch_work(key)
104
+ work.result = value
105
+ end
106
+
107
+ def delete(key)
108
+ r = @lock.synchronize{ @cache.delete(key) }
109
+ r ? true : false
110
+ end
111
+ end
112
+ end
113
+
114
+ # Local Variables:
115
+ # mode: Ruby
116
+ # indent-tabs-mode: nil
117
+ # End:
data/lib/higgs/dbm.rb ADDED
@@ -0,0 +1,55 @@
1
+ # = storage like dbm
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'higgs/storage'
12
+ require 'higgs/tman'
13
+
14
+ module Higgs
15
+ # = storage like dbm
16
+ # == sample script
17
+ # sample/dbmtest.rb
18
+ # :include: sample/dbmtest.rb
19
+ #
20
+ class DBM
21
+ # for ident(1)
22
+ CVS_ID = '$Id: dbm.rb 559 2007-09-25 15:20:20Z toki $'
23
+
24
+ include Storage::Export
25
+ include TransactionManager::Export
26
+
27
+ DECODE = proc{|r| r }
28
+ ENCODE = proc{|w| w }
29
+
30
+ # <tt>name</tt> is a storage name and see Higgs::Storage.new for
31
+ # detail. see Higgs::Storage::InitOptions and
32
+ # Higgs::TransactionManager::InitOptions for <tt>options</tt>.
33
+ def initialize(name, options={})
34
+ @storage = Storage.new(name, options)
35
+ options[:decode] = DECODE
36
+ options[:encode] = ENCODE
37
+ @tman = TransactionManager.new(@storage, options)
38
+ end
39
+
40
+ def self.open(*args)
41
+ dbm = new(*args)
42
+ begin
43
+ r = yield(dbm)
44
+ ensure
45
+ dbm.shutdown
46
+ end
47
+ r
48
+ end
49
+ end
50
+ end
51
+
52
+ # Local Variables:
53
+ # mode: Ruby
54
+ # indent-tabs-mode: nil
55
+ # End:
@@ -0,0 +1,31 @@
1
+ # = exceptions
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ module Higgs
12
+ module Exceptions
13
+ # for ident(1)
14
+ CVS_ID = '$Id: exceptions.rb 559 2007-09-25 15:20:20Z toki $'
15
+
16
+ class HiggsError < StandardError
17
+ end
18
+
19
+ class HiggsException < Exception
20
+ end
21
+
22
+ class ShutdownException < HiggsException
23
+ end
24
+ end
25
+ include Exceptions
26
+ end
27
+
28
+ # Local Variables:
29
+ # mode: Ruby
30
+ # indent-tabs-mode: nil
31
+ # End:
@@ -0,0 +1,77 @@
1
+ # = file lock
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ module Higgs
12
+ class FileLock
13
+ # for ident(1)
14
+ CVS_ID = '$Id: flock.rb 559 2007-09-25 15:20:20Z toki $'
15
+
16
+ def initialize(path, read_only=false)
17
+ @path = path
18
+ @read_only = read_only
19
+ rdwr_mode = (@read_only) ? File::RDONLY : File::RDWR
20
+ begin
21
+ @f = File.open(path, rdwr_mode | File::CREAT | File::EXCL, 0660)
22
+ rescue Errno::EEXIST
23
+ @f = File.open(path, rdwr_mode, 0660)
24
+ end
25
+ end
26
+
27
+ attr_reader :read_only
28
+
29
+ def read_lock
30
+ @f.flock(File::LOCK_SH)
31
+ nil
32
+ end
33
+
34
+ def write_lock
35
+ if (@read_only) then
36
+ raise 'read only'
37
+ end
38
+ @f.flock(File::LOCK_EX)
39
+ nil
40
+ end
41
+
42
+ def unlock
43
+ @f.flock(File::LOCK_UN)
44
+ nil
45
+ end
46
+
47
+ def close
48
+ unlock
49
+ @f.close
50
+ nil
51
+ end
52
+
53
+ def synchronize(mode=:EX)
54
+ case (mode)
55
+ when :SH
56
+ read_lock
57
+ when :EX
58
+ write_lock
59
+ else
60
+ raise ArgumentError, "unknown lock mode: #{mode}"
61
+ end
62
+
63
+ begin
64
+ r = yield
65
+ ensure
66
+ unlock
67
+ end
68
+
69
+ r
70
+ end
71
+ end
72
+ end
73
+
74
+ # Local Variables:
75
+ # mode: Ruby
76
+ # indent-tabs-mode: nil
77
+ # End:
@@ -0,0 +1,164 @@
1
+ # = storage index
2
+ #
3
+ # Author:: $Author: toki $
4
+ # Date:: $Date: 2007-09-26 00:20:20 +0900 (Wed, 26 Sep 2007) $
5
+ # Revision:: $Revision: 559 $
6
+ #
7
+ # == license
8
+ # :include:LICENSE
9
+ #
10
+
11
+ require 'forwardable'
12
+ require 'higgs/block'
13
+
14
+ module Higgs
15
+ # = storage index
16
+ class Index
17
+ # for ident(1)
18
+ CVS_ID = '$Id: index.rb 559 2007-09-25 15:20:20Z toki $'
19
+
20
+ extend Forwardable
21
+ include Block
22
+
23
+ MAGIC_SYMBOL = 'HIGGS_INDEX'
24
+ MAJOR_VERSION = 0
25
+ MINOR_VERSION = 1
26
+
27
+ def initialize
28
+ @change_number = 0
29
+ @eoa = 0
30
+ @free_lists = {}
31
+ @index = {}
32
+ @identities = {}
33
+ end
34
+
35
+ attr_reader :change_number
36
+ attr_accessor :eoa
37
+
38
+ def succ!
39
+ @change_number = @change_number.succ
40
+ self
41
+ end
42
+
43
+ def free_fetch(size)
44
+ @free_lists[size].shift if (@free_lists.key? size)
45
+ end
46
+
47
+ def free_fetch_at(pos, size)
48
+ @free_lists[size].delete(pos) or raise "not found free region: (#{pos},#{size})"
49
+ end
50
+
51
+ def free_store(pos, size)
52
+ @free_lists[size] = [] unless (@free_lists.key? size)
53
+ @free_lists[size] << pos
54
+ nil
55
+ end
56
+
57
+ def_delegator :@index, :key?
58
+ def_delegator :@index, :keys
59
+
60
+ def identity(key)
61
+ i = @index[key] and i[0]
62
+ end
63
+
64
+ def [](key)
65
+ i = @index[key] and i[1]
66
+ end
67
+
68
+ def self.create_id(key, identities)
69
+ id = key.to_s
70
+ if (identities.key? id) then
71
+ id += '.a'
72
+ id.succ! while (identities.key? id)
73
+ end
74
+ id
75
+ end
76
+
77
+ def []=(key, value)
78
+ delete(key)
79
+ id = Index.create_id(key, @identities)
80
+ @identities[id] = key
81
+ @index[key] = [ id, value ]
82
+ value
83
+ end
84
+
85
+ def delete(key)
86
+ id, value = @index.delete(key)
87
+ @identities.delete(id) if id
88
+ value
89
+ end
90
+
91
+ def each_key
92
+ @index.each_key do |key|
93
+ yield(key)
94
+ end
95
+ self
96
+ end
97
+
98
+ def to_h
99
+ { :version => [ MAJOR_VERSION, MINOR_VERSION ],
100
+ :change_number => @change_number,
101
+ :eoa => @eoa,
102
+ :free_lists => @free_lists,
103
+ :index => @index,
104
+ :identities => @identities
105
+ }
106
+ end
107
+
108
+ def migration_0_0_to_0_1(index_data)
109
+ if ((index_data[:version] <=> [ 0, 0 ]) > 0) then
110
+ return
111
+ end
112
+ if ((index_data[:version] <=> [ 0, 0 ]) < 0) then
113
+ raise "unexpected index format version: #{index_data[:version].join('.')}"
114
+ end
115
+
116
+ index = index_data[:index]
117
+ identities = index_data[:identities] = {}
118
+ for key in index.keys
119
+ id = Index.create_id(key, identities)
120
+ identities[id] = key
121
+ value = index[key]
122
+ index[key] = [ id, value ]
123
+ end
124
+ index_data[:version] = [ 0, 1 ]
125
+
126
+ index_data
127
+ end
128
+ private :migration_0_0_to_0_1
129
+
130
+ def save(path)
131
+ tmp_path = "#{path}.tmp.#{$$}"
132
+ File.open(tmp_path, File::WRONLY | File::CREAT | File::TRUNC, 0660) {|f|
133
+ f.binmode
134
+ index_data = self.to_h
135
+ block_write(f, MAGIC_SYMBOL, Marshal.dump(index_data))
136
+ f.fsync
137
+ }
138
+ File.rename(tmp_path, path)
139
+ self
140
+ end
141
+
142
+ def load(path)
143
+ File.open(path, 'r') {|f|
144
+ f.binmode
145
+ index_data = Marshal.load(block_read(f, MAGIC_SYMBOL))
146
+ migration_0_0_to_0_1(index_data)
147
+ if ((index_data[:version] <=> [ MAJOR_VERSION, MINOR_VERSION ]) > 0) then
148
+ raise "unsupported version: #{index_data[:version].join('.')}"
149
+ end
150
+ @change_number = index_data[:change_number]
151
+ @eoa = index_data[:eoa]
152
+ @free_lists = index_data[:free_lists]
153
+ @index = index_data[:index]
154
+ @identities = index_data[:identities]
155
+ }
156
+ self
157
+ end
158
+ end
159
+ end
160
+
161
+ # Local Variables:
162
+ # mode: Ruby
163
+ # indent-tabs-mode: nil
164
+ # End: