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