@@ -281,14 +281,17 @@ public struct Parser {
281281 user: processFlags. user, uid: processFlags. uid,
282282 gid: processFlags. gid, defaultUser: defaultUser)
283283
284+ let rlimits = try Parser . rlimits ( processFlags. ulimits)
285+
284286 return . init(
285287 executable: commandToRun. first!,
286288 arguments: [ String] ( commandToRun. dropFirst ( ) ) ,
287289 environment: envvars,
288290 workingDirectory: workingDir,
289291 terminal: processFlags. tty,
290292 user: user,
291- supplementalGroups: additionalGroups
293+ supplementalGroups: additionalGroups,
294+ rlimits: rlimits
292295 )
293296 }
294297
@@ -867,6 +870,114 @@ public struct Parser {
867870 return !label. ranges ( of: pattern) . isEmpty
868871 }
869872
873+ // TODO: When containerization supports all 16 (minus AS as it's not great) add
874+ // them here.
875+ private static let ulimitNameToRlimit : [ String : String ] = [
876+ " core " : " RLIMIT_CORE " ,
877+ " cpu " : " RLIMIT_CPU " ,
878+ " data " : " RLIMIT_DATA " ,
879+ " fsize " : " RLIMIT_FSIZE " ,
880+ " memlock " : " RLIMIT_MEMLOCK " ,
881+ " nofile " : " RLIMIT_NOFILE " ,
882+ " nproc " : " RLIMIT_NPROC " ,
883+ " rss " : " RLIMIT_RSS " ,
884+ " stack " : " RLIMIT_STACK " ,
885+ ]
886+
887+ /// Parse ulimit specifications into Rlimit objects
888+ /// Format: <type>=<soft>[:<hard>]
889+ /// Examples:
890+ /// - nofile=1024:2048 (soft=1024, hard=2048)
891+ /// - nofile=1024 (soft=hard=1024)
892+ /// - nofile=unlimited (soft=hard=UINT64_MAX)
893+ /// - nofile=1024:unlimited (soft=1024, hard=UINT64_MAX)
894+ public static func rlimits( _ rawUlimits: [ String ] ) throws -> [ ProcessConfiguration . Rlimit ] {
895+ var rlimits : [ ProcessConfiguration . Rlimit ] = [ ]
896+ var seenTypes : Set < String > = [ ]
897+
898+ for ulimit in rawUlimits {
899+ let rlimit = try Parser . rlimit ( ulimit)
900+ if seenTypes. contains ( rlimit. limit) {
901+ throw ContainerizationError (
902+ . invalidArgument,
903+ message: " duplicate ulimit type: \( ulimit. split ( separator: " = " ) . first ?? " " ) "
904+ )
905+ }
906+ seenTypes. insert ( rlimit. limit)
907+ rlimits. append ( rlimit)
908+ }
909+
910+ return rlimits
911+ }
912+
913+ /// Parse a single ulimit specification
914+ public static func rlimit( _ ulimit: String ) throws -> ProcessConfiguration . Rlimit {
915+ let parts = ulimit. split ( separator: " = " , maxSplits: 1 )
916+ guard parts. count == 2 else {
917+ throw ContainerizationError (
918+ . invalidArgument,
919+ message: " invalid ulimit format ' \( ulimit) ': expected <type>=<soft>[:<hard>] "
920+ )
921+ }
922+
923+ let typeName = String ( parts [ 0 ] ) . lowercased ( )
924+ let valuesPart = String ( parts [ 1 ] )
925+
926+ guard let rlimitType = ulimitNameToRlimit [ typeName] else {
927+ let validTypes = ulimitNameToRlimit. keys. sorted ( ) . joined ( separator: " , " )
928+ throw ContainerizationError (
929+ . invalidArgument,
930+ message: " unsupported ulimit type ' \( typeName) ': valid types are \( validTypes) "
931+ )
932+ }
933+
934+ let valueParts = valuesPart. split ( separator: " : " , maxSplits: 1 )
935+ let soft : UInt64
936+ let hard : UInt64
937+
938+ switch valueParts. count {
939+ case 1 :
940+ // Single value: use for both soft and hard
941+ soft = try parseRlimitValue ( String ( valueParts [ 0 ] ) , typeName: typeName)
942+ hard = soft
943+ case 2 :
944+ // Two values: soft:hard
945+ soft = try parseRlimitValue ( String ( valueParts [ 0 ] ) , typeName: typeName)
946+ hard = try parseRlimitValue ( String ( valueParts [ 1 ] ) , typeName: typeName)
947+ default :
948+ throw ContainerizationError (
949+ . invalidArgument,
950+ message: " invalid ulimit format ' \( ulimit) ': expected <type>=<soft>[:<hard>] "
951+ )
952+ }
953+
954+ if soft > hard {
955+ throw ContainerizationError (
956+ . invalidArgument,
957+ message: " ulimit ' \( typeName) ' soft limit ( \( soft) ) cannot exceed hard limit ( \( hard) ) "
958+ )
959+ }
960+
961+ return ProcessConfiguration . Rlimit ( limit: rlimitType, soft: soft, hard: hard)
962+ }
963+
964+ private static func parseRlimitValue( _ value: String , typeName: String ) throws -> UInt64 {
965+ let trimmed = value. trimmingCharacters ( in: . whitespaces) . lowercased ( )
966+
967+ if trimmed == " unlimited " || trimmed == " -1 " {
968+ return UInt64 . max
969+ }
970+
971+ guard let parsed = UInt64 ( trimmed) else {
972+ throw ContainerizationError (
973+ . invalidArgument,
974+ message: " invalid ulimit value ' \( value) ' for ' \( typeName) ': must be a non-negative integer or 'unlimited' "
975+ )
976+ }
977+
978+ return parsed
979+ }
980+
870981 // MARK: Miscellaneous
871982
872983 public static func parseBool( string: String ) -> Bool ? {
0 commit comments