limit.sh 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #!/bin/bash
  2. #
  3. # Resource limiting wrapper for command execution
  4. #
  5. # Why is this in shell script? Because bash has a setrlimit() wrapper
  6. # and is available on most Linux systems. If Perl was distributed with
  7. # BSD::Resource included, we would happily use that instead, but it isn't.
  8. # Clean up cgroup
  9. cleanup() {
  10. # First we have to move the current task into a "garbage" group, otherwise
  11. # the cgroup will not be empty, and attempting to remove it will fail with
  12. # "Device or resource busy"
  13. if [ -w "$MW_CGROUP"/tasks ]; then
  14. GARBAGE="$MW_CGROUP"
  15. else
  16. GARBAGE="$MW_CGROUP"/garbage-`id -un`
  17. if [ ! -e "$GARBAGE" ]; then
  18. mkdir -m 0700 "$GARBAGE"
  19. fi
  20. fi
  21. echo $BASHPID > "$GARBAGE"/tasks
  22. # Suppress errors in case the cgroup has disappeared due to a release script
  23. rmdir "$MW_CGROUP"/$$ 2>/dev/null
  24. }
  25. updateTaskCount() {
  26. # There are lots of ways to count lines in a file in shell script, but this
  27. # is one of the few that doesn't create another process, which would
  28. # increase the returned number of tasks.
  29. readarray < "$MW_CGROUP"/$$/tasks
  30. NUM_TASKS=${#MAPFILE[*]}
  31. }
  32. log() {
  33. echo limit.sh: "$*" >&3
  34. echo limit.sh: "$*" >&2
  35. }
  36. MW_INCLUDE_STDERR=
  37. MW_USE_LOG_PIPE=
  38. MW_CPU_LIMIT=0
  39. MW_CGROUP=
  40. MW_MEM_LIMIT=0
  41. MW_FILE_SIZE_LIMIT=0
  42. MW_WALL_CLOCK_LIMIT=0
  43. # Override settings
  44. eval "$2"
  45. if [ -n "$MW_INCLUDE_STDERR" ]; then
  46. exec 2>&1
  47. fi
  48. if [ -z "$MW_USE_LOG_PIPE" ]; then
  49. # Open a dummy log FD
  50. exec 3>/dev/null
  51. fi
  52. if [ "$MW_CPU_LIMIT" -gt 0 ]; then
  53. ulimit -t "$MW_CPU_LIMIT"
  54. fi
  55. if [ "$MW_MEM_LIMIT" -gt 0 ]; then
  56. if [ -n "$MW_CGROUP" ]; then
  57. # Create cgroup
  58. if ! mkdir -m 0700 "$MW_CGROUP"/$$; then
  59. log "failed to create the cgroup."
  60. MW_CGROUP=""
  61. fi
  62. fi
  63. if [ -n "$MW_CGROUP" ]; then
  64. echo $$ > "$MW_CGROUP"/$$/tasks
  65. if [ -n "$MW_CGROUP_NOTIFY" ]; then
  66. echo "1" > "$MW_CGROUP"/$$/notify_on_release
  67. fi
  68. # Memory
  69. echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.limit_in_bytes
  70. # Memory+swap
  71. # This will be missing if there is no swap
  72. if [ -e "$MW_CGROUP"/$$/memory.memsw.limit_in_bytes ]; then
  73. echo $(($MW_MEM_LIMIT*1024)) > "$MW_CGROUP"/$$/memory.memsw.limit_in_bytes
  74. fi
  75. else
  76. ulimit -v "$MW_MEM_LIMIT"
  77. fi
  78. else
  79. MW_CGROUP=""
  80. fi
  81. if [ "$MW_FILE_SIZE_LIMIT" -gt 0 ]; then
  82. ulimit -f "$MW_FILE_SIZE_LIMIT"
  83. fi
  84. if [ "$MW_WALL_CLOCK_LIMIT" -gt 0 -a -x "/usr/bin/timeout" ]; then
  85. /usr/bin/timeout $MW_WALL_CLOCK_LIMIT /bin/bash -c "$1" 3>&-
  86. STATUS="$?"
  87. if [ "$STATUS" == 124 ]; then
  88. log "timed out executing command \"$1\""
  89. fi
  90. else
  91. eval "$1" 3>&-
  92. STATUS="$?"
  93. fi
  94. if [ -n "$MW_CGROUP" ]; then
  95. updateTaskCount
  96. if [ $NUM_TASKS -gt 1 ]; then
  97. # Spawn a monitor process which will continue to poll for completion
  98. # of all processes in the cgroup after termination of the parent shell
  99. (
  100. while [ $NUM_TASKS -gt 1 ]; do
  101. sleep 10
  102. updateTaskCount
  103. done
  104. cleanup
  105. ) >&/dev/null < /dev/null 3>&- &
  106. disown -a
  107. else
  108. cleanup
  109. fi
  110. fi
  111. exit "$STATUS"