Solaris OS Solutions to 32-Bit stdio's 256 File-Descriptors Limitation

   
By Giri Mandalika, May 2007  

Historically, 32-bit applications running on the Solaris Operating System (OS) have been limited to using only the file descriptors 0 through 255 with the standard I/O functions in the C library. The extended FILE facility on Solaris 10 OS and later versions allows well-behaved 32-bit applications to use any valid file descriptor with the standard I/O functions.

A well-behaved application is one that meets all three of the following requirements:

  • Does not directly access any member fields in the FILE structure pointed to by the FILE pointer associated with any standard I/O stream
  • Checks all return values from standard I/O functions for error conditions
  • Behaves appropriately when an error condition is reported

This article explains in detail the runtime and programming solutions that were introduced under the extended FILE facility. The following discussion is relevant only in 32-bit applications, as 64-bit applications are immune to the limitation to 256 file descriptors.

Historical Background

A quick web search on the keywords Solaris stdio open file descriptors results in numerous references to stdio's limitation of 256 open file descriptors on the Solaris OS. A 1992 request for enhancement (RFE), explains the problem: "32-bit stdio routines should support file descriptors >255." The bug report links to a handful of other bugs related to stdio's limitation to 256 file descriptors.

The reason for this limitation on the Solaris OS is that an unsigned char is used to store the value of the file descriptor associated with a standard I/O stream. Have a look at the definition of the FILE structure that you will find in the header /usr/include/stdio_impl.h on any Solaris OS system that does not have the solutions that this article discusses:

struct __FILE_TAG       
{
#ifdef _STDIO_REVERSE
        unsigned char   *_ptr;
        int             _cnt;   
#else
        int             _cnt;  
        unsigned char   *_ptr;  
#endif
        unsigned char   *_base;
        unsigned char   _flag;
         
                   unsigned char   _file;                           /* UNIX System file descriptor */
        unsigned        __orientation:2; 
        unsigned        __ionolock:1;   
        unsigned        __seekable:1;
        unsigned        __filler:4;
};
                

The name __FILE_TAG is just an alias for FILE. See /usr/include/stdio_tag.h .

The member field _file holds the file descriptor, which was declared as an unsigned char. An unsigned char occupies 8 bits in memory. Hence _file can hold a maximum value of 2^8 = 256. In other words, _file restricts the access to 256 file descriptors per 32-bit process. This limitation was clearly documented in the manual page of stdio(3C) .

Sun did not make changes from an 8-bit unsigned char to a 16-bit int to accommodate more file descriptors. Doing so would have broken the much-promised binary compatibility with earlier releases of the Solaris OS, because it changes the size of the FILE structure.

Solutions

Starting with the Solaris 10 release slated for July 2007, Sun will offer runtime and programming solutions in the form of an extended FILE facility to alleviate stdio's limitation of 256 file descriptors. Systems running the Solaris Express release or any OpenSolaris OS distribution after build 39 will have these solutions. This article's Patches and Bugs section contains instructions for installing the extended FILE facility on systems running any release from Solaris 10 3/05 to Solaris 10 11/06.

Runtime Solution

As the title of this section suggests, a runtime solution does not require any source-code changes or recompilation of the objects to overcome the 256 file-descriptors limitation with the stdio(3C) C library functions. However, the default behavior of existing 32-bit applications will not change unless you explicitly enable the extended FILE facility. Applications that enable this feature will be able to associate any valid file descriptor with a standard I/O -- or stdio -- stream. Any value that lies within the range 3 and the value returned by ulimit -n from the shell used to launch the application is a valid file descriptor. The file descriptors 0, 1, and 2 are reserved for use as the default stdin, stdout, and stderr I/O streams.

You can increase the per-process maximum number of file descriptors in a shell from the default 256 to any value that is less than or equal to the value returned by the command

echo 'rlim_fd_max/D' | mdb -k | awk '{ print $2 }'

To adjust the file-descriptor limit in a shell, run ulimit -n <max_file_descriptors> in sh/ksh/bash or limit descriptors <max_file_descriptors> in csh, where max_file_descriptors is the maximum number of file descriptors you desire.

The default hard limit for the number of files a process can have opened at any time is 65,536. You can tune this limit with the system-tunable parameter rlim_fd_max . Although a very large number of files can be opened by tuning the rlim_fd_max parameter, virtual memory space becomes the limit for 32-bit processes when hundreds of thousands of files are open. When the process reaches the limits of virtual memory, stdio calls fail with a Not enough space error.

Before running the 32-bit application, enable the extended FILE facility by taking the following two actions:

  1. Raise the maximum number of file descriptors in a shell.
  2. Preload the extended FILE facility, /usr/lib/extendedFILE.so.1.

Note that extendedFILE.so.1 is not a library but an enabler of the extended FILE facility.

Here is how to enable the extended FILE facility from ksh:

% ulimit -n
256

% echo 'rlim_fd_max/D' | mdb -k | awk '{ print $2 }'
65536

% ulimit -n 65537
ksh: ulimit: exceeds allowable limit

%  
                   ulimit -n 65536

% ulimit -n
65536

%  
                   export LD_PRELOAD_32=/usr/lib/extendedFILE.so.1
%  
                   application [arg1 arg2 .. arg                      n]                   
                

The following example shows the behavior of a simple 32-bit process with and without the extended FILE facility enabled. The test case, a simple C program, tries to open 65,536 files with the fopen() interface.

% cat fopentestcase.c

#include <stdio.h>
#include <stdlib.h>

#define NoOfFILES 65536

int main()
{
        char filename[10];
        FILE *fds[NoOfFILES];
        int i;

        for (i = 0; i < NoOfFILES; ++i)
        {
                sprintf (filename, "/tmp/%d.log", i);
                fds[i] = fopen(filename, "w");

                if (fds[i] == NULL)
                {
                        printf("\nNumber of open files = %d. " \
                           "fopen() failed with error:  ", i);
                        perror("");
                        exit(1);
                }
                else
                {
                        fprintf (fds[i], "some string");
                }
        }
        return (0);
}

Reproduce the failure with the default maximum number of file descriptors in a shell:

% cc -o fopentestcase fopentestcase.c

% ulimit -a | grep descriptors
nofiles(descriptors) 256

% ./fopentestcase
Number of open files = 253. fopen() failed with error: 
Too many open files

Raise the file-descriptor limit, enable the extended FILE facility, and run the test case again to see the runtime solution at work.

%  
                   ulimit -n 5000

% ulimit -a | grep descriptors
nofiles(descriptors) 5000

%  
                   export LD_PRELOAD_32=/usr/lib/extendedFILE.so.1

% ./fopentestcase
Number of open files = 4996. fopen() failed with error:  
Too many open files

%  
                   ulimit -n 65536

% ulimit -a | grep descriptors
nofiles(descriptors) 65536

% ./fopentestcase
Number of open files = 65532. fopen() failed with error:
Too many open files
                

Observe the shortage of one file descriptor -- excluding 0, 1, and 2 for stdin, stdout, and stderr, respectively -- in the preceding examples. When the extended FILE facility is enabled, the file descriptor 196 will be made unallocatable by default to minimize silent data corruption. See the next section, Environment Variables, for more information.

Here is the pfiles output to confirm this proposition:

% pfiles `pgrep fopentestcase` | egrep "log|:"
 ...
 195: S_IFREG mode:0644 dev:102,7 ino:7380 uid:209044 ...
      /tmp/192.log
 197: S_IFREG mode:0644 dev:102,7 ino:7381 uid:209044 ...
      /tmp/193.log
 ...

Environment Variables

Two environment variables control the behavior of the extended FILE facility: _STDIO_BADFD and _STDIO_BADFD_SIGNAL.

  • _STDIO_BADFD -- This variable takes any integer value in the range from 3 through 255, which will be made unallocatable as a file descriptor. Setting this environment variable will provide a protection mechanism to software with unknown behaviors, such as third-party libraries without source code, so that applications will not experience silent data corruption. In the absence of this environment variable, a default value of 196 will be marked as an unallocatable file descriptor during runtime.

    As you know, object code built on the Solaris OS in the era before the extended FILE facility existed will not be expecting any file descriptor that doesn't fit in an 8-bit unsigned char, and it will not understand how to handle extended FILE pointers. For these reasons, the range has been restricted from 3 through 255, so the code that retrieves the file-descriptor value by dereferencing the FILE -> _file rather than the fileno(3C) function will receive the unallocatable or bad file descriptor when the actual descriptor is indeed an extended file descriptor -- that is, any value greater than 255.

  • _STDIO_BADFD_SIGNAL -- This environment variable has been introduced to specify the signal to be sent to the process when the uninspected code tries to modify the unallocatable file descriptor. This variable takes an integer or string representing any valid signal. See signal.h(3HEAD) for valid values or strings for all supported signals on the Solaris OS. This variable causes the specified signal to be sent to the application if certain exceptional cases are detected during the use of the extended FILE facility. The default signal is SIGABRT.
When Not to Use This Solution

Do not enable the extended FILE facility if the application does either of the following:

  • Directly dereferences the _file member of the FILE structure
  • Uses the fileno() macro, which was removed from headers in Solaris release 2.7, rather than the fileno(FILE) function to get the value of the underlying file descriptor

When this feature is enabled, file descriptors greater than 255 will be stored in an auxiliary location unknown to the application, and an unallocatable or bad file descriptor held by the environment variable _STDIO_BADFD will be stored in the FILE -> _file member field. Improper access by the application to the FILE -> _file member field will yield the unallocatable bad file descriptor when the actual underlying file descriptor is greater than 255, thus leading to silent data corruption.

Also, data corruption can occur if the process truncates the value returned by the fileno(FILE) function. For example, if the 16-bit or 32-bit int value returned by the fileno() function is stored in an 8-bit unsigned char variable, truncation occurs. Accessing the truncated file descriptor may then yield errors.

The following error message during runtime is a clear indication that the application is modifying the internal file-descriptor member field of the FILE structure from stdio.

Application violated extended FILE safety mechanism.
Please read the man page for extendedFILE.
Aborting

When you receive such an error message, stop using the extended FILE facility with the application. If possible, fix the source by replacing all references to FILE -> _file with calls to fileno(FILE). Ignoring this runtime error could lead to data corruption.

Example

The following trivial example illustrates the usage of both environment variables, _STDIO_BADFD and _STDIO_BADFD_SIGNAL, and it shows the subsequent program crash when the code violates the extended FILE safety mechanism.

Compile the following code and build a library on any system running the Solaris OS without the extended FILE solutions. Note: Unpatched systems running Solaris 10 3/05 through Solaris 10 11/06 releases do not contain the extended FILE solutions. However, it is possible to install the extended FILE facility on those systems by applying the latest kernel and libc patches. See this article's Patches and Bugs section for instructions.

% cat thirdpartysrc.c

#include <stdio.h>

void manipulatefd (FILE *fptr)
{
        ;
        ;
        fprintf(stdout, "\n%s : manipulatefd(): " \
                "underlying file descriptor = %d\n", \
                __FILE__, fptr -> _file);
         
                   fptr -> _file = 123;         fprintf(fptr, "This call is gonna fail!\n");
        ;
        ;
}

% cc -G -o /tmp/libthirdparty.so thirdpartysrc.c
                

Compile the following code and build an executable by linking the object code with the library created in the preceding step, on any system running the Solaris OS with the extended FILE solutions:

% cat enableextfile.c

#include <stdio.h>
#include <stdlib.h>

#define NoOfFiles 500

void manipulatefd(FILE *);

int main ()
{
        FILE *fptr;
        int i;

        for (i = 0; i < NoOfFiles; i++)
        {
                fptr = fopen("/tmp/enable_test.txt", "w");

                if (fptr == NULL)
                {
                        perror("fopen failed. ");
                        exit(1);
                }

                printf("\nfd = %d", fileno(fptr));

                if (fileno(fptr) % 400 == 0)
                {
                        manipulatefd(fptr);
                }
        }

        return(0);
}

% export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH
% cc -o enableextfile -lthirdparty enableextfile.c

Raise the limit of maximum file descriptors per process to any number greater than 255, set the environment variables _STDIO_BADFD and _STDIO_BADFD_SIGNAL, enable the extended FILE facility by preloading /usr/lib/extendedFILE.so.1, and finally run the executable:

% ulimit -n
256

%  
                   ulimit -n 500

% ulimit -n
500

%  
                   export _STDIO_BADFD=196
%  
                   export _STDIO_BADFD_SIGNAL=SIGABRT
%  
                   export LD_PRELOAD_32=/usr/lib/extendedFILE.so.1

% ./enableextfile
fd = 3
fd = 4
fd = 5
...
...
fd = 398
fd = 399
fd = 400
thirdpartysrc.c : manipulatefd(): 
        underlying file descriptor = 196
                    Application violated extended FILE safety mechanism. Please read the man page for extendedFILE. Aborting
Abort(coredump)

% /usr/bin/pstack core
core 'core' of 10172:   ./enableextfile
 d1f28e65 _lwp_kill (1, 6) + 15
 d1ee2102 raise    (6) + 22
 d1ec0dad abort    (0, 80677e0, d1f60000, ...) + cd
 d1f01d54 _file_get (80677e0) + b4
 d1efeb21 _findbuf (80677e0) + 31
 d1ef2f16 _ndoprnt (d1f70344, 80471d4, 80677e0, 0) + 46
 d1ef669f  
                   fprintf  (80677e0, d1f70344) + 9f
 d1f702cb  
                   manipulatefd (80677e0) + 3b
 0805097f main     (1, 8047214, 804721c) + 9f
 0805084a _start   (1, 8047360, 0, 8047370, ... ) + 7a
                

If the application does not show any of the previously mentioned patterns, it can take advantage of this runtime solution regardless of its age -- that is, even applications built on the Solaris 7 OS or earlier versions may continue to work flawlessly.

See the manual page of extendedFILE(5) for more examples.

Programming Solutions

This section is intended for new applications and applications that the developer can easily modify.

Two programmatic interfaces will allow access to the larger than 256 file-descriptor FILE pool, provided the maximum file-descriptors resource limit has been raised. Note that the default limit on maximum file descriptors is still 256.

Enhanced Standard I/O Open Calls: fopen(3C), fdopen(3C), and popen(3C)

In order to reduce the effort in modifying the existing sources to take advantage of the extended FILE feature, the existing mode string of stdio open calls such as fopen(3C), fdopen(3C), and popen(3C) have been augmented with a new flag: F. For example:

FILE *fptr = fopen("dummy.txt", "r
                   F");

int fd = creat("dummy2.txt", S_IWUSR);
FILE *stream = fdopen(fd, "w
                   F");

FILE *ptr = popen("/usr/bin/ls *.txt", "r
                   F");
                

If the last character of the mode string is an F, 32-bit processes will be allowed to associate a stream with a file accessed by a file descriptor with a value greater than 255. In the case of 64-bit applications, the application will silently ignore character F in the mode string. Except for this minor enhancement, the existing semantics of stdio open calls have not changed.

The F in the mode string of stdio open calls is intended only for code that does not do the following:

  • Directly dereference the member fields of the FILE structure
  • Return a FILE pointer to the caller

If the application exhibits any of the previously mentioned patterns, the character F must not be appended in the mode string to enable the extended FILE feature. Data corruption could occur if 32-bit applications directly use the member fields in the FILE structure when the last character of mode is F.

FILEFILEFILE enable_extended_FILE_stdio(3C)

For a usage example, rebuild the test case after changing the line

fds[i] = fopen(filename, "w");

to this:

fds[i] = fopen(filename, "wF");

Now raise the file-descriptor limit from the shell, and run the test case again to see the results:

% cc -o fopentestcaseF fopentestcase.c

% ulimit -n 10000

% ulimit -a | grep descriptors
nofiles(descriptors) 10000

% ./fopentestcaseF
Number of open files = 9996. fopen() failed with error:  
Too many open files

Note the absence of linking against any special libraries to make it work. All the stdio routines are still part of libc.

See the manual pages fopen(3C) , fdopen(3C) , and popen(3C) for more information.

New Programming Interface: enable_extended_FILE_stdio(3C)

If the FILE pointer is not confined within the context of a single function, you can use the new programming interface enable_extended_FILE_stdio( 3C) to enable the extended FILE facility. This interface minimizes the data corruption by providing some protection mechanism to software with unknown behaviors, such as third-party libraries without source code. For instance, by using this interface, the user can choose any signal to be sent to the process during runtime when the application dereferences FILE -> _file inappropriately.

This new interface was defined in the /usr/include/stdio_ext.h header as follows:

int enable_extended_FILE_stdio(int, int);

The first argument, an integer, specifies the file descriptor in the range of 3 through 255 that the application wants to be selected as the unallocatable file descriptor. Alternatively, setting it to -1 will request enable_extended_FILE_stdio(3C) to select a reasonable unallocatable file descriptor. This is the equivalent of setting the environment variable _STDIO_BADFD when enabling the runtime solution for extended FILEs.

The second argument, an integer, specifies the signal to be sent to the process when the unallocatable file descriptor is used as a file-descriptor argument to any system call except close(2) or closefrom(3C). Some applications may attempt to close file descriptors that they did not open. This exception prevents the application from crashing because of such harmless calls.

If -1 is passed, the default signal SIGABRT will be sent to the process. A value of 0 ignores any FILE -> _file dereferences by disabling the sending of a signal. Otherwise, the specified signal will be sent to the process. See the manual page of signal.h(3HEAD) for the complete list of signals on the Solaris OS. This is the equivalent of setting the environment variable _STDIO_BADFD_SIGNAL when enabling the runtime solution for extended FILEs.

The enable_extended_FILE_stdio(3C) function is available only in the 32-bit compilation environment.

For the extended FILE facility to be effective, raise the default maximum limit on file descriptors for the process from 256 to any number less than or equal to the hard limit for the number of files a process can have opened at any time. See the kernel tunable rlim_fd_max for more information. You can do this either from the shell with ulimit/limit commands or programmatically by using the getrlimit(2)/setrlimit(2) functions defined in the /usr/include/sys/resource.h header.

The trivial programming example that follows demonstrates three things:

  • Setting the file-descriptor limit through getrlimit(2)/setrlimit(2) interfaces
  • The usage of this new function to enable the extended FILE facility
  • The application crash when an uninspected code abuses the underlying file descriptor by directly changing the value of FILE -> _file

Compile the following code and build an executable by linking the object code with the library libthirdparty.so, created in the Runtime Solution section of this article.

% cat enableextfilestdio.c

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

#define NoOfFiles 500

void manipulatefd(FILE *);

int main ()
{
        FILE *fptr;
        struct rlimit rlp;
        int i;

         
                   (void) getrlimit (RLIMIT_NOFILE, &rlp);
        /* set the desired number of file descriptors */
         
                   rlp.rlim_cur = NoOfFiles;

        if (
                   setrlimit (RLIMIT_NOFILE, &rlp) == -1)
        {
                perror ("setrlimit(): ");
                exit (1);
        }

        if (
                   enable_extended_FILE_stdio (-1, -1) == -1)
        {
                perror ("enable_extended_FILE_stdio(3C): ");
                exit (1);
        }

        for (i = 0; i < NoOfFiles; i++)
        {
                fptr = fopen ("/tmp/enable_test.txt", "w");

                if (fptr == NULL)
                {
                        perror("\nfopen failed. ");
                        exit (1);
                }

                printf ("\nfd = %d", fileno(fptr));

                if (fileno (fptr) % 400 == 0)
                {
                        manipulatefd (fptr);
                }
        }

        return (0);
}

% export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH
% cc -o enableextfilestdio -lthirdparty enableextfilestdio.c

% ./enableextfilestdio
fd = 3
fd = 4
fd = 5
...
...
fd = 398
fd = 399
fd = 400
thirdpartysrc.c : manipulatefd(): 
        underlying file descriptor = 196
                    Application violated extended FILE safety mechanism. Please read the man page for extendedFILE. Aborting Abort (core dumped)
                

See the manual page of enable_extended_FILE_stdio(3C) for more information.

Alert: In the Next Major Customer Release of the Solaris OS, _file Becomes _magic

In order to ensure safety in using the extended FILE mechanism, the member _file in FILE structure has been intentionally renamed to _magic in Solaris Express OS, a monthly snapshot of the next major customer release of the Solaris OS currently under development, and in the OpenSolaris source-code base after build 39. This change would break the compilation of source code containing any references to FILE -> _file. Note: This alert does not apply to Solaris 10 OS, including update releases.

The following diff output shows the changes introduced in the definition of the FILE structure to accommodate the extended FILE facility:

- unsigned char _file;  /* UNIX system file descriptor */
                    + unsigned char _magic; /*Old home of the file descriptor*/ +          /* Only fileno(3C) can retrieve the value now */

- unsigned  __filler:4;
                    + unsigned  __extendedfd:1; /* enable extended FILE */ + unsigned  __xf_nocheck:1; /*no extended FILE runtime check*/
+ unsigned  __filler:10;
                

Hence, the compilation of code with references to FILE -> _file will fail with the following error message on systems running the Solaris Express OS or any OpenSolaris OS distribution after build 39, and on the successor to the Solaris 10 OS when it is available.

"filename.c", line xx:  
                   undefined struct/union member: _file
cc: acomp failed for filename.c
                

The value found in the field formerly known as _file might no longer contain the FILE's file descriptor. If the code is simply reading the value of _file, replace all such references with the more appropriate fileno(FILE) function. See the manual page for fileno(3C) . You should no longer assign a new value to _file.

Impact on Runtime Performance

When the extended FILE facility is enabled, there is no performance impact when accessing file descriptors less than or equal to 255. But there will be a slight performance degradation in accessing file descriptors greater than or equal to 256, due to the storage and retrieval of the file descriptor in an auxiliary location.

Patches and Bugs

If your system is running any existing version of the Solaris 10 OS -- that is, Solaris 10 3/05 through Solaris 10 11/06 -- you can install the extended FILE facility on the system with the following set of three patches (or later revisions) for your hardware platform:

SPARC platform:

x86/x64 platform:

If the application code links with STLport , the C++ standard library that was shipped with the Sun Studio software compiler suite, using the -library=stlport4 compiler option, make sure to patch Sun Studio 11 software with 121017-07 or later on the SPARC platform and with 121018-07 or later on x86/x64 platforms to take advantage of extended FILEs.

Report extended FILE bugs, if any, to the OpenSolaris OS bugs page and use the OpenSolaris OS discussion forums for any clarifications.

References and Suggested Reading

PSARC/2006/162: Extended FILE Space for 32-Bit Solaris Processes

Manual pages:

About the Author

Giri Mandalika is a software engineer in the Sun Microsystems ISV-Engineering group, working with partners and ISVs to make Sun the preferred vendor of choice for running enterprise applications. Giri holds a master's degree in computer science from the University of Texas at Dallas. The author would like to thank Craig Mohrman, Peter Shoults, and Chien Yen of Sun Microsystems for their help.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.