1*134e1779SJakub Wojciech Klama#! /usr/bin/env python 2*134e1779SJakub Wojciech Klama 3*134e1779SJakub Wojciech Klama""" 4*134e1779SJakub Wojciech Klamahandle plan9 server <-> client connections 5*134e1779SJakub Wojciech Klama 6*134e1779SJakub Wojciech Klama(We can act as either server or client.) 7*134e1779SJakub Wojciech Klama 8*134e1779SJakub Wojciech KlamaThis code needs some doctests or other unit tests... 9*134e1779SJakub Wojciech Klama""" 10*134e1779SJakub Wojciech Klama 11*134e1779SJakub Wojciech Klamaimport collections 12*134e1779SJakub Wojciech Klamaimport errno 13*134e1779SJakub Wojciech Klamaimport logging 14*134e1779SJakub Wojciech Klamaimport math 15*134e1779SJakub Wojciech Klamaimport os 16*134e1779SJakub Wojciech Klamaimport socket 17*134e1779SJakub Wojciech Klamaimport stat 18*134e1779SJakub Wojciech Klamaimport struct 19*134e1779SJakub Wojciech Klamaimport sys 20*134e1779SJakub Wojciech Klamaimport threading 21*134e1779SJakub Wojciech Klamaimport time 22*134e1779SJakub Wojciech Klama 23*134e1779SJakub Wojciech Klamaimport lerrno 24*134e1779SJakub Wojciech Klamaimport numalloc 25*134e1779SJakub Wojciech Klamaimport p9err 26*134e1779SJakub Wojciech Klamaimport pfod 27*134e1779SJakub Wojciech Klamaimport protocol 28*134e1779SJakub Wojciech Klama 29*134e1779SJakub Wojciech Klama# Timespec based timestamps, if present, have 30*134e1779SJakub Wojciech Klama# both seconds and nanoseconds. 31*134e1779SJakub Wojciech KlamaTimespec = collections.namedtuple('Timespec', 'sec nsec') 32*134e1779SJakub Wojciech Klama 33*134e1779SJakub Wojciech Klama# File attributes from Tgetattr, or given to Tsetattr. 34*134e1779SJakub Wojciech Klama# (move to protocol.py?) We use pfod here instead of 35*134e1779SJakub Wojciech Klama# namedtuple so that we can create instances with all-None 36*134e1779SJakub Wojciech Klama# fields easily. 37*134e1779SJakub Wojciech KlamaFileattrs = pfod.pfod('Fileattrs', 38*134e1779SJakub Wojciech Klama 'ino mode uid gid nlink rdev size blksize blocks ' 39*134e1779SJakub Wojciech Klama 'atime mtime ctime btime gen data_version') 40*134e1779SJakub Wojciech Klama 41*134e1779SJakub Wojciech Klamaqt2n = protocol.qid_type2name 42*134e1779SJakub Wojciech Klama 43*134e1779SJakub Wojciech KlamaSTD_P9_PORT=564 44*134e1779SJakub Wojciech Klama 45*134e1779SJakub Wojciech Klamaclass P9Error(Exception): 46*134e1779SJakub Wojciech Klama pass 47*134e1779SJakub Wojciech Klama 48*134e1779SJakub Wojciech Klamaclass RemoteError(P9Error): 49*134e1779SJakub Wojciech Klama """ 50*134e1779SJakub Wojciech Klama Used when the remote returns an error. We track the client 51*134e1779SJakub Wojciech Klama (connection instance), the operation being attempted, the 52*134e1779SJakub Wojciech Klama message, and an error number and type. The message may be 53*134e1779SJakub Wojciech Klama from the Rerror reply, or from converting the errno in a dot-L 54*134e1779SJakub Wojciech Klama or dot-u Rerror reply. The error number may be None if the 55*134e1779SJakub Wojciech Klama type is 'Rerror' rather than 'Rlerror'. The message may be 56*134e1779SJakub Wojciech Klama None or empty string if a non-None errno supplies the error 57*134e1779SJakub Wojciech Klama instead. 58*134e1779SJakub Wojciech Klama """ 59*134e1779SJakub Wojciech Klama def __init__(self, client, op, msg, etype, errno): 60*134e1779SJakub Wojciech Klama self.client = str(client) 61*134e1779SJakub Wojciech Klama self.op = op 62*134e1779SJakub Wojciech Klama self.msg = msg 63*134e1779SJakub Wojciech Klama self.etype = etype # 'Rerror' or 'Rlerror' 64*134e1779SJakub Wojciech Klama self.errno = errno # may be None 65*134e1779SJakub Wojciech Klama self.message = self._get_message() 66*134e1779SJakub Wojciech Klama super(RemoteError, self).__init__(self, self.message) 67*134e1779SJakub Wojciech Klama 68*134e1779SJakub Wojciech Klama def __repr__(self): 69*134e1779SJakub Wojciech Klama return ('{0!r}({1}, {2}, {3}, {4}, ' 70*134e1779SJakub Wojciech Klama '{5})'.format(self.__class__.__name__, self.client, self.op, 71*134e1779SJakub Wojciech Klama self.msg, self.errno, self.etype)) 72*134e1779SJakub Wojciech Klama def __str__(self): 73*134e1779SJakub Wojciech Klama prefix = '{0}: {1}: '.format(self.client, self.op) 74*134e1779SJakub Wojciech Klama if self.errno: # check for "is not None", or just non-false-y? 75*134e1779SJakub Wojciech Klama name = {'Rerror': '.u', 'Rlerror': 'Linux'}[self.etype] 76*134e1779SJakub Wojciech Klama middle = '[{0} error {1}] '.format(name, self.errno) 77*134e1779SJakub Wojciech Klama else: 78*134e1779SJakub Wojciech Klama middle = '' 79*134e1779SJakub Wojciech Klama return '{0}{1}{2}'.format(prefix, middle, self.message) 80*134e1779SJakub Wojciech Klama 81*134e1779SJakub Wojciech Klama def is_ENOTSUP(self): 82*134e1779SJakub Wojciech Klama if self.etype == 'Rlerror': 83*134e1779SJakub Wojciech Klama return self.errno == lerrno.EOPNOTSUPP 84*134e1779SJakub Wojciech Klama return self.errno == errno.EOPNOTSUPP 85*134e1779SJakub Wojciech Klama 86*134e1779SJakub Wojciech Klama def _get_message(self): 87*134e1779SJakub Wojciech Klama "get message based on self.msg or self.errno" 88*134e1779SJakub Wojciech Klama if self.errno is not None: 89*134e1779SJakub Wojciech Klama return { 90*134e1779SJakub Wojciech Klama 'Rlerror': p9err.dotl_strerror, 91*134e1779SJakub Wojciech Klama 'Rerror' : p9err.dotu_strerror, 92*134e1779SJakub Wojciech Klama }[self.etype](self.errno) 93*134e1779SJakub Wojciech Klama return self.msg 94*134e1779SJakub Wojciech Klama 95*134e1779SJakub Wojciech Klamaclass LocalError(P9Error): 96*134e1779SJakub Wojciech Klama pass 97*134e1779SJakub Wojciech Klama 98*134e1779SJakub Wojciech Klamaclass TEError(LocalError): 99*134e1779SJakub Wojciech Klama pass 100*134e1779SJakub Wojciech Klama 101*134e1779SJakub Wojciech Klamaclass P9SockIO(object): 102*134e1779SJakub Wojciech Klama """ 103*134e1779SJakub Wojciech Klama Common base for server and client, handle send and 104*134e1779SJakub Wojciech Klama receive to communications channel. Note that this 105*134e1779SJakub Wojciech Klama need not set up the channel initially, only the logger. 106*134e1779SJakub Wojciech Klama The channel is typically connected later. However, you 107*134e1779SJakub Wojciech Klama can provide one initially. 108*134e1779SJakub Wojciech Klama """ 109*134e1779SJakub Wojciech Klama def __init__(self, logger, name=None, server=None, port=STD_P9_PORT): 110*134e1779SJakub Wojciech Klama self.logger = logger 111*134e1779SJakub Wojciech Klama self.channel = None 112*134e1779SJakub Wojciech Klama self.name = name 113*134e1779SJakub Wojciech Klama self.maxio = None 114*134e1779SJakub Wojciech Klama self.size_coder = struct.Struct('<I') 115*134e1779SJakub Wojciech Klama if server is not None: 116*134e1779SJakub Wojciech Klama self.connect(server, port) 117*134e1779SJakub Wojciech Klama self.max_payload = 2**32 - self.size_coder.size 118*134e1779SJakub Wojciech Klama 119*134e1779SJakub Wojciech Klama def __str__(self): 120*134e1779SJakub Wojciech Klama if self.name: 121*134e1779SJakub Wojciech Klama return self.name 122*134e1779SJakub Wojciech Klama return repr(self) 123*134e1779SJakub Wojciech Klama 124*134e1779SJakub Wojciech Klama def get_recommended_maxio(self): 125*134e1779SJakub Wojciech Klama "suggest a max I/O size, for when self.maxio is 0 / unset" 126*134e1779SJakub Wojciech Klama return 16 * 4096 127*134e1779SJakub Wojciech Klama 128*134e1779SJakub Wojciech Klama def min_maxio(self): 129*134e1779SJakub Wojciech Klama "return a minimum size below which we refuse to work" 130*134e1779SJakub Wojciech Klama return self.size_coder.size + 100 131*134e1779SJakub Wojciech Klama 132*134e1779SJakub Wojciech Klama def connect(self, server, port=STD_P9_PORT): 133*134e1779SJakub Wojciech Klama """ 134*134e1779SJakub Wojciech Klama Connect to given server name / IP address. 135*134e1779SJakub Wojciech Klama 136*134e1779SJakub Wojciech Klama If self.name was none, sets self.name to ip:port on success. 137*134e1779SJakub Wojciech Klama """ 138*134e1779SJakub Wojciech Klama if self.is_connected(): 139*134e1779SJakub Wojciech Klama raise LocalError('already connected') 140*134e1779SJakub Wojciech Klama sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 141*134e1779SJakub Wojciech Klama sock.connect((server, port)) 142*134e1779SJakub Wojciech Klama if self.name is None: 143*134e1779SJakub Wojciech Klama if port == STD_P9_PORT: 144*134e1779SJakub Wojciech Klama name = server 145*134e1779SJakub Wojciech Klama else: 146*134e1779SJakub Wojciech Klama name = '{0}:{1}'.format(server, port) 147*134e1779SJakub Wojciech Klama else: 148*134e1779SJakub Wojciech Klama name = None 149*134e1779SJakub Wojciech Klama self.declare_connected(sock, name, None) 150*134e1779SJakub Wojciech Klama 151*134e1779SJakub Wojciech Klama def is_connected(self): 152*134e1779SJakub Wojciech Klama "predicate: are we connected?" 153*134e1779SJakub Wojciech Klama return self.channel != None 154*134e1779SJakub Wojciech Klama 155*134e1779SJakub Wojciech Klama def declare_connected(self, chan, name, maxio): 156*134e1779SJakub Wojciech Klama """ 157*134e1779SJakub Wojciech Klama Now available for normal protocol (size-prefixed) I/O. 158*134e1779SJakub Wojciech Klama 159*134e1779SJakub Wojciech Klama Replaces chan and name and adjusts maxio, if those 160*134e1779SJakub Wojciech Klama parameters are not None. 161*134e1779SJakub Wojciech Klama """ 162*134e1779SJakub Wojciech Klama if maxio: 163*134e1779SJakub Wojciech Klama minio = self.min_maxio() 164*134e1779SJakub Wojciech Klama if maxio < minio: 165*134e1779SJakub Wojciech Klama raise LocalError('maxio={0} < minimum {1}'.format(maxio, minio)) 166*134e1779SJakub Wojciech Klama if chan is not None: 167*134e1779SJakub Wojciech Klama self.channel = chan 168*134e1779SJakub Wojciech Klama if name is not None: 169*134e1779SJakub Wojciech Klama self.name = name 170*134e1779SJakub Wojciech Klama if maxio is not None: 171*134e1779SJakub Wojciech Klama self.maxio = maxio 172*134e1779SJakub Wojciech Klama self.max_payload = maxio - self.size_coder.size 173*134e1779SJakub Wojciech Klama 174*134e1779SJakub Wojciech Klama def reduce_maxio(self, maxio): 175*134e1779SJakub Wojciech Klama "Reduce maximum I/O size per other-side request" 176*134e1779SJakub Wojciech Klama minio = self.min_maxio() 177*134e1779SJakub Wojciech Klama if maxio < minio: 178*134e1779SJakub Wojciech Klama raise LocalError('new maxio={0} < minimum {1}'.format(maxio, minio)) 179*134e1779SJakub Wojciech Klama if maxio > self.maxio: 180*134e1779SJakub Wojciech Klama raise LocalError('new maxio={0} > current {1}'.format(maxio, 181*134e1779SJakub Wojciech Klama self.maxio)) 182*134e1779SJakub Wojciech Klama self.maxio = maxio 183*134e1779SJakub Wojciech Klama self.max_payload = maxio - self.size_coder.size 184*134e1779SJakub Wojciech Klama 185*134e1779SJakub Wojciech Klama def declare_disconnected(self): 186*134e1779SJakub Wojciech Klama "Declare comm channel dead (note: leaves self.name set!)" 187*134e1779SJakub Wojciech Klama self.channel = None 188*134e1779SJakub Wojciech Klama self.maxio = None 189*134e1779SJakub Wojciech Klama 190*134e1779SJakub Wojciech Klama def shutwrite(self): 191*134e1779SJakub Wojciech Klama "Do a SHUT_WR on the outbound channel - can't send more" 192*134e1779SJakub Wojciech Klama chan = self.channel 193*134e1779SJakub Wojciech Klama # we're racing other threads here 194*134e1779SJakub Wojciech Klama try: 195*134e1779SJakub Wojciech Klama chan.shutdown(socket.SHUT_WR) 196*134e1779SJakub Wojciech Klama except (OSError, AttributeError): 197*134e1779SJakub Wojciech Klama pass 198*134e1779SJakub Wojciech Klama 199*134e1779SJakub Wojciech Klama def shutdown(self): 200*134e1779SJakub Wojciech Klama "Shut down comm channel" 201*134e1779SJakub Wojciech Klama if self.channel: 202*134e1779SJakub Wojciech Klama try: 203*134e1779SJakub Wojciech Klama self.channel.shutdown(socket.SHUT_RDWR) 204*134e1779SJakub Wojciech Klama except socket.error: 205*134e1779SJakub Wojciech Klama pass 206*134e1779SJakub Wojciech Klama self.channel.close() 207*134e1779SJakub Wojciech Klama self.declare_disconnected() 208*134e1779SJakub Wojciech Klama 209*134e1779SJakub Wojciech Klama def read(self): 210*134e1779SJakub Wojciech Klama """ 211*134e1779SJakub Wojciech Klama Try to read a complete packet. 212*134e1779SJakub Wojciech Klama 213*134e1779SJakub Wojciech Klama Returns '' for EOF, as read() usually does. 214*134e1779SJakub Wojciech Klama 215*134e1779SJakub Wojciech Klama If we can't even get the size, this still returns ''. 216*134e1779SJakub Wojciech Klama If we get a sensible size but are missing some data, 217*134e1779SJakub Wojciech Klama we can return a short packet. Since we know if we did 218*134e1779SJakub Wojciech Klama this, we also return a boolean: True means "really got a 219*134e1779SJakub Wojciech Klama complete packet." 220*134e1779SJakub Wojciech Klama 221*134e1779SJakub Wojciech Klama Note that '' EOF always returns False: EOF is never a 222*134e1779SJakub Wojciech Klama complete packet. 223*134e1779SJakub Wojciech Klama """ 224*134e1779SJakub Wojciech Klama if self.channel is None: 225*134e1779SJakub Wojciech Klama return b'', False 226*134e1779SJakub Wojciech Klama size_field = self.xread(self.size_coder.size) 227*134e1779SJakub Wojciech Klama if len(size_field) < self.size_coder.size: 228*134e1779SJakub Wojciech Klama if len(size_field) == 0: 229*134e1779SJakub Wojciech Klama self.logger.log(logging.INFO, '%s: normal EOF', self) 230*134e1779SJakub Wojciech Klama else: 231*134e1779SJakub Wojciech Klama self.logger.log(logging.ERROR, 232*134e1779SJakub Wojciech Klama '%s: EOF while reading size (got %d bytes)', 233*134e1779SJakub Wojciech Klama self, len(size_field)) 234*134e1779SJakub Wojciech Klama # should we raise an error here? 235*134e1779SJakub Wojciech Klama return b'', False 236*134e1779SJakub Wojciech Klama 237*134e1779SJakub Wojciech Klama size = self.size_coder.unpack(size_field)[0] - self.size_coder.size 238*134e1779SJakub Wojciech Klama if size <= 0 or size > self.max_payload: 239*134e1779SJakub Wojciech Klama self.logger.log(logging.ERROR, 240*134e1779SJakub Wojciech Klama '%s: incoming size %d is insane ' 241*134e1779SJakub Wojciech Klama '(max payload is %d)', 242*134e1779SJakub Wojciech Klama self, size, self.max_payload) 243*134e1779SJakub Wojciech Klama # indicate EOF - should we raise an error instead, here? 244*134e1779SJakub Wojciech Klama return b'', False 245*134e1779SJakub Wojciech Klama data = self.xread(size) 246*134e1779SJakub Wojciech Klama return data, len(data) == size 247*134e1779SJakub Wojciech Klama 248*134e1779SJakub Wojciech Klama def xread(self, nbytes): 249*134e1779SJakub Wojciech Klama """ 250*134e1779SJakub Wojciech Klama Read nbytes bytes, looping if necessary. Return '' for 251*134e1779SJakub Wojciech Klama EOF; may return a short count if we get some data, then 252*134e1779SJakub Wojciech Klama EOF. 253*134e1779SJakub Wojciech Klama """ 254*134e1779SJakub Wojciech Klama assert nbytes > 0 255*134e1779SJakub Wojciech Klama # Try to get everything at once (should usually succeed). 256*134e1779SJakub Wojciech Klama # Return immediately for EOF or got-all-data. 257*134e1779SJakub Wojciech Klama data = self.channel.recv(nbytes) 258*134e1779SJakub Wojciech Klama if data == b'' or len(data) == nbytes: 259*134e1779SJakub Wojciech Klama return data 260*134e1779SJakub Wojciech Klama 261*134e1779SJakub Wojciech Klama # Gather data fragments into an array, then join it all at 262*134e1779SJakub Wojciech Klama # the end. 263*134e1779SJakub Wojciech Klama count = len(data) 264*134e1779SJakub Wojciech Klama data = [data] 265*134e1779SJakub Wojciech Klama while count < nbytes: 266*134e1779SJakub Wojciech Klama more = self.channel.recv(nbytes - count) 267*134e1779SJakub Wojciech Klama if more == b'': 268*134e1779SJakub Wojciech Klama break 269*134e1779SJakub Wojciech Klama count += len(more) 270*134e1779SJakub Wojciech Klama data.append(more) 271*134e1779SJakub Wojciech Klama return b''.join(data) 272*134e1779SJakub Wojciech Klama 273*134e1779SJakub Wojciech Klama def write(self, data): 274*134e1779SJakub Wojciech Klama """ 275*134e1779SJakub Wojciech Klama Write all the data, in the usual encoding. Note that 276*134e1779SJakub Wojciech Klama the length of the data, including the length of the length 277*134e1779SJakub Wojciech Klama itself, is already encoded in the first 4 bytes of the 278*134e1779SJakub Wojciech Klama data. 279*134e1779SJakub Wojciech Klama 280*134e1779SJakub Wojciech Klama Raises IOError if we can't write everything. 281*134e1779SJakub Wojciech Klama 282*134e1779SJakub Wojciech Klama Raises LocalError if len(data) exceeds max_payload. 283*134e1779SJakub Wojciech Klama """ 284*134e1779SJakub Wojciech Klama size = len(data) 285*134e1779SJakub Wojciech Klama assert size >= 4 286*134e1779SJakub Wojciech Klama if size > self.max_payload: 287*134e1779SJakub Wojciech Klama raise LocalError('data length {0} exceeds ' 288*134e1779SJakub Wojciech Klama 'maximum {1}'.format(size, self.max_payload)) 289*134e1779SJakub Wojciech Klama self.channel.sendall(data) 290*134e1779SJakub Wojciech Klama 291*134e1779SJakub Wojciech Klamadef _pathcat(prefix, suffix): 292*134e1779SJakub Wojciech Klama """ 293*134e1779SJakub Wojciech Klama Concatenate paths we are using on the server side. This is 294*134e1779SJakub Wojciech Klama basically just prefix + / + suffix, with two complications: 295*134e1779SJakub Wojciech Klama 296*134e1779SJakub Wojciech Klama It's possible we don't have a prefix path, in which case 297*134e1779SJakub Wojciech Klama we want the suffix without a leading slash. 298*134e1779SJakub Wojciech Klama 299*134e1779SJakub Wojciech Klama It's possible that the prefix is just b'/', in which case we 300*134e1779SJakub Wojciech Klama want prefix + suffix. 301*134e1779SJakub Wojciech Klama """ 302*134e1779SJakub Wojciech Klama if prefix: 303*134e1779SJakub Wojciech Klama if prefix == b'/': # or prefix.endswith(b'/')? 304*134e1779SJakub Wojciech Klama return prefix + suffix 305*134e1779SJakub Wojciech Klama return prefix + b'/' + suffix 306*134e1779SJakub Wojciech Klama return suffix 307*134e1779SJakub Wojciech Klama 308*134e1779SJakub Wojciech Klamaclass P9Client(P9SockIO): 309*134e1779SJakub Wojciech Klama """ 310*134e1779SJakub Wojciech Klama Act as client. 311*134e1779SJakub Wojciech Klama 312*134e1779SJakub Wojciech Klama We need the a logger (see logging), a timeout, and a protocol 313*134e1779SJakub Wojciech Klama version to request. By default, we will downgrade to a lower 314*134e1779SJakub Wojciech Klama version if asked. 315*134e1779SJakub Wojciech Klama 316*134e1779SJakub Wojciech Klama If server and port are supplied, they are remembered and become 317*134e1779SJakub Wojciech Klama the default for .connect() (which is still deferred). 318*134e1779SJakub Wojciech Klama 319*134e1779SJakub Wojciech Klama Note that we keep a table of fid-to-path in self.live_fids, 320*134e1779SJakub Wojciech Klama but at any time (except while holding the lock) a fid can 321*134e1779SJakub Wojciech Klama be deleted entirely, and the table entry may just be True 322*134e1779SJakub Wojciech Klama if we have no path name. In general, we update the name 323*134e1779SJakub Wojciech Klama when we can. 324*134e1779SJakub Wojciech Klama """ 325*134e1779SJakub Wojciech Klama def __init__(self, logger, timeout, version, may_downgrade=True, 326*134e1779SJakub Wojciech Klama server=None, port=None): 327*134e1779SJakub Wojciech Klama super(P9Client, self).__init__(logger) 328*134e1779SJakub Wojciech Klama self.timeout = timeout 329*134e1779SJakub Wojciech Klama self.iproto = protocol.p9_version(version) 330*134e1779SJakub Wojciech Klama self.may_downgrade = may_downgrade 331*134e1779SJakub Wojciech Klama self.tagalloc = numalloc.NumAlloc(0, 65534) 332*134e1779SJakub Wojciech Klama self.tagstate = {} 333*134e1779SJakub Wojciech Klama # The next bit is slighlty dirty: perhaps we should just 334*134e1779SJakub Wojciech Klama # allocate NOFID out of the 2**32-1 range, so as to avoid 335*134e1779SJakub Wojciech Klama # "knowing" that it's 2**32-1. 336*134e1779SJakub Wojciech Klama self.fidalloc = numalloc.NumAlloc(0, protocol.td.NOFID - 1) 337*134e1779SJakub Wojciech Klama self.live_fids = {} 338*134e1779SJakub Wojciech Klama self.rootfid = None 339*134e1779SJakub Wojciech Klama self.rootqid = None 340*134e1779SJakub Wojciech Klama self.rthread = None 341*134e1779SJakub Wojciech Klama self.lock = threading.Lock() 342*134e1779SJakub Wojciech Klama self.new_replies = threading.Condition(self.lock) 343*134e1779SJakub Wojciech Klama self._monkeywrench = {} 344*134e1779SJakub Wojciech Klama self._server = server 345*134e1779SJakub Wojciech Klama self._port = port 346*134e1779SJakub Wojciech Klama self._unsup = {} 347*134e1779SJakub Wojciech Klama 348*134e1779SJakub Wojciech Klama def get_monkey(self, what): 349*134e1779SJakub Wojciech Klama "check for a monkey-wrench" 350*134e1779SJakub Wojciech Klama with self.lock: 351*134e1779SJakub Wojciech Klama wrench = self._monkeywrench.get(what) 352*134e1779SJakub Wojciech Klama if wrench is None: 353*134e1779SJakub Wojciech Klama return None 354*134e1779SJakub Wojciech Klama if isinstance(wrench, list): 355*134e1779SJakub Wojciech Klama # repeats wrench[0] times, or forever if that's 0 356*134e1779SJakub Wojciech Klama ret = wrench[1] 357*134e1779SJakub Wojciech Klama if wrench[0] > 0: 358*134e1779SJakub Wojciech Klama wrench[0] -= 1 359*134e1779SJakub Wojciech Klama if wrench[0] == 0: 360*134e1779SJakub Wojciech Klama del self._monkeywrench[what] 361*134e1779SJakub Wojciech Klama else: 362*134e1779SJakub Wojciech Klama ret = wrench 363*134e1779SJakub Wojciech Klama del self._monkeywrench[what] 364*134e1779SJakub Wojciech Klama return ret 365*134e1779SJakub Wojciech Klama 366*134e1779SJakub Wojciech Klama def set_monkey(self, what, how, repeat=None): 367*134e1779SJakub Wojciech Klama """ 368*134e1779SJakub Wojciech Klama Set a monkey-wrench. If repeat is not None it is the number of 369*134e1779SJakub Wojciech Klama times the wrench is applied (0 means forever, or until you call 370*134e1779SJakub Wojciech Klama set again with how=None). What is what to monkey-wrench, which 371*134e1779SJakub Wojciech Klama depends on the op. How is generally a replacement value. 372*134e1779SJakub Wojciech Klama """ 373*134e1779SJakub Wojciech Klama if how is None: 374*134e1779SJakub Wojciech Klama with self.lock: 375*134e1779SJakub Wojciech Klama try: 376*134e1779SJakub Wojciech Klama del self._monkeywrench[what] 377*134e1779SJakub Wojciech Klama except KeyError: 378*134e1779SJakub Wojciech Klama pass 379*134e1779SJakub Wojciech Klama return 380*134e1779SJakub Wojciech Klama if repeat is not None: 381*134e1779SJakub Wojciech Klama how = [repeat, how] 382*134e1779SJakub Wojciech Klama with self.lock: 383*134e1779SJakub Wojciech Klama self._monkeywrench[what] = how 384*134e1779SJakub Wojciech Klama 385*134e1779SJakub Wojciech Klama def get_tag(self, for_Tversion=False): 386*134e1779SJakub Wojciech Klama "get next available tag ID" 387*134e1779SJakub Wojciech Klama with self.lock: 388*134e1779SJakub Wojciech Klama if for_Tversion: 389*134e1779SJakub Wojciech Klama tag = 65535 390*134e1779SJakub Wojciech Klama else: 391*134e1779SJakub Wojciech Klama tag = self.tagalloc.alloc() 392*134e1779SJakub Wojciech Klama if tag is None: 393*134e1779SJakub Wojciech Klama raise LocalError('all tags in use') 394*134e1779SJakub Wojciech Klama self.tagstate[tag] = True # ie, in use, still waiting 395*134e1779SJakub Wojciech Klama return tag 396*134e1779SJakub Wojciech Klama 397*134e1779SJakub Wojciech Klama def set_tag(self, tag, reply): 398*134e1779SJakub Wojciech Klama "set the reply info for the given tag" 399*134e1779SJakub Wojciech Klama assert tag >= 0 and tag < 65536 400*134e1779SJakub Wojciech Klama with self.lock: 401*134e1779SJakub Wojciech Klama # check whether we're still waiting for the tag 402*134e1779SJakub Wojciech Klama state = self.tagstate.get(tag) 403*134e1779SJakub Wojciech Klama if state is True: 404*134e1779SJakub Wojciech Klama self.tagstate[tag] = reply # i.e., here's the answer 405*134e1779SJakub Wojciech Klama self.new_replies.notify_all() 406*134e1779SJakub Wojciech Klama return 407*134e1779SJakub Wojciech Klama # state must be one of these... 408*134e1779SJakub Wojciech Klama if state is False: 409*134e1779SJakub Wojciech Klama # We gave up on this tag. Reply came anyway. 410*134e1779SJakub Wojciech Klama self.logger.log(logging.INFO, 411*134e1779SJakub Wojciech Klama '%s: got tag %d = %r after timing out on it', 412*134e1779SJakub Wojciech Klama self, tag, reply) 413*134e1779SJakub Wojciech Klama self.retire_tag_locked(tag) 414*134e1779SJakub Wojciech Klama return 415*134e1779SJakub Wojciech Klama if state is None: 416*134e1779SJakub Wojciech Klama # We got a tag back from the server that was not 417*134e1779SJakub Wojciech Klama # outstanding! 418*134e1779SJakub Wojciech Klama self.logger.log(logging.WARNING, 419*134e1779SJakub Wojciech Klama '%s: got tag %d = %r when tag %d not in use!', 420*134e1779SJakub Wojciech Klama self, tag, reply, tag) 421*134e1779SJakub Wojciech Klama return 422*134e1779SJakub Wojciech Klama # We got a second reply before handling the first reply! 423*134e1779SJakub Wojciech Klama self.logger.log(logging.WARNING, 424*134e1779SJakub Wojciech Klama '%s: got tag %d = %r when tag %d = %r!', 425*134e1779SJakub Wojciech Klama self, tag, reply, tag, state) 426*134e1779SJakub Wojciech Klama return 427*134e1779SJakub Wojciech Klama 428*134e1779SJakub Wojciech Klama def retire_tag(self, tag): 429*134e1779SJakub Wojciech Klama "retire the given tag - only used by the thread that handled the result" 430*134e1779SJakub Wojciech Klama if tag == 65535: 431*134e1779SJakub Wojciech Klama return 432*134e1779SJakub Wojciech Klama assert tag >= 0 and tag < 65535 433*134e1779SJakub Wojciech Klama with self.lock: 434*134e1779SJakub Wojciech Klama self.retire_tag_locked(tag) 435*134e1779SJakub Wojciech Klama 436*134e1779SJakub Wojciech Klama def retire_tag_locked(self, tag): 437*134e1779SJakub Wojciech Klama "retire the given tag while holding self.lock" 438*134e1779SJakub Wojciech Klama # must check "in tagstate" because we can race 439*134e1779SJakub Wojciech Klama # with retire_all_tags. 440*134e1779SJakub Wojciech Klama if tag in self.tagstate: 441*134e1779SJakub Wojciech Klama del self.tagstate[tag] 442*134e1779SJakub Wojciech Klama self.tagalloc.free(tag) 443*134e1779SJakub Wojciech Klama 444*134e1779SJakub Wojciech Klama def retire_all_tags(self): 445*134e1779SJakub Wojciech Klama "retire all tags, after connection drop" 446*134e1779SJakub Wojciech Klama with self.lock: 447*134e1779SJakub Wojciech Klama # release all tags in any state (waiting, answered, timedout) 448*134e1779SJakub Wojciech Klama self.tagalloc.free_multi(self.tagstate.keys()) 449*134e1779SJakub Wojciech Klama self.tagstate = {} 450*134e1779SJakub Wojciech Klama self.new_replies.notify_all() 451*134e1779SJakub Wojciech Klama 452*134e1779SJakub Wojciech Klama def alloc_fid(self): 453*134e1779SJakub Wojciech Klama "allocate new fid" 454*134e1779SJakub Wojciech Klama with self.lock: 455*134e1779SJakub Wojciech Klama fid = self.fidalloc.alloc() 456*134e1779SJakub Wojciech Klama self.live_fids[fid] = True 457*134e1779SJakub Wojciech Klama return fid 458*134e1779SJakub Wojciech Klama 459*134e1779SJakub Wojciech Klama def getpath(self, fid): 460*134e1779SJakub Wojciech Klama "get path from fid, or return None if no path known, or not valid" 461*134e1779SJakub Wojciech Klama with self.lock: 462*134e1779SJakub Wojciech Klama path = self.live_fids.get(fid) 463*134e1779SJakub Wojciech Klama if path is True: 464*134e1779SJakub Wojciech Klama path = None 465*134e1779SJakub Wojciech Klama return path 466*134e1779SJakub Wojciech Klama 467*134e1779SJakub Wojciech Klama def getpathX(self, fid): 468*134e1779SJakub Wojciech Klama """ 469*134e1779SJakub Wojciech Klama Much like getpath, but return <fid N, unknown path> if necessary. 470*134e1779SJakub Wojciech Klama If we do have a path, return its repr(). 471*134e1779SJakub Wojciech Klama """ 472*134e1779SJakub Wojciech Klama path = self.getpath(fid) 473*134e1779SJakub Wojciech Klama if path is None: 474*134e1779SJakub Wojciech Klama return '<fid {0}, unknown path>'.format(fid) 475*134e1779SJakub Wojciech Klama return repr(path) 476*134e1779SJakub Wojciech Klama 477*134e1779SJakub Wojciech Klama def setpath(self, fid, path): 478*134e1779SJakub Wojciech Klama "associate fid with new path (possibly from another fid)" 479*134e1779SJakub Wojciech Klama with self.lock: 480*134e1779SJakub Wojciech Klama if isinstance(path, int): 481*134e1779SJakub Wojciech Klama path = self.live_fids.get(path) 482*134e1779SJakub Wojciech Klama # path might now be None (not a live fid after all), or 483*134e1779SJakub Wojciech Klama # True (we have no path name), or potentially even the 484*134e1779SJakub Wojciech Klama # empty string (invalid for our purposes). Treat all of 485*134e1779SJakub Wojciech Klama # those as True, meaning "no known path". 486*134e1779SJakub Wojciech Klama if not path: 487*134e1779SJakub Wojciech Klama path = True 488*134e1779SJakub Wojciech Klama if self.live_fids.get(fid): 489*134e1779SJakub Wojciech Klama # Existing fid maps to either True or its old path. 490*134e1779SJakub Wojciech Klama # Set the new path (which may be just a placeholder). 491*134e1779SJakub Wojciech Klama self.live_fids[fid] = path 492*134e1779SJakub Wojciech Klama 493*134e1779SJakub Wojciech Klama def did_rename(self, fid, ncomp, newdir=None): 494*134e1779SJakub Wojciech Klama """ 495*134e1779SJakub Wojciech Klama Announce that we renamed using a fid - we'll try to update 496*134e1779SJakub Wojciech Klama other fids based on this (we can't really do it perfectly). 497*134e1779SJakub Wojciech Klama 498*134e1779SJakub Wojciech Klama NOTE: caller must provide a final-component. 499*134e1779SJakub Wojciech Klama The caller can supply the new path (and should 500*134e1779SJakub Wojciech Klama do so if the rename is not based on the retained path 501*134e1779SJakub Wojciech Klama for the supplied fid, i.e., for rename ops where fid 502*134e1779SJakub Wojciech Klama can move across directories). The rules: 503*134e1779SJakub Wojciech Klama 504*134e1779SJakub Wojciech Klama - If newdir is None (default), we use stored path. 505*134e1779SJakub Wojciech Klama - Otherwise, newdir provides the best approximation 506*134e1779SJakub Wojciech Klama we have to the path that needs ncomp appended. 507*134e1779SJakub Wojciech Klama 508*134e1779SJakub Wojciech Klama (This is based on the fact that renames happen via Twstat 509*134e1779SJakub Wojciech Klama or Trename, or Trenameat, which change just one tail component, 510*134e1779SJakub Wojciech Klama but the path names vary.) 511*134e1779SJakub Wojciech Klama """ 512*134e1779SJakub Wojciech Klama if ncomp is None: 513*134e1779SJakub Wojciech Klama return 514*134e1779SJakub Wojciech Klama opath = self.getpath(fid) 515*134e1779SJakub Wojciech Klama if newdir is None: 516*134e1779SJakub Wojciech Klama if opath is None: 517*134e1779SJakub Wojciech Klama return 518*134e1779SJakub Wojciech Klama ocomps = opath.split(b'/') 519*134e1779SJakub Wojciech Klama ncomps = ocomps[0:-1] 520*134e1779SJakub Wojciech Klama else: 521*134e1779SJakub Wojciech Klama ocomps = None # well, none yet anyway 522*134e1779SJakub Wojciech Klama ncomps = newdir.split(b'/') 523*134e1779SJakub Wojciech Klama ncomps.append(ncomp) 524*134e1779SJakub Wojciech Klama if opath is None or opath[0] != '/': 525*134e1779SJakub Wojciech Klama # We don't have enough information to fix anything else. 526*134e1779SJakub Wojciech Klama # Just store the new path and return. We have at least 527*134e1779SJakub Wojciech Klama # a partial path now, which is no worse than before. 528*134e1779SJakub Wojciech Klama npath = b'/'.join(ncomps) 529*134e1779SJakub Wojciech Klama with self.lock: 530*134e1779SJakub Wojciech Klama if fid in self.live_fids: 531*134e1779SJakub Wojciech Klama self.live_fids[fid] = npath 532*134e1779SJakub Wojciech Klama return 533*134e1779SJakub Wojciech Klama if ocomps is None: 534*134e1779SJakub Wojciech Klama ocomps = opath.split(b'/') 535*134e1779SJakub Wojciech Klama olen = len(ocomps) 536*134e1779SJakub Wojciech Klama ofinal = ocomps[olen - 1] 537*134e1779SJakub Wojciech Klama # Old paths is full path. Find any other fids that start 538*134e1779SJakub Wojciech Klama # with some or all the components in ocomps. Note that if 539*134e1779SJakub Wojciech Klama # we renamed /one/two/three to /four/five this winds up 540*134e1779SJakub Wojciech Klama # renaming files /one/a to /four/a, /one/two/b to /four/five/b, 541*134e1779SJakub Wojciech Klama # and so on. 542*134e1779SJakub Wojciech Klama with self.lock: 543*134e1779SJakub Wojciech Klama for fid2, path2 in self.live_fids.iteritems(): 544*134e1779SJakub Wojciech Klama # Skip fids without byte-string paths 545*134e1779SJakub Wojciech Klama if not isinstance(path2, bytes): 546*134e1779SJakub Wojciech Klama continue 547*134e1779SJakub Wojciech Klama # Before splitting (which is a bit expensive), try 548*134e1779SJakub Wojciech Klama # a straightforward prefix match. This might give 549*134e1779SJakub Wojciech Klama # some false hits, e.g., prefix /one/two/threepenny 550*134e1779SJakub Wojciech Klama # starts with /one/two/three, but it quickly eliminates 551*134e1779SJakub Wojciech Klama # /raz/baz/mataz and the like. 552*134e1779SJakub Wojciech Klama if not path2.startswith(opath): 553*134e1779SJakub Wojciech Klama continue 554*134e1779SJakub Wojciech Klama # Split up the path, and use that to make sure that 555*134e1779SJakub Wojciech Klama # the final component is a full match. 556*134e1779SJakub Wojciech Klama parts2 = path2.split(b'/') 557*134e1779SJakub Wojciech Klama if parts2[olen - 1] != ofinal: 558*134e1779SJakub Wojciech Klama continue 559*134e1779SJakub Wojciech Klama # OK, path2 starts with the old (renamed) sequence. 560*134e1779SJakub Wojciech Klama # Replace the old components with the new ones. 561*134e1779SJakub Wojciech Klama # This updates the renamed fid when we come across 562*134e1779SJakub Wojciech Klama # it! It also handles a change in the number of 563*134e1779SJakub Wojciech Klama # components, thanks to Python's slice assignment. 564*134e1779SJakub Wojciech Klama parts2[0:olen] = ncomps 565*134e1779SJakub Wojciech Klama self.live_fids[fid2] = b'/'.join(parts2) 566*134e1779SJakub Wojciech Klama 567*134e1779SJakub Wojciech Klama def retire_fid(self, fid): 568*134e1779SJakub Wojciech Klama "retire one fid" 569*134e1779SJakub Wojciech Klama with self.lock: 570*134e1779SJakub Wojciech Klama self.fidalloc.free(fid) 571*134e1779SJakub Wojciech Klama del self.live_fids[fid] 572*134e1779SJakub Wojciech Klama 573*134e1779SJakub Wojciech Klama def retire_all_fids(self): 574*134e1779SJakub Wojciech Klama "return live fids to pool" 575*134e1779SJakub Wojciech Klama # this is useful for debugging fid leaks: 576*134e1779SJakub Wojciech Klama #for fid in self.live_fids: 577*134e1779SJakub Wojciech Klama # print 'retiring', fid, self.getpathX(fid) 578*134e1779SJakub Wojciech Klama with self.lock: 579*134e1779SJakub Wojciech Klama self.fidalloc.free_multi(self.live_fids.keys()) 580*134e1779SJakub Wojciech Klama self.live_fids = {} 581*134e1779SJakub Wojciech Klama 582*134e1779SJakub Wojciech Klama def read_responses(self): 583*134e1779SJakub Wojciech Klama "Read responses. This gets spun off as a thread." 584*134e1779SJakub Wojciech Klama while self.is_connected(): 585*134e1779SJakub Wojciech Klama pkt, is_full = super(P9Client, self).read() 586*134e1779SJakub Wojciech Klama if pkt == b'': 587*134e1779SJakub Wojciech Klama self.shutwrite() 588*134e1779SJakub Wojciech Klama self.retire_all_tags() 589*134e1779SJakub Wojciech Klama return 590*134e1779SJakub Wojciech Klama if not is_full: 591*134e1779SJakub Wojciech Klama self.logger.log(logging.WARNING, '%s: got short packet', self) 592*134e1779SJakub Wojciech Klama try: 593*134e1779SJakub Wojciech Klama # We have one special case: if we're not yet connected 594*134e1779SJakub Wojciech Klama # with a version, we must unpack *as if* it's a plain 595*134e1779SJakub Wojciech Klama # 9P2000 response. 596*134e1779SJakub Wojciech Klama if self.have_version: 597*134e1779SJakub Wojciech Klama resp = self.proto.unpack(pkt) 598*134e1779SJakub Wojciech Klama else: 599*134e1779SJakub Wojciech Klama resp = protocol.plain.unpack(pkt) 600*134e1779SJakub Wojciech Klama except protocol.SequenceError as err: 601*134e1779SJakub Wojciech Klama self.logger.log(logging.ERROR, '%s: bad response: %s', 602*134e1779SJakub Wojciech Klama self, err) 603*134e1779SJakub Wojciech Klama try: 604*134e1779SJakub Wojciech Klama resp = self.proto.unpack(pkt, noerror=True) 605*134e1779SJakub Wojciech Klama except protocol.SequenceError: 606*134e1779SJakub Wojciech Klama header = self.proto.unpack_header(pkt, noerror=True) 607*134e1779SJakub Wojciech Klama self.logger.log(logging.ERROR, 608*134e1779SJakub Wojciech Klama '%s: (not even raw-decodable)', self) 609*134e1779SJakub Wojciech Klama self.logger.log(logging.ERROR, 610*134e1779SJakub Wojciech Klama '%s: header decode produced %r', 611*134e1779SJakub Wojciech Klama self, header) 612*134e1779SJakub Wojciech Klama else: 613*134e1779SJakub Wojciech Klama self.logger.log(logging.ERROR, 614*134e1779SJakub Wojciech Klama '%s: raw decode produced %r', 615*134e1779SJakub Wojciech Klama self, resp) 616*134e1779SJakub Wojciech Klama # after this kind of problem, probably need to 617*134e1779SJakub Wojciech Klama # shut down, but let's leave that out for a bit 618*134e1779SJakub Wojciech Klama else: 619*134e1779SJakub Wojciech Klama # NB: all protocol responses have a "tag", 620*134e1779SJakub Wojciech Klama # so resp['tag'] always exists. 621*134e1779SJakub Wojciech Klama self.logger.log(logging.DEBUG, "read_resp: tag %d resp %r", resp.tag, resp) 622*134e1779SJakub Wojciech Klama self.set_tag(resp.tag, resp) 623*134e1779SJakub Wojciech Klama 624*134e1779SJakub Wojciech Klama def wait_for(self, tag): 625*134e1779SJakub Wojciech Klama """ 626*134e1779SJakub Wojciech Klama Wait for a response to the given tag. Return the response, 627*134e1779SJakub Wojciech Klama releasing the tag. If self.timeout is not None, wait at most 628*134e1779SJakub Wojciech Klama that long (and release the tag even if there's no reply), else 629*134e1779SJakub Wojciech Klama wait forever. 630*134e1779SJakub Wojciech Klama 631*134e1779SJakub Wojciech Klama If this returns None, either the tag was bad initially, or 632*134e1779SJakub Wojciech Klama a timeout occurred, or the connection got shut down. 633*134e1779SJakub Wojciech Klama """ 634*134e1779SJakub Wojciech Klama self.logger.log(logging.DEBUG, "wait_for: tag %d", tag) 635*134e1779SJakub Wojciech Klama if self.timeout is None: 636*134e1779SJakub Wojciech Klama deadline = None 637*134e1779SJakub Wojciech Klama else: 638*134e1779SJakub Wojciech Klama deadline = time.time() + self.timeout 639*134e1779SJakub Wojciech Klama with self.lock: 640*134e1779SJakub Wojciech Klama while True: 641*134e1779SJakub Wojciech Klama # tagstate is True (waiting) or False (timedout) or 642*134e1779SJakub Wojciech Klama # a valid response, or None if we've reset the tag 643*134e1779SJakub Wojciech Klama # states (retire_all_tags, after connection drop). 644*134e1779SJakub Wojciech Klama resp = self.tagstate.get(tag, None) 645*134e1779SJakub Wojciech Klama if resp is None: 646*134e1779SJakub Wojciech Klama # out of sync, exit loop 647*134e1779SJakub Wojciech Klama break 648*134e1779SJakub Wojciech Klama if resp is True: 649*134e1779SJakub Wojciech Klama # still waiting for a response - wait some more 650*134e1779SJakub Wojciech Klama self.new_replies.wait(self.timeout) 651*134e1779SJakub Wojciech Klama if deadline and time.time() > deadline: 652*134e1779SJakub Wojciech Klama # Halt the waiting, but go around once more. 653*134e1779SJakub Wojciech Klama # Note we may have killed the tag by now though. 654*134e1779SJakub Wojciech Klama if tag in self.tagstate: 655*134e1779SJakub Wojciech Klama self.tagstate[tag] = False 656*134e1779SJakub Wojciech Klama continue 657*134e1779SJakub Wojciech Klama # resp is either False (timeout) or a reply. 658*134e1779SJakub Wojciech Klama # If resp is False, change it to None; the tag 659*134e1779SJakub Wojciech Klama # is now dead until we get a reply (then we 660*134e1779SJakub Wojciech Klama # just toss the reply). 661*134e1779SJakub Wojciech Klama # Otherwise, we're done with the tag: free it. 662*134e1779SJakub Wojciech Klama # In either case, stop now. 663*134e1779SJakub Wojciech Klama if resp is False: 664*134e1779SJakub Wojciech Klama resp = None 665*134e1779SJakub Wojciech Klama else: 666*134e1779SJakub Wojciech Klama self.tagalloc.free(tag) 667*134e1779SJakub Wojciech Klama del self.tagstate[tag] 668*134e1779SJakub Wojciech Klama break 669*134e1779SJakub Wojciech Klama return resp 670*134e1779SJakub Wojciech Klama 671*134e1779SJakub Wojciech Klama def badresp(self, req, resp): 672*134e1779SJakub Wojciech Klama """ 673*134e1779SJakub Wojciech Klama Complain that a response was not something expected. 674*134e1779SJakub Wojciech Klama """ 675*134e1779SJakub Wojciech Klama if resp is None: 676*134e1779SJakub Wojciech Klama self.shutdown() 677*134e1779SJakub Wojciech Klama raise TEError('{0}: {1}: timeout or EOF'.format(self, req)) 678*134e1779SJakub Wojciech Klama if isinstance(resp, protocol.rrd.Rlerror): 679*134e1779SJakub Wojciech Klama raise RemoteError(self, req, None, 'Rlerror', resp.ecode) 680*134e1779SJakub Wojciech Klama if isinstance(resp, protocol.rrd.Rerror): 681*134e1779SJakub Wojciech Klama if resp.errnum is None: 682*134e1779SJakub Wojciech Klama raise RemoteError(self, req, resp.errstr, 'Rerror', None) 683*134e1779SJakub Wojciech Klama raise RemoteError(self, req, None, 'Rerror', resp.errnum) 684*134e1779SJakub Wojciech Klama raise LocalError('{0}: {1} got response {2!r}'.format(self, req, resp)) 685*134e1779SJakub Wojciech Klama 686*134e1779SJakub Wojciech Klama def supports(self, req_code): 687*134e1779SJakub Wojciech Klama """ 688*134e1779SJakub Wojciech Klama Test self.proto.support(req_code) unless we've recorded that 689*134e1779SJakub Wojciech Klama while the protocol supports it, the client does not. 690*134e1779SJakub Wojciech Klama """ 691*134e1779SJakub Wojciech Klama return req_code not in self._unsup and self.proto.supports(req_code) 692*134e1779SJakub Wojciech Klama 693*134e1779SJakub Wojciech Klama def supports_all(self, *req_codes): 694*134e1779SJakub Wojciech Klama "basically just all(supports(...))" 695*134e1779SJakub Wojciech Klama return all(self.supports(code) for code in req_codes) 696*134e1779SJakub Wojciech Klama 697*134e1779SJakub Wojciech Klama def unsupported(self, req_code): 698*134e1779SJakub Wojciech Klama """ 699*134e1779SJakub Wojciech Klama Record an ENOTSUP (RemoteError was ENOTSUP) for a request. 700*134e1779SJakub Wojciech Klama Must be called from the op, this does not happen automatically. 701*134e1779SJakub Wojciech Klama (It's just an optimization.) 702*134e1779SJakub Wojciech Klama """ 703*134e1779SJakub Wojciech Klama self._unsup[req_code] = True 704*134e1779SJakub Wojciech Klama 705*134e1779SJakub Wojciech Klama def connect(self, server=None, port=None): 706*134e1779SJakub Wojciech Klama """ 707*134e1779SJakub Wojciech Klama Connect to given server/port pair. 708*134e1779SJakub Wojciech Klama 709*134e1779SJakub Wojciech Klama The server and port are remembered. If given as None, 710*134e1779SJakub Wojciech Klama the last remembered values are used. The initial 711*134e1779SJakub Wojciech Klama remembered values are from the creation of this client 712*134e1779SJakub Wojciech Klama instance. 713*134e1779SJakub Wojciech Klama 714*134e1779SJakub Wojciech Klama New values are only remembered here on a *successful* 715*134e1779SJakub Wojciech Klama connect, however. 716*134e1779SJakub Wojciech Klama """ 717*134e1779SJakub Wojciech Klama if server is None: 718*134e1779SJakub Wojciech Klama server = self._server 719*134e1779SJakub Wojciech Klama if server is None: 720*134e1779SJakub Wojciech Klama raise LocalError('connect: no server specified and no default') 721*134e1779SJakub Wojciech Klama if port is None: 722*134e1779SJakub Wojciech Klama port = self._port 723*134e1779SJakub Wojciech Klama if port is None: 724*134e1779SJakub Wojciech Klama port = STD_P9_PORT 725*134e1779SJakub Wojciech Klama self.name = None # wipe out previous name, if any 726*134e1779SJakub Wojciech Klama super(P9Client, self).connect(server, port) 727*134e1779SJakub Wojciech Klama maxio = self.get_recommended_maxio() 728*134e1779SJakub Wojciech Klama self.declare_connected(None, None, maxio) 729*134e1779SJakub Wojciech Klama self.proto = self.iproto # revert to initial protocol 730*134e1779SJakub Wojciech Klama self.have_version = False 731*134e1779SJakub Wojciech Klama self.rthread = threading.Thread(target=self.read_responses) 732*134e1779SJakub Wojciech Klama self.rthread.start() 733*134e1779SJakub Wojciech Klama tag = self.get_tag(for_Tversion=True) 734*134e1779SJakub Wojciech Klama req = protocol.rrd.Tversion(tag=tag, msize=maxio, 735*134e1779SJakub Wojciech Klama version=self.get_monkey('version')) 736*134e1779SJakub Wojciech Klama super(P9Client, self).write(self.proto.pack_from(req)) 737*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 738*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rversion): 739*134e1779SJakub Wojciech Klama self.shutdown() 740*134e1779SJakub Wojciech Klama if isinstance(resp, protocol.rrd.Rerror): 741*134e1779SJakub Wojciech Klama version = req.version or self.proto.get_version() 742*134e1779SJakub Wojciech Klama # for python3, we need to convert version to string 743*134e1779SJakub Wojciech Klama if not isinstance(version, str): 744*134e1779SJakub Wojciech Klama version = version.decode('utf-8', 'surrogateescape') 745*134e1779SJakub Wojciech Klama raise RemoteError(self, 'version ' + version, 746*134e1779SJakub Wojciech Klama resp.errstr, 'Rerror', None) 747*134e1779SJakub Wojciech Klama self.badresp('version', resp) 748*134e1779SJakub Wojciech Klama their_maxio = resp.msize 749*134e1779SJakub Wojciech Klama try: 750*134e1779SJakub Wojciech Klama self.reduce_maxio(their_maxio) 751*134e1779SJakub Wojciech Klama except LocalError as err: 752*134e1779SJakub Wojciech Klama raise LocalError('{0}: sent maxio={1}, they tried {2}: ' 753*134e1779SJakub Wojciech Klama '{3}'.format(self, maxio, their_maxio, 754*134e1779SJakub Wojciech Klama err.args[0])) 755*134e1779SJakub Wojciech Klama if resp.version != self.proto.get_version(): 756*134e1779SJakub Wojciech Klama if not self.may_downgrade: 757*134e1779SJakub Wojciech Klama self.shutdown() 758*134e1779SJakub Wojciech Klama raise LocalError('{0}: they only support ' 759*134e1779SJakub Wojciech Klama 'version {1!r}'.format(self, resp.version)) 760*134e1779SJakub Wojciech Klama # raises LocalError if the version is bad 761*134e1779SJakub Wojciech Klama # (should we wrap it with a connect-to-{0} msg?) 762*134e1779SJakub Wojciech Klama self.proto = self.proto.downgrade_to(resp.version) 763*134e1779SJakub Wojciech Klama self._server = server 764*134e1779SJakub Wojciech Klama self._port = port 765*134e1779SJakub Wojciech Klama self.have_version = True 766*134e1779SJakub Wojciech Klama 767*134e1779SJakub Wojciech Klama def attach(self, afid, uname, aname, n_uname): 768*134e1779SJakub Wojciech Klama """ 769*134e1779SJakub Wojciech Klama Attach. 770*134e1779SJakub Wojciech Klama 771*134e1779SJakub Wojciech Klama Currently we don't know how to do authentication, 772*134e1779SJakub Wojciech Klama but we'll pass any provided afid through. 773*134e1779SJakub Wojciech Klama """ 774*134e1779SJakub Wojciech Klama if afid is None: 775*134e1779SJakub Wojciech Klama afid = protocol.td.NOFID 776*134e1779SJakub Wojciech Klama if uname is None: 777*134e1779SJakub Wojciech Klama uname = '' 778*134e1779SJakub Wojciech Klama if aname is None: 779*134e1779SJakub Wojciech Klama aname = '' 780*134e1779SJakub Wojciech Klama if n_uname is None: 781*134e1779SJakub Wojciech Klama n_uname = protocol.td.NONUNAME 782*134e1779SJakub Wojciech Klama tag = self.get_tag() 783*134e1779SJakub Wojciech Klama fid = self.alloc_fid() 784*134e1779SJakub Wojciech Klama pkt = self.proto.Tattach(tag=tag, fid=fid, afid=afid, 785*134e1779SJakub Wojciech Klama uname=uname, aname=aname, 786*134e1779SJakub Wojciech Klama n_uname=n_uname) 787*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 788*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 789*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rattach): 790*134e1779SJakub Wojciech Klama self.retire_fid(fid) 791*134e1779SJakub Wojciech Klama self.badresp('attach', resp) 792*134e1779SJakub Wojciech Klama # probably should check resp.qid 793*134e1779SJakub Wojciech Klama self.rootfid = fid 794*134e1779SJakub Wojciech Klama self.rootqid = resp.qid 795*134e1779SJakub Wojciech Klama self.setpath(fid, b'/') 796*134e1779SJakub Wojciech Klama 797*134e1779SJakub Wojciech Klama def shutdown(self): 798*134e1779SJakub Wojciech Klama "disconnect from server" 799*134e1779SJakub Wojciech Klama if self.rootfid is not None: 800*134e1779SJakub Wojciech Klama self.clunk(self.rootfid, ignore_error=True) 801*134e1779SJakub Wojciech Klama self.retire_all_tags() 802*134e1779SJakub Wojciech Klama self.retire_all_fids() 803*134e1779SJakub Wojciech Klama self.rootfid = None 804*134e1779SJakub Wojciech Klama self.rootqid = None 805*134e1779SJakub Wojciech Klama super(P9Client, self).shutdown() 806*134e1779SJakub Wojciech Klama if self.rthread: 807*134e1779SJakub Wojciech Klama self.rthread.join() 808*134e1779SJakub Wojciech Klama self.rthread = None 809*134e1779SJakub Wojciech Klama 810*134e1779SJakub Wojciech Klama def dupfid(self, fid): 811*134e1779SJakub Wojciech Klama """ 812*134e1779SJakub Wojciech Klama Copy existing fid to a new fid. 813*134e1779SJakub Wojciech Klama """ 814*134e1779SJakub Wojciech Klama tag = self.get_tag() 815*134e1779SJakub Wojciech Klama newfid = self.alloc_fid() 816*134e1779SJakub Wojciech Klama pkt = self.proto.Twalk(tag=tag, fid=fid, newfid=newfid, nwname=0, 817*134e1779SJakub Wojciech Klama wname=[]) 818*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 819*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 820*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rwalk): 821*134e1779SJakub Wojciech Klama self.retire_fid(newfid) 822*134e1779SJakub Wojciech Klama self.badresp('walk {0}'.format(self.getpathX(fid)), resp) 823*134e1779SJakub Wojciech Klama # Copy path too 824*134e1779SJakub Wojciech Klama self.setpath(newfid, fid) 825*134e1779SJakub Wojciech Klama return newfid 826*134e1779SJakub Wojciech Klama 827*134e1779SJakub Wojciech Klama def lookup(self, fid, components): 828*134e1779SJakub Wojciech Klama """ 829*134e1779SJakub Wojciech Klama Do Twalk. Caller must provide a starting fid, which should 830*134e1779SJakub Wojciech Klama be rootfid to look up from '/' - we do not do / vs . here. 831*134e1779SJakub Wojciech Klama Caller must also provide a component-ized path (on purpose, 832*134e1779SJakub Wojciech Klama so that caller can provide invalid components like '' or '/'). 833*134e1779SJakub Wojciech Klama The components must be byte-strings as well, for the same 834*134e1779SJakub Wojciech Klama reason. 835*134e1779SJakub Wojciech Klama 836*134e1779SJakub Wojciech Klama We do allocate the new fid ourselves here, though. 837*134e1779SJakub Wojciech Klama 838*134e1779SJakub Wojciech Klama There's no logic here to split up long walks (yet?). 839*134e1779SJakub Wojciech Klama """ 840*134e1779SJakub Wojciech Klama # these are too easy to screw up, so check 841*134e1779SJakub Wojciech Klama if self.rootfid is None: 842*134e1779SJakub Wojciech Klama raise LocalError('{0}: not attached'.format(self)) 843*134e1779SJakub Wojciech Klama if (isinstance(components, (str, bytes) or 844*134e1779SJakub Wojciech Klama not all(isinstance(i, bytes) for i in components))): 845*134e1779SJakub Wojciech Klama raise LocalError('{0}: lookup: invalid ' 846*134e1779SJakub Wojciech Klama 'components {1!r}'.format(self, components)) 847*134e1779SJakub Wojciech Klama tag = self.get_tag() 848*134e1779SJakub Wojciech Klama newfid = self.alloc_fid() 849*134e1779SJakub Wojciech Klama startpath = self.getpath(fid) 850*134e1779SJakub Wojciech Klama pkt = self.proto.Twalk(tag=tag, fid=fid, newfid=newfid, 851*134e1779SJakub Wojciech Klama nwname=len(components), wname=components) 852*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 853*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 854*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rwalk): 855*134e1779SJakub Wojciech Klama self.retire_fid(newfid) 856*134e1779SJakub Wojciech Klama self.badresp('walk {0} in ' 857*134e1779SJakub Wojciech Klama '{1}'.format(components, self.getpathX(fid)), 858*134e1779SJakub Wojciech Klama resp) 859*134e1779SJakub Wojciech Klama # Just because we got Rwalk does not mean we got ALL the 860*134e1779SJakub Wojciech Klama # way down the path. Raise OSError(ENOENT) if we're short. 861*134e1779SJakub Wojciech Klama if resp.nwqid > len(components): 862*134e1779SJakub Wojciech Klama # ??? this should be impossible. Local error? Remote error? 863*134e1779SJakub Wojciech Klama # OS Error? 864*134e1779SJakub Wojciech Klama self.clunk(newfid, ignore_error=True) 865*134e1779SJakub Wojciech Klama raise LocalError('{0}: walk {1} in {2} returned {3} ' 866*134e1779SJakub Wojciech Klama 'items'.format(self, components, 867*134e1779SJakub Wojciech Klama self.getpathX(fid), resp.nwqid)) 868*134e1779SJakub Wojciech Klama if resp.nwqid < len(components): 869*134e1779SJakub Wojciech Klama self.clunk(newfid, ignore_error=True) 870*134e1779SJakub Wojciech Klama # Looking up a/b/c and got just a/b, c is what's missing. 871*134e1779SJakub Wojciech Klama # Looking up a/b/c and got just a, b is what's missing. 872*134e1779SJakub Wojciech Klama missing = components[resp.nwqid] 873*134e1779SJakub Wojciech Klama within = _pathcat(startpath, b'/'.join(components[:resp.nwqid])) 874*134e1779SJakub Wojciech Klama raise OSError(errno.ENOENT, 875*134e1779SJakub Wojciech Klama '{0}: {1} in {2}'.format(os.strerror(errno.ENOENT), 876*134e1779SJakub Wojciech Klama missing, within)) 877*134e1779SJakub Wojciech Klama self.setpath(newfid, _pathcat(startpath, b'/'.join(components))) 878*134e1779SJakub Wojciech Klama return newfid, resp.wqid 879*134e1779SJakub Wojciech Klama 880*134e1779SJakub Wojciech Klama def lookup_last(self, fid, components): 881*134e1779SJakub Wojciech Klama """ 882*134e1779SJakub Wojciech Klama Like lookup, but return only the last component's qid. 883*134e1779SJakub Wojciech Klama As a special case, if components is an empty list, we 884*134e1779SJakub Wojciech Klama handle that. 885*134e1779SJakub Wojciech Klama """ 886*134e1779SJakub Wojciech Klama rfid, wqid = self.lookup(fid, components) 887*134e1779SJakub Wojciech Klama if len(wqid): 888*134e1779SJakub Wojciech Klama return rfid, wqid[-1] 889*134e1779SJakub Wojciech Klama if fid == self.rootfid: # usually true, if we get here at all 890*134e1779SJakub Wojciech Klama return rfid, self.rootqid 891*134e1779SJakub Wojciech Klama tag = self.get_tag() 892*134e1779SJakub Wojciech Klama pkt = self.proto.Tstat(tag=tag, fid=rfid) 893*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 894*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 895*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rstat): 896*134e1779SJakub Wojciech Klama self.badresp('stat {0}'.format(self.getpathX(fid)), resp) 897*134e1779SJakub Wojciech Klama statval = self.proto.unpack_wirestat(resp.data) 898*134e1779SJakub Wojciech Klama return rfid, statval.qid 899*134e1779SJakub Wojciech Klama 900*134e1779SJakub Wojciech Klama def clunk(self, fid, ignore_error=False): 901*134e1779SJakub Wojciech Klama "issue clunk(fid)" 902*134e1779SJakub Wojciech Klama tag = self.get_tag() 903*134e1779SJakub Wojciech Klama pkt = self.proto.Tclunk(tag=tag, fid=fid) 904*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 905*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 906*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rclunk): 907*134e1779SJakub Wojciech Klama if ignore_error: 908*134e1779SJakub Wojciech Klama return 909*134e1779SJakub Wojciech Klama self.badresp('clunk {0}'.format(self.getpathX(fid)), resp) 910*134e1779SJakub Wojciech Klama self.retire_fid(fid) 911*134e1779SJakub Wojciech Klama 912*134e1779SJakub Wojciech Klama def remove(self, fid, ignore_error=False): 913*134e1779SJakub Wojciech Klama "issue remove (old style), which also clunks fid" 914*134e1779SJakub Wojciech Klama tag = self.get_tag() 915*134e1779SJakub Wojciech Klama pkt = self.proto.Tremove(tag=tag, fid=fid) 916*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 917*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 918*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rremove): 919*134e1779SJakub Wojciech Klama if ignore_error: 920*134e1779SJakub Wojciech Klama # remove failed: still need to clunk the fid 921*134e1779SJakub Wojciech Klama self.clunk(fid, True) 922*134e1779SJakub Wojciech Klama return 923*134e1779SJakub Wojciech Klama self.badresp('remove {0}'.format(self.getpathX(fid)), resp) 924*134e1779SJakub Wojciech Klama self.retire_fid(fid) 925*134e1779SJakub Wojciech Klama 926*134e1779SJakub Wojciech Klama def create(self, fid, name, perm, mode, filetype=None, extension=b''): 927*134e1779SJakub Wojciech Klama """ 928*134e1779SJakub Wojciech Klama Issue create op (note that this may be mkdir, symlink, etc). 929*134e1779SJakub Wojciech Klama fid is the directory in which the create happens, and for 930*134e1779SJakub Wojciech Klama regular files, it becomes, on success, a fid referring to 931*134e1779SJakub Wojciech Klama the now-open file. perm is, e.g., 0644, 0755, etc., 932*134e1779SJakub Wojciech Klama optionally with additional high bits. mode is a mode 933*134e1779SJakub Wojciech Klama byte (e.g., protocol.td.ORDWR, or OWRONLY|OTRUNC, etc.). 934*134e1779SJakub Wojciech Klama 935*134e1779SJakub Wojciech Klama As a service to callers, we take two optional arguments 936*134e1779SJakub Wojciech Klama specifying the file type ('dir', 'symlink', 'device', 937*134e1779SJakub Wojciech Klama 'fifo', or 'socket') and additional info if needed. 938*134e1779SJakub Wojciech Klama The additional info for a symlink is the target of the 939*134e1779SJakub Wojciech Klama link (a byte string), and the additional info for a device 940*134e1779SJakub Wojciech Klama is a byte string with "b <major> <minor>" or "c <major> <minor>". 941*134e1779SJakub Wojciech Klama 942*134e1779SJakub Wojciech Klama Otherwise, callers can leave filetype=None and encode the bits 943*134e1779SJakub Wojciech Klama into the mode (caller must still provide extension if needed). 944*134e1779SJakub Wojciech Klama 945*134e1779SJakub Wojciech Klama We do NOT check whether the extension matches extra DM bits, 946*134e1779SJakub Wojciech Klama or that there's only one DM bit set, or whatever, since this 947*134e1779SJakub Wojciech Klama is a testing setup. 948*134e1779SJakub Wojciech Klama """ 949*134e1779SJakub Wojciech Klama tag = self.get_tag() 950*134e1779SJakub Wojciech Klama if filetype is not None: 951*134e1779SJakub Wojciech Klama perm |= { 952*134e1779SJakub Wojciech Klama 'dir': protocol.td.DMDIR, 953*134e1779SJakub Wojciech Klama 'symlink': protocol.td.DMSYMLINK, 954*134e1779SJakub Wojciech Klama 'device': protocol.td.DMDEVICE, 955*134e1779SJakub Wojciech Klama 'fifo': protocol.td.DMNAMEDPIPE, 956*134e1779SJakub Wojciech Klama 'socket': protocol.td.DMSOCKET, 957*134e1779SJakub Wojciech Klama }[filetype] 958*134e1779SJakub Wojciech Klama pkt = self.proto.Tcreate(tag=tag, fid=fid, name=name, 959*134e1779SJakub Wojciech Klama perm=perm, mode=mode, extension=extension) 960*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 961*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 962*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rcreate): 963*134e1779SJakub Wojciech Klama self.badresp('create {0} in {1}'.format(name, self.getpathX(fid)), 964*134e1779SJakub Wojciech Klama resp) 965*134e1779SJakub Wojciech Klama if resp.qid.type == protocol.td.QTFILE: 966*134e1779SJakub Wojciech Klama # Creating a regular file opens the file, 967*134e1779SJakub Wojciech Klama # thus changing the fid's path. 968*134e1779SJakub Wojciech Klama self.setpath(fid, _pathcat(self.getpath(fid), name)) 969*134e1779SJakub Wojciech Klama return resp.qid, resp.iounit 970*134e1779SJakub Wojciech Klama 971*134e1779SJakub Wojciech Klama def open(self, fid, mode): 972*134e1779SJakub Wojciech Klama "use Topen to open file or directory fid (mode is 1 byte)" 973*134e1779SJakub Wojciech Klama tag = self.get_tag() 974*134e1779SJakub Wojciech Klama pkt = self.proto.Topen(tag=tag, fid=fid, mode=mode) 975*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 976*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 977*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Ropen): 978*134e1779SJakub Wojciech Klama self.badresp('open {0}'.format(self.getpathX(fid)), resp) 979*134e1779SJakub Wojciech Klama return resp.qid, resp.iounit 980*134e1779SJakub Wojciech Klama 981*134e1779SJakub Wojciech Klama def lopen(self, fid, flags): 982*134e1779SJakub Wojciech Klama "use Tlopen to open file or directory fid (flags from L_O_*)" 983*134e1779SJakub Wojciech Klama tag = self.get_tag() 984*134e1779SJakub Wojciech Klama pkt = self.proto.Tlopen(tag=tag, fid=fid, flags=flags) 985*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 986*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 987*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rlopen): 988*134e1779SJakub Wojciech Klama self.badresp('lopen {0}'.format(self.getpathX(fid)), resp) 989*134e1779SJakub Wojciech Klama return resp.qid, resp.iounit 990*134e1779SJakub Wojciech Klama 991*134e1779SJakub Wojciech Klama def read(self, fid, offset, count): 992*134e1779SJakub Wojciech Klama "read (up to) count bytes from offset, given open fid" 993*134e1779SJakub Wojciech Klama tag = self.get_tag() 994*134e1779SJakub Wojciech Klama pkt = self.proto.Tread(tag=tag, fid=fid, offset=offset, count=count) 995*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 996*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 997*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rread): 998*134e1779SJakub Wojciech Klama self.badresp('read {0} bytes at offset {1} in ' 999*134e1779SJakub Wojciech Klama '{2}'.format(count, offset, self.getpathX(fid)), 1000*134e1779SJakub Wojciech Klama resp) 1001*134e1779SJakub Wojciech Klama return resp.data 1002*134e1779SJakub Wojciech Klama 1003*134e1779SJakub Wojciech Klama def write(self, fid, offset, data): 1004*134e1779SJakub Wojciech Klama "write (up to) count bytes to offset, given open fid" 1005*134e1779SJakub Wojciech Klama tag = self.get_tag() 1006*134e1779SJakub Wojciech Klama pkt = self.proto.Twrite(tag=tag, fid=fid, offset=offset, 1007*134e1779SJakub Wojciech Klama count=len(data), data=data) 1008*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1009*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1010*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rwrite): 1011*134e1779SJakub Wojciech Klama self.badresp('write {0} bytes at offset {1} in ' 1012*134e1779SJakub Wojciech Klama '{2}'.format(len(data), offset, self.getpathX(fid)), 1013*134e1779SJakub Wojciech Klama resp) 1014*134e1779SJakub Wojciech Klama return resp.count 1015*134e1779SJakub Wojciech Klama 1016*134e1779SJakub Wojciech Klama # Caller may 1017*134e1779SJakub Wojciech Klama # - pass an actual stat object, or 1018*134e1779SJakub Wojciech Klama # - pass in all the individual to-set items by keyword, or 1019*134e1779SJakub Wojciech Klama # - mix and match a bit: get an existing stat, then use 1020*134e1779SJakub Wojciech Klama # keywords to override fields. 1021*134e1779SJakub Wojciech Klama # We convert "None"s to the internal "do not change" values, 1022*134e1779SJakub Wojciech Klama # and for diagnostic purposes, can turn "do not change" back 1023*134e1779SJakub Wojciech Klama # to None at the end, too. 1024*134e1779SJakub Wojciech Klama def wstat(self, fid, statobj=None, **kwargs): 1025*134e1779SJakub Wojciech Klama if statobj is None: 1026*134e1779SJakub Wojciech Klama statobj = protocol.td.stat() 1027*134e1779SJakub Wojciech Klama else: 1028*134e1779SJakub Wojciech Klama statobj = statobj._copy() 1029*134e1779SJakub Wojciech Klama # Fields in stat that you can't send as a wstat: the 1030*134e1779SJakub Wojciech Klama # type and qid are informative. Similarly, the 1031*134e1779SJakub Wojciech Klama # 'extension' is an input when creating a file but 1032*134e1779SJakub Wojciech Klama # read-only when stat-ing. 1033*134e1779SJakub Wojciech Klama # 1034*134e1779SJakub Wojciech Klama # It's not clear what it means to set dev, but we'll leave 1035*134e1779SJakub Wojciech Klama # it in as an optional parameter here. fs/backend.c just 1036*134e1779SJakub Wojciech Klama # errors out on an attempt to change it. 1037*134e1779SJakub Wojciech Klama if self.proto == protocol.plain: 1038*134e1779SJakub Wojciech Klama forbid = ('type', 'qid', 'extension', 1039*134e1779SJakub Wojciech Klama 'n_uid', 'n_gid', 'n_muid') 1040*134e1779SJakub Wojciech Klama else: 1041*134e1779SJakub Wojciech Klama forbid = ('type', 'qid', 'extension') 1042*134e1779SJakub Wojciech Klama nochange = { 1043*134e1779SJakub Wojciech Klama 'type': 0, 1044*134e1779SJakub Wojciech Klama 'qid': protocol.td.qid(0, 0, 0), 1045*134e1779SJakub Wojciech Klama 'dev': 2**32 - 1, 1046*134e1779SJakub Wojciech Klama 'mode': 2**32 - 1, 1047*134e1779SJakub Wojciech Klama 'atime': 2**32 - 1, 1048*134e1779SJakub Wojciech Klama 'mtime': 2**32 - 1, 1049*134e1779SJakub Wojciech Klama 'length': 2**64 - 1, 1050*134e1779SJakub Wojciech Klama 'name': b'', 1051*134e1779SJakub Wojciech Klama 'uid': b'', 1052*134e1779SJakub Wojciech Klama 'gid': b'', 1053*134e1779SJakub Wojciech Klama 'muid': b'', 1054*134e1779SJakub Wojciech Klama 'extension': b'', 1055*134e1779SJakub Wojciech Klama 'n_uid': 2**32 - 1, 1056*134e1779SJakub Wojciech Klama 'n_gid': 2**32 - 1, 1057*134e1779SJakub Wojciech Klama 'n_muid': 2**32 - 1, 1058*134e1779SJakub Wojciech Klama } 1059*134e1779SJakub Wojciech Klama for field in statobj._fields: 1060*134e1779SJakub Wojciech Klama if field in kwargs: 1061*134e1779SJakub Wojciech Klama if field in forbid: 1062*134e1779SJakub Wojciech Klama raise ValueError('cannot wstat a stat.{0}'.format(field)) 1063*134e1779SJakub Wojciech Klama statobj[field] = kwargs.pop(field) 1064*134e1779SJakub Wojciech Klama else: 1065*134e1779SJakub Wojciech Klama if field in forbid or statobj[field] is None: 1066*134e1779SJakub Wojciech Klama statobj[field] = nochange[field] 1067*134e1779SJakub Wojciech Klama if kwargs: 1068*134e1779SJakub Wojciech Klama raise TypeError('wstat() got an unexpected keyword argument ' 1069*134e1779SJakub Wojciech Klama '{0!r}'.format(kwargs.popitem())) 1070*134e1779SJakub Wojciech Klama 1071*134e1779SJakub Wojciech Klama data = self.proto.pack_wirestat(statobj) 1072*134e1779SJakub Wojciech Klama tag = self.get_tag() 1073*134e1779SJakub Wojciech Klama pkt = self.proto.Twstat(tag=tag, fid=fid, data=data) 1074*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1075*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1076*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rwstat): 1077*134e1779SJakub Wojciech Klama # For error viewing, switch all the do-not-change 1078*134e1779SJakub Wojciech Klama # and can't-change fields to None. 1079*134e1779SJakub Wojciech Klama statobj.qid = None 1080*134e1779SJakub Wojciech Klama for field in statobj._fields: 1081*134e1779SJakub Wojciech Klama if field in forbid: 1082*134e1779SJakub Wojciech Klama statobj[field] = None 1083*134e1779SJakub Wojciech Klama elif field in nochange and statobj[field] == nochange[field]: 1084*134e1779SJakub Wojciech Klama statobj[field] = None 1085*134e1779SJakub Wojciech Klama self.badresp('wstat {0}={1}'.format(self.getpathX(fid), statobj), 1086*134e1779SJakub Wojciech Klama resp) 1087*134e1779SJakub Wojciech Klama # wstat worked - change path names if needed 1088*134e1779SJakub Wojciech Klama if statobj.name != b'': 1089*134e1779SJakub Wojciech Klama self.did_rename(fid, statobj.name) 1090*134e1779SJakub Wojciech Klama 1091*134e1779SJakub Wojciech Klama def readdir(self, fid, offset, count): 1092*134e1779SJakub Wojciech Klama "read (up to) count bytes of dir data from offset, given open fid" 1093*134e1779SJakub Wojciech Klama tag = self.get_tag() 1094*134e1779SJakub Wojciech Klama pkt = self.proto.Treaddir(tag=tag, fid=fid, offset=offset, count=count) 1095*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1096*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1097*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rreaddir): 1098*134e1779SJakub Wojciech Klama self.badresp('readdir {0} bytes at offset {1} in ' 1099*134e1779SJakub Wojciech Klama '{2}'.format(count, offset, self.getpathX(fid)), 1100*134e1779SJakub Wojciech Klama resp) 1101*134e1779SJakub Wojciech Klama return resp.data 1102*134e1779SJakub Wojciech Klama 1103*134e1779SJakub Wojciech Klama def rename(self, fid, dfid, name): 1104*134e1779SJakub Wojciech Klama "invoke Trename: rename file <fid> to <dfid>/name" 1105*134e1779SJakub Wojciech Klama tag = self.get_tag() 1106*134e1779SJakub Wojciech Klama pkt = self.proto.Trename(tag=tag, fid=fid, dfid=dfid, name=name) 1107*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1108*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1109*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rrename): 1110*134e1779SJakub Wojciech Klama self.badresp('rename {0} to {2} in ' 1111*134e1779SJakub Wojciech Klama '{1}'.format(self.getpathX(fid), 1112*134e1779SJakub Wojciech Klama self.getpathX(dfid), name), 1113*134e1779SJakub Wojciech Klama resp) 1114*134e1779SJakub Wojciech Klama self.did_rename(fid, name, self.getpath(dfid)) 1115*134e1779SJakub Wojciech Klama 1116*134e1779SJakub Wojciech Klama def renameat(self, olddirfid, oldname, newdirfid, newname): 1117*134e1779SJakub Wojciech Klama "invoke Trenameat: rename <olddirfid>/oldname to <newdirfid>/newname" 1118*134e1779SJakub Wojciech Klama tag = self.get_tag() 1119*134e1779SJakub Wojciech Klama pkt = self.proto.Trenameat(tag=tag, 1120*134e1779SJakub Wojciech Klama olddirfid=olddirfid, oldname=oldname, 1121*134e1779SJakub Wojciech Klama newdirfid=newdirfid, newname=newname) 1122*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1123*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1124*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rrenameat): 1125*134e1779SJakub Wojciech Klama self.badresp('rename {1} in {0} to {3} in ' 1126*134e1779SJakub Wojciech Klama '{2}'.format(oldname, self.getpathX(olddirfid), 1127*134e1779SJakub Wojciech Klama newname, self.getpathX(newdirdfid)), 1128*134e1779SJakub Wojciech Klama resp) 1129*134e1779SJakub Wojciech Klama # There's no renamed *fid*, just a renamed file! So no 1130*134e1779SJakub Wojciech Klama # call to self.did_rename(). 1131*134e1779SJakub Wojciech Klama 1132*134e1779SJakub Wojciech Klama def unlinkat(self, dirfd, name, flags): 1133*134e1779SJakub Wojciech Klama "invoke Tunlinkat - flags should be 0 or protocol.td.AT_REMOVEDIR" 1134*134e1779SJakub Wojciech Klama tag = self.get_tag() 1135*134e1779SJakub Wojciech Klama pkt = self.proto.Tunlinkat(tag=tag, dirfd=dirfd, 1136*134e1779SJakub Wojciech Klama name=name, flags=flags) 1137*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1138*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1139*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Runlinkat): 1140*134e1779SJakub Wojciech Klama self.badresp('unlinkat {0} in ' 1141*134e1779SJakub Wojciech Klama '{1}'.format(name, self.getpathX(dirfd)), resp) 1142*134e1779SJakub Wojciech Klama 1143*134e1779SJakub Wojciech Klama def decode_stat_objects(self, bstring, noerror=False): 1144*134e1779SJakub Wojciech Klama """ 1145*134e1779SJakub Wojciech Klama Read on a directory returns an array of stat objects. 1146*134e1779SJakub Wojciech Klama Note that for .u these encode extra data. 1147*134e1779SJakub Wojciech Klama 1148*134e1779SJakub Wojciech Klama It's possible for this to produce a SequenceError, if 1149*134e1779SJakub Wojciech Klama the data are incorrect, unless you pass noerror=True. 1150*134e1779SJakub Wojciech Klama """ 1151*134e1779SJakub Wojciech Klama objlist = [] 1152*134e1779SJakub Wojciech Klama offset = 0 1153*134e1779SJakub Wojciech Klama while offset < len(bstring): 1154*134e1779SJakub Wojciech Klama obj, offset = self.proto.unpack_wirestat(bstring, offset, noerror) 1155*134e1779SJakub Wojciech Klama objlist.append(obj) 1156*134e1779SJakub Wojciech Klama return objlist 1157*134e1779SJakub Wojciech Klama 1158*134e1779SJakub Wojciech Klama def decode_readdir_dirents(self, bstring, noerror=False): 1159*134e1779SJakub Wojciech Klama """ 1160*134e1779SJakub Wojciech Klama Readdir on a directory returns an array of dirent objects. 1161*134e1779SJakub Wojciech Klama 1162*134e1779SJakub Wojciech Klama It's possible for this to produce a SequenceError, if 1163*134e1779SJakub Wojciech Klama the data are incorrect, unless you pass noerror=True. 1164*134e1779SJakub Wojciech Klama """ 1165*134e1779SJakub Wojciech Klama objlist = [] 1166*134e1779SJakub Wojciech Klama offset = 0 1167*134e1779SJakub Wojciech Klama while offset < len(bstring): 1168*134e1779SJakub Wojciech Klama obj, offset = self.proto.unpack_dirent(bstring, offset, noerror) 1169*134e1779SJakub Wojciech Klama objlist.append(obj) 1170*134e1779SJakub Wojciech Klama return objlist 1171*134e1779SJakub Wojciech Klama 1172*134e1779SJakub Wojciech Klama def lcreate(self, fid, name, lflags, mode, gid): 1173*134e1779SJakub Wojciech Klama "issue lcreate (.L)" 1174*134e1779SJakub Wojciech Klama tag = self.get_tag() 1175*134e1779SJakub Wojciech Klama pkt = self.proto.Tlcreate(tag=tag, fid=fid, name=name, 1176*134e1779SJakub Wojciech Klama flags=lflags, mode=mode, gid=gid) 1177*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1178*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1179*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rlcreate): 1180*134e1779SJakub Wojciech Klama self.badresp('create {0} in ' 1181*134e1779SJakub Wojciech Klama '{1}'.format(name, self.getpathX(fid)), resp) 1182*134e1779SJakub Wojciech Klama # Creating a file opens the file, 1183*134e1779SJakub Wojciech Klama # thus changing the fid's path. 1184*134e1779SJakub Wojciech Klama self.setpath(fid, _pathcat(self.getpath(fid), name)) 1185*134e1779SJakub Wojciech Klama return resp.qid, resp.iounit 1186*134e1779SJakub Wojciech Klama 1187*134e1779SJakub Wojciech Klama def mkdir(self, dfid, name, mode, gid): 1188*134e1779SJakub Wojciech Klama "issue mkdir (.L)" 1189*134e1779SJakub Wojciech Klama tag = self.get_tag() 1190*134e1779SJakub Wojciech Klama pkt = self.proto.Tmkdir(tag=tag, dfid=dfid, name=name, 1191*134e1779SJakub Wojciech Klama mode=mode, gid=gid) 1192*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1193*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1194*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rmkdir): 1195*134e1779SJakub Wojciech Klama self.badresp('mkdir {0} in ' 1196*134e1779SJakub Wojciech Klama '{1}'.format(name, self.getpathX(dfid)), resp) 1197*134e1779SJakub Wojciech Klama return resp.qid 1198*134e1779SJakub Wojciech Klama 1199*134e1779SJakub Wojciech Klama # We don't call this getattr(), for the obvious reason. 1200*134e1779SJakub Wojciech Klama def Tgetattr(self, fid, request_mask=protocol.td.GETATTR_ALL): 1201*134e1779SJakub Wojciech Klama "issue Tgetattr.L - get what you ask for, or everything by default" 1202*134e1779SJakub Wojciech Klama tag = self.get_tag() 1203*134e1779SJakub Wojciech Klama pkt = self.proto.Tgetattr(tag=tag, fid=fid, request_mask=request_mask) 1204*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1205*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1206*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rgetattr): 1207*134e1779SJakub Wojciech Klama self.badresp('Tgetattr {0} of ' 1208*134e1779SJakub Wojciech Klama '{1}'.format(request_mask, self.getpathX(fid)), resp) 1209*134e1779SJakub Wojciech Klama attrs = Fileattrs() 1210*134e1779SJakub Wojciech Klama # Handle the simplest valid-bit tests: 1211*134e1779SJakub Wojciech Klama for name in ('mode', 'nlink', 'uid', 'gid', 'rdev', 1212*134e1779SJakub Wojciech Klama 'size', 'blocks', 'gen', 'data_version'): 1213*134e1779SJakub Wojciech Klama bit = getattr(protocol.td, 'GETATTR_' + name.upper()) 1214*134e1779SJakub Wojciech Klama if resp.valid & bit: 1215*134e1779SJakub Wojciech Klama attrs[name] = resp[name] 1216*134e1779SJakub Wojciech Klama # Handle the timestamps, which are timespec pairs 1217*134e1779SJakub Wojciech Klama for name in ('atime', 'mtime', 'ctime', 'btime'): 1218*134e1779SJakub Wojciech Klama bit = getattr(protocol.td, 'GETATTR_' + name.upper()) 1219*134e1779SJakub Wojciech Klama if resp.valid & bit: 1220*134e1779SJakub Wojciech Klama attrs[name] = Timespec(sec=resp[name + '_sec'], 1221*134e1779SJakub Wojciech Klama nsec=resp[name + '_nsec']) 1222*134e1779SJakub Wojciech Klama # There is no control bit for blksize; qemu and Linux always 1223*134e1779SJakub Wojciech Klama # provide one. 1224*134e1779SJakub Wojciech Klama attrs.blksize = resp.blksize 1225*134e1779SJakub Wojciech Klama # Handle ino, which comes out of qid.path 1226*134e1779SJakub Wojciech Klama if resp.valid & protocol.td.GETATTR_INO: 1227*134e1779SJakub Wojciech Klama attrs.ino = resp.qid.path 1228*134e1779SJakub Wojciech Klama return attrs 1229*134e1779SJakub Wojciech Klama 1230*134e1779SJakub Wojciech Klama # We don't call this setattr(), for the obvious reason. 1231*134e1779SJakub Wojciech Klama # See wstat for usage. Note that time fields can be set 1232*134e1779SJakub Wojciech Klama # with either second or nanosecond resolutions, and some 1233*134e1779SJakub Wojciech Klama # can be set without supplying an actual timestamp, so 1234*134e1779SJakub Wojciech Klama # this is all pretty ad-hoc. 1235*134e1779SJakub Wojciech Klama # 1236*134e1779SJakub Wojciech Klama # There's also one keyword-only argument, ctime=<anything>, 1237*134e1779SJakub Wojciech Klama # which means "set SETATTR_CTIME". This has the same effect 1238*134e1779SJakub Wojciech Klama # as supplying valid=protocol.td.SETATTR_CTIME. 1239*134e1779SJakub Wojciech Klama def Tsetattr(self, fid, valid=0, attrs=None, **kwargs): 1240*134e1779SJakub Wojciech Klama if attrs is None: 1241*134e1779SJakub Wojciech Klama attrs = Fileattrs() 1242*134e1779SJakub Wojciech Klama else: 1243*134e1779SJakub Wojciech Klama attrs = attrs._copy() 1244*134e1779SJakub Wojciech Klama 1245*134e1779SJakub Wojciech Klama # Start with an empty (all-zero) Tsetattr instance. We 1246*134e1779SJakub Wojciech Klama # don't really need to zero out tag and fid, but it doesn't 1247*134e1779SJakub Wojciech Klama # hurt. Note that if caller says, e.g., valid=SETATTR_SIZE 1248*134e1779SJakub Wojciech Klama # but does not supply an incoming size (via "attrs" or a size= 1249*134e1779SJakub Wojciech Klama # argument), we'll ask to set that field to 0. 1250*134e1779SJakub Wojciech Klama attrobj = protocol.rrd.Tsetattr() 1251*134e1779SJakub Wojciech Klama for field in attrobj._fields: 1252*134e1779SJakub Wojciech Klama attrobj[field] = 0 1253*134e1779SJakub Wojciech Klama 1254*134e1779SJakub Wojciech Klama # In this case, forbid means "only as kwargs": these values 1255*134e1779SJakub Wojciech Klama # in an incoming attrs object are merely ignored. 1256*134e1779SJakub Wojciech Klama forbid = ('ino', 'nlink', 'rdev', 'blksize', 'blocks', 'btime', 1257*134e1779SJakub Wojciech Klama 'gen', 'data_version') 1258*134e1779SJakub Wojciech Klama for field in attrs._fields: 1259*134e1779SJakub Wojciech Klama if field in kwargs: 1260*134e1779SJakub Wojciech Klama if field in forbid: 1261*134e1779SJakub Wojciech Klama raise ValueError('cannot Tsetattr {0}'.format(field)) 1262*134e1779SJakub Wojciech Klama attrs[field] = kwargs.pop(field) 1263*134e1779SJakub Wojciech Klama elif attrs[field] is None: 1264*134e1779SJakub Wojciech Klama continue 1265*134e1779SJakub Wojciech Klama # OK, we're setting this attribute. Many are just 1266*134e1779SJakub Wojciech Klama # numeric - if that's the case, we're good, set the 1267*134e1779SJakub Wojciech Klama # field and the appropriate bit. 1268*134e1779SJakub Wojciech Klama bitname = 'SETATTR_' + field.upper() 1269*134e1779SJakub Wojciech Klama bit = getattr(protocol.td, bitname) 1270*134e1779SJakub Wojciech Klama if field in ('mode', 'uid', 'gid', 'size'): 1271*134e1779SJakub Wojciech Klama valid |= bit 1272*134e1779SJakub Wojciech Klama attrobj[field] = attrs[field] 1273*134e1779SJakub Wojciech Klama continue 1274*134e1779SJakub Wojciech Klama # Timestamps are special: The value may be given as 1275*134e1779SJakub Wojciech Klama # an integer (seconds), or as a float (we convert to 1276*134e1779SJakub Wojciech Klama # (we convert to sec+nsec), or as a timespec (sec+nsec). 1277*134e1779SJakub Wojciech Klama # If specified as 0, we mean "we are not providing the 1278*134e1779SJakub Wojciech Klama # actual time, use the server's time." 1279*134e1779SJakub Wojciech Klama # 1280*134e1779SJakub Wojciech Klama # The ctime field's value, if any, is *ignored*. 1281*134e1779SJakub Wojciech Klama if field in ('atime', 'mtime'): 1282*134e1779SJakub Wojciech Klama value = attrs[field] 1283*134e1779SJakub Wojciech Klama if hasattr(value, '__len__'): 1284*134e1779SJakub Wojciech Klama if len(value) != 2: 1285*134e1779SJakub Wojciech Klama raise ValueError('invalid {0}={1!r}'.format(field, 1286*134e1779SJakub Wojciech Klama value)) 1287*134e1779SJakub Wojciech Klama sec = value[0] 1288*134e1779SJakub Wojciech Klama nsec = value[1] 1289*134e1779SJakub Wojciech Klama else: 1290*134e1779SJakub Wojciech Klama sec = value 1291*134e1779SJakub Wojciech Klama if isinstance(sec, float): 1292*134e1779SJakub Wojciech Klama nsec, sec = math.modf(sec) 1293*134e1779SJakub Wojciech Klama nsec = int(round(nsec * 1000000000)) 1294*134e1779SJakub Wojciech Klama else: 1295*134e1779SJakub Wojciech Klama nsec = 0 1296*134e1779SJakub Wojciech Klama valid |= bit 1297*134e1779SJakub Wojciech Klama attrobj[field + '_sec'] = sec 1298*134e1779SJakub Wojciech Klama attrobj[field + '_nsec'] = nsec 1299*134e1779SJakub Wojciech Klama if sec != 0 or nsec != 0: 1300*134e1779SJakub Wojciech Klama # Add SETATTR_ATIME_SET or SETATTR_MTIME_SET 1301*134e1779SJakub Wojciech Klama # as appropriate, to tell the server to *this 1302*134e1779SJakub Wojciech Klama # specific* time, instead of just "server now". 1303*134e1779SJakub Wojciech Klama bit = getattr(protocol.td, bitname + '_SET') 1304*134e1779SJakub Wojciech Klama valid |= bit 1305*134e1779SJakub Wojciech Klama if 'ctime' in kwargs: 1306*134e1779SJakub Wojciech Klama kwargs.pop('ctime') 1307*134e1779SJakub Wojciech Klama valid |= protocol.td.SETATTR_CTIME 1308*134e1779SJakub Wojciech Klama if kwargs: 1309*134e1779SJakub Wojciech Klama raise TypeError('Tsetattr() got an unexpected keyword argument ' 1310*134e1779SJakub Wojciech Klama '{0!r}'.format(kwargs.popitem())) 1311*134e1779SJakub Wojciech Klama 1312*134e1779SJakub Wojciech Klama tag = self.get_tag() 1313*134e1779SJakub Wojciech Klama attrobj.valid = valid 1314*134e1779SJakub Wojciech Klama attrobj.tag = tag 1315*134e1779SJakub Wojciech Klama attrobj.fid = fid 1316*134e1779SJakub Wojciech Klama pkt = self.proto.pack(attrobj) 1317*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1318*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1319*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rsetattr): 1320*134e1779SJakub Wojciech Klama self.badresp('Tsetattr {0} {1} of ' 1321*134e1779SJakub Wojciech Klama '{2}'.format(valid, attrs, self.getpathX(fid)), resp) 1322*134e1779SJakub Wojciech Klama 1323*134e1779SJakub Wojciech Klama def xattrwalk(self, fid, name=None): 1324*134e1779SJakub Wojciech Klama "walk one name or all names: caller should read() the returned fid" 1325*134e1779SJakub Wojciech Klama tag = self.get_tag() 1326*134e1779SJakub Wojciech Klama newfid = self.alloc_fid() 1327*134e1779SJakub Wojciech Klama pkt = self.proto.Txattrwalk(tag=tag, fid=fid, newfid=newfid, 1328*134e1779SJakub Wojciech Klama name=name or '') 1329*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1330*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1331*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rxattrwalk): 1332*134e1779SJakub Wojciech Klama self.retire_fid(newfid) 1333*134e1779SJakub Wojciech Klama self.badresp('Txattrwalk {0} of ' 1334*134e1779SJakub Wojciech Klama '{1}'.format(name, self.getpathX(fid)), resp) 1335*134e1779SJakub Wojciech Klama if name: 1336*134e1779SJakub Wojciech Klama self.setpath(newfid, 'xattr:' + name) 1337*134e1779SJakub Wojciech Klama else: 1338*134e1779SJakub Wojciech Klama self.setpath(newfid, 'xattr') 1339*134e1779SJakub Wojciech Klama return newfid, resp.size 1340*134e1779SJakub Wojciech Klama 1341*134e1779SJakub Wojciech Klama def _pathsplit(self, path, startdir, allow_empty=False): 1342*134e1779SJakub Wojciech Klama "common code for uxlookup and uxopen" 1343*134e1779SJakub Wojciech Klama if self.rootfid is None: 1344*134e1779SJakub Wojciech Klama raise LocalError('{0}: not attached'.format(self)) 1345*134e1779SJakub Wojciech Klama if path.startswith(b'/') or startdir is None: 1346*134e1779SJakub Wojciech Klama startdir = self.rootfid 1347*134e1779SJakub Wojciech Klama components = [i for i in path.split(b'/') if i != b''] 1348*134e1779SJakub Wojciech Klama if len(components) == 0 and not allow_empty: 1349*134e1779SJakub Wojciech Klama raise LocalError('{0}: {1!r}: empty path'.format(self, path)) 1350*134e1779SJakub Wojciech Klama return components, startdir 1351*134e1779SJakub Wojciech Klama 1352*134e1779SJakub Wojciech Klama def uxlookup(self, path, startdir=None): 1353*134e1779SJakub Wojciech Klama """ 1354*134e1779SJakub Wojciech Klama Unix-style lookup. That is, lookup('/foo/bar') or 1355*134e1779SJakub Wojciech Klama lookup('foo/bar'). If startdir is not None and the 1356*134e1779SJakub Wojciech Klama path does not start with '/' we look up from there. 1357*134e1779SJakub Wojciech Klama """ 1358*134e1779SJakub Wojciech Klama components, startdir = self._pathsplit(path, startdir, allow_empty=True) 1359*134e1779SJakub Wojciech Klama return self.lookup_last(startdir, components) 1360*134e1779SJakub Wojciech Klama 1361*134e1779SJakub Wojciech Klama def uxopen(self, path, oflags=0, perm=None, gid=None, 1362*134e1779SJakub Wojciech Klama startdir=None, filetype=None): 1363*134e1779SJakub Wojciech Klama """ 1364*134e1779SJakub Wojciech Klama Unix-style open()-with-option-to-create, or mkdir(). 1365*134e1779SJakub Wojciech Klama oflags is 0/1/2 with optional os.O_CREAT, perm defaults 1366*134e1779SJakub Wojciech Klama to 0o666 (files) or 0o777 (directories). If we use 1367*134e1779SJakub Wojciech Klama a Linux create or mkdir op, we will need a gid, but it's 1368*134e1779SJakub Wojciech Klama not required if you are opening an existing file. 1369*134e1779SJakub Wojciech Klama 1370*134e1779SJakub Wojciech Klama Adds a final boolean value for "did we actually create". 1371*134e1779SJakub Wojciech Klama Raises OSError if you ask for a directory but it's a file, 1372*134e1779SJakub Wojciech Klama or vice versa. (??? reconsider this later) 1373*134e1779SJakub Wojciech Klama 1374*134e1779SJakub Wojciech Klama Note that this does not handle other file types, only 1375*134e1779SJakub Wojciech Klama directories. 1376*134e1779SJakub Wojciech Klama """ 1377*134e1779SJakub Wojciech Klama needtype = { 1378*134e1779SJakub Wojciech Klama 'dir': protocol.td.QTDIR, 1379*134e1779SJakub Wojciech Klama None: protocol.td.QTFILE, 1380*134e1779SJakub Wojciech Klama }[filetype] 1381*134e1779SJakub Wojciech Klama omode_byte = oflags & 3 # cheating 1382*134e1779SJakub Wojciech Klama # allow looking up /, but not creating / 1383*134e1779SJakub Wojciech Klama allow_empty = (oflags & os.O_CREAT) == 0 1384*134e1779SJakub Wojciech Klama components, startdir = self._pathsplit(path, startdir, 1385*134e1779SJakub Wojciech Klama allow_empty=allow_empty) 1386*134e1779SJakub Wojciech Klama if not (oflags & os.O_CREAT): 1387*134e1779SJakub Wojciech Klama # Not creating, i.e., just look up and open existing file/dir. 1388*134e1779SJakub Wojciech Klama fid, qid = self.lookup_last(startdir, components) 1389*134e1779SJakub Wojciech Klama # If we got this far, use Topen on the fid; we did not 1390*134e1779SJakub Wojciech Klama # create the file. 1391*134e1779SJakub Wojciech Klama return self._uxopen2(path, needtype, fid, qid, omode_byte, False) 1392*134e1779SJakub Wojciech Klama 1393*134e1779SJakub Wojciech Klama # Only used if using dot-L, but make sure it's always provided 1394*134e1779SJakub Wojciech Klama # since this is generic. 1395*134e1779SJakub Wojciech Klama if gid is None: 1396*134e1779SJakub Wojciech Klama raise ValueError('gid is required when creating file or dir') 1397*134e1779SJakub Wojciech Klama 1398*134e1779SJakub Wojciech Klama if len(components) > 1: 1399*134e1779SJakub Wojciech Klama # Look up all but last component; this part must succeed. 1400*134e1779SJakub Wojciech Klama fid, _ = self.lookup(startdir, components[:-1]) 1401*134e1779SJakub Wojciech Klama 1402*134e1779SJakub Wojciech Klama # Now proceed with the final component, using fid 1403*134e1779SJakub Wojciech Klama # as the start dir. Remember to clunk it! 1404*134e1779SJakub Wojciech Klama startdir = fid 1405*134e1779SJakub Wojciech Klama clunk_startdir = True 1406*134e1779SJakub Wojciech Klama components = components[-1:] 1407*134e1779SJakub Wojciech Klama else: 1408*134e1779SJakub Wojciech Klama # Use startdir as the start dir, and get a new fid. 1409*134e1779SJakub Wojciech Klama # Do not clunk startdir! 1410*134e1779SJakub Wojciech Klama clunk_startdir = False 1411*134e1779SJakub Wojciech Klama fid = self.alloc_fid() 1412*134e1779SJakub Wojciech Klama 1413*134e1779SJakub Wojciech Klama # Now look up the (single) component. If this fails, 1414*134e1779SJakub Wojciech Klama # assume the file or directory needs to be created. 1415*134e1779SJakub Wojciech Klama tag = self.get_tag() 1416*134e1779SJakub Wojciech Klama pkt = self.proto.Twalk(tag=tag, fid=startdir, newfid=fid, 1417*134e1779SJakub Wojciech Klama nwname=1, wname=components) 1418*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1419*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1420*134e1779SJakub Wojciech Klama if isinstance(resp, protocol.rrd.Rwalk): 1421*134e1779SJakub Wojciech Klama if clunk_startdir: 1422*134e1779SJakub Wojciech Klama self.clunk(startdir, ignore_error=True) 1423*134e1779SJakub Wojciech Klama # fid successfully walked to refer to final component. 1424*134e1779SJakub Wojciech Klama # Just need to actually open the file. 1425*134e1779SJakub Wojciech Klama self.setpath(fid, _pathcat(self.getpath(startdir), components[0])) 1426*134e1779SJakub Wojciech Klama qid = resp.wqid[0] 1427*134e1779SJakub Wojciech Klama return self._uxopen2(needtype, fid, qid, omode_byte, False) 1428*134e1779SJakub Wojciech Klama 1429*134e1779SJakub Wojciech Klama # Walk failed. If we allocated a fid, retire it. Then set 1430*134e1779SJakub Wojciech Klama # up a fid that points to the parent directory in which to 1431*134e1779SJakub Wojciech Klama # create the file or directory. Note that if we're creating 1432*134e1779SJakub Wojciech Klama # a file, this fid will get changed so that it points to the 1433*134e1779SJakub Wojciech Klama # file instead of the directory, but if we're creating a 1434*134e1779SJakub Wojciech Klama # directory, it will be unchanged. 1435*134e1779SJakub Wojciech Klama if fid != startdir: 1436*134e1779SJakub Wojciech Klama self.retire_fid(fid) 1437*134e1779SJakub Wojciech Klama fid = self.dupfid(startdir) 1438*134e1779SJakub Wojciech Klama 1439*134e1779SJakub Wojciech Klama try: 1440*134e1779SJakub Wojciech Klama qid, iounit = self._uxcreate(filetype, fid, components[0], 1441*134e1779SJakub Wojciech Klama oflags, omode_byte, perm, gid) 1442*134e1779SJakub Wojciech Klama 1443*134e1779SJakub Wojciech Klama # Success. If we created an ordinary file, we have everything 1444*134e1779SJakub Wojciech Klama # now as create alters the incoming (dir) fid to open the file. 1445*134e1779SJakub Wojciech Klama # Otherwise (mkdir), we need to open the file, as with 1446*134e1779SJakub Wojciech Klama # a successful lookup. 1447*134e1779SJakub Wojciech Klama # 1448*134e1779SJakub Wojciech Klama # Note that qid type should match "needtype". 1449*134e1779SJakub Wojciech Klama if filetype != 'dir': 1450*134e1779SJakub Wojciech Klama if qid.type == needtype: 1451*134e1779SJakub Wojciech Klama return fid, qid, iounit, True 1452*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1453*134e1779SJakub Wojciech Klama raise OSError(_wrong_file_type(qid), 1454*134e1779SJakub Wojciech Klama '{0}: server told to create {1} but ' 1455*134e1779SJakub Wojciech Klama 'created {2} instead'.format(path, 1456*134e1779SJakub Wojciech Klama qt2n(needtype), 1457*134e1779SJakub Wojciech Klama qt2n(qid.type))) 1458*134e1779SJakub Wojciech Klama 1459*134e1779SJakub Wojciech Klama # Success: created dir; but now need to walk to and open it. 1460*134e1779SJakub Wojciech Klama fid = self.alloc_fid() 1461*134e1779SJakub Wojciech Klama tag = self.get_tag() 1462*134e1779SJakub Wojciech Klama pkt = self.proto.Twalk(tag=tag, fid=startdir, newfid=fid, 1463*134e1779SJakub Wojciech Klama nwname=1, wname=components) 1464*134e1779SJakub Wojciech Klama super(P9Client, self).write(pkt) 1465*134e1779SJakub Wojciech Klama resp = self.wait_for(tag) 1466*134e1779SJakub Wojciech Klama if not isinstance(resp, protocol.rrd.Rwalk): 1467*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1468*134e1779SJakub Wojciech Klama raise OSError(errno.ENOENT, 1469*134e1779SJakub Wojciech Klama '{0}: server made dir but then failed to ' 1470*134e1779SJakub Wojciech Klama 'find it again'.format(path)) 1471*134e1779SJakub Wojciech Klama self.setpath(fid, _pathcat(self.getpath(fid), components[0])) 1472*134e1779SJakub Wojciech Klama return self._uxopen2(needtype, fid, qid, omode_byte, True) 1473*134e1779SJakub Wojciech Klama finally: 1474*134e1779SJakub Wojciech Klama # Regardless of success/failure/exception, make sure 1475*134e1779SJakub Wojciech Klama # we clunk startdir if needed. 1476*134e1779SJakub Wojciech Klama if clunk_startdir: 1477*134e1779SJakub Wojciech Klama self.clunk(startdir, ignore_error=True) 1478*134e1779SJakub Wojciech Klama 1479*134e1779SJakub Wojciech Klama def _uxcreate(self, filetype, fid, name, oflags, omode_byte, perm, gid): 1480*134e1779SJakub Wojciech Klama """ 1481*134e1779SJakub Wojciech Klama Helper for creating dir-or-file. The fid argument is the 1482*134e1779SJakub Wojciech Klama parent directory on input, but will point to the file (if 1483*134e1779SJakub Wojciech Klama we're creating a file) on return. oflags only applies if 1484*134e1779SJakub Wojciech Klama we're creating a file (even then we use omode_byte if we 1485*134e1779SJakub Wojciech Klama are using the plan9 create op). 1486*134e1779SJakub Wojciech Klama """ 1487*134e1779SJakub Wojciech Klama # Try to create or mkdir as appropriate. 1488*134e1779SJakub Wojciech Klama if self.supports_all(protocol.td.Tlcreate, protocol.td.Tmkdir): 1489*134e1779SJakub Wojciech Klama # Use Linux style create / mkdir. 1490*134e1779SJakub Wojciech Klama if filetype == 'dir': 1491*134e1779SJakub Wojciech Klama if perm is None: 1492*134e1779SJakub Wojciech Klama perm = 0o777 1493*134e1779SJakub Wojciech Klama return self.mkdir(startdir, name, perm, gid), None 1494*134e1779SJakub Wojciech Klama if perm is None: 1495*134e1779SJakub Wojciech Klama perm = 0o666 1496*134e1779SJakub Wojciech Klama lflags = flags_to_linux_flags(oflags) 1497*134e1779SJakub Wojciech Klama return self.lcreate(fid, name, lflags, perm, gid) 1498*134e1779SJakub Wojciech Klama 1499*134e1779SJakub Wojciech Klama if filetype == 'dir': 1500*134e1779SJakub Wojciech Klama if perm is None: 1501*134e1779SJakub Wojciech Klama perm = protocol.td.DMDIR | 0o777 1502*134e1779SJakub Wojciech Klama else: 1503*134e1779SJakub Wojciech Klama perm |= protocol.td.DMDIR 1504*134e1779SJakub Wojciech Klama else: 1505*134e1779SJakub Wojciech Klama if perm is None: 1506*134e1779SJakub Wojciech Klama perm = 0o666 1507*134e1779SJakub Wojciech Klama return self.create(fid, name, perm, omode_byte) 1508*134e1779SJakub Wojciech Klama 1509*134e1779SJakub Wojciech Klama def _uxopen2(self, needtype, fid, qid, omode_byte, didcreate): 1510*134e1779SJakub Wojciech Klama "common code for finishing up uxopen" 1511*134e1779SJakub Wojciech Klama if qid.type != needtype: 1512*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1513*134e1779SJakub Wojciech Klama raise OSError(_wrong_file_type(qid), 1514*134e1779SJakub Wojciech Klama '{0}: is {1}, expected ' 1515*134e1779SJakub Wojciech Klama '{2}'.format(path, qt2n(qid.type), qt2n(needtype))) 1516*134e1779SJakub Wojciech Klama qid, iounit = self.open(fid, omode_byte) 1517*134e1779SJakub Wojciech Klama # ? should we re-check qid? it should not have changed 1518*134e1779SJakub Wojciech Klama return fid, qid, iounit, didcreate 1519*134e1779SJakub Wojciech Klama 1520*134e1779SJakub Wojciech Klama def uxmkdir(self, path, perm, gid, startdir=None): 1521*134e1779SJakub Wojciech Klama """ 1522*134e1779SJakub Wojciech Klama Unix-style mkdir. 1523*134e1779SJakub Wojciech Klama 1524*134e1779SJakub Wojciech Klama The gid is only applied if we are using .L style mkdir. 1525*134e1779SJakub Wojciech Klama """ 1526*134e1779SJakub Wojciech Klama components, startdir = self._pathsplit(path, startdir) 1527*134e1779SJakub Wojciech Klama clunkme = None 1528*134e1779SJakub Wojciech Klama if len(components) > 1: 1529*134e1779SJakub Wojciech Klama fid, _ = self.lookup(startdir, components[:-1]) 1530*134e1779SJakub Wojciech Klama startdir = fid 1531*134e1779SJakub Wojciech Klama clunkme = fid 1532*134e1779SJakub Wojciech Klama components = components[-1:] 1533*134e1779SJakub Wojciech Klama try: 1534*134e1779SJakub Wojciech Klama if self.supports(protocol.td.Tmkdir): 1535*134e1779SJakub Wojciech Klama qid = self.mkdir(startdir, components[0], perm, gid) 1536*134e1779SJakub Wojciech Klama else: 1537*134e1779SJakub Wojciech Klama qid, _ = self.create(startdir, components[0], 1538*134e1779SJakub Wojciech Klama protocol.td.DMDIR | perm, 1539*134e1779SJakub Wojciech Klama protocol.td.OREAD) 1540*134e1779SJakub Wojciech Klama # Should we chown/chgrp the dir? 1541*134e1779SJakub Wojciech Klama finally: 1542*134e1779SJakub Wojciech Klama if clunkme: 1543*134e1779SJakub Wojciech Klama self.clunk(clunkme, ignore_error=True) 1544*134e1779SJakub Wojciech Klama return qid 1545*134e1779SJakub Wojciech Klama 1546*134e1779SJakub Wojciech Klama def uxreaddir(self, path, startdir=None, no_dotl=False): 1547*134e1779SJakub Wojciech Klama """ 1548*134e1779SJakub Wojciech Klama Read a directory to get a list of names (which may or may not 1549*134e1779SJakub Wojciech Klama include '.' and '..'). 1550*134e1779SJakub Wojciech Klama 1551*134e1779SJakub Wojciech Klama If no_dotl is True (or anything non-false-y), this uses the 1552*134e1779SJakub Wojciech Klama plain or .u readdir format, otherwise it uses dot-L readdir 1553*134e1779SJakub Wojciech Klama if possible. 1554*134e1779SJakub Wojciech Klama """ 1555*134e1779SJakub Wojciech Klama components, startdir = self._pathsplit(path, startdir, allow_empty=True) 1556*134e1779SJakub Wojciech Klama fid, qid = self.lookup_last(startdir, components) 1557*134e1779SJakub Wojciech Klama try: 1558*134e1779SJakub Wojciech Klama if qid.type != protocol.td.QTDIR: 1559*134e1779SJakub Wojciech Klama raise OSError(errno.ENOTDIR, 1560*134e1779SJakub Wojciech Klama '{0}: {1}'.format(self.getpathX(fid), 1561*134e1779SJakub Wojciech Klama os.strerror(errno.ENOTDIR))) 1562*134e1779SJakub Wojciech Klama # We need both Tlopen and Treaddir to use Treaddir. 1563*134e1779SJakub Wojciech Klama if not self.supports_all(protocol.td.Tlopen, protocol.td.Treaddir): 1564*134e1779SJakub Wojciech Klama no_dotl = True 1565*134e1779SJakub Wojciech Klama if no_dotl: 1566*134e1779SJakub Wojciech Klama statvals = self.uxreaddir_stat_fid(fid) 1567*134e1779SJakub Wojciech Klama return [i.name for i in statvals] 1568*134e1779SJakub Wojciech Klama 1569*134e1779SJakub Wojciech Klama dirents = self.uxreaddir_dotl_fid(fid) 1570*134e1779SJakub Wojciech Klama return [dirent.name for dirent in dirents] 1571*134e1779SJakub Wojciech Klama finally: 1572*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1573*134e1779SJakub Wojciech Klama 1574*134e1779SJakub Wojciech Klama def uxreaddir_stat(self, path, startdir=None): 1575*134e1779SJakub Wojciech Klama """ 1576*134e1779SJakub Wojciech Klama Use directory read to get plan9 style stat data (plain or .u readdir). 1577*134e1779SJakub Wojciech Klama 1578*134e1779SJakub Wojciech Klama Note that this gets a fid, then opens it, reads, then clunks 1579*134e1779SJakub Wojciech Klama the fid. If you already have a fid, you may want to use 1580*134e1779SJakub Wojciech Klama uxreaddir_stat_fid (but note that this opens, yet does not 1581*134e1779SJakub Wojciech Klama clunk, the fid). 1582*134e1779SJakub Wojciech Klama 1583*134e1779SJakub Wojciech Klama We return the qid plus the list of the contents. If the 1584*134e1779SJakub Wojciech Klama target is not a directory, the qid will not have type QTDIR 1585*134e1779SJakub Wojciech Klama and the contents list will be empty. 1586*134e1779SJakub Wojciech Klama 1587*134e1779SJakub Wojciech Klama Raises OSError if this is applied to a non-directory. 1588*134e1779SJakub Wojciech Klama """ 1589*134e1779SJakub Wojciech Klama components, startdir = self._pathsplit(path, startdir) 1590*134e1779SJakub Wojciech Klama fid, qid = self.lookup_last(startdir, components) 1591*134e1779SJakub Wojciech Klama try: 1592*134e1779SJakub Wojciech Klama if qid.type != protocol.td.QTDIR: 1593*134e1779SJakub Wojciech Klama raise OSError(errno.ENOTDIR, 1594*134e1779SJakub Wojciech Klama '{0}: {1}'.format(self.getpathX(fid), 1595*134e1779SJakub Wojciech Klama os.strerror(errno.ENOTDIR))) 1596*134e1779SJakub Wojciech Klama statvals = self.ux_readdir_stat_fid(fid) 1597*134e1779SJakub Wojciech Klama return qid, statvals 1598*134e1779SJakub Wojciech Klama finally: 1599*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1600*134e1779SJakub Wojciech Klama 1601*134e1779SJakub Wojciech Klama def uxreaddir_stat_fid(self, fid): 1602*134e1779SJakub Wojciech Klama """ 1603*134e1779SJakub Wojciech Klama Implement readdir loop that extracts stat values. 1604*134e1779SJakub Wojciech Klama This opens, but does not clunk, the given fid. 1605*134e1779SJakub Wojciech Klama 1606*134e1779SJakub Wojciech Klama Unlike uxreaddir_stat(), if this is applied to a file, 1607*134e1779SJakub Wojciech Klama rather than a directory, it just returns no entries. 1608*134e1779SJakub Wojciech Klama """ 1609*134e1779SJakub Wojciech Klama statvals = [] 1610*134e1779SJakub Wojciech Klama qid, iounit = self.open(fid, protocol.td.OREAD) 1611*134e1779SJakub Wojciech Klama # ?? is a zero iounit allowed? if so, what do we use here? 1612*134e1779SJakub Wojciech Klama if qid.type == protocol.td.QTDIR: 1613*134e1779SJakub Wojciech Klama if iounit <= 0: 1614*134e1779SJakub Wojciech Klama iounit = 512 # probably good enough 1615*134e1779SJakub Wojciech Klama offset = 0 1616*134e1779SJakub Wojciech Klama while True: 1617*134e1779SJakub Wojciech Klama bstring = self.read(fid, offset, iounit) 1618*134e1779SJakub Wojciech Klama if bstring == b'': 1619*134e1779SJakub Wojciech Klama break 1620*134e1779SJakub Wojciech Klama statvals.extend(self.decode_stat_objects(bstring)) 1621*134e1779SJakub Wojciech Klama offset += len(bstring) 1622*134e1779SJakub Wojciech Klama return statvals 1623*134e1779SJakub Wojciech Klama 1624*134e1779SJakub Wojciech Klama def uxreaddir_dotl_fid(self, fid): 1625*134e1779SJakub Wojciech Klama """ 1626*134e1779SJakub Wojciech Klama Implement readdir loop that uses dot-L style dirents. 1627*134e1779SJakub Wojciech Klama This opens, but does not clunk, the given fid. 1628*134e1779SJakub Wojciech Klama 1629*134e1779SJakub Wojciech Klama If applied to a file, the lopen should fail, because of the 1630*134e1779SJakub Wojciech Klama L_O_DIRECTORY flag. 1631*134e1779SJakub Wojciech Klama """ 1632*134e1779SJakub Wojciech Klama dirents = [] 1633*134e1779SJakub Wojciech Klama qid, iounit = self.lopen(fid, protocol.td.OREAD | 1634*134e1779SJakub Wojciech Klama protocol.td.L_O_DIRECTORY) 1635*134e1779SJakub Wojciech Klama # ?? is a zero iounit allowed? if so, what do we use here? 1636*134e1779SJakub Wojciech Klama # but, we want a minimum of over 256 anyway, let's go for 512 1637*134e1779SJakub Wojciech Klama if iounit < 512: 1638*134e1779SJakub Wojciech Klama iounit = 512 1639*134e1779SJakub Wojciech Klama offset = 0 1640*134e1779SJakub Wojciech Klama while True: 1641*134e1779SJakub Wojciech Klama bstring = self.readdir(fid, offset, iounit) 1642*134e1779SJakub Wojciech Klama if bstring == b'': 1643*134e1779SJakub Wojciech Klama break 1644*134e1779SJakub Wojciech Klama ents = self.decode_readdir_dirents(bstring) 1645*134e1779SJakub Wojciech Klama if len(ents) == 0: 1646*134e1779SJakub Wojciech Klama break # ??? 1647*134e1779SJakub Wojciech Klama dirents.extend(ents) 1648*134e1779SJakub Wojciech Klama offset = ents[-1].offset 1649*134e1779SJakub Wojciech Klama return dirents 1650*134e1779SJakub Wojciech Klama 1651*134e1779SJakub Wojciech Klama def uxremove(self, path, startdir=None, filetype=None, 1652*134e1779SJakub Wojciech Klama force=False, recurse=False): 1653*134e1779SJakub Wojciech Klama """ 1654*134e1779SJakub Wojciech Klama Implement rm / rmdir, with optional -rf. 1655*134e1779SJakub Wojciech Klama if filetype is None, remove dir or file. If 'dir' or 'file' 1656*134e1779SJakub Wojciech Klama remove only if it's one of those. If force is set, ignore 1657*134e1779SJakub Wojciech Klama failures to remove. If recurse is True, remove contents of 1658*134e1779SJakub Wojciech Klama directories (recursively). 1659*134e1779SJakub Wojciech Klama 1660*134e1779SJakub Wojciech Klama File type mismatches (when filetype!=None) raise OSError (?). 1661*134e1779SJakub Wojciech Klama """ 1662*134e1779SJakub Wojciech Klama components, startdir = self._pathsplit(path, startdir, allow_empty=True) 1663*134e1779SJakub Wojciech Klama # Look up all components. If 1664*134e1779SJakub Wojciech Klama # we get an error we'll just assume the file does not 1665*134e1779SJakub Wojciech Klama # exist (is this good?). 1666*134e1779SJakub Wojciech Klama try: 1667*134e1779SJakub Wojciech Klama fid, qid = self.lookup_last(startdir, components) 1668*134e1779SJakub Wojciech Klama except RemoteError: 1669*134e1779SJakub Wojciech Klama return 1670*134e1779SJakub Wojciech Klama if qid.type == protocol.td.QTDIR: 1671*134e1779SJakub Wojciech Klama # it's a directory, remove only if allowed. 1672*134e1779SJakub Wojciech Klama # Note that we must check for "rm -r /" (len(components)==0). 1673*134e1779SJakub Wojciech Klama if filetype == 'file': 1674*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1675*134e1779SJakub Wojciech Klama raise OSError(_wrong_file_type(qid), 1676*134e1779SJakub Wojciech Klama '{0}: is dir, expected file'.format(path)) 1677*134e1779SJakub Wojciech Klama isroot = len(components) == 0 1678*134e1779SJakub Wojciech Klama closer = self.clunk if isroot else self.remove 1679*134e1779SJakub Wojciech Klama if recurse: 1680*134e1779SJakub Wojciech Klama # NB: _rm_recursive does not clunk fid 1681*134e1779SJakub Wojciech Klama self._rm_recursive(fid, filetype, force) 1682*134e1779SJakub Wojciech Klama # This will fail if the directory is non-empty, unless of 1683*134e1779SJakub Wojciech Klama # course we tell it to ignore error. 1684*134e1779SJakub Wojciech Klama closer(fid, ignore_error=force) 1685*134e1779SJakub Wojciech Klama return 1686*134e1779SJakub Wojciech Klama # Not a directory, call it a file (even if socket or fifo etc). 1687*134e1779SJakub Wojciech Klama if filetype == 'dir': 1688*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1689*134e1779SJakub Wojciech Klama raise OSError(_wrong_file_type(qid), 1690*134e1779SJakub Wojciech Klama '{0}: is file, expected dir'.format(path)) 1691*134e1779SJakub Wojciech Klama self.remove(fid, ignore_error=force) 1692*134e1779SJakub Wojciech Klama 1693*134e1779SJakub Wojciech Klama def _rm_file_by_dfid(self, dfid, name, force=False): 1694*134e1779SJakub Wojciech Klama """ 1695*134e1779SJakub Wojciech Klama Remove a file whose name is <name> (no path, just a component 1696*134e1779SJakub Wojciech Klama name) whose parent directory is <dfid>. We may assume that the 1697*134e1779SJakub Wojciech Klama file really is a file (or a socket, or fifo, or some such, but 1698*134e1779SJakub Wojciech Klama definitely not a directory). 1699*134e1779SJakub Wojciech Klama 1700*134e1779SJakub Wojciech Klama If force is set, ignore failures. 1701*134e1779SJakub Wojciech Klama """ 1702*134e1779SJakub Wojciech Klama # If we have unlinkat, that's the fast way. But it may 1703*134e1779SJakub Wojciech Klama # return an ENOTSUP error. If it does we shouldn't bother 1704*134e1779SJakub Wojciech Klama # doing this again. 1705*134e1779SJakub Wojciech Klama if self.supports(protocol.td.Tunlinkat): 1706*134e1779SJakub Wojciech Klama try: 1707*134e1779SJakub Wojciech Klama self.unlinkat(dfid, name, 0) 1708*134e1779SJakub Wojciech Klama return 1709*134e1779SJakub Wojciech Klama except RemoteError as err: 1710*134e1779SJakub Wojciech Klama if not err.is_ENOTSUP(): 1711*134e1779SJakub Wojciech Klama raise 1712*134e1779SJakub Wojciech Klama self.unsupported(protocol.td.Tunlinkat) 1713*134e1779SJakub Wojciech Klama # fall through to remove() op 1714*134e1779SJakub Wojciech Klama # Fall back to lookup + remove. 1715*134e1779SJakub Wojciech Klama try: 1716*134e1779SJakub Wojciech Klama fid, qid = self.lookup_last(dfid, [name]) 1717*134e1779SJakub Wojciech Klama except RemoteError: 1718*134e1779SJakub Wojciech Klama # If this has an errno we could tell ENOENT from EPERM, 1719*134e1779SJakub Wojciech Klama # and actually raise an error for the latter. Should we? 1720*134e1779SJakub Wojciech Klama return 1721*134e1779SJakub Wojciech Klama self.remove(fid, ignore_error=force) 1722*134e1779SJakub Wojciech Klama 1723*134e1779SJakub Wojciech Klama def _rm_recursive(self, dfid, filetype, force): 1724*134e1779SJakub Wojciech Klama """ 1725*134e1779SJakub Wojciech Klama Recursively remove a directory. filetype is probably None, 1726*134e1779SJakub Wojciech Klama but if it's 'dir' we fail if the directory contains non-dir 1727*134e1779SJakub Wojciech Klama files. 1728*134e1779SJakub Wojciech Klama 1729*134e1779SJakub Wojciech Klama If force is set, ignore failures. 1730*134e1779SJakub Wojciech Klama 1731*134e1779SJakub Wojciech Klama Although we open dfid (via the readdir.*_fid calls) we 1732*134e1779SJakub Wojciech Klama do not clunk it here; that's the caller's job. 1733*134e1779SJakub Wojciech Klama """ 1734*134e1779SJakub Wojciech Klama # first, remove contents 1735*134e1779SJakub Wojciech Klama if self.supports_all(protocol.td.Tlopen, protocol.td.Treaddir): 1736*134e1779SJakub Wojciech Klama for entry in self.uxreaddir_dotl_fid(dfid): 1737*134e1779SJakub Wojciech Klama if entry.name in (b'.', b'..'): 1738*134e1779SJakub Wojciech Klama continue 1739*134e1779SJakub Wojciech Klama fid, qid = self.lookup(dfid, [entry.name]) 1740*134e1779SJakub Wojciech Klama try: 1741*134e1779SJakub Wojciech Klama attrs = self.Tgetattr(fid, protocol.td.GETATTR_MODE) 1742*134e1779SJakub Wojciech Klama if stat.S_ISDIR(attrs.mode): 1743*134e1779SJakub Wojciech Klama self.uxremove(entry.name, dfid, filetype, force, True) 1744*134e1779SJakub Wojciech Klama else: 1745*134e1779SJakub Wojciech Klama self.remove(fid) 1746*134e1779SJakub Wojciech Klama fid = None 1747*134e1779SJakub Wojciech Klama finally: 1748*134e1779SJakub Wojciech Klama if fid is not None: 1749*134e1779SJakub Wojciech Klama self.clunk(fid, ignore_error=True) 1750*134e1779SJakub Wojciech Klama else: 1751*134e1779SJakub Wojciech Klama for statobj in self.uxreaddir_stat_fid(dfid): 1752*134e1779SJakub Wojciech Klama # skip . and .. 1753*134e1779SJakub Wojciech Klama name = statobj.name 1754*134e1779SJakub Wojciech Klama if name in (b'.', b'..'): 1755*134e1779SJakub Wojciech Klama continue 1756*134e1779SJakub Wojciech Klama if statobj.qid.type == protocol.td.QTDIR: 1757*134e1779SJakub Wojciech Klama self.uxremove(name, dfid, filetype, force, True) 1758*134e1779SJakub Wojciech Klama else: 1759*134e1779SJakub Wojciech Klama self._rm_file_by_dfid(dfid, name, force) 1760*134e1779SJakub Wojciech Klama 1761*134e1779SJakub Wojciech Klamadef _wrong_file_type(qid): 1762*134e1779SJakub Wojciech Klama "return EISDIR or ENOTDIR for passing to OSError" 1763*134e1779SJakub Wojciech Klama if qid.type == protocol.td.QTDIR: 1764*134e1779SJakub Wojciech Klama return errno.EISDIR 1765*134e1779SJakub Wojciech Klama return errno.ENOTDIR 1766*134e1779SJakub Wojciech Klama 1767*134e1779SJakub Wojciech Klamadef flags_to_linux_flags(flags): 1768*134e1779SJakub Wojciech Klama """ 1769*134e1779SJakub Wojciech Klama Convert OS flags (O_CREAT etc) to Linux flags (protocol.td.L_O_CREAT etc). 1770*134e1779SJakub Wojciech Klama """ 1771*134e1779SJakub Wojciech Klama flagmap = { 1772*134e1779SJakub Wojciech Klama os.O_CREAT: protocol.td.L_O_CREAT, 1773*134e1779SJakub Wojciech Klama os.O_EXCL: protocol.td.L_O_EXCL, 1774*134e1779SJakub Wojciech Klama os.O_NOCTTY: protocol.td.L_O_NOCTTY, 1775*134e1779SJakub Wojciech Klama os.O_TRUNC: protocol.td.L_O_TRUNC, 1776*134e1779SJakub Wojciech Klama os.O_APPEND: protocol.td.L_O_APPEND, 1777*134e1779SJakub Wojciech Klama os.O_DIRECTORY: protocol.td.L_O_DIRECTORY, 1778*134e1779SJakub Wojciech Klama } 1779*134e1779SJakub Wojciech Klama 1780*134e1779SJakub Wojciech Klama result = flags & os.O_RDWR 1781*134e1779SJakub Wojciech Klama flags &= ~os.O_RDWR 1782*134e1779SJakub Wojciech Klama for key, value in flagmap.iteritems(): 1783*134e1779SJakub Wojciech Klama if flags & key: 1784*134e1779SJakub Wojciech Klama result |= value 1785*134e1779SJakub Wojciech Klama flags &= ~key 1786*134e1779SJakub Wojciech Klama if flags: 1787*134e1779SJakub Wojciech Klama raise ValueError('untranslated bits 0x{0:x} in os flags'.format(flags)) 1788*134e1779SJakub Wojciech Klama return result 1789