#!/usr/bin/env python3
#===============================================================================
# Copyright 2012 NetApp, Inc. All Rights Reserved,
# contribution by Jorge Mora <mora@netapp.com>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#===============================================================================
import os
import mmap
import stat
import fcntl
import errno
import posix
import ctypes
import random
import itertools
import traceback
from time import sleep
import nfstest_config as c
from nfstest.test_util import TestUtil
import packet.nfs.nfs3_const as nfs3_const
import packet.nfs.nfs4_const as nfs4_const

# Module constants
__author__    = "Jorge Mora (%s)" % c.NFSTEST_AUTHOR_EMAIL
__copyright__ = "Copyright (C) 2012 NetApp, Inc."
__license__   = "GPL v2"
__version__   = "1.2"

USAGE = """%prog --server <server> [options]

POSIX file system level access tests
====================================
Verify POSIX file system level access over the specified path using
positive and negative testing.

Valid for any version of NFS.

Examples:
    The only required option is --server
    $ %prog --server 192.168.0.11

Notes:
    The user id in the local host must have access to run commands as root
    using the 'sudo' command without the need for a password."""

# Test script ID
SCRIPT_ID = "POSIX"

TESTNAMES = [
    'access',
    'chdir',
    'close',
    'closedir',
    'creat',
    'fcntl',
    'fdatasync',
    'fstat',
    'fstatvfs',
    'fsync',
    'link',
    'lseek',
    'lstat',
    'mkdir',
    'mmap',
    'munmap',
    'opendir',
    'read',
    'readdir',
    'readlink',
    'rename',
    'rewinddir',
    'rmdir',
    'seekdir',
    'stat',
    'statvfs',
    'symlink',
    'sync',
    'telldir',
    'unlink',
    'write',
    'open',
    'chmod',
]

stat_map = {
    1: 'stat',
    2: 'lstat',
    3: 'fstat',
}

access_names = {
    posix.F_OK: 'F_OK',
    posix.R_OK: 'R_OK',
    posix.W_OK: 'W_OK',
    posix.X_OK: 'X_OK',
}

def access_str(mode):
    """Convert the access mode bitmap to its string representation."""
    access_list = []
    for perm in access_names:
        if perm & mode != 0:
            access_list.append(access_names[perm])
    if len(access_list) == 0:
        access_list.append(access_names[0])
    return '|'.join(access_list)

class DirEnt(ctypes.Structure):
    """
       struct dirent {
           ino_t          d_ino;       /* inode number */
           off_t          d_off;       /* offset to the next dirent */
           unsigned short d_reclen;    /* length of this record */
           unsigned char  d_type;      /* type of file; not supported
                                          by all file system types */
           char           d_name[256]; /* filename */
       };
    """
    _fields_ = [
        ("d_ino",    ctypes.c_ulong),
        ("d_off",    ctypes.c_ulong),
        ("d_reclen", ctypes.c_ushort),
        ("d_type",   ctypes.c_char),
        ("d_name",   ctypes.c_char*256),
    ]

class Flock(ctypes.Structure):
    """
       struct flock {
           short l_type;    /* Type of lock: F_RDLCK,
                               F_WRLCK, F_UNLCK */
           short l_whence;  /* How to interpret l_start:
                               SEEK_SET, SEEK_CUR, SEEK_END */
           off_t l_start;   /* Starting offset for lock */
           off_t l_len;     /* Number of bytes to lock */
           pid_t l_pid;     /* PID of process blocking our lock
                               (F_GETLK only) */

       };
    """
    _fields_ = [
        ("l_type",   ctypes.c_short),
        ("l_whence", ctypes.c_short),
        ("l_start",  ctypes.c_ulong),
        ("l_len",    ctypes.c_ulong),
        ("l_pid",    ctypes.c_int),
    ]

# OPEN flags
access_flag_list = [
    posix.O_RDONLY,
    posix.O_WRONLY,
    posix.O_RDWR,
]
open_flag_list = [
    posix.O_RDONLY,
    posix.O_WRONLY,
    posix.O_RDWR,
    posix.O_CREAT,
    posix.O_EXCL,
    posix.O_NOCTTY,
    posix.O_TRUNC,
    posix.O_APPEND,
    posix.O_ASYNC,
    # Linux-specific flags
    posix.O_DIRECTORY,
    posix.O_NOATIME,
    posix.O_NOFOLLOW,
]
open_flag_map = {
    posix.O_RDONLY:    'O_RDONLY',
    posix.O_WRONLY:    'O_WRONLY',
    posix.O_RDWR:      'O_RDWR',
    posix.O_CREAT:     'O_CREAT',
    posix.O_EXCL:      'O_EXCL',
    posix.O_NOCTTY:    'O_NOCTTY',
    posix.O_TRUNC:     'O_TRUNC',
    posix.O_APPEND:    'O_APPEND',
    posix.O_ASYNC:     'O_ASYNC',
    # Linux-specific flags
    posix.O_DIRECTORY: 'O_DIRECTORY',
    posix.O_NOATIME:   'O_NOATIME',
    posix.O_NOFOLLOW:  'O_NOFOLLOW',
}

perm_map = {
    0o0001: 'XOTH',
    0o0002: 'WOTH',
    0o0004: 'ROTH',
    0o0010: 'XGRP',
    0o0020: 'WGRP',
    0o0040: 'RGRP',
    0o0100: 'XUSR',
    0o0200: 'WUSR',
    0o0400: 'RUSR',
    0o1000: 'SVTX',
    0o2000: 'SGID',
    0o4000: 'SUID',
}

def oflag_str(flags):
    """Convert the open flags bitmap to its string representation."""
    flist = []
    flag_list = list(flags)
    if 0 in access_flag_list:
        # Flag with no bits set is in the access list
        found = False
        for flag in access_flag_list:
            if flag in flags:
                # At least one access flag is in flags
                found = True
                break
        if not found:
            flag_list = [0] + flag_list
    for flag in flag_list:
        flist.append(open_flag_map[flag])
    return '|'.join(flist)

class PosixTest(TestUtil):
    """PosixTest object

       PosixTest() -> New test object

       Usage:
           x = PosixTest(testnames=['access', 'chdir', 'creat', ...])

           # Run all the tests
           x.run_tests()
           x.exit()
    """
    def __init__(self, **kwargs):
        """Constructor

           Initialize object's private data.
        """
        TestUtil.__init__(self, **kwargs)
        self.opts.version = "%prog " + __version__
        self.scan_options()

        # Function prototypes for system calls in libc
        self.libc.opendir.restype  = ctypes.c_void_p
        self.libc.closedir.argtypes= ctypes.c_void_p,
        self.libc.rewinddir.argtypes = ctypes.c_void_p,
        self.libc.readdir.argtypes = ctypes.c_void_p,
        self.libc.readdir.restype  = ctypes.POINTER(DirEnt)
        self.libc.telldir.argtypes = ctypes.c_void_p,
        self.libc.telldir.restype  = ctypes.c_long
        self.libc.seekdir.argtypes = ctypes.c_void_p, ctypes.c_long
        self.libc.fcntl.restype    = ctypes.c_int
        self.libc.mmap.argtypes    = ctypes.c_void_p, ctypes.c_ulong, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_long
        self.libc.mmap.restype     = ctypes.c_long  # Use c_long to check for errors
        self.libc.munmap.argtypes  = ctypes.c_void_p, ctypes.c_ulong
        self.libc.munmap.restype   = ctypes.c_int

        # Make sure actimeo is set
        if 'actimeo' not in self.mtopts:
            self.mtopts += ",actimeo=0"

        # Clear umask
        os.umask(0)

    def setup(self, **kwargs):
        """Setup test environment"""
        self.umount()
        x.trace_start()
        self.mount()
        super(PosixTest, self).setup(**kwargs)

        self.setup_readlink = {}
        self.setup_readlink_types = ("file", "directory", "non-existent file")
        if "readlink" in self.testlist:
            # Setup specific to "readlink" test
            for stype in self.setup_readlink_types:
                if stype == "file":
                    self.create_file()
                    srcpath = self.absfile
                elif stype == "directory":
                    self.create_dir()
                    srcpath = self.absdir
                else:
                    self.get_filename()
                    srcpath = self.absfile
                self.get_filename()
                self.dprint('DBG3', "Creating symbolic link to %s [%s -> %s]" % (stype, self.absfile, srcpath))
                os.symlink(srcpath, self.absfile)
                self.setup_readlink[stype] = (srcpath, self.absfile)
        x.umount()
        x.mount()
        x.trace_stop()

    def access(self, path, mode, test, msg=""):
        """Test file access.

           path:
               File system object to get access from
           mode:
               Mode access to check
           test:
               Expected output from access()
           msg:
               Message to be appended to test message
        """
        not_str = "" if test else "not "
        out = posix.access(path, mode)
        self.dprint('DBG4', "access(%s) returns %s" % (access_str(mode), out))
        self.test(out == test, "access - file access %sallowed with mode %s%s" % (not_str, access_str(mode), msg))

    def access_test(self):
        """Verify POSIX API access() on files with different modes."""
        self.test_group("Verify POSIX API access() on %s" % self.nfsstr())
        perms = [0o777, 0o666, 0o555, 0o333, 0o444, 0o222, 0o111, 0o000]
        cperms = [0o7, 0o6, 0o5, 0o3]

        try:
            self.create_file()

            isroot = False
            if os.getuid() == 0:
                fstat = os.stat(self.absfile)
                if fstat.st_uid == 0:
                    isroot = True

            self.access(self.absfile, posix.F_OK, True)
            self.access(self.absfile+'bogus', posix.F_OK, False, " for a non-existent file")
            for perm in perms:
                msg = " for file with permissions %s" % oct(perm)
                os.chmod(self.absfile, perm)
                # When running as root and the file is created as root,
                # access is granted for either R or W even when the file
                # does not have R or W permission.
                self.access(self.absfile, posix.R_OK, isroot or (perm&4)!=0, msg)
                self.access(self.absfile, posix.W_OK, isroot or (perm&2)!=0, msg)
                self.access(self.absfile, posix.X_OK, (perm&1)!=0, msg)
                for mode in cperms:
                    if isroot:
                        # Asking for multiple access bits when running as root
                        # and the file is created as root, expect access unless
                        # asking for X and file does not have X permissions.
                        expr = (not mode&posix.X_OK or perm&posix.X_OK)
                    else:
                        # Running as a regular user
                        expr = (mode&perm == mode)
                    self.access(self.absfile, mode, expr, msg)
        except Exception:
            self.test(False, traceback.format_exc())

    def chdir_test(self):
        """Verify POSIX API chdir() by changing to a newly created directory
           and then by changing back to the original directory.
        """
        self.test_group("Verify POSIX API chdir() on %s" % self.nfsstr())

        try:
            cwd_orig = os.getcwd()
            self.create_dir()
            self.dprint('DBG3', "Change to directory %s using POSIX API chdir()" % self.absdir)
            amsg = "chdir - chdir() should succeed"
            self.run_func(posix.chdir, self.absdir, msg=amsg)
            cwd = os.getcwd()
            self.test(cwd == self.absdir, "chdir - current working directory should be changed")
            self.run_func(posix.chdir, cwd_orig, msg=amsg)
            cwd = os.getcwd()
            self.test(cwd == cwd_orig, "chdir - current working directory should be changed back to the original directory")
            amsg = "chdir - changing to a non-existent directory should return an error"
            self.run_func(posix.chdir, self.absdir+"_bogus", msg=amsg, err=errno.ENOENT)
            self.test(cwd == os.getcwd(), "chdir - current working directory should not be changed")
        except Exception:
            self.test(False, traceback.format_exc())

    def chmod_test(self):
        """Verify POSIX API chmod() on a file and directory by trying all valid
           combinations of modes. Verify that the st_ctime files is updated
           for both the file and directory.
        """
        self.test_group("Verify POSIX API chmod() on %s" % self.nfsstr())

        try:
            for objtype in ['file', 'directory']:
                if objtype == 'file':
                    self.create_file()
                else:
                    self.create_dir()
                    self.absfile = self.absdir
                chmod_count = 0
                max_perm = 0o7777+1
                bad_dict = {}
                bit_list = []
                for perm in range(max_perm):
                    bit_list.append(perm)
                    posix.chmod(self.absfile, perm)
                    fstat = os.stat(self.absfile)
                    mode = fstat.st_mode & 0o7777
                    if mode == perm:
                        chmod_count += 1
                    if self.tverbose != 1:
                        self.test(mode == perm, "chmod - changing %s permission mode to %05o should succeed" % (objtype, perm), failmsg=", got %05o from stat()" % mode)
                    if mode != perm:
                        item = perm^mode
                        if bad_dict.get(item) is None:
                            bad_dict[item] = 0
                        bad_dict[item] += 1
                for item in bad_dict:
                    out = self.bitmap_str(item, bad_dict[item], perm_map, bit_list)
                    if out is None:
                        out = "%05o" % item
                    self.test(False, "chmod - %d %s permission mode changes failed when setting (%s)" % (bad_dict[item], objtype, out))

                if chmod_count == max_perm:
                    msg = "chmod - %d %s permission mode changes succeeded" % (chmod_count, objtype)
                    if self.tverbose == 1:
                        self.test(True, msg)
                    else:
                        self.test_info(">>>>: " + msg)
                fstat_b = os.stat(self.absfile)
                sleep(1)
                posix.chmod(self.absfile, 0o777)
                fstat = os.stat(self.absfile)
                self.test(fstat.st_ctime > fstat_b.st_ctime, "chmod - %s st_ctime should be updated" % objtype)
        except Exception:
            self.test(False, traceback.format_exc())

    def close_test(self):
        """Verify POSIX API close() works and that writing to a closed file
           descriptor returns an error.
        """
        self.test_group("Verify POSIX API close() on %s" % self.nfsstr())

        try:
            fd = None
            self.get_filename()
            self.dprint('DBG3', "Open file %s for writing" % self.absfile)
            fd = posix.open(self.absfile, posix.O_WRONLY|posix.O_CREAT)
            amsg = "close - close() on write file should succeed"
            self.run_func(posix.close, fd, msg=amsg)
            amsg = "close - write after close should return an error"
            self.run_func(posix.write, fd, self.data_pattern(0, 32), msg=amsg, err=errno.EBADF)

            self.dprint('DBG3', "Open file %s for reading" % self.absfile)
            fd = posix.open(self.absfile, posix.O_RDONLY)
            amsg = "close - close() on read file should succeed"
            self.run_func(posix.close, fd, msg=amsg)
            amsg = "close - read after close should return an error"
            self.run_func(posix.read, fd, 32, msg=amsg, err=errno.EBADF)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def closedir_test(self):
        """Verify POSIX API closedir() works
        """
        self.test_group("Verify POSIX API closedir() on %s" % self.nfsstr())

        try:
            fd = None
            self.create_dir()

            fd = self.libc.opendir(self.absdir.encode())
            out = self.libc.closedir(fd)
            fd = None
            self.test(out == 0, "closedir - closedir() should succeed")
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            if fd:
                self.libc.closedir(fd)

    def creat_test(self):
        """Verify POSIX API creat(path, mode) is equivalent to
           open(path, O_WRONLY|O_CREAT|O_TRUNC, mode). First test with a path
           that does not exist to verify the file was created and then test
           with a path that does exist to verify that the file is truncated.
        """
        self.test_group("Verify POSIX API creat() on %s" % self.nfsstr())

        try:
            fd = None
            mode = 0o754
            self.get_filename()
            self.dprint('DBG3', "Create file %s using POSIX API creat()" % self.absfile)
            fd = self.libc.creat(self.absfile.encode(), mode)
            self.test(fd > 0, "creat - creat() should succeed")
            count = posix.write(fd, self.data_pattern(0, self.filesize))
            posix.close(fd)
            self.test(os.path.exists(self.absfile), "creat - file should be created")
            fstat = os.stat(self.absfile)
            self.test(fstat.st_mode & 0o777 == mode, "creat - mode permissions of created file should be correct")
            self.test(fstat.st_size == count, "creat - file size should be correct")

            self.dprint('DBG3', "Open existing file %s using POSIX API creat()" % self.absfile)
            fd = self.libc.creat(self.absfile.encode(), 0o744)
            self.test(fd > 0, "creat - existent file open should succeed")
            posix.close(fd)
            fstat = os.stat(self.absfile)
            self.test(os.path.exists(self.absfile), "creat - file should still exist")
            self.test(fstat.st_mode & 0o777 == mode, "creat - mode permissions of opened file should not be changed")
            self.test(fstat.st_size == 0, "creat - existent file should be truncated")
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def _do_io(self, fd, dmsg, pattern=None):
        """Wrapper to do I/O."""
        self.dprint('DBG3', dmsg)
        if self.posix_read:
            return posix.read(fd, self.posix_iosize)
        else:
            count = posix.write(fd, self.data_pattern(self.posix_offset, self.posix_iosize, pattern=pattern))
            self.posix_offset += count
            return count

    def _fcntl_test(self, objtype):
        """Verify the POSIX API fcntl() commands F_DUPFD, F_GETFD, F_SETFD,
           F_GETFL, F_SETFL, and FD_CLOEXEC. The F_DUPFD command is tested
           by performing operations on the original and dupped file descriptor
           to ensure they behave correctly. The F_GETFD and F_SETFD commands
           are tested by setting the FD_CLOEXEC flag and making sure it gets set.
           The F_GETFL and F_SETFL commands are tested by setting the O_APPEND
           flag and making sure it gets set.
        """
        try:
            fd = None
            if objtype == 'read':
                str_ing = 'reading'
                str_ed = 'read'
                str_lck = 'F_RDLCK'
                mode = posix.O_RDONLY
                lock_type = fcntl.F_RDLCK
                self.posix_read = True
                self.absfile = self.abspath(self.files[0])
            else:
                str_ing = 'writing'
                str_ed = 'written'
                str_lck = 'F_WRLCK'
                mode = posix.O_WRONLY|posix.O_CREAT
                lock_type = fcntl.F_WRLCK
                self.posix_read = False
                self.get_filename()

            self.test_info("fcntl on %s file descriptor" % objtype)
            self.dprint('DBG3', "Open file %s for %s" % (self.absfile, str_ing))
            fd = posix.open(self.absfile, mode)
            self.posix_offset = 0
            self.posix_iosize = 64
            test_pos = 32
            nfd = 1000

            for i in range(10):
                self._do_io(fd, "%s file" % objtype.capitalize())

            self.dprint('DBG3', "DUP file descriptor using POSIX API fcntl(fd, F_DUPFD, n)")
            self.libc.fcntl.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int]
            fd2 = self.libc.fcntl(fd, fcntl.F_DUPFD, nfd)
            self.test(fd2 > 0, "fcntl - F_DUPFD should succeed")
            self.test(fd2 >= nfd, "fcntl - DUP'ed file descriptor should be greater than or equal to the value given")

            self.dprint('DBG3', "Reposition file pointer using DUP'ed file descriptor")
            posix.lseek(fd2, test_pos, os.SEEK_SET)
            pos = posix.lseek(fd, 0, os.SEEK_CUR)
            self.test(pos == test_pos, "fcntl - repositioning file pointer using DUP'ed file descriptor should reposition it in the original file descriptor as well")
            self.posix_offset = pos
            out = self._do_io(fd, "%s file using original file descriptor" % objtype.capitalize(), pattern=b"-")
            if self.posix_read:
                data = self.data_pattern(test_pos, self.posix_iosize)
            else:
                data = self.data_pattern(test_pos, self.posix_iosize, pattern=b"-")
                with open(self.absfile, "rb") as fdr:
                    fdr.seek(test_pos)
                    out = fdr.read(self.posix_iosize)
            self.test(out == data, "fcntl - %s data is correct when repositioning file pointer using DUP'ed file descriptor" % str_ed)

            fdflags = self.libc.fcntl(fd, fcntl.F_GETFD, 0)
            self.test(fdflags >= 0, "fcntl - F_GETFD should succeed")
            out = self.libc.fcntl(fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
            self.test(out != -1, "fcntl - F_SETFD should succeed")
            fdflags = self.libc.fcntl(fd, fcntl.F_GETFD, 0)
            self.test(fdflags & fcntl.FD_CLOEXEC == fcntl.FD_CLOEXEC, "fcntl - F_SETFD should set the given file descriptor flag correctly")
            fdflags = self.libc.fcntl(fd2, fcntl.F_GETFD, 0)
            self.test(fdflags & fcntl.FD_CLOEXEC != fcntl.FD_CLOEXEC, "fcnlt - F_SETFD should change the flag for the given file descriptor only")

            flflags = self.libc.fcntl(fd, fcntl.F_GETFL, 0)
            self.test(flflags >= 0, "fcntl - F_GETFL should succeed")
            out = self.libc.fcntl(fd, fcntl.F_SETFL, os.O_APPEND)
            self.test(out != -1, "fcntl - F_SETFL should succeed")
            flflags = self.libc.fcntl(fd, fcntl.F_GETFL, 0)
            self.test(flflags & os.O_APPEND == os.O_APPEND, "fcntl - F_SETFL should set the given file status flag correctly")
            flflags = self.libc.fcntl(fd2, fcntl.F_GETFL, 0)
            self.test(flflags & os.O_APPEND == os.O_APPEND, "fcnlt - F_SETFL should change the flag for all file descriptors on the same file")

            if not self.posix_read:
                self.dprint('DBG3', "Reposition file pointer using DUP'ed file descriptor")
                posix.lseek(fd2, test_pos, os.SEEK_SET)
                pos = posix.lseek(fd, 0, os.SEEK_CUR)
                self.test(pos == test_pos, "fcntl - repositioning file pointer using DUP'ed file descriptor should reposition it in the original file descriptor as well")
                self.posix_offset = pos
                out = self._do_io(fd2, "%s file using DUP'ed file descriptor" % objtype.capitalize(), pattern=b"+")

                data = self.data_pattern(test_pos, self.posix_iosize, pattern=b"+")
                with open(self.absfile, "rb") as fdr:
                    fdr.seek(-self.posix_iosize, os.SEEK_END)
                    out = fdr.read(self.posix_iosize)
                self.test(out == data, "fcntl - data is %s correctly at the end of the file when O_APPEND is set regardless where the file pointer is set" % str_ed)

            posix.close(fd)

            try:
                werrno = 0
                self._do_io(fd, "%s file using original file descriptor" % objtype.capitalize())
            except OSError as e:
                werrno = e.errno
            self.test(werrno == errno.EBADF, "fcntl - %s after closing original file descriptor should return an error" % objtype)

            flock = Flock(lock_type, 0, 0, int(self.filesize/2), 0)
            self.libc.fcntl.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.POINTER(Flock)]
            out = self.libc.fcntl(fd2, fcntl.F_SETLKW, ctypes.byref(flock))
            self.test(out != -1, "fcntl - F_SETLKW (%s) should succeed" % str_lck)

            pos = posix.lseek(fd2, 0, os.SEEK_SET)
            self.posix_offset = pos
            out = self._do_io(fd2, "%s file using DUP'ed file descriptor" % objtype.capitalize())
            if self.posix_read:
                test_expr = out == self.data_pattern(0, self.posix_iosize)
            else:
                test_expr = out == self.posix_iosize
            self.test(test_expr, "fcntl - %s on DUP'ed descriptor after closing original file descriptor should succeed" % objtype)

            flock = Flock(fcntl.F_UNLCK, 0, 0, int(self.filesize/4), 0)
            out = self.libc.fcntl(fd2, fcntl.F_SETLKW, ctypes.byref(flock))
            self.test(out != -1, "fcntl - F_SETLKW (F_UNLCK) should succeed")

            flock = Flock(lock_type, 0, int(self.filesize/2), self.filesize, 0)
            out = self.libc.fcntl(fd2, fcntl.F_SETLK, ctypes.byref(flock))
            self.test(out != -1, "fcntl - F_SETLK (%s) should succeed" % str_lck)

            self._do_io(fd2, "%s file using DUP'ed file descriptor" % objtype.capitalize())

            flock = Flock(fcntl.F_UNLCK, 0, int(self.filesize/2), int(self.filesize/4), 0)
            out = self.libc.fcntl(fd2, fcntl.F_SETLK, ctypes.byref(flock))
            self.test(out != -1, "fcntl - F_SETLK (F_UNLCK) should succeed")

            posix.close(fd2)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd, fd2)

    def fcntl_test(self):
        """Verify the POSIX API fcntl() commands F_DUPFD, F_GETFD, F_SETFD,
           F_GETFL, F_SETFL, and FD_CLOEXEC. The F_DUPFD command is tested
           by performing operations on the original and dupped file descriptor
           to ensure they behave correctly. The F_GETFD and F_SETFD commands
           are tested by setting the FD_CLOEXEC flag and making sure it gets set.
           The F_GETFL and F_SETFL commands are tested by setting the O_APPEND
           flag and making sure it gets set.
           Run the test for both 'read' and 'write'.
        """
        self.test_group("Verify POSIX API fcntl() on %s" % self.nfsstr())

        self._fcntl_test('read')
        self._fcntl_test('write')

    def fdatasync_test(self):
        """Verify POSIX API fdatasync()."""
        self._sync('fdatasync')

    def fstat_test(self):
        """Verify POSIX API fstat() by checking the mode on a file and that
           it returns the expected structure members. Create a symlink and
           verify that fstat returns information about the link.
        """
        self._stat_test(stat_mode=3)

    def _statvfs_test(self, path=None, fd=None, objtype='file'):
        """Verify POSIX API statvfs() or fstatvfs() by making sure
           all the members of the structure are returned.
           Options path and fd are mutually exclusive.

           path:
               Verify statvfs()
           fd:
               Verify fstatvfs()
           objtype:
               Object type 'file' or 'directory' [default: 'file']
        """
        if path != None:
            op_str = 'statvfs'
            self.dprint('DBG3', "statvfs %s [%s]" % (objtype, path))
            amsg = "statvfs - statvfs() should succeed"
            stv = self.run_func(posix.statvfs, path, msg=amsg)
        elif fd != None:
            op_str = 'fstatvfs'
            self.dprint('DBG3', "fstatvfs %s (%d)" % (objtype, fd))
            amsg = "fstatvfs - fstatvfs() should succeed"
            stv = self.run_func(posix.fstatvfs, fd, msg=amsg)

        self.test(stv.f_bsize != None,   "%s - f_bsize should be returned for %s" % (op_str, objtype))
        self.test(stv.f_frsize != None,  "%s - f_frsize should be returned for %s" % (op_str, objtype))
        self.test(stv.f_blocks != None,  "%s - f_blocks should be returned for %s" % (op_str, objtype))
        self.test(stv.f_bfree != None,   "%s - f_bfree should be returned for %s" % (op_str, objtype))
        self.test(stv.f_bavail != None,  "%s - f_bavail should be returned for %s" % (op_str, objtype))
        self.test(stv.f_files != None,   "%s - f_files should be returned for %s" % (op_str, objtype))
        self.test(stv.f_ffree != None,   "%s - f_ffree should be returned for %s" % (op_str, objtype))
        self.test(stv.f_favail != None,  "%s - f_favail should be returned for %s" % (op_str, objtype))
        self.test(stv.f_flag != None,    "%s - f_flag should be returned for %s" % (op_str, objtype))
        self.test(stv.f_namemax != None, "%s - f_namemax should be returned for %s" % (op_str, objtype))

    def fstatvfs_test(self):
        """Verify POSIX API fstatvfs() by making sure all the members of the
           structure are returned.
        """
        self.test_group("Verify POSIX API fstatvfs() on %s" % self.nfsstr())

        try:
            fd = None
            self.create_file()
            self.dprint('DBG3', "Open file %s for reading" % self.absfile)
            fd = posix.open(self.absfile, posix.O_RDONLY)
            self._statvfs_test(fd=fd)
            posix.close(fd)

            self.create_dir()
            self.dprint('DBG3', "Open directory %s" % self.absdir)
            fd = posix.open(self.absdir, posix.O_RDONLY|posix.O_NONBLOCK|posix.O_DIRECTORY)
            self._statvfs_test(fd=fd, objtype='directory')
            posix.close(fd)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def fsync_test(self):
        """Verify POSIX API fsync()."""
        self._sync('fsync')

    def link_test(self):
        """Verify POSIX API link(src, dst) creates a link and updates
           st_ctime field for the file. Verify that link updates the
           st_ctime and st_mtime for the directory. Verify st_link count
           incremented by 1 for the file.
        """
        self.test_group("Verify POSIX API link() on %s" % self.nfsstr())

        try:
            # Get info of source file
            srcfile = self.abspath(self.files[0])
            fstat_b = os.stat(srcfile)
            dstat_b = os.stat(self.mtdir)
            sleep(1.1)

            self.get_filename()
            self.dprint('DBG3', "Create link %s -> %s using POSIX API link()" % (self.absfile, srcfile))
            amsg = "link - link() should succeed"
            self.run_func(posix.link, srcfile, self.absfile, msg=amsg)
            lstat = os.stat(self.absfile)
            fstat = os.stat(srcfile)
            dstat = os.stat(self.mtdir)
            self.test(lstat != None, "link - link should be created")
            self.test(fstat.st_nlink == fstat_b.st_nlink+1, "link - file st_nlink should be incremented by 1")
            self.test(fstat.st_ctime > fstat_b.st_ctime, "link - file st_ctime should be updated")
            self.test(dstat.st_ctime > dstat_b.st_ctime, "link - parent directory st_ctime should be updated")
            self.test(dstat.st_mtime > dstat_b.st_mtime, "link - parent directory st_mtime should be updated")
        except Exception:
            self.test(False, traceback.format_exc())

    def lseek_test(self):
        """Verify POSIX API lseek() with different offsets and whence
           values including seeking past the end of the file.
        """
        self.test_group("Verify POSIX API lseek() on %s" % self.nfsstr())

        try:
            fd = None
            data  = b"AAAAAAAAAAAAAAAAAAAA"
            dataB = b'BBBB'
            dataC = b'CCCCCC'
            dataD = b'DDDDDDDD'
            self.get_filename()
            self.dprint('DBG3', "Open file %s for writing" % self.absfile)
            fd = posix.open(self.absfile, posix.O_WRONLY|posix.O_CREAT)
            self.dprint('DBG3', "Write file [%s]" % data)
            count = posix.write(fd, data)
            self.test(count == len(data), "lseek - write should succeed at offset=0")
            self.dprint('DBG3', "Set file pointer lseek(4, SEEK_SET)")
            offset = self.run_func(posix.lseek, fd, 4, os.SEEK_SET)
            self.test(offset == 4, "lseek - offset: 4, whence: SEEK_SET succeeds")
            self.dprint('DBG3', "Write file [%s]" % dataB)
            count = posix.write(fd, dataB)
            self.dprint('DBG3', "Set file pointer lseek(8, SEEK_CUR)")
            offset = self.run_func(posix.lseek, fd, 8, os.SEEK_CUR)
            self.test(offset == 16, "lseek - offset: 8, whence: SEEK_CUR succeeds")
            self.dprint('DBG3', "Write file [%s]" % dataC)
            count = posix.write(fd, dataC)
            self.dprint('DBG3', "Set file pointer lseek(1000, SEEK_END)")
            offset = self.run_func(posix.lseek, fd, 1000, os.SEEK_END)
            self.test(offset == 1022, "lseek - offset: 1000, whence: SEEK_END succeeds")
            self.dprint('DBG3', "Write file [%s]" % dataD)
            count = posix.write(fd, dataD)
            if self.nfs_version > 2:
                N = 2*1024*1024*1024
                self.dprint('DBG3', "Set file pointer lseek(2GB, SEEK_SET)")
                offset = self.run_func(posix.lseek, fd, N, os.SEEK_SET)
                self.test(offset == N, "lseek - offset: 2GB, whence: SEEK_SET succeeds")
                self.dprint('DBG3', "Set file pointer lseek(2GB-1, SEEK_CUR)")
                offset = self.run_func(posix.lseek, fd, N-1, os.SEEK_CUR)
                self.test(offset == 2*N-1, "lseek - offset: 4GB-1, whence: SEEK_CUR succeeds")
                self.dprint('DBG3', "Set file pointer lseek(2GB, SEEK_END)")
                offset = self.run_func(posix.lseek, fd, N, os.SEEK_END)
                self.test(offset == N+1030, "lseek - offset: 2GB+22, whence: SEEK_END succeeds")
            posix.close(fd)

            self.dprint('DBG3', "Open file %s for reading" % self.absfile)
            fd = posix.open(self.absfile, posix.O_RDONLY)
            self.dprint('DBG3', "Set file pointer lseek(4, SEEK_SET)")
            offset = self.run_func(posix.lseek, fd, 4, os.SEEK_SET)
            self.dprint('DBG3', "Read 4 bytes")
            data = posix.read(fd, 4)
            self.test(data == dataB, "lseek - read data at offset = 4 should be correct")
            self.dprint('DBG3', "Set file pointer lseek(16, SEEK_SET)")
            offset = self.run_func(posix.lseek, fd, 16, os.SEEK_SET)
            self.dprint('DBG3', "Read 6 bytes")
            data = posix.read(fd, 6)
            self.test(data == dataC, "lseek - read data at offset = 16 should be correct")
            self.dprint('DBG3', "Set file pointer lseek(1022, SEEK_SET)")
            offset = self.run_func(posix.lseek, fd, 1022, os.SEEK_SET)
            self.dprint('DBG3', "Read 8 bytes")
            data = posix.read(fd, 8)
            self.test(data == dataD, "lseek - read data at offset = 1022 should be correct")
            posix.close(fd)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def _stat_obj(self, mode, stat_mode=1, type='file', srcfile=None, srctype='file'):
        """Verify POSIX API stat()/lstat()/fstat() by checking the mode on a file and
           that it returns the expected structure members. Create a symlink and
           verify that stat()/lstat()/fstat() returns information about the file/link.

           mode:
               Expected permission mode
           stat_mode:
               Stat function to use (1: 'stat', 2: 'lstat', 3: 'fstat')
           type:
               Type of file system object under test [default: 'file']
           srcfile:
               Source file of symbolic link [default: None]
           srctype:
               Type of file system object for source of symbolic link [default: 'file']
        """
        op_str = stat_map[stat_mode]
        reg = 'regular ' if type == 'file' else ''
        size = self.filesize
        nlink = 1
        if type == 'file' or (srcfile != None and srctype == 'file'):
            is_func = stat.S_ISREG
        elif type == 'directory' or (srcfile != None and srctype == 'directory'):
            is_func = stat.S_ISDIR
            nlink = 2
            stv = os.statvfs(self.absfile)
            size = stv.f_bsize

        if stat_mode == 1:
            self.dprint('DBG3', "stat %s [%s]" % (type, self.absfile))
            fstat = self.run_func(posix.stat, self.absfile)
        elif stat_mode == 2:
            self.dprint('DBG3', "lstat %s [%s]" % (type, self.absfile))
            fstat = self.run_func(posix.lstat, self.absfile)
            if srcfile != None:
                mode = 0o777
                size = len(srcfile)
                is_func = stat.S_ISLNK
                nlink = 1
        else:
            try:
                fd = None
                self.dprint('DBG3', "fstat %s [%s]" % (type, self.absfile))
                if type == 'directory':
                    self.dprint('DBG3', "Open directory %s" % self.absfile)
                    fd = posix.open(self.absfile, posix.O_RDONLY|posix.O_NONBLOCK|posix.O_DIRECTORY)
                else:
                    self.dprint('DBG3', "Open file %s for reading" % self.absfile)
                    fd = posix.open(self.absfile, posix.O_RDONLY)
                fstat = self.run_func(posix.fstat, fd)
                posix.close(fd)
            finally:
                self.close_files(fd)

        if type != "symbolic link":
            self.test(fstat.st_mode & 0o777 == mode, "%s - %s permissions should be correct" % (op_str, type), failmsg=", expecting %05o and got %05o" % (mode, fstat.st_mode & 0o777))
        self.test(is_func(fstat.st_mode), "%s - object type should be a %s%s" % (op_str, reg, type))
        self.test(fstat.st_nlink == nlink, "%s - %s st_nlink should be equal to %d" % (op_str, type, nlink), failmsg=", expecting %d and got %d" % (nlink, fstat.st_nlink))
        self.test(fstat.st_uid == os.getuid(), "%s - %s st_uid should be correct" % (op_str, type), failmsg=", expecting %d and got %d" % (os.getuid(), fstat.st_uid))
        self.test(fstat.st_gid == os.getgid(), "%s - %s st_gid should be correct" % (op_str, type), failmsg=", expecting %d and got %d" % (os.getgid(), fstat.st_gid))
        expr = (fstat.st_size == size) if type == 'file' else True
        self.test(expr, "%s - %s st_size should be correct" % (op_str, type), failmsg=", expecting %d and got %d" % (size, fstat.st_size))
        self.test(fstat.st_ino != None, "%s - %s st_ino should be returned" % (op_str, type))
        self.test(fstat.st_dev != None, "%s - %s st_dev should be returned" % (op_str, type))
        self.test(fstat.st_atime != None, "%s - %s st_atime should be returned" % (op_str, type))
        self.test(fstat.st_mtime != None, "%s - %s st_mtime should be returned" % (op_str, type))
        self.test(fstat.st_ctime != None, "%s - %s st_ctime should be returned" % (op_str, type))

    def _stat_test(self, stat_mode=1):
        """Verify POSIX API stat()/lstat()/fstat() by checking the mode on a file and
           that it returns the expected structure members. Create a symlink and
           verify that stat()/lstat()/fstat() returns information about the file/link.

           Test a regular file, directory, symbolic link to a file,
           and symbolic link to a directory.
        """
        op_str = stat_map[stat_mode]
        self.test_group("Verify POSIX API %s() on %s" % (op_str, self.nfsstr()))

        try:
            self.test_info("Regular file")
            self.create_file(mode=0o754)
            self._stat_obj(0o754, stat_mode=stat_mode, type='file')
            testfile = self.absfile

            self.test_info("Directory")
            self.create_dir(mode=0o755)
            self.absfile = self.absdir
            self._stat_obj(0o755, stat_mode=stat_mode, type='directory')
            testdir = self.absdir

            self.test_info("Symbolic link to a file")
            srcfile = testfile
            self.get_filename()
            self.dprint('DBG3', "Creating symbolic link to file [%s -> %s]" % (self.absfile, srcfile))
            os.symlink(srcfile, self.absfile)
            self._stat_obj(0o754, stat_mode=stat_mode, type='symbolic link', srcfile=srcfile)

            self.test_info("Symbolic link to a directory")
            srcfile = testdir
            self.get_filename()
            self.dprint('DBG3', "Creating symbolic link to directory [%s -> %s]" % (self.absfile, srcfile))
            os.symlink(srcfile, self.absfile)
            self._stat_obj(0o755, stat_mode=stat_mode, type='symbolic link', srcfile=srcfile, srctype='directory')
        except Exception:
            self.test(False, traceback.format_exc())

    def lstat_test(self):
        """Verify POSIX API lstat() by checking the mode on a file and that
           it returns the expected structure members. Create a symlink and
           verify that lstat returns information about the link.
        """
        self._stat_test(stat_mode=2)

    def mkdir_test(self):
        """Verify POSIX API mkdir(). Verify that mkdir with a path of a symbolic
           link fails. Verify that the st_ctime and st_mtime fields of the
           parent directory are updated.
        """
        self.test_group("Verify POSIX API mkdir() on %s" % self.nfsstr())

        try:
            topdir = self.create_dir()
            abstopdir = self.absdir
            dstat_b = os.stat(abstopdir)
            sleep(1)

            mode = 0o754
            self.get_dirname(dir=topdir)
            self.dprint('DBG3', "Creating directory [%s]" % self.absdir)
            amsg = "mkdir - mkdir() should succeed"
            self.run_func(posix.mkdir, self.absdir, mode, msg=amsg)
            dstat = os.stat(self.absdir)
            self.test(os.path.exists(self.absdir), "mkdir - directory should be created")
            self.test(dstat.st_mode & 0o777 == mode, "mkdir - mode permissions of created directory should be correct")

            dstat = os.stat(abstopdir)
            self.test(dstat.st_ctime > dstat_b.st_ctime, "mkdir - parent directory st_ctime should be updated")
            self.test(dstat.st_mtime > dstat_b.st_mtime, "mkdir - parent directory st_mtime should be updated")

            amsg = "mkdir - create directory should return an error if name already exists as a directory"
            self.run_func(posix.mkdir, self.absdir, mode, msg=amsg, err=errno.EEXIST)

            self.create_file()
            amsg = "mkdir - create directory should return an error if name already exists as a file"
            self.run_func(posix.mkdir, self.absfile, mode, msg=amsg, err=errno.EEXIST)

            self.get_filename()
            self.dprint('DBG3', "Creating symbolic link to directory [%s -> %s]" % (self.absfile, abstopdir))
            os.symlink(abstopdir, self.absfile)
            amsg = "mkdir - create directory should return an error if name already exists as a symbolic link"
            self.run_func(posix.mkdir, self.absfile, mode, msg=amsg, err=errno.EEXIST)
        except Exception:
            self.test(False, traceback.format_exc())

    def _mmap_test(self, objtype):
        """Verify POSIX API mmap() by mapping a file and verifying I/O
           operations. Verify mmap followed by memory read of existing
           file works. Verify mmap followed by memory write to file works.

           Verify POSIX API munmap() by mapping a file and then unmapping
           the file.
        """
        self.test_group("Verify POSIX API %s() on %s" % (objtype, self.nfsstr()))
        N = self.filesize

        try:
            fd = None
            addr = 0
            map_len = N
            absfile = self.abspath(self.files[0])
            self.dprint('DBG3', "Open file %s for reading" % absfile)
            fd = posix.open(absfile, posix.O_RDONLY)

            self.dprint('DBG3', "mmap file for reading")
            addr = self.libc.mmap(0, N, mmap.PROT_READ, mmap.MAP_SHARED, fd, 0)
            self.test(addr > 0, "%s - mmap for reading should succeed" % objtype)
            ptr = ctypes.cast(addr, ctypes.c_char_p)
            self.test(ptr.value[:N] == self.data_pattern(0,N), "%s - read data should be correct" % objtype)
            self.dprint('DBG3', "munmap file")
            out = self.libc.munmap(addr, N)
            addr = 0
            self.test(out == 0, "%s - munmap should succeed" % objtype)

            self.dprint('DBG3', "mmap file for reading using aligned offset")
            map_len = N-self.PAGESIZE
            addr = self.libc.mmap(0, map_len, mmap.PROT_READ, mmap.MAP_SHARED, fd, self.PAGESIZE)
            self.test(addr > 0, "%s - mmap using aligned offset should succeed" % objtype)
            ptr = ctypes.cast(addr, ctypes.c_char_p)
            self.test(ptr.value[:map_len] == self.data_pattern(self.PAGESIZE,map_len), "%s - read data should be correct" % objtype)
            self.dprint('DBG3', "munmap file")
            out = self.libc.munmap(addr, map_len)
            addr = 0
            self.test(out == 0, "%s - munmap should succeed" % objtype)

            self.dprint('DBG3', "mmap file for reading")
            map_len = N
            addr = self.libc.mmap(0, N, mmap.PROT_READ, mmap.MAP_SHARED, fd, 0)
            self.test(addr > 0, "%s - mmap should succeed" % objtype)
            ptr = ctypes.cast(addr, ctypes.c_char_p)
            # Closing the file descriptor does not unmap the region
            self.dprint('DBG3', "Close file")
            posix.close(fd)
            self.test(ptr.value[:N] == self.data_pattern(0,N), "%s - read data after file descriptor has been closed should be correct" % objtype)
            self.dprint('DBG3', "munmap file")
            out = self.libc.munmap(addr, N)
            addr = 0
            self.test(out == 0, "%s - munmap after the file has been closed should succeed" % objtype)

            self.get_filename()
            self.dprint('DBG3', "Open file %s for reading and writing" % self.absfile)
            fd = posix.open(self.absfile, posix.O_RDWR|posix.O_CREAT)
            # Write a dummy byte at end of file to reserve space on the file
            posix.lseek(fd, N-1, os.SEEK_SET)
            posix.write(fd, b'\000')

            if objtype == 'mmap':
                self.dprint('DBG3', "mmap file using length of 0")
                addr = self.libc.mmap(0, 0, mmap.PROT_READ|mmap.PROT_WRITE, mmap.MAP_SHARED, fd, 0)
                self.test(addr == -1, "%s - mmap with length of 0 should return an error" % objtype)
                self.dprint('DBG3', "mmap file using a non-aligned offset")
                addr = self.libc.mmap(0, N, mmap.PROT_READ|mmap.PROT_WRITE, mmap.MAP_SHARED, fd, 1)
                self.test(addr == -1, "%s - mmap with non-aligned offset should return an error" % objtype)

            self.dprint('DBG3', "mmap file for writing")
            addr = self.libc.mmap(0, N, mmap.PROT_WRITE, mmap.MAP_SHARED, fd, 0)
            self.test(addr > 0, "%s - mmap for writing should succeed" % objtype)
            ptr = ctypes.cast(addr, ctypes.c_char_p)
            self.libc.memcpy(ptr, self.data_pattern(0,N), N)
            self.dprint('DBG3', "munmap file")
            if objtype == 'munmap':
                self.dprint('DBG3', "munmap using length of 0")
                out = self.libc.munmap(addr, 0)
                self.test(out == -1, "%s - munmap with length of 0 should return an error" % objtype)
            out = self.libc.munmap(addr, N)
            addr = 0
            self.test(out == 0, "%s - munmap with correct length should succeed" % objtype)
            posix.close(fd)

            self.dprint('DBG3', "Read data from file to verify data written using mmap region")
            with open(self.absfile, "rb") as fdr:
                data = fdr.read()
            self.test(data == self.data_pattern(0,N), "%s - written data should be correct" % objtype)

            absfile = self.abspath(self.files[0])
            fd = posix.open(absfile, posix.O_RDONLY)
            posix.close(fd)

            if objtype == 'mmap':
                self.dprint('DBG3', "mmap on closed file")
                addr = self.libc.mmap(0, N, mmap.PROT_READ, mmap.MAP_SHARED, fd, 0)
                self.test(addr == -1, "%s - mmap on closed file should return an error" % objtype)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)
            if addr > 0:
                # Make sure to also unmap the file region
                self.libc.munmap(addr, map_len)

    def mmap_test(self):
        """Verify POSIX API mmap() by mapping a file and verifying I/O
           operations. Verify mmap followed by memory read of existing
           file works. Verify mmap followed by memory write to file works.
        """
        self._mmap_test('mmap')

    def munmap_test(self):
        """Verify POSIX API munmap() by mapping a file and then unmapping
           the file.
        """
        self._mmap_test('munmap')

    def _oserror(self, oserrno, err):
        """Internal method to return fail message when expecting an error"""
        fmsg = ""
        # Test expression to return
        expr = oserrno == err
        if oserrno == 0:
            fmsg = ": no error was returned"
        elif oserrno != err:
            # Got the wrong error
            expected = errno.errorcode[err]
            error = errno.errorcode[oserrno]
            fmsg =  ": expecting %s, got %s" % (expected, error)
        return (expr, fmsg)

    def open_test(self):
        """Verify POSIX API open() on a file. Verify file creation using
           the O_CREAT flag and verifying the file mode is set to the
           specified value. Verify the st_ctime and st_mtime are updated
           on the parent directory after the file was created. Verify open
           on existing file fails with the O_EXCL flag set. Verify write
           succeeds and read fails if file was open with O_WRONLY. Verify
           read succeeds and write fails if file was open with O_RDONLY.
           Verify that all writes with O_APPEND set are to the end of the file.
           Use O_DSYNC, O_RSYNC, and O_SYNC flags in open calls.
           Verify file open with O_CREAT and O_TRUNC set will truncate an
           existing file. Verify that it updates the file st_ctime and st_mtime.
        """
        self.test_group("Verify POSIX API open() on %s" % self.nfsstr())

        self.set_nfserr_list(
            nfs3list=[nfs3_const.NFS3ERR_NOENT, nfs3_const.NFS3ERR_INVAL],
            nfs4list=[nfs4_const.NFS4ERR_NOENT, nfs4_const.NFS4ERR_INVAL,
                      nfs4_const.NFS4ERR_OPENMODE, nfs4_const.NFS4ERR_BADXDR],
        )

        flist = []
        flag_list = list(open_flag_list)
        try:
            # Remove flag with no bits set
            flag_list.remove(0)
            flist += [(0,)]
        except:
            pass

        # Create a list of all possible combination of flags
        for i in range(1, len(flag_list)+1):
            flist += list(itertools.combinations(flag_list, i))

        iosize = 1024
        EXISTENT    = 1
        NONEXISTENT = 2
        SYMLINK     = 3
        fmap = {
            EXISTENT    : 'existent file',
            NONEXISTENT : 'non-existent file',
            SYMLINK     : 'symbolic link',
        }

        for ftype in [NONEXISTENT, EXISTENT, SYMLINK]:
            srcfile = None
            if ftype == NONEXISTENT:
                # Get a new name for non-existent file
                self.get_filename()
            else:
                # Create file to use as existent file or as the source file
                # for the symbolic link
                self.create_file()
                os.chmod(self.absfile, 0o777)
                if ftype == SYMLINK:
                    # Create symbolic link
                    srcfile = self.absfile
                    self.get_filename()
                    self.dprint('DBG3', "Creating symbolic link file [%s -> %s]" % (self.absfile, srcfile))
                    os.symlink(srcfile, self.absfile)

            # Get the name for file under test
            filename = os.path.basename(self.absfile)

            for flags in flist:
                try:
                    tryopen = False
                    opened = False
                    flag_str = ", flags %s" % oflag_str(flags)
                    wflag_str = " with flags %s" % oflag_str(flags)
                    mode = 0
                    for flag in flags:
                        mode |= flag

                    perms = 0o700 | random.randint(0,0o77)
                    if perms == 0o777:
                        # Change open permissions to make it different than current
                        # file permissions
                        perms = 0o755

                    # Booleans for checking file if able to read or/and write
                    is_write_allowed = posix.O_WRONLY in flags or posix.O_RDWR in flags
                    is_read_allowed = posix.O_WRONLY not in flags or posix.O_RDWR in flags

                    if ftype in [EXISTENT, SYMLINK]:
                        # Get file stats before open
                        ofstat = os.stat(self.absfile)

                    try:
                        openerr = 0
                        tryopen = True
                        self.dprint('DBG7', "Open %s %s%s" % (fmap[ftype], filename, wflag_str))
                        fd = posix.open(self.absfile, mode, perms)
                        opened = True
                    except OSError as e:
                        openerr = e.errno
                        self.dprint('DBG7', "Open error: %s" % e.strerror)
                    strerror = ": %s" % os.strerror(openerr) if openerr != 0 else ""

                    fstat = None
                    if opened:
                        # Get file stats after open
                        fstat = posix.fstat(fd)

                    if ftype in [EXISTENT, SYMLINK]:
                        if posix.O_EXCL in flags and posix.O_CREAT in flags:
                            # O_EXCL and O_CREAT are set
                            (expr, fmsg) = self._oserror(openerr, errno.EEXIST)
                            msg = "open - opening %s should return an error when O_EXCL|O_CREAT is used" % fmap[ftype]
                            self.test(expr, msg, subtest=flag_str, failmsg=fmsg)
                        elif openerr != 0 and posix.O_EXCL in flags and posix.O_CREAT not in flags:
                            msg = "open - opening %s should be unspecified when O_EXCL is used and O_CREAT is not specified" % fmap[ftype]
                            self.test(True, msg, subtest=flag_str)
                        elif posix.O_DIRECTORY in flags and posix.O_CREAT in flags:
                            msg = "open - opening %s should be unspecified when O_DIRECTORY|O_CREAT is used" % fmap[ftype]
                            self.test(True, msg, subtest=flag_str)
                        elif posix.O_DIRECTORY in flags:
                            # O_DIRECTORY is set
                            (expr, fmsg) = self._oserror(openerr, errno.ENOTDIR)
                            if not expr and ftype == SYMLINK:
                                (expr, fmsg) = self._oserror(openerr, errno.ELOOP)
                            msg = "open - opening %s should return an error when O_DIRECTORY is used" % fmap[ftype]
                            self.test(expr, msg, subtest=flag_str, failmsg=fmsg)
                        elif posix.O_WRONLY not in flags and posix.O_RDWR not in flags and posix.O_TRUNC in flags:
                            # O_RDONLY and O_TRUNC are set
                            msg = "open - opening %s should be unspecified when O_RDONLY|O_TRUNC is used" % fmap[ftype]
                            self.test(True, msg, subtest=flag_str)
                        elif posix.O_NOFOLLOW in flags and ftype == SYMLINK:
                            (expr, fmsg) = self._oserror(openerr, errno.ELOOP)
                            msg = "open - opening %s should return an error when O_NOFOLLOW is used" % fmap[ftype]
                            self.test(expr, msg, subtest=flag_str, failmsg=fmsg)
                        elif posix.O_NOATIME in flags and openerr != 0:
                            msg = "open - opening %s should be unspecified when O_NOATIME is used" % fmap[ftype]
                            self.test(True, msg, subtest=wflag_str)
                        else:
                            msg = "open - opening %s should succeed" % fmap[ftype]
                            self.test(openerr == 0, msg, subtest=wflag_str, failmsg=strerror)
                            if openerr == 0:
                                expr = fstat.st_mode & 0o777 == ofstat.st_mode & 0o777
                                msg = "open - permission mode should not be changed when opening %s" % fmap[ftype]
                                fmsg = ": changed from %04o to %04o" % (ofstat.st_mode & 0o777, fstat.st_mode & 0o777)
                                self.test(expr, msg, subtest=wflag_str, failmsg=fmsg)
                    else:
                        if openerr != 0 and posix.O_CREAT in flags and posix.O_DIRECTORY in flags:
                            msg = "open - opening %s should be unspecified when O_CREAT|O_DIRECTORY is used" % fmap[ftype]
                            self.test(True, msg, subtest=flag_str)
                        elif posix.O_CREAT in flags:
                            # O_CREAT is set
                            msg = "open - file should be created when O_CREAT is used"
                            file_exists = os.path.exists(self.absfile)
                            self.test(file_exists, msg, subtest=flag_str)
                            if file_exists:
                                if fstat is None:
                                    fstat = os.stat(self.absfile)
                                expr = fstat.st_mode & 0o777 == perms
                                msg = "open - file should be created with correct permission mode"
                                fmsg = ": expecting %04o, got %04o" % (perms, fstat.st_mode & 0o777)
                                self.test(expr, msg, subtest=flag_str, failmsg=fmsg)
                        else:
                            # O_CREAT is not set
                            (expr, fmsg) = self._oserror(openerr, errno.ENOENT)
                            msg = "open - opening %s should return an error when O_CREAT is not used" % fmap[ftype]
                            self.test(expr, msg, subtest=flag_str, failmsg=fmsg)

                    if openerr != 0:
                        if not is_write_allowed and posix.O_EXCL in flags:
                            msg = "open - opening %s should be unspecified when O_RDONLY|O_EXCL" % fmap[ftype]
                            self.test(True, msg, subtest=flag_str)
                        continue

                    if posix.O_TRUNC in flags:
                        expr = fstat.st_size == 0
                        msg = "open - file size should be 0 after open when O_TRUNC is used"
                        fmsg = ": file size = %d" % fstat.st_size
                        self.test(expr, msg, subtest=flag_str, failmsg=fmsg)

                    wdata = self.data_pattern(0, iosize)
                    try:
                        ioerr = 0
                        posix.write(fd, wdata)
                    except OSError as e:
                        ioerr = e.errno
                    wstrerror = ": %s" % os.strerror(ioerr) if ioerr != 0 else ""

                    if ioerr == 0:
                        # Get file stats after write
                        nfstat = posix.fstat(fd)

                    if is_write_allowed:
                        if ioerr != 0 and posix.O_WRONLY in flags and posix.O_RDWR in flags:
                            msg = "open - writing should be unspecified when opening with O_WRONLY|O_RDWR"
                            self.test(True, msg, subtest=flag_str)
                        else:
                            msg = "open - writing should succeed when opening with O_WRONLY or O_RDWR"
                            self.test(ioerr == 0, msg, subtest=flag_str, failmsg=wstrerror)
                            if posix.O_TRUNC in flags:
                                expr = nfstat.st_size == iosize
                                msg = "open - data should be written at the beginning of file when O_TRUNC is used"
                                fmsg = ": expecting file size = %d, got %d" % (iosize, nfstat.st_size)
                                self.test(expr, msg, subtest=flag_str, failmsg=fmsg)
                            elif posix.O_APPEND in flags:
                                expr = nfstat.st_size == fstat.st_size + iosize
                                msg = "open - data should be written at the end of file when O_APPEND is used"
                                fmsg = ": expecting file size = %d, got %d" % (fstat.st_size + iosize, nfstat.st_size)
                                self.test(expr, msg, subtest=flag_str, failmsg=fmsg)
                    else:
                        (expr, fmsg) = self._oserror(ioerr, errno.EBADF)
                        msg = "open - writing should return an error when opening with O_RDONLY"
                        self.test(expr, msg, subtest=flag_str, failmsg=fmsg)

                    try:
                        ioerr = 0
                        posix.lseek(fd, 0, os.SEEK_SET)
                        rdata = posix.read(fd, iosize)
                    except OSError as e:
                        ioerr = e.errno
                    rstrerror = ": %s" % os.strerror(ioerr) if ioerr != 0 else ""

                    if is_read_allowed:
                        if ioerr != 0 and posix.O_WRONLY in flags and posix.O_RDWR in flags:
                            msg = "open - reading should be unspecified when opening with O_WRONLY|O_RDWR"
                            self.test(True, msg, subtest=flag_str)
                        else:
                            msg = "open - reading should succeed when opening with O_RDONLY or O_RDWR"
                            self.test(ioerr == 0, msg, subtest=flag_str, failmsg=rstrerror)
                    else:
                        (expr, fmsg) = self._oserror(ioerr, errno.EBADF)
                        msg = "open - reading should return an error when opening with O_WRONLY"
                        self.test(expr, msg, subtest=flag_str, failmsg=fmsg)

                except Exception:
                    self.test(False, traceback.format_exc())
                finally:
                    if opened:
                        self.dprint('DBG7', "Close file %s" % filename)
                        posix.close(fd)
                    if tryopen and ftype == NONEXISTENT and (opened or os.path.exists(self.absfile)):
                        # Remove file so the file does not exist
                        # for next iteration
                        self.dprint('DBG5', "Removing file [%s]" % self.absfile)
                        os.unlink(self.absfile)

    def opendir_test(self):
        """Verify POSIX API opendir() on a directory."""
        self.test_group("Verify POSIX API opendir() on %s" % self.nfsstr())

        try:
            fd = None
            self.create_dir()
            fd = self.libc.opendir(self.absdir.encode())
            self.test(fd != None, "opendir - opendir() on an existent directory should succeed")
            self.libc.closedir(fd)
            fd = None
            fd = self.libc.opendir(self.absdir.encode() + b'bogus')
            self.test(fd == None, "opendir - opendir() on a non-existent directory should fail")
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            if fd:
                self.libc.closedir(fd)

    def read_test(self):
        """Verify POSIX API read() by reading data from a file. Verify that
           the st_atime of the file is updated after the read. Verify a read
           of 0 bytes returns 0.
        """
        self.test_group("Verify POSIX API read() on %s" % self.nfsstr())

        try:
            fd = None
            absfile = self.abspath(self.files[1])
            self.dprint('DBG3', "Open file %s for reading" % absfile)
            fd = posix.open(absfile, posix.O_RDONLY)
            fstat_b = os.stat(absfile)
            sleep(1)

            self.dprint('DBG3', "Read data from file %s using POSIX API read()" % absfile)
            amsg = "read - read() should succeed"
            data = self.run_func(posix.read, fd, 0, msg=amsg)
            self.test(len(data) == 0, "read - reading 0 bytes should return 0")
            data = self.run_func(posix.read, fd, self.filesize, msg=amsg)
            self.test(data == self.data_pattern(0, len(data)), "read - data returned should be correct")
            posix.close(fd)
            fstat = os.stat(absfile)
            self.test(fstat.st_atime > fstat_b.st_atime, "read - file st_atime should be updated")
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def readdir_test(self):
        """Verify POSIX API readdir() on a directory."""
        self.test_group("Verify POSIX API readdir() on %s" % self.nfsstr())

        try:
            fd = None
            self.create_dir()
            self.create_file(dir=self.dirname)
            filename = os.path.basename(self.filename).encode()

            fd = self.libc.opendir(self.absdir.encode())
            dirlist = []
            while True:
                dirent = self.libc.readdir(fd)
                if not bool(dirent):
                    break
                dirlist.append(dirent[0].d_name)
            self.libc.closedir(fd)
            fd = None

            self.test(len(dirlist) > 0, "readdir - readdir() on an open directory should succeed")
            self.test(filename in dirlist, "readdir - file in directory should be returned by readdir()")
            self.test(True, "readdir - readdir() should return 0 at the end of the list")
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            if fd:
                self.libc.closedir(fd)

    def readlink_test(self):
        """Verify Test POSIX API readlink() by reading a symbolic link."""
        self.test_group("Verify POSIX API readlink() on %s" % self.nfsstr())

        try:
            for stype in self.setup_readlink_types:
                srcpath, symlink = self.setup_readlink.get(stype)
                self.dprint('DBG3', "Reading symbolic link to %s [%s]" % (stype, symlink))
                amsg = "readlink - reading symbolic link to a %s should succeed" % stype
                data = self.run_func(posix.readlink, symlink, msg=amsg)
                if self.oserror is None:
                    src_str = "source " if stype in ("file", "directory") else ""
                    self.test(data == srcpath, "readlink - data of symbolic link should be the name of the %s%s" % (src_str, stype))

            # The named file does not exist.
            symlink += "_bogus"
            self.dprint('DBG3', "Reading non-existent symbolic link [%s]" % symlink)
            amsg = "readlink - reading non-existent symbolic link should fail"
            self.run_func(posix.readlink, symlink, msg=amsg, err=errno.ENOENT)

            # The named file is not a symbolic link.
            for stype in ("file", "directory"):
                srcpath, symlink = self.setup_readlink.get(stype)
                self.dprint('DBG3', "Reading a %s as a symbolic link [%s]" % (stype, srcpath))
                amsg = "readlink - reading a %s as a symbolic link should fail" % stype
                self.run_func(posix.readlink, srcpath, msg=amsg, err=errno.EINVAL)
        except Exception:
            self.test(False, traceback.format_exc())

    def rename_test(self):
        """Verify POSIX API rename() by renaming a file, directory, and a
           symbolic link. Verify that a rename from a file to a symbolic
           link will cause the symbolic link to be removed.
        """
        self.test_group("Verify POSIX API rename() on %s" % self.nfsstr())

        try:
            self.test_info("Rename file")
            self.create_file()
            oldname = self.absfile
            self.get_filename()
            self.dprint('DBG3', "Rename file %s to %s using POSIX API rename()" % (oldname, self.absfile))
            amsg = "rename - rename() should succeed"
            self.run_func(posix.rename, oldname, self.absfile, msg=amsg)
            self.test(os.path.exists(self.absfile), "rename - new file name should exist")
            self.test(not os.path.exists(oldname), "rename - old file name should not exist")

            self.test_info("Rename directory")
            self.create_dir()
            oldname = self.absdir
            self.get_dirname()
            self.dprint('DBG3', "Rename directory %s to %s using POSIX API rename()" % (oldname, self.absdir))
            self.run_func(posix.rename, oldname, self.absdir, msg=amsg)
            self.test(os.path.exists(self.absdir), "rename - new directory name should exist")
            self.test(not os.path.exists(oldname), "rename - old directory name should not exist")

            self.test_info("Rename symbolic link")
            srcfile = self.absfile
            self.get_filename()
            self.dprint('DBG3', "Creating symbolic link [%s -> %s]" % (self.absfile, srcfile))
            os.symlink(srcfile, self.absfile)
            oldname = self.absfile
            self.get_filename()
            self.dprint('DBG3', "Rename symbolic link %s to %s using POSIX API rename()" % (oldname, self.absfile))
            self.run_func(posix.rename, oldname, self.absfile, msg=amsg)
            self.test(os.path.exists(self.absfile), "rename - new symbolic link name should exist")
            self.test(not os.path.exists(oldname), "rename - old symbolic link name should not exist")
            self.test(os.path.islink(self.absfile), "rename - new name should be a symbolic link")

            self.test_info("Rename file to an existing symbolic link")
            newname = self.absfile
            self.create_file()
            oldname = self.absfile
            self.dprint('DBG3', "Rename file %s to an existing symbolic link %s using POSIX API rename()" % (oldname, newname))
            try:
                self.run_func(posix.rename, oldname, newname, msg=amsg)
                self.test(os.path.exists(newname), "rename - new file name should exist")
                self.test(not os.path.exists(oldname), "rename - old file name should not exist")
                self.test(os.path.isfile(newname) and not os.path.islink(newname), "rename - new name should be a regular file")
            except:
                self.test(False, "rename - renaming file to an existing symbolic link should have succeeded")
        except Exception:
            self.test(False, traceback.format_exc())

    def rewinddir_test(self):
        """Verify POSIX API rewinddir() on a directory."""
        self._tell_seek_dir_test('rewinddir')

    def rmdir_test(self):
        """Verify POSIX API rmdir() by removing a directory. Verify that the
           parent's st_ctime and st_mtime are updated.
        """
        self.set_nfserr_list(
            nfs3list=[nfs3_const.NFS3ERR_NOENT, nfs3_const.NFS3ERR_NOTEMPTY],
            nfs4list=[nfs4_const.NFS4ERR_NOENT, nfs4_const.NFS4ERR_NOTEMPTY],
        )
        self.test_group("Verify POSIX API rmdir() on %s" % self.nfsstr())

        try:
            dir = self.create_dir()
            topdir = self.absdir
            self.create_dir(dir=dir)
            dstat_b = os.stat(topdir)
            sleep(1)

            self.dprint('DBG3', "Remove directory %s using POSIX API rmdir()" % self.absdir)
            amsg = "rmdir - rmdir() should succeed"
            self.run_func(posix.rmdir, self.absdir, msg=amsg)
            dstat = os.stat(topdir)
            self.test(not os.path.exists(self.absdir),   "rmdir - directory should be removed")
            self.test(dstat.st_ctime > dstat_b.st_ctime, "rmdir - parent directory st_ctime should be updated")
            self.test(dstat.st_mtime > dstat_b.st_mtime, "rmdir - parent directory st_mtime should be updated")

            amsg = "rmdir - removing non-existent directory should return an error"
            self.run_func(posix.rmdir, self.absdir+'_bogus', msg=amsg, err=errno.ENOENT)

            self.create_dir(dir=dir)
            self.create_file(dir=dir)
            amsg = "rmdir - removing non-empty directory should return an error"
            self.run_func(posix.rmdir, topdir, msg=amsg, err=errno.ENOTEMPTY)
        except Exception:
            self.test(False, traceback.format_exc())

    def seekdir_test(self):
        """Verify POSIX API seekdir() on a directory."""
        self._tell_seek_dir_test('seekdir')

    def stat_test(self):
        """Verify POSIX API stat() by checking the mode on a file and that
           it returns the expected structure members. Create a symlink and
           verify that stat returns information about the file.
        """
        self._stat_test(stat_mode=1)

    def statvfs_test(self):
        """Verify POSIX API statvfs() by making sure all the members of the
           structure are returned.
        """
        self.test_group("Verify POSIX API statvfs() on %s" % self.nfsstr())

        try:
            self.create_file()
            self._statvfs_test(path=self.absfile)

            self.create_dir()
            self._statvfs_test(path=self.absdir, objtype='directory')
        except Exception:
            self.test(False, traceback.format_exc())

    def symlink_test(self):
        """Verify POSIX API symlink() by creating a symbolic link and verify
           that the file type is slnk.
        """
        self.test_group("Verify POSIX API symlink() on %s" % self.nfsstr())

        try:
            srcfile = self.abspath(self.files[0])
            self.get_filename()
            self.dprint('DBG3', "Create symbolic link %s -> %s using POSIX API symlink()" % (self.absfile, srcfile))
            amsg = "symlink - symlink() should succeed"
            self.run_func(posix.symlink, srcfile, self.absfile, msg=amsg)
            lstat = os.lstat(self.absfile)
            rlink = os.readlink(self.absfile)
            self.test(lstat != None, "symlink - symbolic link should be created")
            self.test(stat.S_ISLNK(lstat.st_mode), "symlink - object type should be a symbolic link")
            self.test(rlink == srcfile, "symlink - symbolic link data should be the name of the source file")
        except Exception:
            self.test(False, traceback.format_exc())

    def _sync(self, objtype):
        """Verify POSIX API sync(), fdatasync() or fsync()."""
        self.test_group("Verify POSIX API %s() on %s" % (objtype, self.nfsstr()))

        try:
            fd = None
            self.get_filename()
            self.dprint('DBG3', "Open file %s for writing" % self.absfile)
            fd = posix.open(self.absfile, posix.O_WRONLY|posix.O_CREAT)

            offset = 0
            self.dprint('DBG3', "Write data to file")
            count = posix.write(fd, self.data_pattern(0, self.filesize))
            offset += count
            sleep(1)
            self.dprint('DBG3', "Write data to file")
            count = posix.write(fd, self.data_pattern(offset, self.filesize))
            offset += count
            sleep(1)
            self.dprint('DBG3', "Write data to file")
            count = posix.write(fd, self.data_pattern(offset, self.filesize))
            offset += count

            self.dprint('DBG3', "Sync data to file")
            amsg = "%s - sync should succeed" % objtype
            if objtype == 'sync':
                out = self.libc.sync()
                self.test(out == 0, amsg)
            elif objtype == 'fsync':
                self.run_func(posix.fsync, fd, msg=amsg)
            else:
                self.run_func(posix.fdatasync, fd, msg=amsg)
            posix.close(fd)

            amsg = "%s - sync after close should return an error" % objtype
            if objtype == 'sync':
                out = self.libc.sync()
                self.test(out == 0, "%s - sync after close should succeed" % objtype)
            elif objtype == 'fsync':
                self.run_func(posix.fsync, fd, msg=amsg, err=errno.EBADF)
            elif objtype == 'fdatasync':
                self.run_func(posix.fdatasync, fd, msg=amsg, err=errno.EBADF)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def sync_test(self):
        """Verify POSIX API sync()."""
        self._sync('sync')

    def _readdir(self, fd, offset):
        """Wrapper for readdir()."""
        self.libc.seekdir(fd, offset)
        c_dirent = self.libc.readdir(fd)
        dirent = ctypes.cast(c_dirent, ctypes.POINTER(DirEnt))
        return dirent[0].d_name

    def _tell_seek_dir_test(self, objtype):
        """Verify POSIX API telldir(), seekdir() or rewinddir() on a directory."""
        self.test_group("Verify POSIX API %s() on %s" % (objtype, self.nfsstr()))

        try:
            fd = None
            self.create_dir()
            N = 5
            for i in range(N):
                self.create_file(dir=self.dirname)

            fd = self.libc.opendir(self.absdir.encode())

            dirlist = []
            offsets = []
            while True:
                offset = self.libc.telldir(fd)
                offsets.append(offset)
                dirent = self.libc.readdir(fd)
                if not bool(dirent):
                    break
                dirlist.append(dirent[0].d_name)

            if objtype == 'rewinddir':
                self.libc.rewinddir(fd)
                self.test(True, "%s - directory rewind should succeed after reaching end of list" % objtype)
                dirent = self.libc.readdir(fd)
                self.test(dirent[0].d_name == dirlist[0], "%s - directory entry is correct after rewind" % objtype)
                index = int(N/2)
                name = self._readdir(fd, index)
                self.libc.rewinddir(fd)
                self.test(True, "%s - directory rewind from the middle of list should succeed" % objtype)
                c_dirent = self.libc.readdir(fd)
                dirent = ctypes.cast(c_dirent, ctypes.POINTER(DirEnt))
                self.test(dirent[0].d_name == dirlist[0], "%s - directory entry is correct after rewind" % objtype)
            else:
                random_list = list(range(N))
                random.shuffle(random_list)
                for index in random_list:
                    name = self._readdir(fd, offsets[index])
                    self.test(name == dirlist[index], "%s - directory entry is correct at offset = %d" % (objtype, offsets[index]))

            self.libc.closedir(fd)
            fd = None
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            if fd:
                self.libc.closedir(fd)

    def telldir_test(self):
        """Verify POSIX API telldir() on a directory."""
        self._tell_seek_dir_test('telldir')

    def unlink_test(self):
        """Verify POSIX API unlink() by unlinking a file and verify that it
           was removed. Verify that the st_ctime and st_mtime fields of the
           parent directory were updated. Then unlink a symbolic link and
           verify that the symbolic link was removed but not the referenced
           file. Then remove an opened file and verify that I/O still occurs
           to the file after the unlink and that the file gets removed when
           the file is closed. Create a file and then hard link to it so the
           link count is greater than 1. Unlink the hard file and verify that
           st_ctime field is updated.
        """
        self.test_group("Verify POSIX API unlink() on %s" % self.nfsstr())

        try:
            fd = None
            self.create_file()
            dstat_b = os.stat(self.mtdir)
            sleep(1)

            self.test_info("Unlink file")
            self.dprint('DBG3', "Remove file %s using POSIX API unlink()" % self.absfile)
            amsg = "unlink - unlink() should succeed"
            self.run_func(posix.unlink, self.absfile, msg=amsg)
            dstat = os.stat(self.mtdir)
            self.test(not os.path.exists(self.absfile), "unlink - file should be removed")
            self.test(dstat.st_ctime > dstat_b.st_ctime, "unlink - parent directory st_ctime should be updated")
            self.test(dstat.st_mtime > dstat_b.st_mtime, "unlink - parent directory st_mtime should be updated")

            self.test_info("Unlink symbolic link")
            self.create_file()
            srcfile = self.absfile
            self.get_filename()
            os.symlink(srcfile, self.absfile)
            self.dprint('DBG3', "Remove symbolic link %s using POSIX API unlink()" % self.absfile)
            self.run_func(posix.unlink, self.absfile, msg=amsg)
            self.test(not os.path.exists(self.absfile), "unlink - symbolic link should be removed")
            self.test(os.path.exists(srcfile), "unlink - symbolic link source file should not be removed")

            self.test_info("Unlink opened file")
            self.get_filename()
            self.dprint('DBG3', "Open file %s for writing" % self.absfile)
            fd = posix.open(self.absfile, posix.O_WRONLY|posix.O_CREAT)
            self.dprint('DBG3', "Remove file %s using POSIX API unlink()" % self.absfile)
            self.run_func(posix.unlink, self.absfile, msg=amsg)
            self.test(not os.path.exists(self.absfile), "unlink - opened file should be removed")
            self.dprint('DBG3', "Write file %s %s@0" % (self.absfile, self.filesize))
            count = posix.write(fd, self.data_pattern(0, self.filesize))
            self.test(count > 0, "unlink - write should succeed after unlink()")
            posix.close(fd)
            self.test(not os.path.exists(self.absfile), "unlink - opened file should be removed after close()")

            self.test_info("Unlink file after hard link is created")
            self.create_file()
            srcfile = self.absfile
            self.get_filename()
            self.dprint('DBG3', "Create hard link %s to file %s" % (self.absfile, srcfile))
            os.link(srcfile, self.absfile)
            fstat_b = os.stat(self.absfile)
            sleep(1)

            self.dprint('DBG3', "Remove file %s using POSIX API unlink()" % srcfile)
            self.run_func(posix.unlink, srcfile, msg=amsg)
            fstat = os.stat(self.absfile)
            self.test(not os.path.exists(srcfile), "unlink - original file should be removed")
            self.test(os.path.exists(self.absfile), "unlink - hard link file should not be removed")
            self.test(fstat.st_nlink == fstat_b.st_nlink-1, "unlink - file st_nlink should be decremented by 1")
            self.test(fstat.st_ctime > fstat_b.st_ctime, "unlink - file st_ctime should be updated")
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

    def write_test(self):
        """Verify POSIX API write() by writing 0 bytes and verifying 0
           is returned. Write a pattern the file, seek +N, write another
           pattern, and close the file. Open the file and read in both
           written patterns and verify that it is the correct pattern.
           Read in the data from the hole in the file and verify that it is 0.
        """
        self.test_group("Verify POSIX API write() on %s" % self.nfsstr())

        try:
            fd = None
            self.get_filename()
            self.dprint('DBG3', "Open file %s for writing" % self.absfile)
            fd = posix.open(self.absfile, posix.O_WRONLY|posix.O_CREAT)
            fstat_b = os.stat(self.absfile)
            sleep(1)

            self.dprint('DBG3', "Write 0 bytes to file %s using POSIX API write()" % self.absfile)
            amsg = "write - write() should succeed"
            count = self.run_func(posix.write, fd, b'', msg=amsg)
            self.test(count == 0, "write - writing 0 bytes should return 0")

            self.dprint('DBG3', "Write data to file %s at offset = 0 using POSIX API write()" % self.absfile)
            count = self.run_func(posix.write, fd, self.data_pattern(0, self.filesize), msg=amsg)
            self.test(count == self.filesize, "write - writing N bytes should return N")

            offset = posix.lseek(fd, self.filesize, os.SEEK_CUR)
            self.dprint('DBG3', "Write data to file %s at different offset = N using POSIX API write()" % self.absfile)
            count = self.run_func(posix.write, fd, self.data_pattern(offset, self.filesize), msg=amsg)
            posix.close(fd)

            fstat = os.stat(self.absfile)
            self.test(fstat.st_ctime > fstat_b.st_ctime, "write - file st_ctime should be updated")
            self.test(fstat.st_mtime > fstat_b.st_mtime, "write - file st_mtime should be updated")

            self.dprint('DBG3', "Open file %s for reading" % self.absfile)
            fd = posix.open(self.absfile, posix.O_RDONLY)

            self.dprint('DBG3', "Read data from file %s at offset = 0" % self.absfile)
            data = posix.read(fd, 1000)
            self.test(data == self.data_pattern(0, len(data)), "write - written data should be correct at offset = 0")

            off = posix.lseek(fd, offset, os.SEEK_SET)
            data = posix.read(fd, 1000)
            self.test(data == self.data_pattern(off, len(data)), "write - written data should be correct at offset = N")

            # Read hole
            hsize = int(self.filesize/2)
            off = posix.lseek(fd, offset-hsize, os.SEEK_SET)
            data = posix.read(fd, hsize)
            self.test(data == bytes(len(data)), "write - hole in middle of file should be read correctly")

            posix.close(fd)
        except Exception:
            self.test(False, traceback.format_exc())
        finally:
            self.close_files(fd)

################################################################################
#  Entry point
x = PosixTest(usage=USAGE, testnames=TESTNAMES, sid=SCRIPT_ID)

try:
    x.setup(nfiles=2)

    # Run all the tests
    x.run_tests()
except Exception:
    x.test(False, traceback.format_exc())
finally:
    x.cleanup()
    x.exit()
