source: project/release/4/ugarit/trunk/backend-fs.scm @ 25479

Last change on this file since 25479 was 25479, checked in by Alaric Snell-Pym, 9 years ago

ugarit: Dotting is, crossing ts...

File size: 14.0 KB
Line 
1(use ugarit-backend)
2(use sql-de-lite)
3(use srfi-69)
4(use matchable)
5(use regex)
6
7(define (backend-fs base)
8   (define (make-name key extension) ; Break into levels to reduce files-in-one-dir strain
9      (cond
10         ((< (string-length key) 4)
11            (string-append base "/" key extension))
12         ((< (string-length key) 7)
13            (string-append base "/" (string-take key 3) "/" (string-drop key 3) extension))
14         ((< (string-length key) 10)
15            (string-append base "/" (string-take key 3) "/" (string-take (string-drop key 3) 3)
16               "/" (string-drop key 6) extension))
17         (else
18            (string-append base "/" (string-take key 3) "/" (string-take (string-drop key 3) 3)
19               "/" (string-take (string-drop key 6) 3) "/"
20               (string-drop key 9) extension))))
21   (define (ensure-directory! key)
22      (let
23         ((ed (lambda (path)
24            (if (not (directory? path))
25               (create-directory path)))))
26         (if (>= (string-length key) 4)
27            (ed (string-append base "/" (string-take key 3))))
28         (if (>= (string-length key) 7)
29            (ed (string-append base "/" (string-take key 3) "/" (string-take (string-drop key 3) 3))))
30         (if (>= (string-length key) 10)
31            (ed (string-append base "/" (string-take key 3) "/" (string-take (string-drop key 3) 3) "/" (string-take (string-drop key 6) 3))))
32         (void)))
33   (define (delete-dir-if-empty! key)
34      (let
35         ((dd (lambda (path)
36            (if (and (directory? path) (null? (directory path)))
37               (delete-directory path)))))
38
39         (if (>= (string-length key) 10)
40            (dd (string-append base "/" (string-take key 3) "/" (string-take (string-drop key 3) 3) "/" (string-take (string-drop key 6) 3))))
41         (if (>= (string-length key) 7)
42            (dd (string-append base "/" (string-take key 3) "/" (string-take (string-drop key 3) 3))))
43         (if (>= (string-length key) 4)
44            (dd (string-append base "/" (string-take key 3))))
45         (void)))
46
47   (define (make-tag-name tag)
48      (string-append base "/" tag ".tag"))
49
50   (if (not (directory? base))
51      (signal (make-property-condition 'exn 'message "The archive does not exist" 'arguments base)))
52
53   (make-storage
54      (* 1024 1024) ; 1MiB blocks since local disk is fast and cheap
55      #t ; We are writable
56      #t ; We support unlink!
57      (lambda (key data type) ; put!
58         (if (file-read-access? (make-name key ".type"))
59            (signal (make-property-condition 'exn 'message "Duplicate block: put! should not be called on an existing hash" 'arguments (list key type)))
60            (begin
61               (ensure-directory! key)
62               ; Note: We save to ...~ files then mv them into place, so as to avoid ending up with a partial block
63               ; in the archive if it dies in mid-write. We move the .type file in last, since the existance of that is what
64               ; makes the block "official".
65               ; The only thing we need worry about is a race between two snapshots writing the same block at once...
66               ; However, since we can't easily provide atomicity on link!, we just say "don't do that" for now.
67               (with-output-to-file (make-name key ".data~")
68                  (lambda () (write-u8vector data)))
69               (with-output-to-file (make-name key ".type~")
70                  (lambda () (write type)))
71               (with-output-to-file (make-name key ".refcount~")
72                  (lambda () (write 1)))
73               (rename-file (make-name key ".data~") (make-name key ".data"))
74               (rename-file (make-name key ".refcount~") (make-name key ".refcount"))
75               (rename-file (make-name key ".type~") (make-name key ".type"))
76               (void))))
77      (lambda (key) ; exists?
78         (if (file-read-access? (make-name key ".data"))
79            (with-input-from-file (make-name key ".type")
80               (lambda () (read)))
81            #f))
82      (lambda (key) ; get
83         (if (file-read-access? (make-name key ".data"))
84            (with-input-from-file (make-name key ".data")
85               (lambda () (read-u8vector)))
86            #f))
87      (lambda (key) ; link!
88         (if
89            (file-read-access? (make-name key ".data"))
90            (let
91               ((current-refcount
92                  (with-input-from-file (make-name key ".refcount")
93                     (lambda () (read)))))
94               (begin
95                  (with-output-to-file (make-name key ".refcount~")
96                     (lambda () (write (+ current-refcount 1))))
97                     (rename-file (make-name key ".refcount~") (make-name key ".refcount"))))))
98      (lambda (key) ; unlink!
99         (and-let*
100            (((file-read-access? (make-name key ".data")))
101            (current-refcount
102               (with-input-from-file (make-name key ".refcount")
103                  (lambda () (read))))
104            (new-refcount (- current-refcount 1)))
105            (if (zero? new-refcount)
106               (let
107                  ((data (with-input-from-file (make-name key ".data")
108                     (lambda () (read-u8vector)))))
109                  (begin
110                     (delete-file (make-name key ".data"))
111                     (delete-file (make-name key ".type"))
112                     (delete-file (make-name key ".refcount"))
113                     (delete-dir-if-empty! key)
114                     data)) ; returned in case of deletion
115               (begin
116                  (with-output-to-file (make-name key ".refcount~")
117                     (lambda () (write new-refcount)))
118                  (rename-file (make-name key ".refcount~") (make-name key ".refcount"))
119                  #f))))
120      (lambda (tag key) ; set-tag!
121         (with-output-to-file (make-tag-name tag)
122            (lambda () (write key))))
123      (lambda (tag) ; tag
124         (if (file-read-access? (make-tag-name tag))
125            (with-input-from-file (make-tag-name tag)
126               (lambda () (read)))
127            #f))
128      (lambda () ; all-tags
129         (let
130            ((tag-path-regexp (regexp (make-tag-name "(.*)"))))
131            (map
132               (lambda (path)
133                  (cadr (string-match tag-path-regexp path)))
134               (glob (make-tag-name "*")))))
135      (lambda (tag) ; remove-tag!
136         (if (file-write-access? (make-tag-name tag))
137            (delete-file (make-tag-name tag))
138            #f))
139      (lambda (tag) ; lock-tag!
140         ; (printf "FIXME: Implement lock-tag! in backend-fs.scm\n")
141         #f)
142      (lambda (tag) ; tag-locked?
143         ; (printf "FIXME: Implement tag-locked? in backend-fs.scm\n")
144         #f)
145      (lambda (tag) ; unlock-tag!
146         ; (printf "FIXME: Implement unlock-tag! in backend-fs.scm\n")
147         #f)
148      (lambda () ; close!
149         (void))))
150
151(define splitlog-sql-schema
152  (list
153   "CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT);"
154   "INSERT INTO metadata VALUES ('version','1');"
155   "INSERT INTO metadata VALUES ('current-logfile','0');"
156   "CREATE TABLE blocks (key TEXT PRIMARY KEY, type TEXT, fileno INTEGER, position INTEGER, length INTEGER);"
157   "CREATE TABLE tags (tag TEXT PRIMARY KEY, key TEXT, locked INTEGER DEFAULT 0);"))
158
159(define (backend-splitlog logdir metapath max-logpart-size)
160   (let*
161        ((*db*
162          (let ((db (open-database metapath)))
163            (when (null? (schema db))
164                  (for-each (lambda (statement)
165                              (exec (sql db statement)))
166                            splitlog-sql-schema))
167            db))
168         (*logcount* (string->number (car (query fetch (sql *db* "SELECT value FROM metadata WHERE key = 'current-logfile'")))))
169         (set-logcount! (lambda (newcount)
170                         (set! *logcount* newcount)
171                         (exec (sql *db* "INSERT OR REPLACE INTO metadata (key,value) VALUES ('current-logfile',?)") newcount)))
172         (*log* (file-open (string-append logdir "/log" (number->string *logcount*))
173                  (+ open/creat open/rdwr open/append) perm/irwxu))
174         (*logfiles* (make-hash-table)) ; hash of file number to FD
175         (get-block-data (lambda (key) ; Returns #f for nonexistant blocks
176                           (let ((bd (query fetch (sql *db* "SELECT type, fileno, position, length FROM blocks WHERE key = ?") key)))
177                             (if (pair? bd)
178                                 (let ((type (string->symbol (first bd)))
179                                       (fileno (second bd))
180                                       (position (third bd))
181                                       (length (fourth bd)))
182                                   (list type fileno position length))
183                                 #f))))
184         (set-block-data! (lambda (key type fileno position length)
185                           (exec (sql *db* "INSERT INTO blocks (key,type,fileno,position,length) VALUES (?,?,?,?,?)") key (symbol->string type) fileno position length)))
186         (set-tag! (lambda (tag key)
187                    (exec (sql *db* "INSERT OR REPLACE INTO tags (tag,key) VALUES (?,?)") tag key)))
188         (remove-tag! (lambda (tag)
189                        (exec (sql *db* "DELETE FROM tags WHERE tag = ?") tag)))
190         (get-tag (lambda (tag)
191                         (let ((td (query fetch (sql *db* "SELECT key FROM tags WHERE tag = ?") tag)))
192                           (if (pair? td)
193                               (car td)
194                               #f))))
195         (set-tag-lock! (lambda (tag lock)
196                      (exec (sql *db* "UPDATE tags SET locked = ? WHERE tag = ?") lock tag)))
197         (get-tag-lock (lambda (tag lock)
198                         (let ((td (query fetch (sql *db* "SELECT locked FROM tags WHERE tag = ?") tag)))
199                           (if (pair? td)
200                               (car td)
201                               #f))))
202         (get-tags (lambda ()
203                     (map car (query fetch-all (sql *db* "SELECT tag FROM tags")))))
204         (get-log (lambda (index)
205            (if (hash-table-exists? *logfiles* index)
206               (hash-table-ref *logfiles* index)
207               (begin
208                  (let ((fd (file-open (string-append logdir "/log" (number->string index)) open/rdonly perm/irwxu)))
209                     (set! (hash-table-ref *logfiles* index) fd)
210                     fd))))))
211
212      ; FIXME: Sanity check that all opened OK
213
214      (make-storage
215         (* 1024 1024) ; 1MiB blocks since local disk is fast and cheap
216         #t ; We are writable
217         #f ; We DO NOT support unlink!
218
219         (lambda (key data type) ; put!
220           (with-transaction *db*
221                             (lambda ()
222                               (when (pair? (get-block-data key))
223                                     (signal (make-property-condition 'exn 'message "Duplicate block: put! should not be called on an existing hash" 'arguments (list key type))))
224
225                               (set-file-position! *log* 0 seek/end)
226
227                               (let ((header (sprintf "(block ~S ~S ~S)" key type (u8vector-length data)))
228                                     (posn (file-position *log*)))
229                                 (if (> posn max-logpart-size)
230                                     (begin
231                                       (file-close *log*)
232                                       (set! posn 0)
233                                       (set-logcount! (+ *logcount* 1))
234                                       (set! *log* (file-open (string-append logdir "/log" (number->string *logcount*))
235                                                              (+ open/creat open/rdwr open/append) perm/irwxu))))
236                                 (file-write *log* header)
237                                 (file-write *log* (u8vector->blob/shared data))
238                                 (set-block-data! key type *logcount* (+ (string-length header) posn) (u8vector-length data))
239                                 (void)))))
240
241         (lambda (key) ; exists?
242           (let ((bd (get-block-data key)))
243             (if (pair? bd)
244                 (car bd)
245                 #f)))
246
247         (lambda (key) ; get
248            (let* ((entry (get-block-data key)))
249              (if (pair? entry)
250               (let* ((type (first entry))
251                      (index (second entry))
252                      (position (third entry))
253                      (length (fourth entry))
254                      (buffer (make-blob length))
255                      (logpart (get-log index)))
256                 (set-file-position! logpart position seek/set)
257                 (file-read logpart length buffer)
258                 (blob->u8vector/shared buffer))
259               #f)))
260
261         (lambda (key) ; link!
262            (void))
263
264         (lambda (key) ; unlink!
265            (signal (make-property-condition 'exn 'message "Log archives do not support deletion")))
266
267         (lambda (tag key) ; set-tag!
268            (file-write *log* (sprintf "(tag ~S ~S)" tag key))
269            (set-tag! tag key)
270            (void))
271         (lambda (tag) ; tag
272           (get-tag tag))
273         (lambda () ; all-tags
274           (get-tags))
275         (lambda (tag) ; remove-tag!
276           (remove-tag! tag)
277           (void))
278         (lambda (tag) ; lock-tag!
279           (set-tag-lock! tag 1)
280           (void))
281         (lambda (tag) ; tag-locked?
282           (if (zero? (get-tag-lock tag))
283               #f
284               #t))
285         (lambda (tag) ; unlock-tag!
286           (set-tag-lock! tag 0))
287         (lambda () ; close!
288           (close-database *db*)
289           (file-close *log*)
290           (hash-table-for-each *logfiles*
291                                (lambda (key value)
292                                  (file-close value)))))))
293
294(define backend
295  (match (command-line-arguments)
296         (("fs" base)
297          (backend-fs base))
298
299         (("splitlog" logdir metadir max-logpart-size)
300          (backend-splitlog logdir metadir (string->number max-logpart-size)))
301
302         (else
303          (printf "USAGE:\nbackend-fs fs <basedir-path>\nbackend-fs splitlog <logdir-path> <metadata-file-path> <max-file-size>\n")
304          #f)))
305
306(if backend
307    (export-storage! backend))
Note: See TracBrowser for help on using the repository browser.