| 1 | /*- |
| 2 | * Copyright 2009 Colin Percival |
| 3 | * All rights reserved. |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms, with or without |
| 6 | * modification, are permitted provided that the following conditions |
| 7 | * are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 24 | * SUCH DAMAGE. |
| 25 | * |
| 26 | * This file was originally written by Colin Percival as part of the Tarsnap |
| 27 | * online backup system. |
| 28 | */ |
| 29 | |
| 30 | /* We use non-POSIX functionality in this file. */ |
| 31 | #undef _POSIX_C_SOURCE |
| 32 | |
| 33 | #include "scrypt_platform.h" |
| 34 | |
| 35 | #include <sys/types.h> |
| 36 | #include <sys/resource.h> |
| 37 | |
| 38 | #ifdef HAVE_SYS_PARAM_H |
| 39 | #include <sys/param.h> |
| 40 | #endif |
| 41 | #ifdef HAVE_SYS_SYSCTL_H |
| 42 | #include <sys/sysctl.h> |
| 43 | #endif |
| 44 | #ifdef HAVE_SYS_SYSINFO_H |
| 45 | #include <sys/sysinfo.h> |
| 46 | #endif |
| 47 | |
| 48 | #include <errno.h> |
| 49 | #include <stddef.h> |
| 50 | #include <stdint.h> |
| 51 | #include <string.h> |
| 52 | #include <unistd.h> |
| 53 | |
| 54 | #ifdef DEBUG |
| 55 | #include <stdio.h> |
| 56 | #endif |
| 57 | |
| 58 | #include "memlimit.h" |
| 59 | |
| 60 | /* If we don't have CTL_HW, we can't use HW_USERMEM. */ |
| 61 | #ifndef CTL_HW |
| 62 | #undef HW_USERMEM |
| 63 | #endif |
| 64 | |
| 65 | #ifdef CTL_HW |
| 66 | static int |
| 67 | memlimit_sysctl_hw(size_t * memlimit, int mibleaf) |
| 68 | { |
| 69 | int mib[2]; |
| 70 | uint8_t sysctlbuf[8]; |
| 71 | size_t sysctlbuflen = 8; |
| 72 | uint64_t sysctlval; |
| 73 | |
| 74 | /* Ask the kernel how much RAM we have. */ |
| 75 | mib[0] = CTL_HW; |
| 76 | mib[1] = mibleaf; |
| 77 | if (sysctl(mib, 2, sysctlbuf, &sysctlbuflen, NULL, 0)) |
| 78 | return (1); |
| 79 | |
| 80 | /* |
| 81 | * If we read 8 bytes out, assume this is a system-endian uint64_t. |
| 82 | * If we only read 4 bytes out, the OS is trying to give us a |
| 83 | * uint32_t answer -- but given how many systems now have 4GB+ of RAM, |
| 84 | * it's probably truncating, and we really can't trust the value we |
| 85 | * have returned to us. |
| 86 | */ |
| 87 | if (sysctlbuflen == sizeof(uint64_t)) |
| 88 | memcpy(&sysctlval, sysctlbuf, sizeof(uint64_t)); |
| 89 | else if (sysctlbuflen == sizeof(uint32_t)) |
| 90 | sysctlval = SIZE_MAX; |
| 91 | else |
| 92 | return (1); |
| 93 | |
| 94 | /* Return the sysctl value, but clamp to SIZE_MAX if necessary. */ |
| 95 | #if UINT64_MAX > SIZE_MAX |
| 96 | if (sysctlval > SIZE_MAX) |
| 97 | *memlimit = SIZE_MAX; |
| 98 | else |
| 99 | *memlimit = (size_t)sysctlval; |
| 100 | #else |
| 101 | *memlimit = sysctlval; |
| 102 | #endif |
| 103 | |
| 104 | /* Success! */ |
| 105 | return (0); |
| 106 | } |
| 107 | #endif |
| 108 | |
| 109 | /* If we don't HAVE_STRUCT_SYSINFO, we can't use sysinfo. */ |
| 110 | #ifndef HAVE_STRUCT_SYSINFO |
| 111 | #undef HAVE_SYSINFO |
| 112 | #endif |
| 113 | |
| 114 | /* If we don't HAVE_STRUCT_SYSINFO_TOTALRAM, we can't use sysinfo. */ |
| 115 | #ifndef HAVE_STRUCT_SYSINFO_TOTALRAM |
| 116 | #undef HAVE_SYSINFO |
| 117 | #endif |
| 118 | |
| 119 | #ifdef HAVE_SYSINFO |
| 120 | static int |
| 121 | memlimit_sysinfo(size_t * memlimit) |
| 122 | { |
| 123 | struct sysinfo info; |
| 124 | uint64_t totalmem; |
| 125 | |
| 126 | /* Get information from the kernel. */ |
| 127 | if (sysinfo(&info)) |
| 128 | return (1); |
| 129 | totalmem = info.totalram; |
| 130 | |
| 131 | /* If we're on a modern kernel, adjust based on mem_unit. */ |
| 132 | #ifdef HAVE_STRUCT_SYSINFO_MEM_UNIT |
| 133 | totalmem = totalmem * info.mem_unit; |
| 134 | #endif |
| 135 | |
| 136 | /* Return the value, but clamp to SIZE_MAX if necessary. */ |
| 137 | #if UINT64_MAX > SIZE_MAX |
| 138 | if (totalmem > SIZE_MAX) |
| 139 | *memlimit = SIZE_MAX; |
| 140 | else |
| 141 | *memlimit = (size_t)totalmem; |
| 142 | #else |
| 143 | *memlimit = totalmem; |
| 144 | #endif |
| 145 | |
| 146 | /* Success! */ |
| 147 | return (0); |
| 148 | } |
| 149 | #endif /* HAVE_SYSINFO */ |
| 150 | |
| 151 | static int |
| 152 | memlimit_rlimit(size_t * memlimit) |
| 153 | { |
| 154 | struct rlimit rl; |
| 155 | uint64_t memrlimit; |
| 156 | |
| 157 | /* Find the least of... */ |
| 158 | memrlimit = (uint64_t)(-1); |
| 159 | |
| 160 | /* ... RLIMIT_AS... */ |
| 161 | #ifdef RLIMIT_AS |
| 162 | if (getrlimit(RLIMIT_AS, &rl)) |
| 163 | return (1); |
| 164 | if ((rl.rlim_cur != RLIM_INFINITY) && |
| 165 | ((uint64_t)rl.rlim_cur < memrlimit)) |
| 166 | memrlimit = (uint64_t)rl.rlim_cur; |
| 167 | #endif |
| 168 | |
| 169 | /* ... RLIMIT_DATA... */ |
| 170 | if (getrlimit(RLIMIT_DATA, &rl)) |
| 171 | return (1); |
| 172 | if ((rl.rlim_cur != RLIM_INFINITY) && |
| 173 | ((uint64_t)rl.rlim_cur < memrlimit)) |
| 174 | memrlimit = (uint64_t)rl.rlim_cur; |
| 175 | |
| 176 | /* ... and RLIMIT_RSS. */ |
| 177 | #ifdef RLIMIT_RSS |
| 178 | if (getrlimit(RLIMIT_RSS, &rl)) |
| 179 | return (1); |
| 180 | if ((rl.rlim_cur != RLIM_INFINITY) && |
| 181 | ((uint64_t)rl.rlim_cur < memrlimit)) |
| 182 | memrlimit = (uint64_t)rl.rlim_cur; |
| 183 | #endif |
| 184 | |
| 185 | /* Return the value, but clamp to SIZE_MAX if necessary. */ |
| 186 | #if UINT64_MAX > SIZE_MAX |
| 187 | if (memrlimit > SIZE_MAX) |
| 188 | *memlimit = SIZE_MAX; |
| 189 | else |
| 190 | *memlimit = (size_t)memrlimit; |
| 191 | #else |
| 192 | *memlimit = memrlimit; |
| 193 | #endif |
| 194 | |
| 195 | /* Success! */ |
| 196 | return (0); |
| 197 | } |
| 198 | |
| 199 | #ifdef _SC_PHYS_PAGES |
| 200 | |
| 201 | /* Some systems define _SC_PAGESIZE instead of _SC_PAGE_SIZE. */ |
| 202 | #ifndef _SC_PAGE_SIZE |
| 203 | #define _SC_PAGE_SIZE _SC_PAGESIZE |
| 204 | #endif |
| 205 | |
| 206 | static int |
| 207 | memlimit_sysconf(size_t * memlimit) |
| 208 | { |
| 209 | long pagesize; |
| 210 | long physpages; |
| 211 | uint64_t totalmem; |
| 212 | |
| 213 | /* Set errno to 0 in order to distinguish "no limit" from "error". */ |
| 214 | errno = 0; |
| 215 | |
| 216 | /* Read the two limits. */ |
| 217 | if (((pagesize = sysconf(_SC_PAGE_SIZE)) == -1) || |
| 218 | ((physpages = sysconf(_SC_PHYS_PAGES)) == -1)) { |
| 219 | /* |
| 220 | * Did an error occur? OS X may return EINVAL due to not |
| 221 | * supporting _SC_PHYS_PAGES in spite of defining it. |
| 222 | */ |
| 223 | if (errno != 0 && errno != EINVAL) |
| 224 | return (1); |
| 225 | |
| 226 | /* If not, there is no limit. */ |
| 227 | totalmem = (uint64_t)(-1); |
| 228 | } else { |
| 229 | /* Compute the limit. */ |
| 230 | totalmem = (uint64_t)(pagesize) * (uint64_t)(physpages); |
| 231 | } |
| 232 | |
| 233 | /* Return the value, but clamp to SIZE_MAX if necessary. */ |
| 234 | #if UINT64_MAX > SIZE_MAX |
| 235 | if (totalmem > SIZE_MAX) |
| 236 | *memlimit = SIZE_MAX; |
| 237 | else |
| 238 | *memlimit = (size_t)totalmem; |
| 239 | #else |
| 240 | *memlimit = totalmem; |
| 241 | #endif |
| 242 | |
| 243 | /* Success! */ |
| 244 | return (0); |
| 245 | } |
| 246 | #endif |
| 247 | |
| 248 | int |
| 249 | memtouse(size_t maxmem, double maxmemfrac, size_t * memlimit) |
| 250 | { |
| 251 | size_t usermem_memlimit, memsize_memlimit; |
| 252 | size_t sysinfo_memlimit, rlimit_memlimit; |
| 253 | size_t sysconf_memlimit; |
| 254 | size_t memlimit_min; |
| 255 | size_t memavail; |
| 256 | |
| 257 | /* Get memory limits. */ |
| 258 | #ifdef HW_USERMEM |
| 259 | if (memlimit_sysctl_hw(&usermem_memlimit, HW_USERMEM)) |
| 260 | return (1); |
| 261 | #else |
| 262 | usermem_memlimit = SIZE_MAX; |
| 263 | #endif |
| 264 | #ifdef HW_MEMSIZE |
| 265 | if (memlimit_sysctl_hw(&memsize_memlimit, HW_MEMSIZE)) |
| 266 | return (1); |
| 267 | #else |
| 268 | memsize_memlimit = SIZE_MAX; |
| 269 | #endif |
| 270 | #ifdef HAVE_SYSINFO |
| 271 | if (memlimit_sysinfo(&sysinfo_memlimit)) |
| 272 | return (1); |
| 273 | #else |
| 274 | sysinfo_memlimit = SIZE_MAX; |
| 275 | #endif |
| 276 | if (memlimit_rlimit(&rlimit_memlimit)) |
| 277 | return (1); |
| 278 | #ifdef _SC_PHYS_PAGES |
| 279 | if (memlimit_sysconf(&sysconf_memlimit)) |
| 280 | return (1); |
| 281 | #else |
| 282 | sysconf_memlimit = SIZE_MAX; |
| 283 | #endif |
| 284 | |
| 285 | #ifdef DEBUG |
| 286 | fprintf(stderr, "Memory limits are %zu %zu %zu %zu %zu\n", |
| 287 | usermem_memlimit, memsize_memlimit, |
| 288 | sysinfo_memlimit, rlimit_memlimit, |
| 289 | sysconf_memlimit); |
| 290 | #endif |
| 291 | |
| 292 | /* Find the smallest of them. */ |
| 293 | memlimit_min = SIZE_MAX; |
| 294 | if (memlimit_min > usermem_memlimit) |
| 295 | memlimit_min = usermem_memlimit; |
| 296 | if (memlimit_min > memsize_memlimit) |
| 297 | memlimit_min = memsize_memlimit; |
| 298 | if (memlimit_min > sysinfo_memlimit) |
| 299 | memlimit_min = sysinfo_memlimit; |
| 300 | if (memlimit_min > rlimit_memlimit) |
| 301 | memlimit_min = rlimit_memlimit; |
| 302 | if (memlimit_min > sysconf_memlimit) |
| 303 | memlimit_min = sysconf_memlimit; |
| 304 | |
| 305 | /* Only use the specified fraction of the available memory. */ |
| 306 | if ((maxmemfrac > 0.5) || (maxmemfrac == 0.0)) |
| 307 | maxmemfrac = 0.5; |
| 308 | memavail = (size_t)(maxmemfrac * memlimit_min); |
| 309 | |
| 310 | /* Don't use more than the specified maximum. */ |
| 311 | if ((maxmem > 0) && (memavail > maxmem)) |
| 312 | memavail = maxmem; |
| 313 | |
| 314 | /* But always allow at least 1 MiB. */ |
| 315 | if (memavail < 1048576) |
| 316 | memavail = 1048576; |
| 317 | |
| 318 | #ifdef DEBUG |
| 319 | fprintf(stderr, "Allowing up to %zu memory to be used\n", memavail); |
| 320 | #endif |
| 321 | |
| 322 | /* Return limit via the provided pointer. */ |
| 323 | *memlimit = memavail; |
| 324 | return (0); |
| 325 | } |