Skip to content

Commit b086024

Browse files
committed
Convert the fcntl module to use the PosixSupportLibrary
1 parent da9496e commit b086024

File tree

9 files changed

+250
-76
lines changed

9 files changed

+250
-76
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/runtime/EmulatedPosixSupport.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
import java.net.UnknownHostException;
6666
import java.nio.ByteBuffer;
6767
import java.nio.channels.Channel;
68+
import java.nio.channels.FileChannel;
69+
import java.nio.channels.FileLock;
6870
import java.nio.channels.ReadableByteChannel;
6971
import java.nio.channels.SeekableByteChannel;
7072
import java.nio.channels.SelectableChannel;
@@ -601,6 +603,69 @@ public void fsyncMessage(int fd) throws PosixException {
601603
}
602604
}
603605

606+
@ExportMessage
607+
final void flock(int fd, int operation,
608+
@Shared("errorBranch") @Cached BranchProfile errorBranch,
609+
@Shared("channelClass") @Cached("createClassProfile()") ValueProfile channelClassProfile) throws PosixException {
610+
Channel channel = getFileChannel(fd, channelClassProfile);
611+
if (channel == null) {
612+
errorBranch.enter();
613+
throw posixException(OSErrorEnum.EBADFD);
614+
}
615+
// TODO: support other types, throw unsupported feature exception otherwise (GR-28740)
616+
if (channel instanceof FileChannel) {
617+
FileChannel fc = (FileChannel) channel;
618+
FileLock lock = getFileLock(fd);
619+
try {
620+
lock = doLockOperation(operation, fc, lock);
621+
} catch (IOException e) {
622+
throw posixException(OSErrorEnum.fromException(e));
623+
}
624+
setFileLock(fd, lock);
625+
}
626+
}
627+
628+
@TruffleBoundary
629+
private static FileLock doLockOperation(int operation, FileChannel fc, FileLock oldLock) throws IOException {
630+
FileLock lock = oldLock;
631+
if (lock == null) {
632+
if ((operation & PosixSupportLibrary.LOCK_SH) != 0) {
633+
if ((operation & PosixSupportLibrary.LOCK_NB) != 0) {
634+
lock = fc.tryLock(0, Long.MAX_VALUE, true);
635+
} else {
636+
lock = fc.lock(0, Long.MAX_VALUE, true);
637+
}
638+
} else if ((operation & PosixSupportLibrary.LOCK_EX) != 0) {
639+
if ((operation & PosixSupportLibrary.LOCK_NB) != 0) {
640+
lock = fc.tryLock();
641+
} else {
642+
lock = fc.lock();
643+
}
644+
} else {
645+
// not locked, that's ok
646+
}
647+
} else {
648+
if ((operation & PosixSupportLibrary.LOCK_UN) != 0) {
649+
lock.release();
650+
lock = null;
651+
} else if ((operation & PosixSupportLibrary.LOCK_EX) != 0) {
652+
if (lock.isShared()) {
653+
if ((operation & PosixSupportLibrary.LOCK_NB) != 0) {
654+
FileLock newLock = fc.tryLock();
655+
if (newLock != null) {
656+
lock = newLock;
657+
}
658+
} else {
659+
lock = fc.lock();
660+
}
661+
}
662+
} else {
663+
// we already have a suitable lock
664+
}
665+
}
666+
return lock;
667+
}
668+
604669
@ExportMessage
605670
public boolean getBlocking(int fd,
606671
@Shared("channelClass") @Cached("createClassProfile()") ValueProfile channelClassProfile) throws PosixException {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,17 @@ final void fsync(int fd,
280280
}
281281
}
282282

283+
@ExportMessage
284+
final void flock(int fd, int operation,
285+
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {
286+
logEnter("flock", "%d %d", fd, operation);
287+
try {
288+
lib.flock(delegate, fd, operation);
289+
} catch (PosixException e) {
290+
throw logException("flock", e);
291+
}
292+
}
293+
283294
@ExportMessage
284295
final boolean getBlocking(int fd,
285296
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {

0 commit comments

Comments
 (0)