Unrepresentable Limit Values RLIM_SAVED_CUR, RLIM_SAVED_MAX

 

36.2d — Unrepresentable Limit Values
RLIM_SAVED_CUR, RLIM_SAVED_MAX & the 32-bit rlim_t Problem | EmbeddedPathashala

The Problem: When rlim_t Cannot Hold the Value

In some programming environments, the rlim_t data type is too small to represent a resource limit value that the kernel actually holds. This is a portability problem that arises specifically on 32-bit systems compiled with large-file support. It leads to silent incorrect behaviour where setting a resource limit appears to succeed but the value stored in the kernel is different from what you asked for.

How the Problem Arises

rlim_t is defined to be the same size as off_t. On a 32-bit system with standard compilation, both are 32 bits. But if you compile with _FILE_OFFSET_BITS=64 (large-file support), off_t becomes 64 bits — and so does rlim_t in the C library.

The problem is that the Linux kernel on x86-32 represents resource limits internally as unsigned long, which is only 32 bits. So you have a 64-bit glibc wrapper talking to a 32-bit kernel representation.

64-bit Linux (x86-64)
rlim_t = 64 bits, kernel = 64 bits. No mismatch. RLIM_SAVED_CUR and RLIM_SAVED_MAX are defined equal to RLIM_INFINITY (meaning all values are representable).
32-bit Linux (x86-32) + _FILE_OFFSET_BITS=64
glibc rlim_t = 64 bits, kernel unsigned long = 32 bits. Mismatch! Values larger than 2^32-1 cannot be correctly passed to the kernel.

RLIM_SAVED_CUR and RLIM_SAVED_MAX

SUSv3 defines two special constants for portable handling of this situation:

  • RLIM_SAVED_CUR — returned by getrlimit() in rlim_cur when the soft limit cannot be represented in the current rlim_t type.
  • RLIM_SAVED_MAX — returned by getrlimit() in rlim_max when the hard limit cannot be represented.

On 64-bit Linux, these constants are defined equal to RLIM_INFINITY because there is no representability problem — all values fit. On 32-bit Linux with large-file support, they would theoretically indicate an unrepresentable value, but glibc takes a different approach (see below).

/* Portable reading of a limit */
struct rlimit rl;
getrlimit(RLIMIT_FSIZE, &rl);

if (rl.rlim_cur == RLIM_INFINITY)
    printf("No file size limit\n");
#ifdef RLIM_SAVED_CUR
else if (rl.rlim_cur == RLIM_SAVED_CUR)
    printf("Limit unrepresentable in this environment\n");
#endif
else
    printf("File size limit: %lld bytes\n", (long long)rl.rlim_cur);

glibc’s Silent Conversion Behaviour on x86-32

On 32-bit x86 with _FILE_OFFSET_BITS=64, the glibc setrlimit() wrapper handles the overflow differently from what RLIM_SAVED_CUR would suggest. The behaviour is:

If your program compiled with _FILE_OFFSET_BITS=64 tries to set a resource limit to a value larger than can fit in a 32-bit unsigned long (i.e. > 0xFFFFFFFF), the glibc setrlimit() wrapper silently converts that value to RLIM_INFINITY instead of returning an error. In other words, the limit you requested is silently not honored.

This is a design compromise the glibc developers made. Ideally setrlimit() would return EINVAL, but the fundamental problem is a kernel limitation. Returning success while not honoring the request is arguably worse, but it is the current behaviour.

This affects not just application programmers but end users — many file-handling utilities on x86-32 distributions are compiled with _FILE_OFFSET_BITS=64 and thus silently fail to set large resource limits.

Code Example — Checking for Unrepresentable Values

#include <stdio.h>
#include <sys/resource.h>

/* Portable limit printing that handles all three cases */
void print_rlimit_value(const char *label, rlim_t val)
{
    printf("%s: ", label);
    if (val == RLIM_INFINITY) {
        printf("unlimited (RLIM_INFINITY)\n");
        return;
    }
#ifdef RLIM_SAVED_CUR
    /* On some 32-bit systems this may differ from RLIM_INFINITY */
    if (val == RLIM_SAVED_CUR || val == RLIM_SAVED_MAX) {
        printf("UNREPRESENTABLE in this rlim_t type\n");
        return;
    }
#endif
    printf("%lld\n", (long long)val);
}

int main(void)
{
    struct rlimit rl;

    getrlimit(RLIMIT_FSIZE, &rl);
    print_rlimit_value("RLIMIT_FSIZE soft", rl.rlim_cur);
    print_rlimit_value("RLIMIT_FSIZE hard", rl.rlim_max);

    getrlimit(RLIMIT_AS, &rl);
    print_rlimit_value("RLIMIT_AS soft", rl.rlim_cur);
    print_rlimit_value("RLIMIT_AS hard", rl.rlim_max);

    return 0;
}
/* On 64-bit Linux: RLIM_SAVED_CUR == RLIM_INFINITY so the
   #ifdef block is never entered — all values are representable. */

Interview Questions

Q1. What is RLIM_SAVED_CUR and when is it returned by getrlimit()?

RLIM_SAVED_CUR is an SUSv3-defined constant returned by getrlimit() in the rlim_cur field when the current soft limit cannot be represented in the rlim_t data type of the calling program’s environment. It indicates an “unrepresentable” limit value. On 64-bit Linux, RLIM_SAVED_CUR is defined equal to RLIM_INFINITY because all values fit in the 64-bit rlim_t.

Q2. Why does the unrepresentable limit problem only affect 32-bit Linux and not 64-bit?

On 64-bit Linux, both glibc’s rlim_t and the kernel’s internal representation are 64 bits wide — there is no mismatch. On 32-bit x86 with _FILE_OFFSET_BITS=64, glibc’s rlim_t becomes 64 bits wide but the kernel still uses a 32-bit unsigned long internally, creating a size mismatch where large values cannot be passed correctly.

Q3. What does glibc’s setrlimit() wrapper do when a 32-bit overflow occurs on x86-32?

If a value larger than a 32-bit unsigned long is passed to setrlimit() on an x86-32 system compiled with _FILE_OFFSET_BITS=64, the glibc wrapper silently converts the value to RLIM_INFINITY. It does not return an error. This means the requested limit is silently not honored — the kernel sees “unlimited” instead of the large value you specified.

Leave a Reply

Your email address will not be published. Required fields are marked *