1"""Test program for the fcntl C module.
2"""
3import platform
4import os
5import struct
6import sys
7import unittest
8from test.support import (verbose, TESTFN, unlink, run_unittest, import_module,
9                          cpython_only)
10
11# Skip test if no fcntl module.
12fcntl = import_module('fcntl')
13
14
15# TODO - Write tests for flock() and lockf().
16
17def get_lockdata():
18    try:
19        os.O_LARGEFILE
20    except AttributeError:
21        start_len = "ll"
22    else:
23        start_len = "qq"
24
25    if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd', 'bsdos'))
26        or sys.platform == 'darwin'):
27        if struct.calcsize('l') == 8:
28            off_t = 'l'
29            pid_t = 'i'
30        else:
31            off_t = 'lxxxx'
32            pid_t = 'l'
33        lockdata = struct.pack(off_t + off_t + pid_t + 'hh', 0, 0, 0,
34                               fcntl.F_WRLCK, 0)
35    elif sys.platform.startswith('gnukfreebsd'):
36        lockdata = struct.pack('qqihhi', 0, 0, 0, fcntl.F_WRLCK, 0, 0)
37    elif sys.platform in ['aix3', 'aix4', 'hp-uxB', 'unixware7']:
38        lockdata = struct.pack('hhlllii', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0)
39    else:
40        lockdata = struct.pack('hh'+start_len+'hh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
41    if lockdata:
42        if verbose:
43            print('struct.pack: ', repr(lockdata))
44    return lockdata
45
46lockdata = get_lockdata()
47
48class BadFile:
49    def __init__(self, fn):
50        self.fn = fn
51    def fileno(self):
52        return self.fn
53
54class TestFcntl(unittest.TestCase):
55
56    def setUp(self):
57        self.f = None
58
59    def tearDown(self):
60        if self.f and not self.f.closed:
61            self.f.close()
62        unlink(TESTFN)
63
64    def test_fcntl_fileno(self):
65        # the example from the library docs
66        self.f = open(TESTFN, 'wb')
67        rv = fcntl.fcntl(self.f.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
68        if verbose:
69            print('Status from fcntl with O_NONBLOCK: ', rv)
70        rv = fcntl.fcntl(self.f.fileno(), fcntl.F_SETLKW, lockdata)
71        if verbose:
72            print('String from fcntl with F_SETLKW: ', repr(rv))
73        self.f.close()
74
75    def test_fcntl_file_descriptor(self):
76        # again, but pass the file rather than numeric descriptor
77        self.f = open(TESTFN, 'wb')
78        rv = fcntl.fcntl(self.f, fcntl.F_SETFL, os.O_NONBLOCK)
79        if verbose:
80            print('Status from fcntl with O_NONBLOCK: ', rv)
81        rv = fcntl.fcntl(self.f, fcntl.F_SETLKW, lockdata)
82        if verbose:
83            print('String from fcntl with F_SETLKW: ', repr(rv))
84        self.f.close()
85
86    def test_fcntl_bad_file(self):
87        with self.assertRaises(ValueError):
88            fcntl.fcntl(-1, fcntl.F_SETFL, os.O_NONBLOCK)
89        with self.assertRaises(ValueError):
90            fcntl.fcntl(BadFile(-1), fcntl.F_SETFL, os.O_NONBLOCK)
91        with self.assertRaises(TypeError):
92            fcntl.fcntl('spam', fcntl.F_SETFL, os.O_NONBLOCK)
93        with self.assertRaises(TypeError):
94            fcntl.fcntl(BadFile('spam'), fcntl.F_SETFL, os.O_NONBLOCK)
95
96    @cpython_only
97    def test_fcntl_bad_file_overflow(self):
98        from _testcapi import INT_MAX, INT_MIN
99        # Issue 15989
100        with self.assertRaises(OverflowError):
101            fcntl.fcntl(INT_MAX + 1, fcntl.F_SETFL, os.O_NONBLOCK)
102        with self.assertRaises(OverflowError):
103            fcntl.fcntl(BadFile(INT_MAX + 1), fcntl.F_SETFL, os.O_NONBLOCK)
104        with self.assertRaises(OverflowError):
105            fcntl.fcntl(INT_MIN - 1, fcntl.F_SETFL, os.O_NONBLOCK)
106        with self.assertRaises(OverflowError):
107            fcntl.fcntl(BadFile(INT_MIN - 1), fcntl.F_SETFL, os.O_NONBLOCK)
108
109    @unittest.skipIf(
110        platform.machine().startswith('arm') and platform.system() == 'Linux',
111        "ARM Linux returns EINVAL for F_NOTIFY DN_MULTISHOT")
112    def test_fcntl_64_bit(self):
113        # Issue #1309352: fcntl shouldn't fail when the third arg fits in a
114        # C 'long' but not in a C 'int'.
115        try:
116            cmd = fcntl.F_NOTIFY
117            # This flag is larger than 2**31 in 64-bit builds
118            flags = fcntl.DN_MULTISHOT
119        except AttributeError:
120            self.skipTest("F_NOTIFY or DN_MULTISHOT unavailable")
121        fd = os.open(os.path.dirname(os.path.abspath(TESTFN)), os.O_RDONLY)
122        try:
123            fcntl.fcntl(fd, cmd, flags)
124        finally:
125            os.close(fd)
126
127    def test_flock(self):
128        # Solaris needs readable file for shared lock
129        self.f = open(TESTFN, 'wb+')
130        fileno = self.f.fileno()
131        fcntl.flock(fileno, fcntl.LOCK_SH)
132        fcntl.flock(fileno, fcntl.LOCK_UN)
133        fcntl.flock(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
134        fcntl.flock(self.f, fcntl.LOCK_UN)
135        fcntl.flock(fileno, fcntl.LOCK_EX)
136        fcntl.flock(fileno, fcntl.LOCK_UN)
137
138        self.assertRaises(ValueError, fcntl.flock, -1, fcntl.LOCK_SH)
139        self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH)
140
141    @cpython_only
142    def test_flock_overflow(self):
143        import _testcapi
144        self.assertRaises(OverflowError, fcntl.flock, _testcapi.INT_MAX+1,
145                          fcntl.LOCK_SH)
146
147
148def test_main():
149    run_unittest(TestFcntl)
150
151if __name__ == '__main__':
152    test_main()
153