github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/fuse/connection_control.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fuse
    16  
    17  import (
    18  	"sync/atomic"
    19  
    20  	"golang.org/x/sys/unix"
    21  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    22  	"github.com/SagerNet/gvisor/pkg/context"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/auth"
    24  )
    25  
    26  // consts used by FUSE_INIT negotiation.
    27  const (
    28  	// fuseMaxMaxPages is the maximum value for MaxPages received in InitOut.
    29  	// Follow the same behavior as unix fuse implementation.
    30  	fuseMaxMaxPages = 256
    31  
    32  	// Maximum value for the time granularity for file time stamps, 1s.
    33  	// Follow the same behavior as unix fuse implementation.
    34  	fuseMaxTimeGranNs = 1000000000
    35  
    36  	// Minimum value for MaxWrite and MaxRead.
    37  	// Follow the same behavior as unix fuse implementation.
    38  	fuseMinMaxWrite = 4096
    39  	fuseMinMaxRead  = 4096
    40  
    41  	// Temporary default value for max readahead, 128kb.
    42  	fuseDefaultMaxReadahead = 131072
    43  
    44  	// The FUSE_INIT_IN flags sent to the daemon.
    45  	// TODO(github.com/SagerNet/issue/3199): complete the flags.
    46  	fuseDefaultInitFlags = linux.FUSE_MAX_PAGES
    47  )
    48  
    49  // Adjustable maximums for Connection's cogestion control parameters.
    50  // Used as the upperbound of the config values.
    51  // Currently we do not support adjustment to them.
    52  var (
    53  	MaxUserBackgroundRequest   uint16 = fuseDefaultMaxBackground
    54  	MaxUserCongestionThreshold uint16 = fuseDefaultCongestionThreshold
    55  )
    56  
    57  // SetInitialized atomically sets the connection as initialized.
    58  func (conn *connection) SetInitialized() {
    59  	// Unblock the requests sent before INIT.
    60  	close(conn.initializedChan)
    61  
    62  	// Close the channel first to avoid the non-atomic situation
    63  	// where conn.initialized is true but there are
    64  	// tasks being blocked on the channel.
    65  	// And it prevents the newer tasks from gaining
    66  	// unnecessary higher chance to be issued before the blocked one.
    67  
    68  	atomic.StoreInt32(&(conn.initialized), int32(1))
    69  }
    70  
    71  // IsInitialized atomically check if the connection is initialized.
    72  // pairs with SetInitialized().
    73  func (conn *connection) Initialized() bool {
    74  	return atomic.LoadInt32(&(conn.initialized)) != 0
    75  }
    76  
    77  // InitSend sends a FUSE_INIT request.
    78  func (conn *connection) InitSend(creds *auth.Credentials, pid uint32) error {
    79  	in := linux.FUSEInitIn{
    80  		Major: linux.FUSE_KERNEL_VERSION,
    81  		Minor: linux.FUSE_KERNEL_MINOR_VERSION,
    82  		// TODO(github.com/SagerNet/issue/3196): find appropriate way to calculate this
    83  		MaxReadahead: fuseDefaultMaxReadahead,
    84  		Flags:        fuseDefaultInitFlags,
    85  	}
    86  
    87  	req := conn.NewRequest(creds, pid, 0, linux.FUSE_INIT, &in)
    88  	// Since there is no task to block on and FUSE_INIT is the request
    89  	// to unblock other requests, use nil.
    90  	return conn.CallAsync(nil, req)
    91  }
    92  
    93  // InitRecv receives a FUSE_INIT reply and process it.
    94  //
    95  // Preconditions: conn.asyncMu must not be held if minor verion is newer than 13.
    96  func (conn *connection) InitRecv(res *Response, hasSysAdminCap bool) error {
    97  	if err := res.Error(); err != nil {
    98  		return err
    99  	}
   100  
   101  	initRes := fuseInitRes{initLen: res.DataLen()}
   102  	if err := res.UnmarshalPayload(&initRes); err != nil {
   103  		return err
   104  	}
   105  
   106  	return conn.initProcessReply(&initRes.initOut, hasSysAdminCap)
   107  }
   108  
   109  // Process the FUSE_INIT reply from the FUSE server.
   110  // It tries to acquire the conn.asyncMu lock if minor version is newer than 13.
   111  func (conn *connection) initProcessReply(out *linux.FUSEInitOut, hasSysAdminCap bool) error {
   112  	// No matter error or not, always set initialzied.
   113  	// to unblock the blocked requests.
   114  	defer conn.SetInitialized()
   115  
   116  	// No support for old major fuse versions.
   117  	if out.Major != linux.FUSE_KERNEL_VERSION {
   118  		conn.connInitError = true
   119  		return nil
   120  	}
   121  
   122  	// Start processing the reply.
   123  	conn.connInitSuccess = true
   124  	conn.minor = out.Minor
   125  
   126  	// No support for negotiating MaxWrite before minor version 5.
   127  	if out.Minor >= 5 {
   128  		conn.maxWrite = out.MaxWrite
   129  	} else {
   130  		conn.maxWrite = fuseMinMaxWrite
   131  	}
   132  	if conn.maxWrite < fuseMinMaxWrite {
   133  		conn.maxWrite = fuseMinMaxWrite
   134  	}
   135  
   136  	// No support for the following flags before minor version 6.
   137  	if out.Minor >= 6 {
   138  		conn.asyncRead = out.Flags&linux.FUSE_ASYNC_READ != 0
   139  		conn.bigWrites = out.Flags&linux.FUSE_BIG_WRITES != 0
   140  		conn.dontMask = out.Flags&linux.FUSE_DONT_MASK != 0
   141  		conn.writebackCache = out.Flags&linux.FUSE_WRITEBACK_CACHE != 0
   142  
   143  		// TODO(github.com/SagerNet/issue/3195): figure out how to use TimeGran (0 < TimeGran <= fuseMaxTimeGranNs).
   144  
   145  		if out.Flags&linux.FUSE_MAX_PAGES != 0 {
   146  			maxPages := out.MaxPages
   147  			if maxPages < 1 {
   148  				maxPages = 1
   149  			}
   150  			if maxPages > fuseMaxMaxPages {
   151  				maxPages = fuseMaxMaxPages
   152  			}
   153  			conn.maxPages = maxPages
   154  		}
   155  	}
   156  
   157  	// No support for limits before minor version 13.
   158  	if out.Minor >= 13 {
   159  		conn.asyncMu.Lock()
   160  
   161  		if out.MaxBackground > 0 {
   162  			conn.asyncNumMax = out.MaxBackground
   163  
   164  			if !hasSysAdminCap &&
   165  				conn.asyncNumMax > MaxUserBackgroundRequest {
   166  				conn.asyncNumMax = MaxUserBackgroundRequest
   167  			}
   168  		}
   169  
   170  		if out.CongestionThreshold > 0 {
   171  			conn.asyncCongestionThreshold = out.CongestionThreshold
   172  
   173  			if !hasSysAdminCap &&
   174  				conn.asyncCongestionThreshold > MaxUserCongestionThreshold {
   175  				conn.asyncCongestionThreshold = MaxUserCongestionThreshold
   176  			}
   177  		}
   178  
   179  		conn.asyncMu.Unlock()
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  // Abort this FUSE connection.
   186  // It tries to acquire conn.fd.mu, conn.lock, conn.bgLock in order.
   187  // All possible requests waiting or blocking will be aborted.
   188  //
   189  // Preconditions: conn.fd.mu is locked.
   190  func (conn *connection) Abort(ctx context.Context) {
   191  	conn.mu.Lock()
   192  	conn.asyncMu.Lock()
   193  
   194  	if !conn.connected {
   195  		conn.asyncMu.Unlock()
   196  		conn.mu.Unlock()
   197  		return
   198  	}
   199  
   200  	conn.connected = false
   201  
   202  	// Empty the `fd.queue` that holds the requests
   203  	// not yet read by the FUSE daemon yet.
   204  	// These are a subset of the requests in `fuse.completion` map.
   205  	for !conn.fd.queue.Empty() {
   206  		req := conn.fd.queue.Front()
   207  		conn.fd.queue.Remove(req)
   208  	}
   209  
   210  	var terminate []linux.FUSEOpID
   211  
   212  	// 2. Collect the requests have not been sent to FUSE daemon,
   213  	// or have not received a reply.
   214  	for unique := range conn.fd.completions {
   215  		terminate = append(terminate, unique)
   216  	}
   217  
   218  	// Release locks to avoid deadlock.
   219  	conn.asyncMu.Unlock()
   220  	conn.mu.Unlock()
   221  
   222  	// 1. The requets blocked before initialization.
   223  	// Will reach call() `connected` check and return.
   224  	if !conn.Initialized() {
   225  		conn.SetInitialized()
   226  	}
   227  
   228  	// 2. Terminate the requests collected above.
   229  	// Set ECONNABORTED error.
   230  	// sendError() will remove them from `fd.completion` map.
   231  	// Will enter the path of a normally received error.
   232  	for _, toTerminate := range terminate {
   233  		conn.fd.sendError(ctx, -int32(unix.ECONNABORTED), toTerminate)
   234  	}
   235  
   236  	// 3. The requests not yet written to FUSE device.
   237  	// Early terminate.
   238  	// Will reach callFutureLocked() `connected` check and return.
   239  	close(conn.fd.fullQueueCh)
   240  
   241  	// TODO(github.com/SagerNet/issue/3528): Forget all pending forget reqs.
   242  }