server/filesystem.rkt
#lang racket
(require
 racket/system
 "../main.rkt"
 "../network.rkt"
 "../errno.rkt"
 "data.rkt"
 "handle.rkt")

(define ((make-t-handler fs) request)
  (let* ([t-fids (current-fids)]
         [fid->file (λ (fid) (hash-ref t-fids fid (λ () (raise-9p-error EBADF))))])
    (match request
      [(struct message:t:auth (tag (box afid) user root))
       (let* ([auth (send fs authenticate root #:user user)]
              [stat (send auth read-stat)])
         (hash-set! t-fids afid auth)
         (make-message:r:auth tag (stat-qid stat)))]
      [(struct message:t:attach (tag (box fid) afid user root))
       (let* ([file (send fs attach root #:token (fid->file afid) #:user user)]
              [stat (send file read-stat)])
         (hash-set! t-fids fid file)
         (make-message:r:attach tag (stat-qid stat)))]
      [(struct message:t:stat (tag fid))
       (make-message:r:stat tag (send (fid->file fid) read-stat))]
      [(struct message:t:wstat (tag fid stat))
       (send (fid->file fid) write-stat stat)
       (make-message:r:wstat tag)]
      [(struct message:t:walk (tag from-fid (box to-fid) names))
       (let*-values ([(from-file)
                      (fid->file from-fid)]
                     [(to-file qids)
                      (if (zero? (vector-length names))
                          (let ([file (send from-file walk)])
                            (values file '#()))
                          (let/ec break
                            (for/fold ([file from-file]
                                       [qids (make-vector (vector-length names))])
                              ([name (in-vector names)]
                               [i (in-naturals)])
                              (with-handlers ([exn:fail:filesystem:9p?
                                               (λ (exn)
                                                 (if (positive? i)
                                                     (break #f (vector-take qids i))
                                                     (raise exn)))])
                                (let* ([file (send from-file walk name)]
                                       [stat (send file read-stat)])
                                  (vector-set! qids i (stat-qid stat))
                                  (values file qids))))))])
         (when to-file
           (hash-set! t-fids to-fid to-file))
         (make-message:r:walk tag qids))]
      [(struct message:t:create (tag fid name perm mode))
       (let ([old-file (fid->file fid)])
         (if (is-a? old-file directory-handle<%>)
             (let*-values ([(new-file i/o-unit) (send old-file create name perm mode)]
                           [(stat) (send new-file read-stat)])
               (hash-set! t-fids fid new-file)
               (send old-file clunk)
               (make-message:r:create tag (stat-qid stat) i/o-unit))
             (make-message:r:error tag ENOTDIR)))]
      [(struct message:t:open (tag fid mode))
       (let* ([file (fid->file fid)]
              [i/o-unit (send file open mode)]
              [stat (send file read-stat)])
         (make-message:r:open tag (stat-qid stat) i/o-unit))]
      [(struct message:t:read (tag fid offset size))
       (let ([data (send (fid->file fid) read size offset)])
         (make-message:r:read tag (if (eof-object? data) #"" data)))]
      [(struct message:t:write (tag fid offset data))
       (make-message:r:write tag (send (fid->file fid) write data offset))]
      [(struct message:t:clunk (tag fid))
       (let ([file (fid->file fid)])
         (hash-remove! t-fids fid)
         (send file clunk)
         (make-message:r:clunk tag))]
      [(struct message:t:remove (tag fid))
       (let ([file (fid->file fid)])
         (hash-remove! t-fids fid)
         (send file remove)
         (make-message:r:remove tag))]
      [(struct message:t (tag))
       (make-message:r:error tag ENOSYS)])))

(define server-filesystem%
  (class* object% (filesystem<%>)
    
    (super-new)
    
    (init-field [port-no 564] [hostname #f] [9wrapper (find-executable-path "9")])
    
    (init [with-root #f] [with-roots #f] [max-allow-wait 4] [reuse? #f])
    
    (define roots
      (cond
        [(or (and with-root with-roots) (not (or with-root with-roots)))
         (error 'server-filesystem% "expected either with-root or with-roots init argument")]
        [with-root
         (list (cons "" with-root))]
        [else
         with-roots]))
    
    (define custodian
      (start-server
       (make-t-handler this) (λ (handle) (send handle clunk))
       port-no max-allow-wait reuse? hostname))
    
    (define mountpoint
      #f)
    
    (define/public (make-context user)
      (new server-context% [user user]))
    
    (define/public (authenticate root #:user user)
      (raise-9p-error ENOSYS))
    
    (define/public-final (attach root #:user user #:token auth)
      (let ([root (dict-ref roots root (λ () (raise-9p-error ENOENT)))]
            [context (if auth (send auth context) (make-context user))])
        (send root attach context)))
    
    (define/public-final (mount at-mountpoint)
      (cond
        [(not (dict-ref roots "" #f))
         (error 'mount "filesystem has no default root")]
        [(not 9wrapper)
         (error 'mount "Plan9 tool wrapper not available")]
        [mountpoint
         (error 'mount "filesystem already mounted at ~e" mountpoint)]
        [else
         (let* ([at-mountpoint (path->string (expand-user-path at-mountpoint))]
                [success? (system* 9wrapper "mount"
                                   (format "tcp!~a!~a" (or hostname "localhost") port-no)
                                   at-mountpoint)])
           (when success?
             (set! mountpoint at-mountpoint))
           success?)]))
    
    (define/public-final (unmount)
      (let ([success? (and mountpoint (system* 9wrapper "unmount" mountpoint))])
        (when success?
          (set! mountpoint #f))
        success?))
      
    (define/pubment (clunk)
      (when custodian
        (dynamic-wind
         void
         (λ ()
           (inner (void) clunk))
         (λ ()
           (unmount)
           (custodian-shutdown-all custodian)
           (set! custodian #f)))))
    
    ))

(provide
 server-filesystem%)