Skip to content

Commit e09efe1

Browse files
committed
[GR-28430] [GR-28431] Posix support: convert fcntl module to use PosixSupportLibrary.
PullRequest: graalpython/1502
2 parents 7417192 + b086024 commit e09efe1

File tree

15 files changed

+271
-84
lines changed

15 files changed

+271
-84
lines changed

graalpython/com.oracle.graal.python.cext/posix/posix.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include <sys/types.h>
6161
#include <sys/utsname.h>
6262
#include <sys/wait.h>
63+
#include <sys/file.h>
6364
#include <unistd.h>
6465

6566

@@ -217,6 +218,10 @@ int32_t call_fsync(int32_t fd) {
217218
return fsync(fd);
218219
}
219220

221+
int32_t call_flock(int32_t fd, int32_t operation) {
222+
return flock(fd, operation);
223+
}
224+
220225
int32_t get_blocking(int32_t fd) {
221226
int flags = fcntl(fd, F_GETFL, 0);
222227
if (flags < 0) {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
try:
41+
__graalpython__.posix_module_backend()
42+
except:
43+
class GP:
44+
def posix_module_backend(self):
45+
return 'cpython'
46+
__graalpython__ = GP()
47+
48+
import fcntl
49+
import os
50+
import subprocess
51+
import tempfile
52+
import time
53+
import unittest
54+
55+
PREFIX = 'select_graalpython_test'
56+
TEMP_DIR = tempfile.gettempdir()
57+
TEST_FILENAME = f'{PREFIX}_{os.getpid()}_tmp_fcntl'
58+
TEST_FILENAME_FULL_PATH = os.path.join(TEMP_DIR, TEST_FILENAME)
59+
60+
def log(msg):
61+
# print(msg)
62+
pass
63+
64+
def python_flock_blocks_sh_flock(python_flock_type, sh_flock_type):
65+
os.close(os.open(TEST_FILENAME_FULL_PATH, os.O_WRONLY | os.O_CREAT))
66+
file = os.open(TEST_FILENAME_FULL_PATH, os.O_WRONLY)
67+
try:
68+
fcntl.flock(file, python_flock_type)
69+
p = subprocess.Popen("flock -%s %s -c 'exit 42'" % (sh_flock_type, TEST_FILENAME_FULL_PATH), shell=True)
70+
log("sleeping...")
71+
time.sleep(0.25)
72+
assert p.poll() is None # the process should be still waiting for the lock
73+
log("unlocking the file...")
74+
fcntl.flock(file, fcntl.LOCK_UN) # release the lock
75+
log("checking the retcode...")
76+
time.sleep(0.25)
77+
assert p.poll() == 42
78+
log(f"{p.returncode=}")
79+
finally:
80+
fcntl.flock(file, fcntl.LOCK_UN)
81+
os.close(file)
82+
83+
class FcntlTests(unittest.TestCase):
84+
@unittest.skipUnless(__graalpython__.posix_module_backend() != 'java', 'No support in Truffle API (GR-28740)')
85+
def test_flock_x_and_x(self):
86+
python_flock_blocks_sh_flock(fcntl.LOCK_EX, 'x')
87+
88+
@unittest.skipUnless(__graalpython__.posix_module_backend() != 'java', 'No support in Truffle API (GR-28740)')
89+
def test_flock_x_and_s(self):
90+
python_flock_blocks_sh_flock(fcntl.LOCK_EX, 's')
91+
92+
@unittest.skipUnless(__graalpython__.posix_module_backend() != 'java', 'No support in Truffle API (GR-28740)')
93+
def test_flock_s_and_x(self):
94+
python_flock_blocks_sh_flock(fcntl.LOCK_SH, 'x')
95+
96+
@unittest.skipUnless(__graalpython__.posix_module_backend() != 'java', 'No support in Truffle API (GR-28740)')
97+
def test_flock_s_and_s(self):
98+
os.close(os.open(TEST_FILENAME_FULL_PATH, os.O_WRONLY | os.O_CREAT))
99+
file = os.open(TEST_FILENAME_FULL_PATH, os.O_WRONLY)
100+
try:
101+
fcntl.flock(file, fcntl.LOCK_SH)
102+
p = subprocess.Popen("flock -s %s -c 'exit 42'" % TEST_FILENAME_FULL_PATH, shell=True)
103+
time.sleep(0.25)
104+
assert p.poll() == 42
105+
finally:
106+
fcntl.flock(file, fcntl.LOCK_UN)
107+
os.close(file)

graalpython/com.oracle.graal.python.test/src/tests/test_posix.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,17 @@ def __fspath__(self):
216216
with self.assertRaisesRegex(TypeError, r"expected C.__fspath__\(\) to return str or bytes, not bytearray"):
217217
os.open(C(), 0)
218218

219+
def test_fd_converter(self):
220+
class MyInt(int):
221+
def fileno(self): return 0
222+
223+
class MyObj:
224+
def fileno(self): return -1
225+
226+
self.assertRaises(ValueError, os.fsync, -1)
227+
self.assertRaises(ValueError, os.fsync, MyInt(-1)) # fileno should be ignored
228+
self.assertRaises(ValueError, os.fsync, MyObj())
229+
219230

220231
class WithCurdirFdTests(unittest.TestCase):
221232

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*graalpython.lib-python.3.test.test_fcntl.TestFcntl.test_fcntl_64_bit
2+
*graalpython.lib-python.3.test.test_fcntl.TestFcntl.test_fcntl_bad_file_overflow
3+
*graalpython.lib-python.3.test.test_fcntl.TestFcntl.test_flock_overflow
4+
*graalpython.lib-python.3.test.test_fcntl.TestFcntl.test_flock

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/FcntlModuleBuiltins.java

Lines changed: 30 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -40,35 +40,31 @@
4040
*/
4141
package com.oracle.graal.python.builtins.modules;
4242

43-
import java.io.IOException;
44-
import java.nio.channels.Channel;
45-
import java.nio.channels.FileChannel;
46-
import java.nio.channels.FileLock;
4743
import java.util.List;
4844

45+
import com.oracle.graal.python.annotations.ArgumentClinic;
46+
import com.oracle.graal.python.annotations.ArgumentClinic.ClinicConversion;
4947
import com.oracle.graal.python.builtins.Builtin;
5048
import com.oracle.graal.python.builtins.CoreFunctions;
5149
import com.oracle.graal.python.builtins.PythonBuiltins;
50+
import com.oracle.graal.python.builtins.modules.FcntlModuleBuiltinsClinicProviders.FlockNodeClinicProviderGen;
51+
import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins.FileDescriptorConversionNode;
5252
import com.oracle.graal.python.builtins.objects.PNone;
53-
import com.oracle.graal.python.builtins.objects.exception.OSErrorEnum;
5453
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
55-
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
56-
import com.oracle.graal.python.runtime.PosixResources;
54+
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode;
55+
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
56+
import com.oracle.graal.python.runtime.PosixSupportLibrary;
57+
import com.oracle.graal.python.runtime.PosixSupportLibrary.PosixException;
5758
import com.oracle.graal.python.runtime.PythonCore;
58-
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
5959
import com.oracle.truffle.api.dsl.Cached;
6060
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
6161
import com.oracle.truffle.api.dsl.NodeFactory;
6262
import com.oracle.truffle.api.dsl.Specialization;
6363
import com.oracle.truffle.api.frame.VirtualFrame;
64-
import com.oracle.truffle.api.profiles.ValueProfile;
64+
import com.oracle.truffle.api.library.CachedLibrary;
6565

6666
@CoreFunctions(defineModule = "fcntl")
6767
public class FcntlModuleBuiltins extends PythonBuiltins {
68-
private static final int LOCK_SH = 1;
69-
private static final int LOCK_EX = 2;
70-
private static final int LOCK_NB = 4;
71-
private static final int LOCK_UN = 8;
7268

7369
@Override
7470
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
@@ -77,78 +73,36 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
7773

7874
@Override
7975
public void initialize(PythonCore core) {
80-
builtinConstants.put("LOCK_SH", LOCK_SH);
81-
builtinConstants.put("LOCK_EX", LOCK_EX);
82-
builtinConstants.put("LOCK_NB", LOCK_NB);
83-
builtinConstants.put("LOCK_UN", LOCK_UN);
76+
builtinConstants.put("LOCK_SH", PosixSupportLibrary.LOCK_SH);
77+
builtinConstants.put("LOCK_EX", PosixSupportLibrary.LOCK_EX);
78+
builtinConstants.put("LOCK_NB", PosixSupportLibrary.LOCK_NB);
79+
builtinConstants.put("LOCK_UN", PosixSupportLibrary.LOCK_UN);
80+
81+
builtinConstants.put("F_WRLCK", 42); // TODO
8482
super.initialize(core);
8583
}
8684

87-
@Builtin(name = "flock", minNumOfPositionalArgs = 2)
85+
@Builtin(name = "flock", parameterNames = {"fd", "operation"})
86+
@ArgumentClinic(name = "fd", conversionClass = FileDescriptorConversionNode.class)
87+
@ArgumentClinic(name = "operation", conversion = ClinicConversion.Int)
8888
@GenerateNodeFactory
89-
abstract static class FlockNode extends PythonBinaryBuiltinNode {
89+
abstract static class FlockNode extends PythonBinaryClinicBuiltinNode {
90+
@Override
91+
protected ArgumentClinicProvider getArgumentClinic() {
92+
return FlockNodeClinicProviderGen.INSTANCE;
93+
}
94+
9095
@Specialization
9196
synchronized PNone flock(VirtualFrame frame, int fd, int operation,
92-
@Cached("createClassProfile()") ValueProfile profile) {
93-
PosixResources rs = getContext().getResources();
94-
Channel channel;
97+
@Cached SysModuleBuiltins.AuditNode auditNode,
98+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posix) {
99+
auditNode.audit("fcntl.flock", fd, operation);
95100
try {
96-
channel = rs.getFileChannel(fd, profile);
97-
} catch (IndexOutOfBoundsException e) {
98-
throw raiseOSError(frame, OSErrorEnum.EBADFD);
99-
}
100-
if (channel instanceof FileChannel) {
101-
FileChannel fc = (FileChannel) channel;
102-
FileLock lock = rs.getFileLock(fd);
103-
try {
104-
lock = doLockOperation(operation, fc, lock);
105-
} catch (IOException e) {
106-
throw raiseOSError(frame, e);
107-
}
108-
rs.setFileLock(fd, lock);
101+
posix.flock(getPosixSupport(), fd, operation);
102+
} catch (PosixException e) {
103+
throw raiseOSErrorFromPosixException(frame, e);
109104
}
110105
return PNone.NONE;
111106
}
112-
113-
@TruffleBoundary
114-
private static FileLock doLockOperation(int operation, FileChannel fc, FileLock oldLock) throws IOException {
115-
FileLock lock = oldLock;
116-
if (lock == null) {
117-
if ((operation & LOCK_SH) != 0) {
118-
if ((operation & LOCK_NB) != 0) {
119-
lock = fc.tryLock(0, Long.MAX_VALUE, true);
120-
} else {
121-
lock = fc.lock(0, Long.MAX_VALUE, true);
122-
}
123-
} else if ((operation & LOCK_EX) != 0) {
124-
if ((operation & LOCK_NB) != 0) {
125-
lock = fc.tryLock();
126-
} else {
127-
lock = fc.lock();
128-
}
129-
} else {
130-
// not locked, that's ok
131-
}
132-
} else {
133-
if ((operation & LOCK_UN) != 0) {
134-
lock.release();
135-
lock = null;
136-
} else if ((operation & LOCK_EX) != 0) {
137-
if (lock.isShared()) {
138-
if ((operation & LOCK_NB) != 0) {
139-
FileLock newLock = fc.tryLock();
140-
if (newLock != null) {
141-
lock = newLock;
142-
}
143-
} else {
144-
lock = fc.lock();
145-
}
146-
}
147-
} else {
148-
// we already have a suitable lock
149-
}
150-
}
151-
return lock;
152-
}
153107
}
154108
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixModuleBuiltins.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,13 +2407,13 @@ public static OffsetConversionNode create() {
24072407
}
24082408

24092409
/**
2410-
* Equivalent of CPython's {@code fildes_converter()}. Always returns an {@code int}.
2410+
* Equivalent of CPython's {@code fildes_converter()}, which in turn delegates to
2411+
* {@code PyObject_AsFileDescriptor}. Always returns an {@code int}.
24112412
*/
24122413
public abstract static class FileDescriptorConversionNode extends ArgumentCastNodeWithRaise {
2413-
24142414
@Specialization
24152415
int doFdInt(int value) {
2416-
return value;
2416+
return PInt.asFileDescriptor(value, getRaiseNode());
24172417
}
24182418

24192419
@Specialization(guards = "!isInt(value)", limit = "3")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/PythonAbstractObject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ public int asFileDescriptorWithState(ThreadState state,
863863
Object result = methodLib.callObjectWithState(filenoFunc, state);
864864
if (isIntProfile.profileObject(result, PythonBuiltinClassType.PInt)) {
865865
try {
866-
return castToJavaIntNode.execute(result);
866+
return PInt.asFileDescriptor(castToJavaIntNode.execute(result), raiseNode);
867867
} catch (PException e) {
868868
e.expect(PythonBuiltinClassType.TypeError, isAttrError);
869869
throw raiseNode.raise(PythonBuiltinClassType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO, "int");

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/PInt.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,21 @@ public Object asIndexWithState(@SuppressWarnings("unused") ThreadState threadSta
251251
public int asFileDescriptorWithState(@SuppressWarnings("unused") ThreadState state,
252252
@Exclusive @Cached PRaiseNode raiseNode,
253253
@Exclusive @Cached CastToJavaIntExactNode castToJavaIntNode) {
254+
int result;
254255
try {
255-
return castToJavaIntNode.execute(this);
256+
result = castToJavaIntNode.execute(this);
256257
} catch (PException e) {
257258
throw raiseNode.raise(PythonBuiltinClassType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO, "int");
258259
}
260+
return asFileDescriptor(result, raiseNode);
261+
}
262+
263+
@Ignore
264+
public static int asFileDescriptor(int value, PRaiseNode raiseNode) {
265+
if (value < 0) {
266+
raiseNode.raise(PythonBuiltinClassType.ValueError, ErrorMessages.S_CANNOT_BE_NEGATIVE_INTEGER_D, "file descriptor", value);
267+
}
268+
return value;
259269
}
260270

261271
@SuppressWarnings("static-method")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/DefaultPythonIntegerExports.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.oracle.graal.python.builtins.objects.function.PArguments.ThreadState;
4848
import com.oracle.graal.python.builtins.objects.ints.PInt;
4949
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
50+
import com.oracle.graal.python.nodes.PRaiseNode;
5051
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
5152
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
5253
import com.oracle.graal.python.runtime.PythonOptions;
@@ -257,8 +258,9 @@ static String asPStringWithState(Integer receiver, @SuppressWarnings("unused") T
257258
}
258259

259260
@ExportMessage
260-
static int asFileDescriptorWithState(Integer x, @SuppressWarnings("unused") ThreadState state) {
261-
return x;
261+
static int asFileDescriptorWithState(Integer x, @SuppressWarnings("unused") ThreadState state,
262+
@Cached PRaiseNode raiseNode) {
263+
return PInt.asFileDescriptor(x, raiseNode);
262264
}
263265

264266
@SuppressWarnings("static-method")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/DefaultPythonLongExports.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ static int asFileDescriptorWithState(Long x, @SuppressWarnings("unused") ThreadS
285285
@Exclusive @Cached CastToJavaIntExactNode castToJavaIntNode,
286286
@Exclusive @Cached IsBuiltinClassProfile errorProfile) {
287287
try {
288-
return castToJavaIntNode.execute(x);
288+
return PInt.asFileDescriptor(castToJavaIntNode.execute(x), raiseNode);
289289
} catch (PException e) {
290290
e.expect(PythonBuiltinClassType.TypeError, errorProfile);
291291
// we need to convert the TypeError to an OverflowError

0 commit comments

Comments
 (0)