Skip to content

Commit 8605743

Browse files
committed
[GR-48686] Implement os.chown and os.getgroups
PullRequest: graalpython/2984
2 parents ebf581e + dff6cf6 commit 8605743

File tree

14 files changed

+363
-29
lines changed

14 files changed

+363
-29
lines changed

docs/contributor/MISSING.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ This is just a snapshot as of 2023-09-14.
99
* **_dbm**: Use from C
1010
* **_gdbm**: Use from C
1111
* **_tkinter**: Should be used from C
12-
* **audioop**: Should be useable from C
13-
* **nis**: We should just use the C module
1412
* **syslog**: Access to syslog. We should probably just use this from the C module.
1513

1614
#### These are not strictly needed for now
@@ -24,10 +22,14 @@ This is just a snapshot as of 2023-09-14.
2422
#### These we probably won't support
2523
* **_opcode**: We won't have it, our opcodes are different from CPython.
2624
* **_symtable**: Interface for the compilers internal symboltable.
27-
* **ossaudiodev**: Not needed, it's for Linux OSS audio
2825

2926
#### These we should re-implement
3027
* **_uuid**: Can be implemented ourselves, is just 1 function
3128
* **grp**: UNIX group file access. Should be added to our POSIX APIs
3229
* **spwd**: UNIX shadow password file access. Should be added to our POSIX APIs
3330
* **parser**: We need to implement this for our parser
31+
32+
#### Deprecated in CPython, won't implement
33+
* **audioop**: Scheduled for removal in 3.13
34+
* **ossaudiodev**: Scheduled for removal in 3.13
35+
* **nis**: Scheduled for removal in 3.13

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_concurrent_futures.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnFailingInitializerTest.test_initializer
4747
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnInitializerTest.test_initializer
4848
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnProcessPoolExecutorTest.test_idle_process_reuse_multiple
49-
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnProcessPoolExecutorTest.test_idle_process_reuse_one
5049
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnProcessPoolExecutorTest.test_killed_child
5150
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnProcessPoolExecutorTest.test_map
5251
*graalpython.lib-python.3.test.test_concurrent_futures.ProcessPoolSpawnProcessPoolExecutorTest.test_map_chunksize

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_importlib.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,6 @@
837837
*test.test_importlib.test_lazy.LazyLoaderTests.test_new_attr
838838
*test.test_importlib.test_locks.Frozen_DeadlockAvoidanceTests.test_deadlock
839839
*test.test_importlib.test_locks.Frozen_DeadlockAvoidanceTests.test_no_deadlock
840-
*test.test_importlib.test_locks.Frozen_LifetimeTests.test_all_locks
841840
*test.test_importlib.test_locks.Frozen_LifetimeTests.test_lock_lifetime
842841
*test.test_importlib.test_locks.Frozen_ModuleLockAsRLockTests.test_acquire_contended
843842
*test.test_importlib.test_locks.Frozen_ModuleLockAsRLockTests.test_acquire_destroy
@@ -851,7 +850,6 @@
851850
*test.test_importlib.test_locks.Frozen_ModuleLockAsRLockTests.test_weakref_exists
852851
*test.test_importlib.test_locks.Source_DeadlockAvoidanceTests.test_deadlock
853852
*test.test_importlib.test_locks.Source_DeadlockAvoidanceTests.test_no_deadlock
854-
*test.test_importlib.test_locks.Source_LifetimeTests.test_all_locks
855853
*test.test_importlib.test_locks.Source_LifetimeTests.test_lock_lifetime
856854
*test.test_importlib.test_locks.Source_ModuleLockAsRLockTests.test_acquire_contended
857855
*test.test_importlib.test_locks.Source_ModuleLockAsRLockTests.test_acquire_destroy

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

Lines changed: 195 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@
109109
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
110110
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
111111
import com.oracle.graal.python.nodes.util.CastToJavaLongLossyNode;
112-
import com.oracle.graal.python.nodes.util.CastToJavaUnsignedLongNode;
113112
import com.oracle.graal.python.nodes.util.CastToTruffleStringNode;
114113
import com.oracle.graal.python.runtime.GilNode;
115114
import com.oracle.graal.python.runtime.PosixConstants;
@@ -126,6 +125,7 @@
126125
import com.oracle.graal.python.runtime.exception.PythonExitException;
127126
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
128127
import com.oracle.graal.python.runtime.sequence.PSequence;
128+
import com.oracle.graal.python.runtime.sequence.storage.LongSequenceStorage;
129129
import com.oracle.graal.python.runtime.sequence.storage.ObjectSequenceStorage;
130130
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
131131
import com.oracle.graal.python.util.OverflowException;
@@ -720,6 +720,24 @@ Object setsid(VirtualFrame frame,
720720
}
721721
}
722722

723+
@Builtin(name = "getgroups")
724+
@GenerateNodeFactory
725+
abstract static class GetGroupsNode extends PythonBuiltinNode {
726+
@Specialization
727+
Object getgroups(VirtualFrame frame,
728+
@Bind("this") Node inliningTarget,
729+
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib,
730+
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode,
731+
@Cached PythonObjectFactory factory) {
732+
try {
733+
long[] groups = posixLib.getgroups(getPosixSupport());
734+
return factory.createList(new LongSequenceStorage(groups));
735+
} catch (PosixException e) {
736+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e);
737+
}
738+
}
739+
}
740+
723741
@Builtin(name = "openpty")
724742
@GenerateNodeFactory
725743
public abstract static class OpenPtyNode extends PythonBuiltinNode {
@@ -2212,6 +2230,134 @@ PNone chmodFollow(VirtualFrame frame, PosixFd fd, int mode, int dirFd, @Suppress
22122230
}
22132231
}
22142232

2233+
@Builtin(name = "fchown", parameterNames = {"fd", "uid", "gid"})
2234+
@ArgumentClinic(name = "fd", conversion = ClinicConversion.Int)
2235+
@ArgumentClinic(name = "uid", conversionClass = UidConversionNode.class)
2236+
@ArgumentClinic(name = "gid", conversionClass = GidConversionNode.class)
2237+
@GenerateNodeFactory
2238+
abstract static class FChownNode extends PythonTernaryClinicBuiltinNode {
2239+
@Specialization
2240+
Object chown(VirtualFrame frame, int fd, long uid, long gid,
2241+
@Bind("this") Node inliningTarget,
2242+
@Cached SysModuleBuiltins.AuditNode auditNode,
2243+
@CachedLibrary(limit = "1") PosixSupportLibrary posixLib,
2244+
@Cached GilNode gil,
2245+
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
2246+
auditNode.audit(inliningTarget, "os.chown", fd, uid, gid, -1);
2247+
try {
2248+
gil.release(true);
2249+
try {
2250+
posixLib.fchown(getPosixSupport(), fd, uid, gid);
2251+
} finally {
2252+
gil.acquire();
2253+
}
2254+
} catch (PosixException e) {
2255+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e, fd);
2256+
}
2257+
return PNone.NONE;
2258+
}
2259+
2260+
@Override
2261+
protected ArgumentClinicProvider getArgumentClinic() {
2262+
return PosixModuleBuiltinsClinicProviders.FChownNodeClinicProviderGen.INSTANCE;
2263+
}
2264+
}
2265+
2266+
@Builtin(name = "lchown", parameterNames = {"path", "uid", "gid"})
2267+
@ArgumentClinic(name = "path", conversionClass = PathConversionNode.class, args = {"false", "false"})
2268+
@ArgumentClinic(name = "uid", conversionClass = UidConversionNode.class)
2269+
@ArgumentClinic(name = "gid", conversionClass = GidConversionNode.class)
2270+
@GenerateNodeFactory
2271+
abstract static class LChownNode extends PythonTernaryClinicBuiltinNode {
2272+
@Specialization
2273+
Object chown(VirtualFrame frame, PosixPath path, long uid, long gid,
2274+
@Bind("this") Node inliningTarget,
2275+
@Cached SysModuleBuiltins.AuditNode auditNode,
2276+
@CachedLibrary(limit = "1") PosixSupportLibrary posixLib,
2277+
@Cached GilNode gil,
2278+
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
2279+
auditNode.audit(inliningTarget, "os.chown", path.originalObject, uid, gid, -1);
2280+
try {
2281+
gil.release(true);
2282+
try {
2283+
posixLib.fchownat(getPosixSupport(), AT_FDCWD.value, path.value, uid, gid, false);
2284+
} finally {
2285+
gil.acquire();
2286+
}
2287+
} catch (PosixException e) {
2288+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e, path.originalObject);
2289+
}
2290+
return PNone.NONE;
2291+
}
2292+
2293+
@Override
2294+
protected ArgumentClinicProvider getArgumentClinic() {
2295+
return PosixModuleBuiltinsClinicProviders.LChownNodeClinicProviderGen.INSTANCE;
2296+
}
2297+
}
2298+
2299+
@Builtin(name = "chown", minNumOfPositionalArgs = 3, parameterNames = {"path", "uid", "gid"}, keywordOnlyNames = {"dir_fd", "follow_symlinks"})
2300+
@ArgumentClinic(name = "path", conversionClass = PathConversionNode.class, args = {"false", "true"})
2301+
@ArgumentClinic(name = "uid", conversionClass = UidConversionNode.class)
2302+
@ArgumentClinic(name = "gid", conversionClass = GidConversionNode.class)
2303+
@ArgumentClinic(name = "dir_fd", conversionClass = DirFdConversionNode.class)
2304+
@ArgumentClinic(name = "follow_symlinks", conversion = ClinicConversion.Boolean, defaultValue = "true")
2305+
@GenerateNodeFactory
2306+
abstract static class ChownNode extends PythonClinicBuiltinNode {
2307+
@Specialization
2308+
Object chown(VirtualFrame frame, PosixPath path, long uid, long gid, int dirFd, boolean followSymlinks,
2309+
@Bind("this") Node inliningTarget,
2310+
@Shared @Cached SysModuleBuiltins.AuditNode auditNode,
2311+
@Shared @CachedLibrary(limit = "1") PosixSupportLibrary posixLib,
2312+
@Shared @Cached GilNode gil,
2313+
@Shared @Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
2314+
auditNode.audit(inliningTarget, "os.chown", path.originalObject, uid, gid, dirFd != AT_FDCWD.value ? dirFd : -1);
2315+
try {
2316+
gil.release(true);
2317+
try {
2318+
posixLib.fchownat(getPosixSupport(), dirFd, path.value, uid, gid, followSymlinks);
2319+
} finally {
2320+
gil.acquire();
2321+
}
2322+
} catch (PosixException e) {
2323+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e, path.originalObject);
2324+
}
2325+
return PNone.NONE;
2326+
}
2327+
2328+
@Specialization
2329+
Object chown(VirtualFrame frame, PosixFd fd, long uid, long gid, int dirFd, boolean followSymlinks,
2330+
@Bind("this") Node inliningTarget,
2331+
@Shared @Cached SysModuleBuiltins.AuditNode auditNode,
2332+
@Shared @CachedLibrary(limit = "1") PosixSupportLibrary posixLib,
2333+
@Shared @Cached GilNode gil,
2334+
@Shared @Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
2335+
if (dirFd != AT_FDCWD.value) {
2336+
throw raise(ValueError, ErrorMessages.CANT_SPECIFY_BOTH_DIR_FD_AND_FD);
2337+
}
2338+
if (followSymlinks) {
2339+
throw raise(ValueError, ErrorMessages.CANNOT_USE_FD_AND_FOLLOW_SYMLINKS_TOGETHER, "chown");
2340+
}
2341+
auditNode.audit(inliningTarget, "os.chown", fd.originalObject, uid, gid, -1);
2342+
try {
2343+
gil.release(true);
2344+
try {
2345+
posixLib.fchown(getPosixSupport(), fd.fd, uid, gid);
2346+
} finally {
2347+
gil.acquire();
2348+
}
2349+
} catch (PosixException e) {
2350+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e, fd.originalObject);
2351+
}
2352+
return PNone.NONE;
2353+
}
2354+
2355+
@Override
2356+
protected ArgumentClinicProvider getArgumentClinic() {
2357+
return PosixModuleBuiltinsClinicProviders.ChownNodeClinicProviderGen.INSTANCE;
2358+
}
2359+
}
2360+
22152361
@Builtin(name = "readlink", minNumOfPositionalArgs = 1, parameterNames = {"path"}, varArgsMarker = true, keywordOnlyNames = {"dir_fd"}, doc = "readlink(path, *, dir_fd=None) -> path\n" +
22162362
"\nReturn a string representing the path to which the symbolic link points.\n")
22172363
@ArgumentClinic(name = "path", conversionClass = PathConversionNode.class, args = {"false", "false"})
@@ -3240,10 +3386,11 @@ public static PidtConversionNode create() {
32403386
}
32413387
}
32423388

3243-
/**
3244-
* Emulates CPython's {@code _Py_Uid_Converter()}. Always returns an {@code long}.
3245-
*/
3246-
public abstract static class UidConversionNode extends ArgumentCastNodeWithRaise {
3389+
@GenerateCached(false)
3390+
public abstract static class AbstractIdConversionNode extends ArgumentCastNodeWithRaise {
3391+
3392+
private static final long MAX_UINT32 = (1L << 32) - 1;
3393+
32473394
public abstract long executeLong(VirtualFrame frame, Object value);
32483395

32493396
@Specialization
@@ -3261,39 +3408,69 @@ long doLong(long value) {
32613408
long doGeneric(VirtualFrame frame, Object value,
32623409
@Bind("this") Node inliningTarget,
32633410
@Cached PyNumberIndexNode pyNumberIndexNode,
3264-
@Cached PyLongAsLongAndOverflowNode asLongAndOverflowNode,
3265-
@Cached CastToJavaUnsignedLongNode asUnsignedLong) {
3411+
@Cached PyLongAsLongNode asLongNode) {
32663412
Object index;
32673413
try {
32683414
index = pyNumberIndexNode.execute(frame, inliningTarget, value);
32693415
} catch (PException ex) {
3270-
throw raise(TypeError, ErrorMessages.UID_SHOULD_BE_INTEGER_NOT_P, value);
3271-
}
3272-
try {
3273-
return checkValue(asLongAndOverflowNode.execute(frame, inliningTarget, index));
3274-
} catch (OverflowException e) {
3275-
// fall through
3416+
throw raise(TypeError, ErrorMessages.S_SHOULD_BE_INTEGER_NOT_P, getIdName(), value);
32763417
}
3277-
return asUnsignedLong.execute(inliningTarget, index);
3278-
// We have no means to distinguish overflow/underflow so we just let any OverflowError
3279-
// from asUnsignedLong fall through. It will not have the same message as CPython, but
3280-
// still correct type.
3418+
/*
3419+
* We have no means to distinguish overflow/underflow, so we just let any OverflowError
3420+
* from asLongNode fall through. It will not have the same message as CPython, but still
3421+
* correct type.
3422+
*/
3423+
return checkValue(asLongNode.execute(frame, inliningTarget, index));
32813424
}
32823425

32833426
private long checkValue(long value) {
3427+
// Note that -1 is intentionally allowed
32843428
if (value < -1) {
3285-
throw raise(OverflowError, ErrorMessages.UID_IS_LESS_THAN_MINIMUM);
3429+
throw raise(OverflowError, ErrorMessages.S_IS_LESS_THAN_MINIMUM, getIdName());
3430+
} else if (value > MAX_UINT32) {
3431+
/* uid_t is uint32_t on Linux */
3432+
throw raise(OverflowError, ErrorMessages.S_IS_GREATER_THAN_MAXIUMUM, getIdName());
32863433
}
32873434
return value;
32883435
}
32893436

3437+
protected abstract String getIdName();
3438+
}
3439+
3440+
/**
3441+
* Emulates CPython's {@code _Py_Uid_Converter()}. Always returns a {@code long}.
3442+
*/
3443+
public abstract static class UidConversionNode extends AbstractIdConversionNode {
3444+
3445+
@Override
3446+
protected String getIdName() {
3447+
return "uid";
3448+
}
3449+
32903450
@ClinicConverterFactory(shortCircuitPrimitive = {PrimitiveType.Int, PrimitiveType.Long})
32913451
@NeverDefault
32923452
public static UidConversionNode create() {
32933453
return PosixModuleBuiltinsFactory.UidConversionNodeGen.create();
32943454
}
32953455
}
32963456

3457+
/**
3458+
* Emulates CPython's {@code _Py_Gid_Converter()}. Always returns a {@code long}.
3459+
*/
3460+
public abstract static class GidConversionNode extends AbstractIdConversionNode {
3461+
3462+
@Override
3463+
protected String getIdName() {
3464+
return "gid";
3465+
}
3466+
3467+
@ClinicConverterFactory(shortCircuitPrimitive = {PrimitiveType.Int, PrimitiveType.Long})
3468+
@NeverDefault
3469+
public static GidConversionNode create() {
3470+
return PosixModuleBuiltinsFactory.GidConversionNodeGen.create();
3471+
}
3472+
}
3473+
32973474
/**
32983475
* Represents the result of {@code path_t} conversion. Similar to CPython's {@code path_t}
32993476
* structure, but only contains the results of the conversion, not the conversion parameters.

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongAsLongNode.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@
5858

5959
/**
6060
* Equivalent of CPython's {@code PyLong_AsLong}. Converts an object into a Java long using it's
61-
* {@code __index__} or (deprecated) {@code __int__} method. Raises {@code OverflowError} on
62-
* overflow.
61+
* {@code __index__} method. Raises {@code OverflowError} on overflow.
6362
*/
6463
@GenerateUncached
6564
@GenerateInline(inlineByDefault = true)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ public abstract class ErrorMessages {
198198
public static final TruffleString CANNOT_SPECIFY_BOTH_COMMA_AND_UNDERSCORE = tsLiteral("Cannot specify both ',' and '_'.");
199199
public static final TruffleString CANNOT_SPECIFY_C_WITH_C = tsLiteral("Cannot specify '%c' with '%c'.");
200200
public static final TruffleString CANNOT_USE_FD_AND_FOLLOW_SYMLINKS_TOGETHER = tsLiteral("%s: cannot use fd and follow_symlinks together");
201+
public static final TruffleString CANT_SPECIFY_BOTH_DIR_FD_AND_FD = tsLiteral("%s: can't specify both dir_fd and fd");
201202
public static final TruffleString CANNOT_CONVERT_FLOAT_INFINITY_TO_INTEGER = tsLiteral("cannot convert float infinity to integer");
202203
public static final TruffleString CANNOT_CONVERT_FLOAT_NAN_TO_INTEGER = tsLiteral("cannot convert float NaN to integer");
203204
public static final TruffleString CANT_CAPTURE_NAME_UNDERSCORE_IN_PATTERNS = tsLiteral("can't capture name '_' in patterns");
@@ -859,8 +860,9 @@ public abstract class ErrorMessages {
859860
public static final TruffleString LINE_BUFFERING_ISNT_SUPPORTED = tsLiteral("line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used");
860861
public static final TruffleString UNCLOSED_FILE = tsLiteral("unclosed file %r");
861862
public static final TruffleString MAXIMUM_RECURSION_DEPTH_EXCEEDED = tsLiteral("maximum recursion depth exceeded");
862-
public static final TruffleString UID_IS_LESS_THAN_MINIMUM = tsLiteral("uid is less than minimum");
863-
public static final TruffleString UID_SHOULD_BE_INTEGER_NOT_P = tsLiteral("uid should be integer, not %p");
863+
public static final TruffleString S_IS_LESS_THAN_MINIMUM = tsLiteral("%s is less than minimum");
864+
public static final TruffleString S_IS_GREATER_THAN_MAXIUMUM = tsLiteral("%s is greater than maxiumum");
865+
public static final TruffleString S_SHOULD_BE_INTEGER_NOT_P = tsLiteral("%s should be integer, not %p");
864866
public static final TruffleString RANGE_OBJ_IDX_OUT_OF_RANGE = tsLiteral("range object index out of range");
865867
public static final TruffleString NUMBER_OF_BITS_MUST_BE_NON_NEGATIVE = tsLiteral("number of bits must be non-negative");
866868
public static final TruffleString TIMEOUT_MUST_BE_NON_NEG_NUM = tsLiteral("'timeout' must be a non-negative number");

0 commit comments

Comments
 (0)