Linux-NFS Archive mirror
 help / color / mirror / Atom feed
From: Jianhong Yin <jiyin@redhat.com>
To: linux-nfs@vger.kernel.org
Cc: calum.mackay@oracle.com, jlayton@kernel.org, bcodding@redhat.com,
	 smayhew@redhat.com
Subject: Re: [PATCH v3 1/4] pynfs: nfs4.1/nfs4server.py fixes
Date: Sat, 28 Mar 2026 13:54:02 +0800	[thread overview]
Message-ID: <CAFAKQ62GTXEs6XNyHShp56CSLh23uXzavGRTMk5M9VMSs==g9g@mail.gmail.com> (raw)
In-Reply-To: <CAFAKQ6091kgRKp9xKmLsyRYeY33X4B-2PKQi63DgeCbNTB=JMA@mail.gmail.com>

test log:
'''
[root@rhel-9-latest ~]# bash test.sh
tmux-3.2a-5.el9.x86_64
no server running on /tmp/tmux-0/default
mount.nfs: timeout set for Sat Mar 28 01:43:35 2026
mount.nfs: trying text-based options
'vers=4.2,addr=127.0.0.1,clientaddr=127.0.0.1'
127.0.0.1:/ on /mnt/nfsmp type nfs4
(rw,relatime,vers=4.2,rsize=4096,wsize=4096,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=127.0.0.1,local_lock=none,addr=127.0.0.1)
nfsv4: bm0=0xe0180fff,bm1=0x80000a,bm2=0x0,acl=0x0,sessions,pnfs=not
configured,lease_time=60,lease_expired=0
nfsv4: bm0=0xe0180fff,bm1=0x4080000a,bm2=0x0,acl=0x0,sessions,pnfs=LAYOUT_NFSV4_1_FILES,lease_time=60,lease_expired=0
nfsv4: bm0=0x20180fff,bm1=0x80000a,bm2=0x0,acl=0x0,sessions,pnfs=not
configured,lease_time=60,lease_expired=0
88888
99999
[root@rhel-9-latest ~]# cat test.sh
systemctl stop nfs-server; rpm -q tmux || yum install -y tmux
nfsmp=/mnt/nfsmp; mkdir -p $nfsmp
umount -a -t nfs,nfs4
tmux kill-server
cd /usr/src/pynfs/nfs4.1  # nfs4.1 includes feature pnfs
echo "${IP:-127.0.0.1}:12345/pynfs_mds" >dataservers.conf
\cp -f sample_code/ds_exports.py .
tmux new -s ds -d "./nfs4server.py -r -v --is_ds --exports=ds_exports
--port=12345" \; pipe-pane "exec cat >/tmp/ds.log"
tmux new -s mds -d "./nfs4server.py -r -v --use_files
--dataservers=dataservers.conf" \; pipe-pane "exec cat >/tmp/mds.log"
mount -vvv -o vers=4.2 ${IP:-127.0.0.1}:/ ${nfsmp}
mount -t nfs4
echo 88888 >${nfsmp}/files/testfile
echo 99999 >${nfsmp}/a/testfile
grep pnfs= /proc/self/mountstats
cat ${nfsmp}/files/testfile
cat ${nfsmp}/a/testfile
'''


On Sat, Mar 28, 2026 at 1:49 PM Jianhong Yin <jiyin@redhat.com> wrote:
>
> test log:
> '''
> [root@rhel-9-latest ~]# bash test.sh
> tmux-3.2a-5.el9.x86_64
> no server running on /tmp/tmux-0/default
> mount.nfs: timeout set for Sat Mar 28 01:43:35 2026
> mount.nfs: trying text-based options 'vers=4.2,addr=127.0.0.1,clientaddr=127.0.0.1'
> 127.0.0.1:/ on /mnt/nfsmp type nfs4 (rw,relatime,vers=4.2,rsize=4096,wsize=4096,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=127.0.0.1,local_lock=none,addr=127.0.0.1)
> nfsv4: bm0=0xe0180fff,bm1=0x80000a,bm2=0x0,acl=0x0,sessions,pnfs=not configured,lease_time=60,lease_expired=0
> nfsv4: bm0=0xe0180fff,bm1=0x4080000a,bm2=0x0,acl=0x0,sessions,pnfs=LAYOUT_NFSV4_1_FILES,lease_time=60,lease_expired=0
> nfsv4: bm0=0x20180fff,bm1=0x80000a,bm2=0x0,acl=0x0,sessions,pnfs=not configured,lease_time=60,lease_expired=0
> 88888
> 99999
> [root@rhel-9-latest ~]# cat test.sh
> systemctl stop nfs-server; rpm -q tmux || yum install -y tmux
> nfsmp=/mnt/nfsmp; mkdir -p $nfsmp
> umount -a -t nfs,nfs4
> tmux kill-server
> cd /usr/src/pynfs/nfs4.1  # nfs4.1 includes feature pnfs
> echo "${IP:-127.0.0.1}:12345/pynfs_mds" >dataservers.conf
> \cp -f sample_code/ds_exports.py .
> tmux new -s ds -d "./nfs4server.py -r -v --is_ds --exports=ds_exports --port=12345" \; pipe-pane "exec cat >/tmp/ds.log"
> tmux new -s mds -d "./nfs4server.py -r -v --use_files --dataservers=dataservers.conf" \; pipe-pane "exec cat >/tmp/mds.log"
> mount -vvv -o vers=4.2 ${IP:-127.0.0.1}:/ ${nfsmp}
> mount -t nfs4
> echo 88888 >${nfsmp}/files/testfile
> echo 99999 >${nfsmp}/a/testfile
> grep pnfs= /proc/self/mountstats
> cat ${nfsmp}/files/testfile
> cat ${nfsmp}/a/testfile
> '''
>
> On Sat, Mar 28, 2026 at 1:43 PM Jianhong Yin <jiyin@redhat.com> wrote:
>>
>> From: Scott Mayhew <smayhew@redhat.com>
>>
>> Most of the strings in pynfs are not str but rather "bytes" objects.
>> - fixed default string type
>>   - "string" -> b"string"
>>   - .encode("utf-8")
>>   - StringIO -> BytesIO
>>   - etc
>> - nfsstat4 -> const4.nfsstat4
>> - python3: fixed syntax of Exception process code
>> - python3: d.itervalues() -> d.values()
>>
>> Signed-off-by: Scott Mayhew <smayhew@redhat.com>
>> ---
>>  nfs4.1/config.py                 | 20 ++++----
>>  nfs4.1/dataserver.py             | 32 ++++++-------
>>  nfs4.1/fs.py                     | 52 +++++++++++---------
>>  nfs4.1/nfs4client.py             |  4 +-
>>  nfs4.1/nfs4server.py             | 81 +++++++++++++++++---------------
>>  nfs4.1/nfs4state.py              | 46 +++++++++---------
>>  nfs4.1/sample_code/ds_exports.py |  2 +-
>>  nfs4.1/server_exports.py         | 12 ++---
>>  8 files changed, 129 insertions(+), 120 deletions(-)
>>
>> diff --git a/nfs4.1/config.py b/nfs4.1/config.py
>> index 3777e31..8691e1c 100644
>> --- a/nfs4.1/config.py
>> +++ b/nfs4.1/config.py
>> @@ -117,8 +117,7 @@ class MetaConfig(type):
>>              setattr(cls, attr.name, property(make_get(i), make_set(i),
>>                                               None, attr.comment))
>>
>> -class ServerConfig(object):
>> -    __metaclass__ = MetaConfig
>> +class ServerConfig(metaclass=MetaConfig):
>>      attrs =  [ConfigLine("allow_null_data", False,
>>                           "Server allows NULL calls to contain data"),
>>                ConfigLine("tag_info", True,
>> @@ -131,17 +130,16 @@ class ServerConfig(object):
>>
>>      def __init__(self):
>>          self.minor_id = os.getpid()
>> -        self.major_id = "PyNFSv4.1"
>> +        self.major_id = b"PyNFSv4.1"
>>          self._owner = server_owner4(self.minor_id, self.major_id)
>> -        self.scope = "Default_Scope"
>> -        self.impl_domain = "citi.umich.edu"
>> -        self.impl_name = "pynfs X.X"
>> +        self.scope = b"Default_Scope"
>> +        self.impl_domain = b"citi.umich.edu"
>> +        self.impl_name = b"pynfs X.X"
>>          self.impl_date = 1172852767 # int(time.time())
>>          self.impl_id = nfs_impl_id4(self.impl_domain, self.impl_name,
>>                                   nfs4lib.get_nfstime(self.impl_date))
>>
>> -class ServerPerClientConfig(object):
>> -    __metaclass__ = MetaConfig
>> +class ServerPerClientConfig(metaclass=MetaConfig):
>>      attrs = [ConfigLine("maxrequestsize", 16384,
>>                          "Maximum request size the server will accept"),
>>               ConfigLine("maxresponsesize", 16384,
>> @@ -175,15 +173,13 @@ _invalid_ops = [
>>      OP_RELEASE_LOCKOWNER, OP_ILLEGAL,
>>      ]
>>
>> -class OpsConfigServer(object):
>> -    __metaclass__ = MetaConfig
>> +class OpsConfigServer(metaclass=MetaConfig):
>>      value = ['ERROR', 0, 0] # Note must have value == _opline(value)
>>      attrs = (lambda value=value: [ConfigLine(name.lower()[3:], value, "Generic comment", _opline)
>>                                    for name in nfs_opnum4.values()])()
>>
>>
>> -class Actions(object):
>> -    __metaclass__ = MetaConfig
>> +class Actions(metaclass=MetaConfig):
>>      attrs = [ConfigLine("reboot", 0,
>>                          "Any write here will simulate a server reboot",
>>                          _action),
>> diff --git a/nfs4.1/dataserver.py b/nfs4.1/dataserver.py
>> index f76ca5a..4d3a305 100644
>> --- a/nfs4.1/dataserver.py
>> +++ b/nfs4.1/dataserver.py
>> @@ -59,27 +59,27 @@ class DataServer(object):
>>
>>      def get_netaddr4(self):
>>          # STUB server multipathing not supported yet
>> -        uaddr = '.'.join([self.server,
>> -                          str(self.port >> 8),
>> -                          str(self.port & 0xff)])
>> -        return type4.netaddr4(self.proto, uaddr)
>> +        uaddr = b'.'.join([self.server.encode("utf-8"),
>> +                          str(self.port >> 8).encode("utf-8"),
>> +                          str(self.port & 0xff).encode("utf-8")])
>> +        return type4.netaddr4(self.proto.encode("utf-8"), uaddr)
>>
>>      def get_multipath_netaddr4s(self):
>>          netaddr4s = []
>>          for addr in self.multipath_servers:
>>              server, port = addr
>> -            uaddr = '.'.join([server,
>> -                            str(port >> 8),
>> -                            str(port & 0xff)])
>> -            proto = "tcp"
>> +            uaddr = b'.'.join([server.encode("utf-8"),
>> +                            str(port >> 8).encode("utf-8"),
>> +                            str(port & 0xff).encode("utf-8")])
>> +            proto = b"tcp"
>>              if server.find(':') >= 0:
>> -                proto = "tcp6"
>> +                proto = b"tcp6"
>>
>>              netaddr4s.append(type4.netaddr4(proto, uaddr))
>>          return netaddr4s
>>
>>      def fh_to_name(self, mds_fh):
>> -        return hashlib.sha1("%r" % mds_fh).hexdigest()
>> +        return hashlib.sha1(mds_fh).hexdigest()
>>
>>      def connect(self):
>>          raise NotImplemented
>> @@ -111,7 +111,7 @@ class DataServer41(DataServer):
>>                  self.reset()
>>              else:
>>                  log.error("Unhandled status %s from DS %s" %
>> -                          (nfsstat4[res.status], self.server))
>> +                          (const4.nfsstat4[res.status], self.server))
>>                  raise Exception("Dataserver communication error")
>>
>>      def connect(self):
>> @@ -122,7 +122,7 @@ class DataServer41(DataServer):
>>                                          summary=self.summary)
>>          self.c1.set_cred(self.cred1)
>>          self.c1.null()
>> -        c = self.c1.new_client("DS.init_%s" % self.server)
>> +        c = self.c1.new_client(b"DS.init_%s" % self.server.encode("utf-8"))
>>          # This is a hack to ensure MDS/DS communication path is at least
>>          # as wide as the client/MDS channel (at least for linux client)
>>          fore_attrs = type4.channel_attrs4(0, 16384, 16384, 2868, 8, 8, [])
>> @@ -154,11 +154,11 @@ class DataServer41(DataServer):
>>          access = const4.OPEN4_SHARE_ACCESS_BOTH
>>          deny = const4.OPEN4_SHARE_DENY_NONE
>>          attrs = {const4.FATTR4_MODE: 0o777}
>> -        owner = "mds"
>> +        owner = b"mds"
>>          mode = const4.GUARDED4
>>          verifier = self.sess.c.verifier
>>          openflag = type4.openflag4(const4.OPEN4_CREATE, type4.createhow4(mode, attrs, verifier))
>> -        name = self.fh_to_name(mds_fh)
>> +        name = self.fh_to_name(mds_fh).encode("utf-8")
>>          while True:
>>              if mds_fh in self.filehandles:
>>                  return
>> @@ -338,14 +338,14 @@ class DSDevice(object):
>>                  server_list = server_list[:-1]
>>                  try:
>>                      log.info("Adding dataserver ip:%s port:%s path:%s" %
>> -                             (server, port, '/'.join(path)))
>> +                             (server, port, b'/'.join(path)))
>>                      ds = DataServer41(server, port, path, mdsds=self.mdsds,
>>                                      multipath_servers=server_list,
>>                                      summary=server_obj.summary)
>>                      self.list.append(ds)
>>                  except socket.error:
>>                      log.critical("cannot access %s:%i/%s" %
>> -                                 (server, port, '/'.join(path)))
>> +                                 (server, port, b'/'.join(path)))
>>                      sys.exit(1)
>>          self.active = 1
>>          self.address_body = self._get_address_body()
>> diff --git a/nfs4.1/fs.py b/nfs4.1/fs.py
>> index 7f690bb..31e7528 100644
>> --- a/nfs4.1/fs.py
>> +++ b/nfs4.1/fs.py
>> @@ -6,10 +6,7 @@ from nfs4lib import NFS4Error
>>  import struct
>>  import logging
>>  from locking import Lock, RWLock
>> -try:
>> -    import cStringIO.StringIO as StringIO
>> -except:
>> -    from io import StringIO
>> +from io import BytesIO
>>  import time
>>  from xdrdef.nfs4_pack import NFS4Packer
>>
>> @@ -17,6 +14,14 @@ log_o = logging.getLogger("fs.obj")
>>  log_fs = logging.getLogger("fs")
>>  logging.addLevelName(5, "FUNCT")
>>
>> +def _hasattr(obj, attr):
>> +    try:
>> +        getattr(obj, attr)
>> +        return True
>> +    except:
>> +        pass
>> +    return False
>> +
>>  class MetaData(object):
>>      """Contains everything that needs to be stored
>>
>> @@ -61,7 +66,7 @@ class FSObject(object):
>>      def _getsize_locked(self):
>>          # STUB
>>          if self.fattr4_type == NF4REG:
>> -            if hasattr(self.file, "__len__"):
>> +            if _hasattr(self.file, "__len__"):
>>                  return len(self.file)
>>              else:
>>                  orig = self.file.tell()
>> @@ -182,13 +187,13 @@ class FSObject(object):
>>
>>      def init_file(self):
>>          """Hook for subclasses that want to use their own file class"""
>> -        return StringIO()
>> +        return BytesIO()
>>
>>      def _init_hook(self):
>>          pass
>>
>>      def __setattr__(self, name, value):
>> -        if name != "meta" and hasattr(self.meta, name):
>> +        if name != "meta" and _hasattr(self.meta, name):
>>              setattr(self.meta, name, value)
>>          else:
>>              object.__setattr__(self, name, value)
>> @@ -320,7 +325,8 @@ class FSObject(object):
>>                                      tag = "attr %i not writable" % attr)
>>                  name = "fattr4_%s" % nfs4lib.attr_name(attr)
>>                  # Note all writable attrs are object attrs
>> -                if hasattr(self, name):
>> +
>> +                if _hasattr(self, name):
>>                      base = self
>>                  else:
>>                      base = self.meta
>> @@ -711,7 +717,7 @@ class ConfigObj(FSObject):
>>          self._reset()
>>
>>      def _reset(self):
>> -        self.file = StringIO()
>> +        self.file = BytesIO()
>>          self.file.write("# %s\n" % self.configline.comment)
>>          value = self.configline.value
>>          if type(value) is list:
>> @@ -931,7 +937,7 @@ import shutil
>>  import shelve
>>
>>  class StubFS_Disk(FileSystem):
>> -    _fs_data_name = "fs_info" # DB name where we store persistent data
>> +    _fs_data_name = b"fs_info" # DB name where we store persistent data
>>      def __init__(self, path, reset=False, fsid=None):
>>          self._nextid = 0
>>          self.path = path
>> @@ -964,7 +970,7 @@ class StubFS_Disk(FileSystem):
>>          d["root"] = self.root.id
>>          d["fsid"] = self.fsid
>>          for attr in dir(self):
>> -            if attr.startswith("fattr4_") and not hasattr(self.__class__, attr):
>> +            if attr.startswith("fattr4_") and not _hasattr(self.__class__, attr):
>>                  d[attr] = getattr(self, attr)
>>          d.sync()
>>
>> @@ -991,17 +997,17 @@ class StubFS_Disk(FileSystem):
>>          self.root = self.find(d["root"])
>>
>>      def find_on_disk(self, id):
>> -        fd = open(os.path.join(self.path, "m_%i" % id), "r")
>> +        fd = open(os.path.join(self.path, b"m_%i" % id), "rb")
>>          # BUG - need to trap for file not found error
>>          meta = pickle.load(fd)
>>          fd.close()
>>          obj = self.objclass(self, id, meta)
>>          if obj.type == NF4REG:
>> -            fd = open(os.path.join(self.path, "d_%i" % id), "r")
>> -            obj.file = StringIO(fd.read())
>> +            fd = open(os.path.join(self.path, b"d_%i" % id), "rb")
>> +            obj.file = BytesIO(fd.read())
>>              fd.close()
>>          elif obj.type == NF4DIR:
>> -            fd = open(os.path.join(self.path, "d_%i" % id), "r")
>> +            fd = open(os.path.join(self.path, b"d_%i" % id), "rb")
>>              obj.entries = pickle.load(fd)
>>              fd.close()
>>          return obj
>> @@ -1018,10 +1024,10 @@ class StubFS_Disk(FileSystem):
>>              self._fs_data["_nextid"] = id
>>              self._fs_data.sync()
>>              # Create meta-data file
>> -            fd = open(os.path.join(self.path, "m_%i" % id), "w")
>> +            fd = open(os.path.join(self.path, b"m_%i" % id), "wb")
>>              fd.close()
>>              # Create data file
>> -            # fd = open(os.path.join(self.path, "d_%i" % id), "w")
>> +            # fd = open(os.path.join(self.path, b"d_%i" % id), "wb")
>>              # fd.close()
>>          finally:
>>              self._disk_lock.release()
>> @@ -1032,11 +1038,11 @@ class StubFS_Disk(FileSystem):
>>          self._disk_lock.acquire()
>>          try:
>>              # Remove meta-data file
>> -            meta = os.path.join(self.path, "m_%i" % id)
>> +            meta = os.path.join(self.path, b"m_%i" % id)
>>              if os.path.isfile(meta):
>>                  os.remove(meta)
>>              # Remove data file
>> -            data = os.path.join(self.path, "d_%i" % id)
>> +            data = os.path.join(self.path, b"d_%i" % id)
>>              if os.path.isfile(data):
>>                  os.remove(data)
>>          finally:
>> @@ -1049,20 +1055,20 @@ class StubFS_Disk(FileSystem):
>>          try:
>>              # Create meta-data file
>>              log_fs.debug("writing metadata for id=%i" % id)
>> -            fd = open(os.path.join(self.path, "m_%i" % id), "w")
>> +            fd = open(os.path.join(self.path, b"m_%i" % id), "wb")
>>              log_fs.debug("%r" % obj.meta.__dict__)
>>              pickle.dump(obj.meta, fd)
>>              fd.close()
>>              if obj.type == NF4REG:
>>                  # Create data file
>> -                fd = open(os.path.join(self.path, "d_%i" % id), "w")
>> +                fd = open(os.path.join(self.path, b"d_%i" % id), "wb")
>>                  obj.file.seek(0)
>>                  fd.write(obj.file.read())
>>                  fd.close()
>>              elif obj.type == NF4DIR:
>>                  # Create dir entries
>>                  log_fs.debug("writing dir %r" % obj.entries.keys())
>> -                fd = open(os.path.join(self.path, "d_%i" % id), "w")
>> +                fd = open(os.path.join(self.path, b"d_%i" % id), "wb")
>>                  pickle.dump(obj.entries, fd)
>>                  fd.close()
>>          finally:
>> @@ -1408,7 +1414,7 @@ class FSLayoutFSObj(FSObject):
>>      def init_file(self):
>>          self.stripe_size = NFL4_UFLG_STRIPE_UNIT_SIZE_MASK & 0x4000
>>          if self.fs.dsdevice.mdsds:
>> -            return StringIO()
>> +            return BytesIO()
>>          else:
>>              return FileLayoutFile(self)
>>
>> diff --git a/nfs4.1/nfs4client.py b/nfs4.1/nfs4client.py
>> index 25d7fd1..cf844e1 100644
>> --- a/nfs4.1/nfs4client.py
>> +++ b/nfs4.1/nfs4client.py
>> @@ -183,7 +183,7 @@ class NFS4Client(rpc.Client, rpc.Server):
>>                  return env
>>          try:
>>              self.check_utf8str_cs(args.tag)
>> -        except NFS4Errror as e:
>> +        except NFS4Error as e:
>>              env.results.set_empty_return(e.status, "Invalid utf8 tag")
>>              return env
>>          # Handle the individual operations
>> @@ -211,7 +211,7 @@ class NFS4Client(rpc.Client, rpc.Server):
>>                  except NFS4Replay:
>>                      # Just pass this on up
>>                      raise
>> -                except Exception:
>> +                except StandardError:
>>                      # Uh-oh.  This is a server bug
>>                      traceback.print_exc()
>>                      result = encode_status_by_name(opname.lower()[3:],
>> diff --git a/nfs4.1/nfs4server.py b/nfs4.1/nfs4server.py
>> index d51732b..16ef113 100755
>> --- a/nfs4.1/nfs4server.py
>> +++ b/nfs4.1/nfs4server.py
>> @@ -16,17 +16,25 @@ import random
>>  import struct
>>  import collections
>>  import logging
>> -from nfs4state import find_state
>> +from nfs4state import find_state, SHARE, BYTE
>>  from nfs4commoncode import CompoundState, encode_status, encode_status_by_name
>>  from fs import RootFS, ConfigFS
>>  from config import ServerConfig, ServerPerClientConfig, OpsConfigServer, Actions
>>
>> -logging.basicConfig(level=logging.WARN,
>> +logging.basicConfig(level=logging.DEBUG,
>>                      format="%(levelname)-7s:%(name)s:%(message)s")
>>  log_41 = logging.getLogger("nfs.server")
>>
>>  log_cfg = logging.getLogger("nfs.server.opconfig")
>>
>> +def _hasattr(obj, attr):
>> +    try:
>> +        getattr(obj, attr)
>> +        return True
>> +    except:
>> +        pass
>> +    return False
>> +
>>  ##################################################
>>  # Set various global constants and magic numbers #
>>  ##################################################
>> @@ -365,7 +373,7 @@ class SessionRecord(object):
>>      """The server's representation of a session and its state"""
>>      def __init__(self, client, csa):
>>          self.client = client # reference back to client which created this session
>> -        self.sessionid = "%08x%08x" % (client.clientid,
>> +        self.sessionid = b"%08x%08x" % (client.clientid,
>>                                      client.session_replay.seqid) # XXX does this work?
>>          self.channel_fore = Channel(csa.csa_fore_chan_attrs, client.config) # Normal communication
>>          self.channel_back = Channel(csa.csa_back_chan_attrs, client.config) # Callback communication
>> @@ -574,14 +582,14 @@ class NFS4Server(rpc.Server):
>>          self.config = ServerConfig()
>>          self.opsconfig = OpsConfigServer()
>>          self.actions = Actions()
>> -        self.mount(ConfigFS(self), path="/config")
>> +        self.mount(ConfigFS(self), path=b"/config")
>>          self.verifier = struct.pack('>d', time.time())
>>          self.recording = Recording()
>>          self.devid_counter = Counter(name="devid_counter")
>>          self.devids = {} # {devid: device}
>>          # default cred for the backchannel -- currently supports only AUTH_SYS
>>          rpcsec = rpc.security.instance(rpc.AUTH_SYS)
>> -        self.default_cred = rpcsec.init_cred(uid=4321,gid=42,name="mystery")
>> +        self.default_cred = rpcsec.init_cred(uid=4321,gid=42,name=b"mystery")
>>          self.err_inc_dict = self.init_err_inc_dict()
>>
>>      def start(self):
>> @@ -796,7 +804,7 @@ class NFS4Server(rpc.Server):
>>          self.check_utf8str_cs(str)
>>          if not str:
>>              raise NFS4Error(NFS4ERR_INVAL, tag="Empty component")
>> -        if '/' in str:
>> +        if b'/' in str:
>>              raise NFS4Error(NFS4ERR_BADCHAR)
>>
>>      def op_compound(self, args, cred):
>> @@ -808,7 +816,7 @@ class NFS4Server(rpc.Server):
>>              return env
>>          try:
>>              self.check_utf8str_cs(args.tag)
>> -        except NFS4Errror as e:
>> +        except NFS4Error as e:
>>              env.results.set_empty_return(e.status, "Invalid utf8 tag")
>>              return env
>>          # Handle the individual operations
>> @@ -834,10 +842,10 @@ class NFS4Server(rpc.Server):
>>                      # catch error themselves to encode properly.
>>                      result = encode_status_by_name(opname.lower()[3:],
>>                                                     e.status, msg=e.tag)
>> -                except NFS4Replay:
>> +                except NFS4Replay as e:
>>                      # Just pass this on up
>>                      raise
>> -                except Exception:
>> +                except Exception as e:
>>                      # Uh-oh.  This is a server bug
>>                      traceback.print_exc()
>>                      result = encode_status_by_name(opname.lower()[3:],
>> @@ -919,7 +927,7 @@ class NFS4Server(rpc.Server):
>>          check_session(env, unique=True)
>>          if arg.csa_flags & ~nfs4lib.create_session_mask:
>>              return encode_status(NFS4ERR_INVAL,
>> -                                 msg="Unknown bits set in flag")
>> +                                 msg=b"Unknown bits set in flag")
>>          # Step 1: Client record lookup
>>          c = self.clients[arg.csa_clientid]
>>          if c is None: # STUB - or if c.frozen ???
>> @@ -982,13 +990,13 @@ class NFS4Server(rpc.Server):
>>          if protect.type != SP4_SSV:
>>              # Per draft26 18.47.3
>>              return encode_status(NFS4ERR_INVAL,
>> -                                 msg="Did not request SP4_SSV protection")
>> +                                 msg=b"Did not request SP4_SSV protection")
>>          # Do some argument checking
>>          size = protect.context.ssv_len
>>          if len(arg.ssa_ssv) != size:
>> -            return encode_status(NFS4ERR_INVAL, msg="SSV size != %i" % size)
>> +            return encode_status(NFS4ERR_INVAL, msg=b"SSV size != %i" % size)
>>          if arg.ssa_ssv == "\0" * size:
>> -            return encode_status(NFS4ERR_INVAL, msg="SSV==0 not allowed")
>> +            return encode_status(NFS4ERR_INVAL, msg=b"SSV==0 not allowed")
>>          # Now we need to compute and check digest, using SEQUENCE args
>>          p = nfs4lib.FancyNFS4Packer()
>>          p.pack_SEQUENCE4args(env.argarray[0].opsequence)
>> @@ -1009,10 +1017,10 @@ class NFS4Server(rpc.Server):
>>          check_session(env, unique=True)
>>          # Check arguments for blatent errors
>>          if arg.eia_flags & ~nfs4lib.exchgid_mask:
>> -            return encode_status(NFS4ERR_INVAL, msg="Unknown flag")
>> +            return encode_status(NFS4ERR_INVAL, msg=b"Unknown flag")
>>          if arg.eia_flags & EXCHGID4_FLAG_CONFIRMED_R:
>>              return encode_status(NFS4ERR_INVAL,
>> -                                 msg="Client used server-only flag")
>> +                                 msg=b"Client used server-only flag")
>>          if arg.eia_client_impl_id:
>>              impl_id = arg.eia_client_impl_id[0]
>>              self.check_utf8str_cis(impl_id.nii_domain)
>> @@ -1034,7 +1042,7 @@ class NFS4Server(rpc.Server):
>>              if c is None:
>>                  if update:
>>                      # Case 7
>> -                    return encode_status(NFS4ERR_NOENT, msg="No such client")
>> +                    return encode_status(NFS4ERR_NOENT, msg=b"No such client")
>>                  else:
>>                      # The simple, common case 1: a new client
>>                      c = self.clients.add(arg, env.principal, self.sec_flavors)
>> @@ -1042,7 +1050,7 @@ class NFS4Server(rpc.Server):
>>                  if update:
>>                      # Case 7
>>                      return encode_status(NFS4ERR_NOENT,
>> -                                         msg="Client not confirmed")
>> +                                         msg=b"Client not confirmed")
>>                  else:
>>                      # Case 4
>>                      self.clients.remove(c.clientid)
>> @@ -1057,11 +1065,11 @@ class NFS4Server(rpc.Server):
>>                      if c.verifier != verf:
>>                          # Case 8
>>                          return encode_status(NFS4ERR_NOT_SAME,
>> -                                             msg="Verifier mismatch")
>> +                                             msg=b"Verifier mismatch")
>>                      elif c.principal != env.principal:
>>                          # Case 9
>>                          return encode_status(NFS4ERR_PERM,
>> -                                             msg="Principal mismatch")
>> +                                             msg=b"Principal mismatch")
>>                      else:
>>                          # Case 6 - update
>>                          c.update(arg, env.principal)
>> @@ -1069,7 +1077,7 @@ class NFS4Server(rpc.Server):
>>                      # Case 3
>>                      # STUB - need to check state
>>                      return encode_status(NFS4ERR_CLID_INUSE,
>> -                                         msg="Principal mismatch")
>> +                                         msg=b"Principal mismatch")
>>                  elif c.verifier != verf:
>>                      # Case 5
>>                      # Confirmed client reboot: this is the hard case
>> @@ -1103,7 +1111,7 @@ class NFS4Server(rpc.Server):
>>                                  c.protection.rv(arg.eia_state_protect),
>>                                  self.config._owner, self.config.scope,
>>                                  [self.config.impl_id])
>> -        return encode_status(NFS4_OK, res, msg="draft21")
>> +        return encode_status(NFS4_OK, res, msg=b"draft21")
>>
>>      def client_reboot(self, c):
>>          # STUB - locking?
>> @@ -1143,9 +1151,9 @@ class NFS4Server(rpc.Server):
>>              if arg.bctsa_digest:
>>                  # QUESTION _INVAL or _BAD_SESSION_DIGEST also possible
>>                  return encode_status(NFS4ERR_CONN_BINDING_NOT_ENFORCED,
>> -                                     msg="Expected zero length digest")
>> +                                     msg=b"Expected zero length digest")
>>              if arg.bctsa_step1 is False:
>> -                return encode_status(NFS4ERR_INVAL, msg="Expected step1==True")
>> +                return encode_status(NFS4ERR_INVAL, msg=b"Expected step1==True")
>>              dir = bind_to_channels(arg.bctsa_dir)
>>              nonce = session.get_nonce(connection, [arg.bctsa_nonce])
>>              # STUB this should be a session method
>> @@ -1175,9 +1183,9 @@ class NFS4Server(rpc.Server):
>>                  old_s_nonce, old_c_nonce = session.nonce[connection]
>>              except KeyError:
>>                  return encode_status(NFS4ERR_INVAL,
>> -                                     msg="server has no record of step1")
>> +                                     msg=b"server has no record of step1")
>>              if old_c_nonce == arg.bctsa_nonce:
>> -                return encode_status(NFS4ERR_INVAL, msg="Client reused nonce")
>> +                return encode_status(NFS4ERR_INVAL, msg=b"Client reused nonce")
>>              p = nfs4lib.FancyNFS4Packer()
>>              p.pack_bctsr_digest_input4(bctsr_digest_input4(arg.bctsa_sessid,
>>                                                             arg.bctsa_nonce,
>> @@ -1208,8 +1216,7 @@ class NFS4Server(rpc.Server):
>>          check_session(env)
>>          # xxx add gss support
>>          secinfo4_list = [ secinfo4(rpc.AUTH_SYS) ]
>> -        res = SECINFO_NO_NAME4res(NFS4_OK, secinfo4_list)
>> -        return encode_status(NFS4_OK, res)
>> +        return encode_status(NFS4_OK, secinfo4_list)
>>
>>      # op_putpubfh SHOULD be the same as op_putrootfh
>>      # See draft23, section 18.20.3, line 25005
>> @@ -1356,14 +1363,14 @@ class NFS4Server(rpc.Server):
>>          claim_type = arg.claim.claim
>>          if claim_type != CLAIM_NULL and arg.openhow.opentype == OPEN4_CREATE:
>>              return encode_status(NFS4ERR_INVAL,
>> -                                 msg="OPEN4_CREATE not compatible with %s" %
>> +                                 msg=b"OPEN4_CREATE not compatible with %s" %
>>                                   open_claim_type4[claim_type])
>>          # emulate switch(claim_type)
>>          try:
>>              func = getattr(self,
>>                             "open_%s" % open_claim_type4[claim_type].lower())
>>          except AttributeError:
>> -            return encode_status(NFS4ERR_NOTSUPP, msg="Unsupported claim type")
>> +            return encode_status(NFS4ERR_NOTSUPP, msg=b"Unsupported claim type")
>>          existing, cinfo, bitmask = func(arg, env)
>>          # existing now points to file we want to open
>>          if existing is None:
>> @@ -1462,7 +1469,7 @@ class NFS4Server(rpc.Server):
>>              ace = nfsace4(ACE4_ACCESS_DENIED_ACE_TYPE, 0,
>>                            ACE4_GENERIC_EXECUTE |
>>                            ACE4_GENERIC_WRITE | ACE4_GENERIC_READ,
>> -                          "EVERYONE@")
>> +                          b"EVERYONE@")
>>              deleg = open_read_delegation4(entry.get_id(), False, ace)
>>              return open_delegation4(entry.deleg_type, deleg)
>>
>> @@ -1511,7 +1518,7 @@ class NFS4Server(rpc.Server):
>>              else:
>>                  base = obj
>>              name = "fattr4_%s" % nfs4lib.attr_name(attr)
>> -            if hasattr(base, name) and (obj.fs.fattr4_supported_attrs & 1<<attr): # STUB we should be able to remove hasattr
>> +            if _hasattr(base, name) and (obj.fs.fattr4_supported_attrs & 1<<attr): # STUB we should be able to remove hasattr
>>                  ret_dict[attr] = getattr(base, name)
>>              else:
>>                  if ignore:
>> @@ -1557,7 +1564,7 @@ class NFS4Server(rpc.Server):
>>          check_cfh(env)
>>          env.cfh.check_dir()
>>          if arg.cookie in (1, 2) or \
>> -               (arg.cookie==0 and arg.cookieverf != "\0" * 8):
>> +               (arg.cookie==0 and arg.cookieverf != b"\0" * 8):
>>              return encode_status(NFS4ERR_BAD_COOKIE)
>>          objlist, verifier = env.cfh.readdir(arg.cookieverf, env.session.client, env.principal) # (name, obj) pairs
>>          # STUB - think through rdattr_error handling
>> @@ -1690,7 +1697,7 @@ class NFS4Server(rpc.Server):
>>          check_session(env)
>>          check_cfh(env)
>>          if env.cfh.fattr4_type != NF4LNK:
>> -            return encode_status(NFS4_INVAL, msg="cfh type was %i" % i)
>> +            return encode_status(NFS4_INVAL, msg=b"cfh type was %i" % i)
>>          res = READLINK4resok(env.cfh.linkdata)
>>          return encode_status(NFS4_OK, res)
>>
>> @@ -1734,7 +1741,7 @@ class NFS4Server(rpc.Server):
>>          self.check_component(arg.newname)
>>          if not nfs4lib.test_equal(env.sfh.fattr4_fsid, env.cfh.fattr4_fsid,
>>                                    kind="fsid4"):
>> -            return encode_status(NFS4ERR_XDEV, msg="%r != %r" % (env.sfh.fattr4_fsid, env.cfh.fattr4_fsid))
>> +            return encode_status(NFS4ERR_XDEV, msg=b"%r != %r" % (env.sfh.fattr4_fsid, env.cfh.fattr4_fsid))
>>          order = sorted(set([env.cfh, env.sfh])) # Used to prevent locking problems
>>          # BUG fs locking
>>          old_change_src = env.sfh.fattr4_change
>> @@ -1891,10 +1898,10 @@ class NFS4Server(rpc.Server):
>>              check_session(env)
>>              check_cfh(env)
>>              if arg.loga_length == 0:
>> -                return encode_status(NFS4_INVAL, msg="length == 0")
>> +                return encode_status(NFS4_INVAL, msg=b"length == 0")
>>              if arg.loga_length != 0xffffffffffffffff:
>>                  if arg.loga_length + arg.loga_offset > 0xffffffffffffffff:
>> -                     return encode_status(NFS4_INVAL, msg="offset+length too big")
>> +                     return encode_status(NFS4_INVAL, msg=b"offset+length too big")
>>              if not env.session.has_backchannel:
>>                  raise NFS4Error(NFS4ERR_LAYOUTTRYLATER)
>>              # STUB do state locking and check on iomode,offset,length triple
>> @@ -2047,7 +2054,7 @@ class NFS4Server(rpc.Server):
>>          # "The server MUST specify...an ONC RPC version number equal to 4",
>>          # Per the May 17, 2010 discussion on the ietf list, errataID 2291
>>          # indicates it should in fact be 1
>> -        return pipe.send_call(prog, 1, 0, "", credinfo)
>> +        return pipe.send_call(prog, 1, 0, b"", credinfo)
>>
>>      def cb_null(self, prog, pipe, credinfo=None):
>>          """ Sends bc_null."""
>> diff --git a/nfs4.1/nfs4state.py b/nfs4.1/nfs4state.py
>> index e57b90a..3ff6cdf 100644
>> --- a/nfs4.1/nfs4state.py
>> +++ b/nfs4.1/nfs4state.py
>> @@ -21,7 +21,7 @@ POSIXLOCK = False
>>  SHARE, BYTE, DELEG, LAYOUT, ANON = range(5) # State types
>>  NORMAL, CB_INIT, CB_SENT, CB_RECEIVED, INVALID = range(5) # delegation/layout states
>>
>> -DS_MAGIC = "\xa5" # STUB part of HACK code to ignore DS stateid
>> +DS_MAGIC = b"\xa5" # STUB part of HACK code to ignore DS stateid
>>
>>  @contextmanager
>>  def find_state(env, stateid, allow_0=True, allow_bypass=False):
>> @@ -34,7 +34,7 @@ def find_state(env, stateid, allow_0=True, allow_bypass=False):
>>          # Could meddle with state.other here if needed
>>          anon = True
>>      # First we convert special stateids, see draft22 8.2.3
>> -    if stateid.other == "\0" * 12:
>> +    if stateid.other == b"\0" * 12:
>>          if allow_0 and stateid.seqid == 0:
>>              state = env.cfh.state.anon0
>>              anon = True
>> @@ -43,10 +43,10 @@ def find_state(env, stateid, allow_0=True, allow_bypass=False):
>>              # Special stateids must be passed in explicitly
>>              if stateid in [None, nfs4lib.state00, nfs4lib.state11]:
>>                  raise NFS4Error(NFS4ERR_BAD_STATEID,
>> -                                tag="Current stateid not useable")
>> +                                tag=b"Current stateid not useable")
>>          else:
>>              raise NFS4Error(NFS4ERR_BAD_STATEID)
>> -    elif stateid.other == "\xff" * 12:
>> +    elif stateid.other == b"\xff" * 12:
>>          if allow_0 and stateid.seqid == 0xffffffff:
>>              stateid = nfs4lib.state00 # Needed to pass seqid checks below
>>              state = (env.cfh.state.anon1 if allow_bypass else env.cfh.state.anon0)
>> @@ -57,31 +57,31 @@ def find_state(env, stateid, allow_0=True, allow_bypass=False):
>>          # Now map stateid to find state
>>          state = env.session.client.state.get(stateid.other, None)
>>          if state is None:
>> -            raise NFS4Error(NFS4ERR_BAD_STATEID, tag="stateid not known")
>> +            raise NFS4Error(NFS4ERR_BAD_STATEID, tag=b"stateid not known")
>>          if state.file != env.cfh:
>>              raise NFS4Error(NFS4ERR_BAD_STATEID,
>> -                            tag="cfh %r does not match stateid %r" %
>> +                            tag=b"cfh %r does not match stateid %r" %
>>                              (state.file.fh, env.cfh.fh))
>>      state.lock.acquire()
>>      # It is possible that while waiting to get the lock, the state has been
>>      # removed.  In that case, the removal sets the invalid flag.
>>      if state.invalid:
>>          state.release()
>> -        raise NFS4Error(NFS4ERR_BAD_STATEID, tag="stateid not known (race)")
>> +        raise NFS4Error(NFS4ERR_BAD_STATEID, tag=b"stateid not known (race)")
>>      if state.type != LAYOUT:
>>          # See draft22 8.2.2
>>          if stateid.seqid != 0 and stateid.seqid != state.seqid:
>>              old = (stateid.seqid < state.seqid)
>>              state.lock.release()
>>              if old:
>> -                raise NFS4Error(NFS4ERR_OLD_STATEID, tag="bad stateid.seqid")
>> +                raise NFS4Error(NFS4ERR_OLD_STATEID, tag=b"bad stateid.seqid")
>>              else:
>> -                raise NFS4Error(NFS4ERR_BAD_STATEID, tag="bad stateid.seqid")
>> +                raise NFS4Error(NFS4ERR_BAD_STATEID, tag=b"bad stateid.seqid")
>>      else:
>>          # See draft22 12.5.3
>>          if stateid.seqid == 0:
>>              state.lock.release()
>> -            raise NFS4Error(NFS4ERR_BAD_STATEID, tag="layout stateid.seqid==0")
>> +            raise NFS4Error(NFS4ERR_BAD_STATEID, tag=b"layout stateid.seqid==0")
>>      try:
>>          yield state
>>      finally:
>> @@ -215,10 +215,10 @@ class DictTree(object):
>>      def itervalues(self):
>>          def myiter(d, depth):
>>              if depth == 1:
>> -                for value in d.itervalues():
>> +                for value in d.values():
>>                      yield value
>>              else:
>> -                for sub_d in d.itervalues():
>> +                for sub_d in d.values():
>>                      for i in myiter(sub_d, depth - 1):
>>                          yield i
>>          for i in myiter(self._data, self._depth):
>> @@ -250,7 +250,7 @@ class FileStateTyped(object):
>>          # NOTE we are only using 9 bytes of 12
>>          # NOTE this needs to be client-wide, since keys of client.state[]
>>          # must be unique
>> -        return "%s%s" % (struct.pack("!xxxB", self.type),
>> +        return b"%s%s" % (struct.pack("!xxxB", self.type),
>>                           client.get_new_other())
>>
>>      def grab_entry(self, key, klass):
>> @@ -290,13 +290,13 @@ class DelegState(FileStateTyped):
>>              return True
>>          # Find any delegation - use fact that all are of same type
>>          for e in self._tree.itervalues():
>> +            # The only thing that doesn't conflict is access==READ with READ deleg
>> +            if e.deleg_type == OPEN_DELEGATE_READ and \
>> +                    not (access & OPEN4_SHARE_ACCESS_WRITE):
>> +                return False
>> +            else:
>> +                return True
>>              break
>> -        # The only thing that doesn't conflict is access==READ with READ deleg
>> -        if e.deleg_type == OPEN_DELEGATE_READ and \
>> -                not (access & OPEN4_SHARE_ACCESS_WRITE):
>> -            return False
>> -        else:
>> -            return True
>>
>>      def recall_conflicting_delegations(self, dispatcher, client, access, deny):
>>          # NOTE OK to have extra access/deny flags
>> @@ -342,8 +342,8 @@ class AnonState(FileStateTyped):
>>      def __init__(self, *args, **kwargs):
>>          kwargs["depth"] = 1 # key = (int,)
>>          FileStateTyped.__init__(self, *args, **kwargs)
>> -        self._tree[(0 ,)] = AnonEntry("\x00" * 12, self, (0,))
>> -        self._tree[(1 ,)] = AnonEntry("\xff" * 12, self, (1,))
>> +        self._tree[(0 ,)] = AnonEntry(b"\x00" * 12, self, (0,))
>> +        self._tree[(1 ,)] = AnonEntry(b"\xff" * 12, self, (1,))
>>          self._tree[(DS_MAGIC, )] = DSEntry(DS_MAGIC * 12, self, (DS_MAGIC, ))
>>
>>  class ShareState(FileStateTyped):
>> @@ -686,9 +686,9 @@ class ShareEntry(StateTableEntry):
>>  #             # This test is the whole reason for the silly 3-bit
>>  #             # representation.  It basically prevents the seqence
>>  #             # OPEN(share=BOTH), OPENDOWNGRADE(share=SINGLE).
>> -#             raise NFS4Error(NFS4ERR_INVAL, tag="Failed history test")
>> +#             raise NFS4Error(NFS4ERR_INVAL, tag=b"Failed history test")
>>  #         if access == 0 and deny != 0:
>> -#             raise NFS4Error(NFS4ERR_INVAL, tag="access==0")
>> +#             raise NFS4Error(NFS4ERR_INVAL, tag=b"access==0")
>>  #         self.access_hist, self.deny_hist = new_access, new_deny
>>  #         self.share_access, self.share_deny = access, deny
>>
>> diff --git a/nfs4.1/sample_code/ds_exports.py b/nfs4.1/sample_code/ds_exports.py
>> index 5d31148..1975e16 100644
>> --- a/nfs4.1/sample_code/ds_exports.py
>> +++ b/nfs4.1/sample_code/ds_exports.py
>> @@ -6,4 +6,4 @@ from fs import StubFS_Mem
>>
>>  def mount_stuff(server, opts):
>>      B = StubFS_Mem(2)
>> -    server.mount(B, path="/pynfs_mds")
>> +    server.mount(B, path=b"/pynfs_mds")
>> diff --git a/nfs4.1/server_exports.py b/nfs4.1/server_exports.py
>> index ef857ee..1271c4a 100644
>> --- a/nfs4.1/server_exports.py
>> +++ b/nfs4.1/server_exports.py
>> @@ -4,22 +4,22 @@ from dataserver import DSDevice
>>  def mount_stuff(server, opts):
>>      """Mount some filesystems to the server"""
>>      # STUB - just testing stuff out
>> -    A = StubFS_Disk("/tmp/py41/fs1", opts.reset, 1)
>> +    A = StubFS_Disk(b"/tmp/py41/fs1", opts.reset, 1)
>>      B = StubFS_Mem(2)
>>      C = StubFS_Mem(3)
>> -    server.mount(A, path="/a")
>> -    server.mount(B, path="/b")
>> -    server.mount(C, path="/foo/bar/c")
>> +    server.mount(A, path=b"/a")
>> +    server.mount(B, path=b"/b")
>> +    server.mount(C, path=b"/foo/bar/c")
>>      if opts.use_block:
>>          dev = _create_simple_block_dev()
>>          E = BlockLayoutFS(5, backing_device=dev)
>> -        server.mount(E, path="/block")
>> +        server.mount(E, path=b"/block")
>>      if opts.use_files:
>>          dservers = _load_dataservers(opts.dataservers, server)
>>          if dservers is None:
>>              return
>>          F = FileLayoutFS(6, dservers)
>> -        server.mount(F, path="/files")
>> +        server.mount(F, path=b"/files")
>>
>>  def _create_simple_block_dev():
>>      from block import Simple, Slice, Concat, Stripe, BlockVolume
>> --
>> 2.53.0
>>


  parent reply	other threads:[~2026-03-28  5:54 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-28  5:42 [PATCH v3 1/4] pynfs: nfs4.1/nfs4server.py fixes Jianhong Yin
2026-03-28  5:42 ` [PATCH v3 2/4] pynfs: fix nfs4.1/nfs4server.py error with python3.12+ Jianhong Yin
2026-03-28  5:42 ` [PATCH v3 3/4] pynfs: nfs4.1/nfs4server.py allow v4.2 mount Jianhong Yin
2026-03-28  5:42 ` [PATCH v3 4/4] pynfs: fix various types of errors in nfs4.1/nfs4proxy.py Jianhong Yin
     [not found] ` <CAFAKQ6091kgRKp9xKmLsyRYeY33X4B-2PKQi63DgeCbNTB=JMA@mail.gmail.com>
2026-03-28  5:54   ` Jianhong Yin [this message]
  -- strict thread matches above, loose matches on Subject: below --
2026-03-28  7:15 [PATCH v3 1/4] pynfs: nfs4.1/nfs4server.py fixes Jianhong Yin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAFAKQ62GTXEs6XNyHShp56CSLh23uXzavGRTMk5M9VMSs==g9g@mail.gmail.com' \
    --to=jiyin@redhat.com \
    --cc=bcodding@redhat.com \
    --cc=calum.mackay@oracle.com \
    --cc=jlayton@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=smayhew@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).