Resolving “Bad file descriptor” Error During SQLite Compilation with libtool


Understanding the libtool File Descriptor Error During SQLite Compilation

Issue Overview: libtool File Descriptor 0 Failure in Remote Builds

The core problem involves a compilation failure in SQLite (specifically version sqlite-autoconf-3450200) when building via a remote script, resulting in the error:
./libtool: line 3109: 0: Bad file descriptor.

This error occurs during the linking phase (mode=link) of libsqlite3.la, where libtool attempts to manipulate file descriptors (FDs) for input/output redirection. The critical line in libtool (line 3109) is:

if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then

Here, exec 5<&0 duplicates the standard input (FD 0) to FD 5, redirecting input from the file "$1". If FD 0 is closed or invalid, this operation fails with a "Bad file descriptor" error.

Key observations from the discussion:

  • The error only manifests in remote builds (triggered via a script from a remote server), not in local manual builds.
  • FD 0 (stdin) is confirmed to be open before compilation via /proc/self/fd checks, suggesting the issue arises during the build process itself.
  • The build uses the --with-gnu-ld flag, which is not standard for SQLite compilation and might interact unexpectedly with Autotools.

This problem is environment-specific, tied to how the remote execution context manages file descriptors and interacts with libtool’s internals. The error indicates a disconnect between the shell’s expectation of available FDs and the actual state during scripted builds.


Potential Causes: Environment, Flags, and FD Management

Three primary factors contribute to this error:

1. Remote Execution Environment Closing FD 0

When triggering builds remotely (e.g., via SSH or CI/CD pipelines), the handling of standard input differs from interactive shells. For example:

  • SSH Non-Interactive Sessions: By default, SSH closes stdin in non-interactive modes. Commands like ssh user@host "command" do not allocate a pseudo-terminal (PTY), and stdin may be redirected from /dev/null. If the build process assumes stdin is open (e.g., for reading input or redirecting FDs), this causes failures.
  • Docker or Containerized Builds: Running builds in containers without the -i (interactive) flag closes stdin unless explicitly kept open.
  • Background Processes: Scripts running builds in the background (e.g., with &) may inadvertently close stdin.

2. Autotools Configuration Flags (–with-gnu-ld)

The --with-gnu-ld flag is a GNU Autotools option that forces the use of the GNU linker (ld) instead of the system’s default. While SQLite’s build system does not require this flag, its presence alters libtool’s behavior:

  • Libtool Assumptions: The flag might trigger libtool to use non-portable GNU extensions for FD handling or linker interactions, leading to edge cases in environments where stdin is closed.
  • Incompatibility with Non-GNU Systems: On systems where GNU ld is not the default (e.g., BSD-based linkers), this flag introduces inconsistencies.

3. Libtool’s FD Redirection Logic

Libtool’s internal logic for duplicating and redirecting FDs is sensitive to the state of FD 0. The line exec 5<&0 <"$1" fails if:

  • FD 0 is closed (e.g., by a parent process or script).
  • The shell restricts FD duplication due to ulimit settings or security policies (e.g., noclobber or restricted shells).
  • The build process itself closes FD 0 during prior steps (e.g., via exec 0<&-).

Solutions: Fixing FD 0 in Remote Builds and Configuration

1. Ensure stdin Is Open in Remote Execution

For SSH-Based Builds:

  • Force a PTY allocation with ssh -t or ssh -tt:
    ssh -t user@remote_host "cd /path/to/sqlite; ./configure && make"
    

    This ensures stdin is connected to a terminal, preventing FD 0 from being closed.

For CI/CD or Scripted Builds:

  • Avoid closing stdin in wrapper scripts. For example, replace:
    ./build_script.sh < /dev/null  # Closes stdin
    

    with:

    ./build_script.sh  # Let stdin inherit the current context
    
  • If input must be suppressed, use </dev/null only after verifying it doesn’t break FD-dependent steps.

For Docker/Container Builds:

  • Use the -i flag to keep stdin open:
    docker run -i image_name /bin/sh -c "./configure && make"
    

2. Omit –with-gnu-ld from Configure Flags

Remove the --with-gnu-ld flag unless explicitly required for cross-compilation or linker compatibility. Re-run configure as:

./configure  # Default settings

If GNU ld is mandatory, verify its presence with ld --version and ensure the build environment’s PATH prioritizes the correct ld.

3. Use the Amalgamation Build (Bypassing Autotools)

The SQLite amalgamation (a single sqlite3.c file) avoids Autotools and libtool entirely. Steps:

  1. Download the amalgamation:
    wget https://sqlite.org/YYYY/sqlite-amalgamation-3450200.zip
    unzip sqlite-amalgamation-3450200.zip
    cd sqlite-amalgamation-3450200
    
  2. Compile directly:
    gcc -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_RTREE -c sqlite3.c
    gcc -o sqlite3 sqlite3.o -ldl -lpthread
    

4. Patch Libtool to Use a Different FD

Modify libtool to avoid FD 0. In the problematic line (3109), change:

exec 5<&0 <"$1"

to use a higher FD (e.g., 9):

exec 9<&0 <"$1"

Apply this patch in the build script or regenerate libtool after modifying configure.ac.

5. Diagnose FD Leaks with strace or lsof

Trace FD operations during the build:

strace -f -e trace=openat,dup2,close,execve -o strace.log ./configure && make

Search for close(0) or dup2 calls involving FD 0 in strace.log. Similarly, use lsof to monitor FDs:

lsof -p $(pgrep -f "libtool --tag=CC") 

6. Update Autotools and Libtool

Regenerate the build scripts with updated Autotools to fix potential FD handling bugs:

autoreconf -fvi
./configure
make

7. Test with a Minimal Example

Create a minimal test.sh to replicate the FD redirection:

#!/bin/sh
exec 5<&0 </dev/null
echo "Success"

If this fails with Bad file descriptor, the shell or environment is closing FD 0. Switch to bash or ksh instead of sh.


By methodically addressing the remote execution environment, configure flags, and libtool internals, this error can be resolved without resorting to workarounds like the amalgamation build. The root cause typically lies in the interaction between scripted builds and FD management in Autotools-generated scripts.

Related Guides

Leave a Reply

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