4 handle plan9 server <-> client connections
6 (We can act as either server or client.)
8 This code needs some doctests or other unit tests...
29 # Timespec based timestamps, if present, have
30 # both seconds and nanoseconds.
31 Timespec = collections.namedtuple('Timespec', 'sec nsec')
33 # File attributes from Tgetattr, or given to Tsetattr.
34 # (move to protocol.py?) We use pfod here instead of
35 # namedtuple so that we can create instances with all-None
37 Fileattrs = pfod.pfod('Fileattrs',
38 'ino mode uid gid nlink rdev size blksize blocks '
39 'atime mtime ctime btime gen data_version')
41 qt2n = protocol.qid_type2name
45 class P9Error(Exception):
48 class RemoteError(P9Error):
50 Used when the remote returns an error. We track the client
51 (connection instance), the operation being attempted, the
52 message, and an error number and type. The message may be
53 from the Rerror reply, or from converting the errno in a dot-L
54 or dot-u Rerror reply. The error number may be None if the
55 type is 'Rerror' rather than 'Rlerror'. The message may be
56 None or empty string if a non-None errno supplies the error
59 def __init__(self, client, op, msg, etype, errno):
60 self.client = str(client)
63 self.etype = etype # 'Rerror' or 'Rlerror'
64 self.errno = errno # may be None
65 self.message = self._get_message()
66 super(RemoteError, self).__init__(self, self.message)
69 return ('{0!r}({1}, {2}, {3}, {4}, '
70 '{5})'.format(self.__class__.__name__, self.client, self.op,
71 self.msg, self.errno, self.etype))
73 prefix = '{0}: {1}: '.format(self.client, self.op)
74 if self.errno: # check for "is not None", or just non-false-y?
75 name = {'Rerror': '.u', 'Rlerror': 'Linux'}[self.etype]
76 middle = '[{0} error {1}] '.format(name, self.errno)
79 return '{0}{1}{2}'.format(prefix, middle, self.message)
82 if self.etype == 'Rlerror':
83 return self.errno == lerrno.EOPNOTSUPP
84 return self.errno == errno.EOPNOTSUPP
86 def _get_message(self):
87 "get message based on self.msg or self.errno"
88 if self.errno is not None:
90 'Rlerror': p9err.dotl_strerror,
91 'Rerror' : p9err.dotu_strerror,
92 }[self.etype](self.errno)
95 class LocalError(P9Error):
98 class TEError(LocalError):
101 class P9SockIO(object):
103 Common base for server and client, handle send and
104 receive to communications channel. Note that this
105 need not set up the channel initially, only the logger.
106 The channel is typically connected later. However, you
107 can provide one initially.
109 def __init__(self, logger, name=None, server=None, port=STD_P9_PORT):
114 self.size_coder = struct.Struct('<I')
115 if server is not None:
116 self.connect(server, port)
117 self.max_payload = 2**32 - self.size_coder.size
124 def get_recommended_maxio(self):
125 "suggest a max I/O size, for when self.maxio is 0 / unset"
129 "return a minimum size below which we refuse to work"
130 return self.size_coder.size + 100
132 def connect(self, server, port=STD_P9_PORT):
134 Connect to given server name / IP address.
136 If self.name was none, sets self.name to ip:port on success.
138 if self.is_connected():
139 raise LocalError('already connected')
140 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
141 sock.connect((server, port))
142 if self.name is None:
143 if port == STD_P9_PORT:
146 name = '{0}:{1}'.format(server, port)
149 self.declare_connected(sock, name, None)
151 def is_connected(self):
152 "predicate: are we connected?"
153 return self.channel != None
155 def declare_connected(self, chan, name, maxio):
157 Now available for normal protocol (size-prefixed) I/O.
159 Replaces chan and name and adjusts maxio, if those
160 parameters are not None.
163 minio = self.min_maxio()
165 raise LocalError('maxio={0} < minimum {1}'.format(maxio, minio))
170 if maxio is not None:
172 self.max_payload = maxio - self.size_coder.size
174 def reduce_maxio(self, maxio):
175 "Reduce maximum I/O size per other-side request"
176 minio = self.min_maxio()
178 raise LocalError('new maxio={0} < minimum {1}'.format(maxio, minio))
179 if maxio > self.maxio:
180 raise LocalError('new maxio={0} > current {1}'.format(maxio,
183 self.max_payload = maxio - self.size_coder.size
185 def declare_disconnected(self):
186 "Declare comm channel dead (note: leaves self.name set!)"
191 "Do a SHUT_WR on the outbound channel - can't send more"
193 # we're racing other threads here
195 chan.shutdown(socket.SHUT_WR)
196 except (OSError, AttributeError):
200 "Shut down comm channel"
203 self.channel.shutdown(socket.SHUT_RDWR)
207 self.declare_disconnected()
211 Try to read a complete packet.
213 Returns '' for EOF, as read() usually does.
215 If we can't even get the size, this still returns ''.
216 If we get a sensible size but are missing some data,
217 we can return a short packet. Since we know if we did
218 this, we also return a boolean: True means "really got a
221 Note that '' EOF always returns False: EOF is never a
224 if self.channel is None:
226 size_field = self.xread(self.size_coder.size)
227 if len(size_field) < self.size_coder.size:
228 if len(size_field) == 0:
229 self.logger.log(logging.INFO, '%s: normal EOF', self)
231 self.logger.log(logging.ERROR,
232 '%s: EOF while reading size (got %d bytes)',
233 self, len(size_field))
234 # should we raise an error here?
237 size = self.size_coder.unpack(size_field)[0] - self.size_coder.size
238 if size <= 0 or size > self.max_payload:
239 self.logger.log(logging.ERROR,
240 '%s: incoming size %d is insane '
241 '(max payload is %d)',
242 self, size, self.max_payload)
243 # indicate EOF - should we raise an error instead, here?
245 data = self.xread(size)
246 return data, len(data) == size
248 def xread(self, nbytes):
250 Read nbytes bytes, looping if necessary. Return '' for
251 EOF; may return a short count if we get some data, then
255 # Try to get everything at once (should usually succeed).
256 # Return immediately for EOF or got-all-data.
257 data = self.channel.recv(nbytes)
258 if data == b'' or len(data) == nbytes:
261 # Gather data fragments into an array, then join it all at
265 while count < nbytes:
266 more = self.channel.recv(nbytes - count)
271 return b''.join(data)
273 def write(self, data):
275 Write all the data, in the usual encoding. Note that
276 the length of the data, including the length of the length
277 itself, is already encoded in the first 4 bytes of the
280 Raises IOError if we can't write everything.
282 Raises LocalError if len(data) exceeds max_payload.
286 if size > self.max_payload:
287 raise LocalError('data length {0} exceeds '
288 'maximum {1}'.format(size, self.max_payload))
289 self.channel.sendall(data)
291 def _pathcat(prefix, suffix):
293 Concatenate paths we are using on the server side. This is
294 basically just prefix + / + suffix, with two complications:
296 It's possible we don't have a prefix path, in which case
297 we want the suffix without a leading slash.
299 It's possible that the prefix is just b'/', in which case we
300 want prefix + suffix.
303 if prefix == b'/': # or prefix.endswith(b'/')?
304 return prefix + suffix
305 return prefix + b'/' + suffix
308 class P9Client(P9SockIO):
312 We need the a logger (see logging), a timeout, and a protocol
313 version to request. By default, we will downgrade to a lower
316 If server and port are supplied, they are remembered and become
317 the default for .connect() (which is still deferred).
319 Note that we keep a table of fid-to-path in self.live_fids,
320 but at any time (except while holding the lock) a fid can
321 be deleted entirely, and the table entry may just be True
322 if we have no path name. In general, we update the name
325 def __init__(self, logger, timeout, version, may_downgrade=True,
326 server=None, port=None):
327 super(P9Client, self).__init__(logger)
328 self.timeout = timeout
329 self.iproto = protocol.p9_version(version)
330 self.may_downgrade = may_downgrade
331 self.tagalloc = numalloc.NumAlloc(0, 65534)
333 # The next bit is slighlty dirty: perhaps we should just
334 # allocate NOFID out of the 2**32-1 range, so as to avoid
335 # "knowing" that it's 2**32-1.
336 self.fidalloc = numalloc.NumAlloc(0, protocol.td.NOFID - 1)
341 self.lock = threading.Lock()
342 self.new_replies = threading.Condition(self.lock)
343 self._monkeywrench = {}
344 self._server = server
348 def get_monkey(self, what):
349 "check for a monkey-wrench"
351 wrench = self._monkeywrench.get(what)
354 if isinstance(wrench, list):
355 # repeats wrench[0] times, or forever if that's 0
360 del self._monkeywrench[what]
363 del self._monkeywrench[what]
366 def set_monkey(self, what, how, repeat=None):
368 Set a monkey-wrench. If repeat is not None it is the number of
369 times the wrench is applied (0 means forever, or until you call
370 set again with how=None). What is what to monkey-wrench, which
371 depends on the op. How is generally a replacement value.
376 del self._monkeywrench[what]
380 if repeat is not None:
383 self._monkeywrench[what] = how
385 def get_tag(self, for_Tversion=False):
386 "get next available tag ID"
391 tag = self.tagalloc.alloc()
393 raise LocalError('all tags in use')
394 self.tagstate[tag] = True # ie, in use, still waiting
397 def set_tag(self, tag, reply):
398 "set the reply info for the given tag"
399 assert tag >= 0 and tag < 65536
401 # check whether we're still waiting for the tag
402 state = self.tagstate.get(tag)
404 self.tagstate[tag] = reply # i.e., here's the answer
405 self.new_replies.notify_all()
407 # state must be one of these...
409 # We gave up on this tag. Reply came anyway.
410 self.logger.log(logging.INFO,
411 '%s: got tag %d = %r after timing out on it',
413 self.retire_tag_locked(tag)
416 # We got a tag back from the server that was not
418 self.logger.log(logging.WARNING,
419 '%s: got tag %d = %r when tag %d not in use!',
420 self, tag, reply, tag)
422 # We got a second reply before handling the first reply!
423 self.logger.log(logging.WARNING,
424 '%s: got tag %d = %r when tag %d = %r!',
425 self, tag, reply, tag, state)
428 def retire_tag(self, tag):
429 "retire the given tag - only used by the thread that handled the result"
432 assert tag >= 0 and tag < 65535
434 self.retire_tag_locked(tag)
436 def retire_tag_locked(self, tag):
437 "retire the given tag while holding self.lock"
438 # must check "in tagstate" because we can race
439 # with retire_all_tags.
440 if tag in self.tagstate:
441 del self.tagstate[tag]
442 self.tagalloc.free(tag)
444 def retire_all_tags(self):
445 "retire all tags, after connection drop"
447 # release all tags in any state (waiting, answered, timedout)
448 self.tagalloc.free_multi(self.tagstate.keys())
450 self.new_replies.notify_all()
455 fid = self.fidalloc.alloc()
456 self.live_fids[fid] = True
459 def getpath(self, fid):
460 "get path from fid, or return None if no path known, or not valid"
462 path = self.live_fids.get(fid)
467 def getpathX(self, fid):
469 Much like getpath, but return <fid N, unknown path> if necessary.
470 If we do have a path, return its repr().
472 path = self.getpath(fid)
474 return '<fid {0}, unknown path>'.format(fid)
477 def setpath(self, fid, path):
478 "associate fid with new path (possibly from another fid)"
480 if isinstance(path, int):
481 path = self.live_fids.get(path)
482 # path might now be None (not a live fid after all), or
483 # True (we have no path name), or potentially even the
484 # empty string (invalid for our purposes). Treat all of
485 # those as True, meaning "no known path".
488 if self.live_fids.get(fid):
489 # Existing fid maps to either True or its old path.
490 # Set the new path (which may be just a placeholder).
491 self.live_fids[fid] = path
493 def did_rename(self, fid, ncomp, newdir=None):
495 Announce that we renamed using a fid - we'll try to update
496 other fids based on this (we can't really do it perfectly).
498 NOTE: caller must provide a final-component.
499 The caller can supply the new path (and should
500 do so if the rename is not based on the retained path
501 for the supplied fid, i.e., for rename ops where fid
502 can move across directories). The rules:
504 - If newdir is None (default), we use stored path.
505 - Otherwise, newdir provides the best approximation
506 we have to the path that needs ncomp appended.
508 (This is based on the fact that renames happen via Twstat
509 or Trename, or Trenameat, which change just one tail component,
510 but the path names vary.)
514 opath = self.getpath(fid)
518 ocomps = opath.split(b'/')
519 ncomps = ocomps[0:-1]
521 ocomps = None # well, none yet anyway
522 ncomps = newdir.split(b'/')
524 if opath is None or opath[0] != '/':
525 # We don't have enough information to fix anything else.
526 # Just store the new path and return. We have at least
527 # a partial path now, which is no worse than before.
528 npath = b'/'.join(ncomps)
530 if fid in self.live_fids:
531 self.live_fids[fid] = npath
534 ocomps = opath.split(b'/')
536 ofinal = ocomps[olen - 1]
537 # Old paths is full path. Find any other fids that start
538 # with some or all the components in ocomps. Note that if
539 # we renamed /one/two/three to /four/five this winds up
540 # renaming files /one/a to /four/a, /one/two/b to /four/five/b,
543 for fid2, path2 in self.live_fids.iteritems():
544 # Skip fids without byte-string paths
545 if not isinstance(path2, bytes):
547 # Before splitting (which is a bit expensive), try
548 # a straightforward prefix match. This might give
549 # some false hits, e.g., prefix /one/two/threepenny
550 # starts with /one/two/three, but it quickly eliminates
551 # /raz/baz/mataz and the like.
552 if not path2.startswith(opath):
554 # Split up the path, and use that to make sure that
555 # the final component is a full match.
556 parts2 = path2.split(b'/')
557 if parts2[olen - 1] != ofinal:
559 # OK, path2 starts with the old (renamed) sequence.
560 # Replace the old components with the new ones.
561 # This updates the renamed fid when we come across
562 # it! It also handles a change in the number of
563 # components, thanks to Python's slice assignment.
564 parts2[0:olen] = ncomps
565 self.live_fids[fid2] = b'/'.join(parts2)
567 def retire_fid(self, fid):
570 self.fidalloc.free(fid)
571 del self.live_fids[fid]
573 def retire_all_fids(self):
574 "return live fids to pool"
575 # this is useful for debugging fid leaks:
576 #for fid in self.live_fids:
577 # print 'retiring', fid, self.getpathX(fid)
579 self.fidalloc.free_multi(self.live_fids.keys())
582 def read_responses(self):
583 "Read responses. This gets spun off as a thread."
584 while self.is_connected():
585 pkt, is_full = super(P9Client, self).read()
588 self.retire_all_tags()
591 self.logger.log(logging.WARNING, '%s: got short packet', self)
593 # We have one special case: if we're not yet connected
594 # with a version, we must unpack *as if* it's a plain
596 if self.have_version:
597 resp = self.proto.unpack(pkt)
599 resp = protocol.plain.unpack(pkt)
600 except protocol.SequenceError as err:
601 self.logger.log(logging.ERROR, '%s: bad response: %s',
604 resp = self.proto.unpack(pkt, noerror=True)
605 except protocol.SequenceError:
606 header = self.proto.unpack_header(pkt, noerror=True)
607 self.logger.log(logging.ERROR,
608 '%s: (not even raw-decodable)', self)
609 self.logger.log(logging.ERROR,
610 '%s: header decode produced %r',
613 self.logger.log(logging.ERROR,
614 '%s: raw decode produced %r',
616 # after this kind of problem, probably need to
617 # shut down, but let's leave that out for a bit
619 # NB: all protocol responses have a "tag",
620 # so resp['tag'] always exists.
621 self.logger.log(logging.DEBUG, "read_resp: tag %d resp %r", resp.tag, resp)
622 self.set_tag(resp.tag, resp)
624 def wait_for(self, tag):
626 Wait for a response to the given tag. Return the response,
627 releasing the tag. If self.timeout is not None, wait at most
628 that long (and release the tag even if there's no reply), else
631 If this returns None, either the tag was bad initially, or
632 a timeout occurred, or the connection got shut down.
634 self.logger.log(logging.DEBUG, "wait_for: tag %d", tag)
635 if self.timeout is None:
638 deadline = time.time() + self.timeout
641 # tagstate is True (waiting) or False (timedout) or
642 # a valid response, or None if we've reset the tag
643 # states (retire_all_tags, after connection drop).
644 resp = self.tagstate.get(tag, None)
646 # out of sync, exit loop
649 # still waiting for a response - wait some more
650 self.new_replies.wait(self.timeout)
651 if deadline and time.time() > deadline:
652 # Halt the waiting, but go around once more.
653 # Note we may have killed the tag by now though.
654 if tag in self.tagstate:
655 self.tagstate[tag] = False
657 # resp is either False (timeout) or a reply.
658 # If resp is False, change it to None; the tag
659 # is now dead until we get a reply (then we
660 # just toss the reply).
661 # Otherwise, we're done with the tag: free it.
662 # In either case, stop now.
666 self.tagalloc.free(tag)
667 del self.tagstate[tag]
671 def badresp(self, req, resp):
673 Complain that a response was not something expected.
677 raise TEError('{0}: {1}: timeout or EOF'.format(self, req))
678 if isinstance(resp, protocol.rrd.Rlerror):
679 raise RemoteError(self, req, None, 'Rlerror', resp.ecode)
680 if isinstance(resp, protocol.rrd.Rerror):
681 if resp.errnum is None:
682 raise RemoteError(self, req, resp.errstr, 'Rerror', None)
683 raise RemoteError(self, req, None, 'Rerror', resp.errnum)
684 raise LocalError('{0}: {1} got response {2!r}'.format(self, req, resp))
686 def supports(self, req_code):
688 Test self.proto.support(req_code) unless we've recorded that
689 while the protocol supports it, the client does not.
691 return req_code not in self._unsup and self.proto.supports(req_code)
693 def supports_all(self, *req_codes):
694 "basically just all(supports(...))"
695 return all(self.supports(code) for code in req_codes)
697 def unsupported(self, req_code):
699 Record an ENOTSUP (RemoteError was ENOTSUP) for a request.
700 Must be called from the op, this does not happen automatically.
701 (It's just an optimization.)
703 self._unsup[req_code] = True
705 def connect(self, server=None, port=None):
707 Connect to given server/port pair.
709 The server and port are remembered. If given as None,
710 the last remembered values are used. The initial
711 remembered values are from the creation of this client
714 New values are only remembered here on a *successful*
718 server = self._server
720 raise LocalError('connect: no server specified and no default')
725 self.name = None # wipe out previous name, if any
726 super(P9Client, self).connect(server, port)
727 maxio = self.get_recommended_maxio()
728 self.declare_connected(None, None, maxio)
729 self.proto = self.iproto # revert to initial protocol
730 self.have_version = False
731 self.rthread = threading.Thread(target=self.read_responses)
733 tag = self.get_tag(for_Tversion=True)
734 req = protocol.rrd.Tversion(tag=tag, msize=maxio,
735 version=self.get_monkey('version'))
736 super(P9Client, self).write(self.proto.pack_from(req))
737 resp = self.wait_for(tag)
738 if not isinstance(resp, protocol.rrd.Rversion):
740 if isinstance(resp, protocol.rrd.Rerror):
741 version = req.version or self.proto.get_version()
742 # for python3, we need to convert version to string
743 if not isinstance(version, str):
744 version = version.decode('utf-8', 'surrogateescape')
745 raise RemoteError(self, 'version ' + version,
746 resp.errstr, 'Rerror', None)
747 self.badresp('version', resp)
748 their_maxio = resp.msize
750 self.reduce_maxio(their_maxio)
751 except LocalError as err:
752 raise LocalError('{0}: sent maxio={1}, they tried {2}: '
753 '{3}'.format(self, maxio, their_maxio,
755 if resp.version != self.proto.get_version():
756 if not self.may_downgrade:
758 raise LocalError('{0}: they only support '
759 'version {1!r}'.format(self, resp.version))
760 # raises LocalError if the version is bad
761 # (should we wrap it with a connect-to-{0} msg?)
762 self.proto = self.proto.downgrade_to(resp.version)
763 self._server = server
765 self.have_version = True
767 def attach(self, afid, uname, aname, n_uname):
771 Currently we don't know how to do authentication,
772 but we'll pass any provided afid through.
775 afid = protocol.td.NOFID
781 n_uname = protocol.td.NONUNAME
783 fid = self.alloc_fid()
784 pkt = self.proto.Tattach(tag=tag, fid=fid, afid=afid,
785 uname=uname, aname=aname,
787 super(P9Client, self).write(pkt)
788 resp = self.wait_for(tag)
789 if not isinstance(resp, protocol.rrd.Rattach):
791 self.badresp('attach', resp)
792 # probably should check resp.qid
794 self.rootqid = resp.qid
795 self.setpath(fid, b'/')
798 "disconnect from server"
799 if self.rootfid is not None:
800 self.clunk(self.rootfid, ignore_error=True)
801 self.retire_all_tags()
802 self.retire_all_fids()
805 super(P9Client, self).shutdown()
810 def dupfid(self, fid):
812 Copy existing fid to a new fid.
815 newfid = self.alloc_fid()
816 pkt = self.proto.Twalk(tag=tag, fid=fid, newfid=newfid, nwname=0,
818 super(P9Client, self).write(pkt)
819 resp = self.wait_for(tag)
820 if not isinstance(resp, protocol.rrd.Rwalk):
821 self.retire_fid(newfid)
822 self.badresp('walk {0}'.format(self.getpathX(fid)), resp)
824 self.setpath(newfid, fid)
827 def lookup(self, fid, components):
829 Do Twalk. Caller must provide a starting fid, which should
830 be rootfid to look up from '/' - we do not do / vs . here.
831 Caller must also provide a component-ized path (on purpose,
832 so that caller can provide invalid components like '' or '/').
833 The components must be byte-strings as well, for the same
836 We do allocate the new fid ourselves here, though.
838 There's no logic here to split up long walks (yet?).
840 # these are too easy to screw up, so check
841 if self.rootfid is None:
842 raise LocalError('{0}: not attached'.format(self))
843 if (isinstance(components, (str, bytes) or
844 not all(isinstance(i, bytes) for i in components))):
845 raise LocalError('{0}: lookup: invalid '
846 'components {1!r}'.format(self, components))
848 newfid = self.alloc_fid()
849 startpath = self.getpath(fid)
850 pkt = self.proto.Twalk(tag=tag, fid=fid, newfid=newfid,
851 nwname=len(components), wname=components)
852 super(P9Client, self).write(pkt)
853 resp = self.wait_for(tag)
854 if not isinstance(resp, protocol.rrd.Rwalk):
855 self.retire_fid(newfid)
856 self.badresp('walk {0} in '
857 '{1}'.format(components, self.getpathX(fid)),
859 # Just because we got Rwalk does not mean we got ALL the
860 # way down the path. Raise OSError(ENOENT) if we're short.
861 if resp.nwqid > len(components):
862 # ??? this should be impossible. Local error? Remote error?
864 self.clunk(newfid, ignore_error=True)
865 raise LocalError('{0}: walk {1} in {2} returned {3} '
866 'items'.format(self, components,
867 self.getpathX(fid), resp.nwqid))
868 if resp.nwqid < len(components):
869 self.clunk(newfid, ignore_error=True)
870 # Looking up a/b/c and got just a/b, c is what's missing.
871 # Looking up a/b/c and got just a, b is what's missing.
872 missing = components[resp.nwqid]
873 within = _pathcat(startpath, b'/'.join(components[:resp.nwqid]))
874 raise OSError(errno.ENOENT,
875 '{0}: {1} in {2}'.format(os.strerror(errno.ENOENT),
877 self.setpath(newfid, _pathcat(startpath, b'/'.join(components)))
878 return newfid, resp.wqid
880 def lookup_last(self, fid, components):
882 Like lookup, but return only the last component's qid.
883 As a special case, if components is an empty list, we
886 rfid, wqid = self.lookup(fid, components)
888 return rfid, wqid[-1]
889 if fid == self.rootfid: # usually true, if we get here at all
890 return rfid, self.rootqid
892 pkt = self.proto.Tstat(tag=tag, fid=rfid)
893 super(P9Client, self).write(pkt)
894 resp = self.wait_for(tag)
895 if not isinstance(resp, protocol.rrd.Rstat):
896 self.badresp('stat {0}'.format(self.getpathX(fid)), resp)
897 statval = self.proto.unpack_wirestat(resp.data)
898 return rfid, statval.qid
900 def clunk(self, fid, ignore_error=False):
903 pkt = self.proto.Tclunk(tag=tag, fid=fid)
904 super(P9Client, self).write(pkt)
905 resp = self.wait_for(tag)
906 if not isinstance(resp, protocol.rrd.Rclunk):
909 self.badresp('clunk {0}'.format(self.getpathX(fid)), resp)
912 def remove(self, fid, ignore_error=False):
913 "issue remove (old style), which also clunks fid"
915 pkt = self.proto.Tremove(tag=tag, fid=fid)
916 super(P9Client, self).write(pkt)
917 resp = self.wait_for(tag)
918 if not isinstance(resp, protocol.rrd.Rremove):
920 # remove failed: still need to clunk the fid
921 self.clunk(fid, True)
923 self.badresp('remove {0}'.format(self.getpathX(fid)), resp)
926 def create(self, fid, name, perm, mode, filetype=None, extension=b''):
928 Issue create op (note that this may be mkdir, symlink, etc).
929 fid is the directory in which the create happens, and for
930 regular files, it becomes, on success, a fid referring to
931 the now-open file. perm is, e.g., 0644, 0755, etc.,
932 optionally with additional high bits. mode is a mode
933 byte (e.g., protocol.td.ORDWR, or OWRONLY|OTRUNC, etc.).
935 As a service to callers, we take two optional arguments
936 specifying the file type ('dir', 'symlink', 'device',
937 'fifo', or 'socket') and additional info if needed.
938 The additional info for a symlink is the target of the
939 link (a byte string), and the additional info for a device
940 is a byte string with "b <major> <minor>" or "c <major> <minor>".
942 Otherwise, callers can leave filetype=None and encode the bits
943 into the mode (caller must still provide extension if needed).
945 We do NOT check whether the extension matches extra DM bits,
946 or that there's only one DM bit set, or whatever, since this
950 if filetype is not None:
952 'dir': protocol.td.DMDIR,
953 'symlink': protocol.td.DMSYMLINK,
954 'device': protocol.td.DMDEVICE,
955 'fifo': protocol.td.DMNAMEDPIPE,
956 'socket': protocol.td.DMSOCKET,
958 pkt = self.proto.Tcreate(tag=tag, fid=fid, name=name,
959 perm=perm, mode=mode, extension=extension)
960 super(P9Client, self).write(pkt)
961 resp = self.wait_for(tag)
962 if not isinstance(resp, protocol.rrd.Rcreate):
963 self.badresp('create {0} in {1}'.format(name, self.getpathX(fid)),
965 if resp.qid.type == protocol.td.QTFILE:
966 # Creating a regular file opens the file,
967 # thus changing the fid's path.
968 self.setpath(fid, _pathcat(self.getpath(fid), name))
969 return resp.qid, resp.iounit
971 def open(self, fid, mode):
972 "use Topen to open file or directory fid (mode is 1 byte)"
974 pkt = self.proto.Topen(tag=tag, fid=fid, mode=mode)
975 super(P9Client, self).write(pkt)
976 resp = self.wait_for(tag)
977 if not isinstance(resp, protocol.rrd.Ropen):
978 self.badresp('open {0}'.format(self.getpathX(fid)), resp)
979 return resp.qid, resp.iounit
981 def lopen(self, fid, flags):
982 "use Tlopen to open file or directory fid (flags from L_O_*)"
984 pkt = self.proto.Tlopen(tag=tag, fid=fid, flags=flags)
985 super(P9Client, self).write(pkt)
986 resp = self.wait_for(tag)
987 if not isinstance(resp, protocol.rrd.Rlopen):
988 self.badresp('lopen {0}'.format(self.getpathX(fid)), resp)
989 return resp.qid, resp.iounit
991 def read(self, fid, offset, count):
992 "read (up to) count bytes from offset, given open fid"
994 pkt = self.proto.Tread(tag=tag, fid=fid, offset=offset, count=count)
995 super(P9Client, self).write(pkt)
996 resp = self.wait_for(tag)
997 if not isinstance(resp, protocol.rrd.Rread):
998 self.badresp('read {0} bytes at offset {1} in '
999 '{2}'.format(count, offset, self.getpathX(fid)),
1003 def write(self, fid, offset, data):
1004 "write (up to) count bytes to offset, given open fid"
1005 tag = self.get_tag()
1006 pkt = self.proto.Twrite(tag=tag, fid=fid, offset=offset,
1007 count=len(data), data=data)
1008 super(P9Client, self).write(pkt)
1009 resp = self.wait_for(tag)
1010 if not isinstance(resp, protocol.rrd.Rwrite):
1011 self.badresp('write {0} bytes at offset {1} in '
1012 '{2}'.format(len(data), offset, self.getpathX(fid)),
1017 # - pass an actual stat object, or
1018 # - pass in all the individual to-set items by keyword, or
1019 # - mix and match a bit: get an existing stat, then use
1020 # keywords to override fields.
1021 # We convert "None"s to the internal "do not change" values,
1022 # and for diagnostic purposes, can turn "do not change" back
1023 # to None at the end, too.
1024 def wstat(self, fid, statobj=None, **kwargs):
1026 statobj = protocol.td.stat()
1028 statobj = statobj._copy()
1029 # Fields in stat that you can't send as a wstat: the
1030 # type and qid are informative. Similarly, the
1031 # 'extension' is an input when creating a file but
1032 # read-only when stat-ing.
1034 # It's not clear what it means to set dev, but we'll leave
1035 # it in as an optional parameter here. fs/backend.c just
1036 # errors out on an attempt to change it.
1037 if self.proto == protocol.plain:
1038 forbid = ('type', 'qid', 'extension',
1039 'n_uid', 'n_gid', 'n_muid')
1041 forbid = ('type', 'qid', 'extension')
1044 'qid': protocol.td.qid(0, 0, 0),
1049 'length': 2**64 - 1,
1057 'n_muid': 2**32 - 1,
1059 for field in statobj._fields:
1062 raise ValueError('cannot wstat a stat.{0}'.format(field))
1063 statobj[field] = kwargs.pop(field)
1065 if field in forbid or statobj[field] is None:
1066 statobj[field] = nochange[field]
1068 raise TypeError('wstat() got an unexpected keyword argument '
1069 '{0!r}'.format(kwargs.popitem()))
1071 data = self.proto.pack_wirestat(statobj)
1072 tag = self.get_tag()
1073 pkt = self.proto.Twstat(tag=tag, fid=fid, data=data)
1074 super(P9Client, self).write(pkt)
1075 resp = self.wait_for(tag)
1076 if not isinstance(resp, protocol.rrd.Rwstat):
1077 # For error viewing, switch all the do-not-change
1078 # and can't-change fields to None.
1080 for field in statobj._fields:
1082 statobj[field] = None
1083 elif field in nochange and statobj[field] == nochange[field]:
1084 statobj[field] = None
1085 self.badresp('wstat {0}={1}'.format(self.getpathX(fid), statobj),
1087 # wstat worked - change path names if needed
1088 if statobj.name != b'':
1089 self.did_rename(fid, statobj.name)
1091 def readdir(self, fid, offset, count):
1092 "read (up to) count bytes of dir data from offset, given open fid"
1093 tag = self.get_tag()
1094 pkt = self.proto.Treaddir(tag=tag, fid=fid, offset=offset, count=count)
1095 super(P9Client, self).write(pkt)
1096 resp = self.wait_for(tag)
1097 if not isinstance(resp, protocol.rrd.Rreaddir):
1098 self.badresp('readdir {0} bytes at offset {1} in '
1099 '{2}'.format(count, offset, self.getpathX(fid)),
1103 def rename(self, fid, dfid, name):
1104 "invoke Trename: rename file <fid> to <dfid>/name"
1105 tag = self.get_tag()
1106 pkt = self.proto.Trename(tag=tag, fid=fid, dfid=dfid, name=name)
1107 super(P9Client, self).write(pkt)
1108 resp = self.wait_for(tag)
1109 if not isinstance(resp, protocol.rrd.Rrename):
1110 self.badresp('rename {0} to {2} in '
1111 '{1}'.format(self.getpathX(fid),
1112 self.getpathX(dfid), name),
1114 self.did_rename(fid, name, self.getpath(dfid))
1116 def renameat(self, olddirfid, oldname, newdirfid, newname):
1117 "invoke Trenameat: rename <olddirfid>/oldname to <newdirfid>/newname"
1118 tag = self.get_tag()
1119 pkt = self.proto.Trenameat(tag=tag,
1120 olddirfid=olddirfid, oldname=oldname,
1121 newdirfid=newdirfid, newname=newname)
1122 super(P9Client, self).write(pkt)
1123 resp = self.wait_for(tag)
1124 if not isinstance(resp, protocol.rrd.Rrenameat):
1125 self.badresp('rename {1} in {0} to {3} in '
1126 '{2}'.format(oldname, self.getpathX(olddirfid),
1127 newname, self.getpathX(newdirdfid)),
1129 # There's no renamed *fid*, just a renamed file! So no
1130 # call to self.did_rename().
1132 def unlinkat(self, dirfd, name, flags):
1133 "invoke Tunlinkat - flags should be 0 or protocol.td.AT_REMOVEDIR"
1134 tag = self.get_tag()
1135 pkt = self.proto.Tunlinkat(tag=tag, dirfd=dirfd,
1136 name=name, flags=flags)
1137 super(P9Client, self).write(pkt)
1138 resp = self.wait_for(tag)
1139 if not isinstance(resp, protocol.rrd.Runlinkat):
1140 self.badresp('unlinkat {0} in '
1141 '{1}'.format(name, self.getpathX(dirfd)), resp)
1143 def decode_stat_objects(self, bstring, noerror=False):
1145 Read on a directory returns an array of stat objects.
1146 Note that for .u these encode extra data.
1148 It's possible for this to produce a SequenceError, if
1149 the data are incorrect, unless you pass noerror=True.
1153 while offset < len(bstring):
1154 obj, offset = self.proto.unpack_wirestat(bstring, offset, noerror)
1158 def decode_readdir_dirents(self, bstring, noerror=False):
1160 Readdir on a directory returns an array of dirent objects.
1162 It's possible for this to produce a SequenceError, if
1163 the data are incorrect, unless you pass noerror=True.
1167 while offset < len(bstring):
1168 obj, offset = self.proto.unpack_dirent(bstring, offset, noerror)
1172 def lcreate(self, fid, name, lflags, mode, gid):
1173 "issue lcreate (.L)"
1174 tag = self.get_tag()
1175 pkt = self.proto.Tlcreate(tag=tag, fid=fid, name=name,
1176 flags=lflags, mode=mode, gid=gid)
1177 super(P9Client, self).write(pkt)
1178 resp = self.wait_for(tag)
1179 if not isinstance(resp, protocol.rrd.Rlcreate):
1180 self.badresp('create {0} in '
1181 '{1}'.format(name, self.getpathX(fid)), resp)
1182 # Creating a file opens the file,
1183 # thus changing the fid's path.
1184 self.setpath(fid, _pathcat(self.getpath(fid), name))
1185 return resp.qid, resp.iounit
1187 def mkdir(self, dfid, name, mode, gid):
1189 tag = self.get_tag()
1190 pkt = self.proto.Tmkdir(tag=tag, dfid=dfid, name=name,
1192 super(P9Client, self).write(pkt)
1193 resp = self.wait_for(tag)
1194 if not isinstance(resp, protocol.rrd.Rmkdir):
1195 self.badresp('mkdir {0} in '
1196 '{1}'.format(name, self.getpathX(dfid)), resp)
1199 # We don't call this getattr(), for the obvious reason.
1200 def Tgetattr(self, fid, request_mask=protocol.td.GETATTR_ALL):
1201 "issue Tgetattr.L - get what you ask for, or everything by default"
1202 tag = self.get_tag()
1203 pkt = self.proto.Tgetattr(tag=tag, fid=fid, request_mask=request_mask)
1204 super(P9Client, self).write(pkt)
1205 resp = self.wait_for(tag)
1206 if not isinstance(resp, protocol.rrd.Rgetattr):
1207 self.badresp('Tgetattr {0} of '
1208 '{1}'.format(request_mask, self.getpathX(fid)), resp)
1210 # Handle the simplest valid-bit tests:
1211 for name in ('mode', 'nlink', 'uid', 'gid', 'rdev',
1212 'size', 'blocks', 'gen', 'data_version'):
1213 bit = getattr(protocol.td, 'GETATTR_' + name.upper())
1214 if resp.valid & bit:
1215 attrs[name] = resp[name]
1216 # Handle the timestamps, which are timespec pairs
1217 for name in ('atime', 'mtime', 'ctime', 'btime'):
1218 bit = getattr(protocol.td, 'GETATTR_' + name.upper())
1219 if resp.valid & bit:
1220 attrs[name] = Timespec(sec=resp[name + '_sec'],
1221 nsec=resp[name + '_nsec'])
1222 # There is no control bit for blksize; qemu and Linux always
1224 attrs.blksize = resp.blksize
1225 # Handle ino, which comes out of qid.path
1226 if resp.valid & protocol.td.GETATTR_INO:
1227 attrs.ino = resp.qid.path
1230 # We don't call this setattr(), for the obvious reason.
1231 # See wstat for usage. Note that time fields can be set
1232 # with either second or nanosecond resolutions, and some
1233 # can be set without supplying an actual timestamp, so
1234 # this is all pretty ad-hoc.
1236 # There's also one keyword-only argument, ctime=<anything>,
1237 # which means "set SETATTR_CTIME". This has the same effect
1238 # as supplying valid=protocol.td.SETATTR_CTIME.
1239 def Tsetattr(self, fid, valid=0, attrs=None, **kwargs):
1243 attrs = attrs._copy()
1245 # Start with an empty (all-zero) Tsetattr instance. We
1246 # don't really need to zero out tag and fid, but it doesn't
1247 # hurt. Note that if caller says, e.g., valid=SETATTR_SIZE
1248 # but does not supply an incoming size (via "attrs" or a size=
1249 # argument), we'll ask to set that field to 0.
1250 attrobj = protocol.rrd.Tsetattr()
1251 for field in attrobj._fields:
1254 # In this case, forbid means "only as kwargs": these values
1255 # in an incoming attrs object are merely ignored.
1256 forbid = ('ino', 'nlink', 'rdev', 'blksize', 'blocks', 'btime',
1257 'gen', 'data_version')
1258 for field in attrs._fields:
1261 raise ValueError('cannot Tsetattr {0}'.format(field))
1262 attrs[field] = kwargs.pop(field)
1263 elif attrs[field] is None:
1265 # OK, we're setting this attribute. Many are just
1266 # numeric - if that's the case, we're good, set the
1267 # field and the appropriate bit.
1268 bitname = 'SETATTR_' + field.upper()
1269 bit = getattr(protocol.td, bitname)
1270 if field in ('mode', 'uid', 'gid', 'size'):
1272 attrobj[field] = attrs[field]
1274 # Timestamps are special: The value may be given as
1275 # an integer (seconds), or as a float (we convert to
1276 # (we convert to sec+nsec), or as a timespec (sec+nsec).
1277 # If specified as 0, we mean "we are not providing the
1278 # actual time, use the server's time."
1280 # The ctime field's value, if any, is *ignored*.
1281 if field in ('atime', 'mtime'):
1282 value = attrs[field]
1283 if hasattr(value, '__len__'):
1285 raise ValueError('invalid {0}={1!r}'.format(field,
1291 if isinstance(sec, float):
1292 nsec, sec = math.modf(sec)
1293 nsec = int(round(nsec * 1000000000))
1297 attrobj[field + '_sec'] = sec
1298 attrobj[field + '_nsec'] = nsec
1299 if sec != 0 or nsec != 0:
1300 # Add SETATTR_ATIME_SET or SETATTR_MTIME_SET
1301 # as appropriate, to tell the server to *this
1302 # specific* time, instead of just "server now".
1303 bit = getattr(protocol.td, bitname + '_SET')
1305 if 'ctime' in kwargs:
1307 valid |= protocol.td.SETATTR_CTIME
1309 raise TypeError('Tsetattr() got an unexpected keyword argument '
1310 '{0!r}'.format(kwargs.popitem()))
1312 tag = self.get_tag()
1313 attrobj.valid = valid
1316 pkt = self.proto.pack(attrobj)
1317 super(P9Client, self).write(pkt)
1318 resp = self.wait_for(tag)
1319 if not isinstance(resp, protocol.rrd.Rsetattr):
1320 self.badresp('Tsetattr {0} {1} of '
1321 '{2}'.format(valid, attrs, self.getpathX(fid)), resp)
1323 def xattrwalk(self, fid, name=None):
1324 "walk one name or all names: caller should read() the returned fid"
1325 tag = self.get_tag()
1326 newfid = self.alloc_fid()
1327 pkt = self.proto.Txattrwalk(tag=tag, fid=fid, newfid=newfid,
1329 super(P9Client, self).write(pkt)
1330 resp = self.wait_for(tag)
1331 if not isinstance(resp, protocol.rrd.Rxattrwalk):
1332 self.retire_fid(newfid)
1333 self.badresp('Txattrwalk {0} of '
1334 '{1}'.format(name, self.getpathX(fid)), resp)
1336 self.setpath(newfid, 'xattr:' + name)
1338 self.setpath(newfid, 'xattr')
1339 return newfid, resp.size
1341 def _pathsplit(self, path, startdir, allow_empty=False):
1342 "common code for uxlookup and uxopen"
1343 if self.rootfid is None:
1344 raise LocalError('{0}: not attached'.format(self))
1345 if path.startswith(b'/') or startdir is None:
1346 startdir = self.rootfid
1347 components = [i for i in path.split(b'/') if i != b'']
1348 if len(components) == 0 and not allow_empty:
1349 raise LocalError('{0}: {1!r}: empty path'.format(self, path))
1350 return components, startdir
1352 def uxlookup(self, path, startdir=None):
1354 Unix-style lookup. That is, lookup('/foo/bar') or
1355 lookup('foo/bar'). If startdir is not None and the
1356 path does not start with '/' we look up from there.
1358 components, startdir = self._pathsplit(path, startdir, allow_empty=True)
1359 return self.lookup_last(startdir, components)
1361 def uxopen(self, path, oflags=0, perm=None, gid=None,
1362 startdir=None, filetype=None):
1364 Unix-style open()-with-option-to-create, or mkdir().
1365 oflags is 0/1/2 with optional os.O_CREAT, perm defaults
1366 to 0o666 (files) or 0o777 (directories). If we use
1367 a Linux create or mkdir op, we will need a gid, but it's
1368 not required if you are opening an existing file.
1370 Adds a final boolean value for "did we actually create".
1371 Raises OSError if you ask for a directory but it's a file,
1372 or vice versa. (??? reconsider this later)
1374 Note that this does not handle other file types, only
1378 'dir': protocol.td.QTDIR,
1379 None: protocol.td.QTFILE,
1381 omode_byte = oflags & 3 # cheating
1382 # allow looking up /, but not creating /
1383 allow_empty = (oflags & os.O_CREAT) == 0
1384 components, startdir = self._pathsplit(path, startdir,
1385 allow_empty=allow_empty)
1386 if not (oflags & os.O_CREAT):
1387 # Not creating, i.e., just look up and open existing file/dir.
1388 fid, qid = self.lookup_last(startdir, components)
1389 # If we got this far, use Topen on the fid; we did not
1391 return self._uxopen2(path, needtype, fid, qid, omode_byte, False)
1393 # Only used if using dot-L, but make sure it's always provided
1394 # since this is generic.
1396 raise ValueError('gid is required when creating file or dir')
1398 if len(components) > 1:
1399 # Look up all but last component; this part must succeed.
1400 fid, _ = self.lookup(startdir, components[:-1])
1402 # Now proceed with the final component, using fid
1403 # as the start dir. Remember to clunk it!
1405 clunk_startdir = True
1406 components = components[-1:]
1408 # Use startdir as the start dir, and get a new fid.
1409 # Do not clunk startdir!
1410 clunk_startdir = False
1411 fid = self.alloc_fid()
1413 # Now look up the (single) component. If this fails,
1414 # assume the file or directory needs to be created.
1415 tag = self.get_tag()
1416 pkt = self.proto.Twalk(tag=tag, fid=startdir, newfid=fid,
1417 nwname=1, wname=components)
1418 super(P9Client, self).write(pkt)
1419 resp = self.wait_for(tag)
1420 if isinstance(resp, protocol.rrd.Rwalk):
1422 self.clunk(startdir, ignore_error=True)
1423 # fid successfully walked to refer to final component.
1424 # Just need to actually open the file.
1425 self.setpath(fid, _pathcat(self.getpath(startdir), components[0]))
1427 return self._uxopen2(needtype, fid, qid, omode_byte, False)
1429 # Walk failed. If we allocated a fid, retire it. Then set
1430 # up a fid that points to the parent directory in which to
1431 # create the file or directory. Note that if we're creating
1432 # a file, this fid will get changed so that it points to the
1433 # file instead of the directory, but if we're creating a
1434 # directory, it will be unchanged.
1436 self.retire_fid(fid)
1437 fid = self.dupfid(startdir)
1440 qid, iounit = self._uxcreate(filetype, fid, components[0],
1441 oflags, omode_byte, perm, gid)
1443 # Success. If we created an ordinary file, we have everything
1444 # now as create alters the incoming (dir) fid to open the file.
1445 # Otherwise (mkdir), we need to open the file, as with
1446 # a successful lookup.
1448 # Note that qid type should match "needtype".
1449 if filetype != 'dir':
1450 if qid.type == needtype:
1451 return fid, qid, iounit, True
1452 self.clunk(fid, ignore_error=True)
1453 raise OSError(_wrong_file_type(qid),
1454 '{0}: server told to create {1} but '
1455 'created {2} instead'.format(path,
1459 # Success: created dir; but now need to walk to and open it.
1460 fid = self.alloc_fid()
1461 tag = self.get_tag()
1462 pkt = self.proto.Twalk(tag=tag, fid=startdir, newfid=fid,
1463 nwname=1, wname=components)
1464 super(P9Client, self).write(pkt)
1465 resp = self.wait_for(tag)
1466 if not isinstance(resp, protocol.rrd.Rwalk):
1467 self.clunk(fid, ignore_error=True)
1468 raise OSError(errno.ENOENT,
1469 '{0}: server made dir but then failed to '
1470 'find it again'.format(path))
1471 self.setpath(fid, _pathcat(self.getpath(fid), components[0]))
1472 return self._uxopen2(needtype, fid, qid, omode_byte, True)
1474 # Regardless of success/failure/exception, make sure
1475 # we clunk startdir if needed.
1477 self.clunk(startdir, ignore_error=True)
1479 def _uxcreate(self, filetype, fid, name, oflags, omode_byte, perm, gid):
1481 Helper for creating dir-or-file. The fid argument is the
1482 parent directory on input, but will point to the file (if
1483 we're creating a file) on return. oflags only applies if
1484 we're creating a file (even then we use omode_byte if we
1485 are using the plan9 create op).
1487 # Try to create or mkdir as appropriate.
1488 if self.supports_all(protocol.td.Tlcreate, protocol.td.Tmkdir):
1489 # Use Linux style create / mkdir.
1490 if filetype == 'dir':
1493 return self.mkdir(startdir, name, perm, gid), None
1496 lflags = flags_to_linux_flags(oflags)
1497 return self.lcreate(fid, name, lflags, perm, gid)
1499 if filetype == 'dir':
1501 perm = protocol.td.DMDIR | 0o777
1503 perm |= protocol.td.DMDIR
1507 return self.create(fid, name, perm, omode_byte)
1509 def _uxopen2(self, needtype, fid, qid, omode_byte, didcreate):
1510 "common code for finishing up uxopen"
1511 if qid.type != needtype:
1512 self.clunk(fid, ignore_error=True)
1513 raise OSError(_wrong_file_type(qid),
1514 '{0}: is {1}, expected '
1515 '{2}'.format(path, qt2n(qid.type), qt2n(needtype)))
1516 qid, iounit = self.open(fid, omode_byte)
1517 # ? should we re-check qid? it should not have changed
1518 return fid, qid, iounit, didcreate
1520 def uxmkdir(self, path, perm, gid, startdir=None):
1524 The gid is only applied if we are using .L style mkdir.
1526 components, startdir = self._pathsplit(path, startdir)
1528 if len(components) > 1:
1529 fid, _ = self.lookup(startdir, components[:-1])
1532 components = components[-1:]
1534 if self.supports(protocol.td.Tmkdir):
1535 qid = self.mkdir(startdir, components[0], perm, gid)
1537 qid, _ = self.create(startdir, components[0],
1538 protocol.td.DMDIR | perm,
1540 # Should we chown/chgrp the dir?
1543 self.clunk(clunkme, ignore_error=True)
1546 def uxreaddir(self, path, startdir=None, no_dotl=False):
1548 Read a directory to get a list of names (which may or may not
1549 include '.' and '..').
1551 If no_dotl is True (or anything non-false-y), this uses the
1552 plain or .u readdir format, otherwise it uses dot-L readdir
1555 components, startdir = self._pathsplit(path, startdir, allow_empty=True)
1556 fid, qid = self.lookup_last(startdir, components)
1558 if qid.type != protocol.td.QTDIR:
1559 raise OSError(errno.ENOTDIR,
1560 '{0}: {1}'.format(self.getpathX(fid),
1561 os.strerror(errno.ENOTDIR)))
1562 # We need both Tlopen and Treaddir to use Treaddir.
1563 if not self.supports_all(protocol.td.Tlopen, protocol.td.Treaddir):
1566 statvals = self.uxreaddir_stat_fid(fid)
1567 return [i.name for i in statvals]
1569 dirents = self.uxreaddir_dotl_fid(fid)
1570 return [dirent.name for dirent in dirents]
1572 self.clunk(fid, ignore_error=True)
1574 def uxreaddir_stat(self, path, startdir=None):
1576 Use directory read to get plan9 style stat data (plain or .u readdir).
1578 Note that this gets a fid, then opens it, reads, then clunks
1579 the fid. If you already have a fid, you may want to use
1580 uxreaddir_stat_fid (but note that this opens, yet does not
1583 We return the qid plus the list of the contents. If the
1584 target is not a directory, the qid will not have type QTDIR
1585 and the contents list will be empty.
1587 Raises OSError if this is applied to a non-directory.
1589 components, startdir = self._pathsplit(path, startdir)
1590 fid, qid = self.lookup_last(startdir, components)
1592 if qid.type != protocol.td.QTDIR:
1593 raise OSError(errno.ENOTDIR,
1594 '{0}: {1}'.format(self.getpathX(fid),
1595 os.strerror(errno.ENOTDIR)))
1596 statvals = self.ux_readdir_stat_fid(fid)
1597 return qid, statvals
1599 self.clunk(fid, ignore_error=True)
1601 def uxreaddir_stat_fid(self, fid):
1603 Implement readdir loop that extracts stat values.
1604 This opens, but does not clunk, the given fid.
1606 Unlike uxreaddir_stat(), if this is applied to a file,
1607 rather than a directory, it just returns no entries.
1610 qid, iounit = self.open(fid, protocol.td.OREAD)
1611 # ?? is a zero iounit allowed? if so, what do we use here?
1612 if qid.type == protocol.td.QTDIR:
1614 iounit = 512 # probably good enough
1617 bstring = self.read(fid, offset, iounit)
1620 statvals.extend(self.decode_stat_objects(bstring))
1621 offset += len(bstring)
1624 def uxreaddir_dotl_fid(self, fid):
1626 Implement readdir loop that uses dot-L style dirents.
1627 This opens, but does not clunk, the given fid.
1629 If applied to a file, the lopen should fail, because of the
1633 qid, iounit = self.lopen(fid, protocol.td.OREAD |
1634 protocol.td.L_O_DIRECTORY)
1635 # ?? is a zero iounit allowed? if so, what do we use here?
1636 # but, we want a minimum of over 256 anyway, let's go for 512
1641 bstring = self.readdir(fid, offset, iounit)
1644 ents = self.decode_readdir_dirents(bstring)
1647 dirents.extend(ents)
1648 offset = ents[-1].offset
1651 def uxremove(self, path, startdir=None, filetype=None,
1652 force=False, recurse=False):
1654 Implement rm / rmdir, with optional -rf.
1655 if filetype is None, remove dir or file. If 'dir' or 'file'
1656 remove only if it's one of those. If force is set, ignore
1657 failures to remove. If recurse is True, remove contents of
1658 directories (recursively).
1660 File type mismatches (when filetype!=None) raise OSError (?).
1662 components, startdir = self._pathsplit(path, startdir, allow_empty=True)
1663 # Look up all components. If
1664 # we get an error we'll just assume the file does not
1665 # exist (is this good?).
1667 fid, qid = self.lookup_last(startdir, components)
1670 if qid.type == protocol.td.QTDIR:
1671 # it's a directory, remove only if allowed.
1672 # Note that we must check for "rm -r /" (len(components)==0).
1673 if filetype == 'file':
1674 self.clunk(fid, ignore_error=True)
1675 raise OSError(_wrong_file_type(qid),
1676 '{0}: is dir, expected file'.format(path))
1677 isroot = len(components) == 0
1678 closer = self.clunk if isroot else self.remove
1680 # NB: _rm_recursive does not clunk fid
1681 self._rm_recursive(fid, filetype, force)
1682 # This will fail if the directory is non-empty, unless of
1683 # course we tell it to ignore error.
1684 closer(fid, ignore_error=force)
1686 # Not a directory, call it a file (even if socket or fifo etc).
1687 if filetype == 'dir':
1688 self.clunk(fid, ignore_error=True)
1689 raise OSError(_wrong_file_type(qid),
1690 '{0}: is file, expected dir'.format(path))
1691 self.remove(fid, ignore_error=force)
1693 def _rm_file_by_dfid(self, dfid, name, force=False):
1695 Remove a file whose name is <name> (no path, just a component
1696 name) whose parent directory is <dfid>. We may assume that the
1697 file really is a file (or a socket, or fifo, or some such, but
1698 definitely not a directory).
1700 If force is set, ignore failures.
1702 # If we have unlinkat, that's the fast way. But it may
1703 # return an ENOTSUP error. If it does we shouldn't bother
1705 if self.supports(protocol.td.Tunlinkat):
1707 self.unlinkat(dfid, name, 0)
1709 except RemoteError as err:
1710 if not err.is_ENOTSUP():
1712 self.unsupported(protocol.td.Tunlinkat)
1713 # fall through to remove() op
1714 # Fall back to lookup + remove.
1716 fid, qid = self.lookup_last(dfid, [name])
1718 # If this has an errno we could tell ENOENT from EPERM,
1719 # and actually raise an error for the latter. Should we?
1721 self.remove(fid, ignore_error=force)
1723 def _rm_recursive(self, dfid, filetype, force):
1725 Recursively remove a directory. filetype is probably None,
1726 but if it's 'dir' we fail if the directory contains non-dir
1729 If force is set, ignore failures.
1731 Although we open dfid (via the readdir.*_fid calls) we
1732 do not clunk it here; that's the caller's job.
1734 # first, remove contents
1735 if self.supports_all(protocol.td.Tlopen, protocol.td.Treaddir):
1736 for entry in self.uxreaddir_dotl_fid(dfid):
1737 if entry.name in (b'.', b'..'):
1739 fid, qid = self.lookup(dfid, [entry.name])
1741 attrs = self.Tgetattr(fid, protocol.td.GETATTR_MODE)
1742 if stat.S_ISDIR(attrs.mode):
1743 self.uxremove(entry.name, dfid, filetype, force, True)
1749 self.clunk(fid, ignore_error=True)
1751 for statobj in self.uxreaddir_stat_fid(dfid):
1754 if name in (b'.', b'..'):
1756 if statobj.qid.type == protocol.td.QTDIR:
1757 self.uxremove(name, dfid, filetype, force, True)
1759 self._rm_file_by_dfid(dfid, name, force)
1761 def _wrong_file_type(qid):
1762 "return EISDIR or ENOTDIR for passing to OSError"
1763 if qid.type == protocol.td.QTDIR:
1765 return errno.ENOTDIR
1767 def flags_to_linux_flags(flags):
1769 Convert OS flags (O_CREAT etc) to Linux flags (protocol.td.L_O_CREAT etc).
1772 os.O_CREAT: protocol.td.L_O_CREAT,
1773 os.O_EXCL: protocol.td.L_O_EXCL,
1774 os.O_NOCTTY: protocol.td.L_O_NOCTTY,
1775 os.O_TRUNC: protocol.td.L_O_TRUNC,
1776 os.O_APPEND: protocol.td.L_O_APPEND,
1777 os.O_DIRECTORY: protocol.td.L_O_DIRECTORY,
1780 result = flags & os.O_RDWR
1782 for key, value in flagmap.iteritems():
1787 raise ValueError('untranslated bits 0x{0:x} in os flags'.format(flags))