Wie erzwinge ich die Verknüpfung mit der älteren libc `fcntl` anstelle von` fcntl64`?

8

Es scheint, dass GLIBC 2.28 (veröffentlicht im August 2018) eine ziemlich aggressive Änderung an fcntl vorgenommen hat. Die Definition wurde geändert, <fcntl.h>um keine externe Funktion mehr zu sein, sondern eine #definezu fcntl64 .

Das Ergebnis ist , dass , wenn Sie Ihren Code auf einem System mit diesem glibc kompilieren - wenn es fcntl () verwendet bei alle --Die resultierende Binärdatei wird nicht aus der Zeit vor August auf einem System ausführen 2018. Dies betrifft eine ganz Vielzahl von Anwendungen .. Die Handbuchseite für fcntl () zeigt, dass dies der Einstiegspunkt für ein kleines Universum von Unterfunktionen ist:

https://linux.die.net/man/2/fcntl

Es wäre schön, wenn Sie dem Linker mitteilen könnten, welche spezifische Version einer GLIBC-Funktion Sie möchten. Am nächsten fand ich diesen Trick, der in einer Antwort auf einen anderen Beitrag beschrieben wurde:

Antwort auf "Verknüpfen mit einer älteren Symbolversion in einer .so-Datei"

Das ist etwas komplizierter. fcntlist variadisch ohne ein vffcntl, das eine va_list nimmt. In solchen Situationen können Sie einen Aufruf einer variadischen Funktion nicht weiterleiten . :-(

Wenn man stabilen Code mit absichtlich geringen Abhängigkeiten hat, ist es eine Enttäuschung, ihn auf einem aktuellen Ubuntu aufzubauen ... dann und die ausführbare Datei weigert sich, auf einem anderen Ubuntu zu laufen, das erst ein Jahr zuvor (fast auf den Tag genau) veröffentlicht wurde. Welchen Rückgriff hat man dafür?

HostileFork sagt, vertraue SE nicht
quelle

Antworten:

6

Welchen Rückgriff hat man dafür?

Die Tatsache, dass GLIBC nicht #define USE_FCNTL_NOT_FCNTL64viel sagen konnte. Ob richtig oder falsch, die meisten OS + Toolchain-Hersteller scheinen entschieden zu haben, dass das Targeting von Binärdateien für ältere Versionen ihrer Systeme von einer neueren Version keine hohe Priorität hat.

Der Weg des geringsten Widerstands besteht darin, eine virtuelle Maschine in der Nähe der ältesten OS + -Toolchain zu halten, die Ihr Projekt erstellt. Verwenden Sie diese Option, um Binärdateien zu erstellen, wenn Sie glauben, dass Binärdateien auf einem alten System ausgeführt werden.

Aber...

  • Wenn Sie glauben, dass Ihre Verwendungen in der Teilmenge von fcntl () -Aufrufen enthalten sind, die von der Änderung der Offsetgröße nicht betroffen sind (dh, Sie verwenden keine Bytebereichssperren).
  • ODER sind bereit, Ihren Code auf Offset-Fälle zu überprüfen, um eine abwärtskompatible Strukturdefinition zu verwenden
  • UND haben keine Angst vor Voodoo

... dann lies weiter.

Der Name ist anders und fcntl ist variadic ohne ein vffcntl, das eine va_list nimmt. In solchen Situationen können Sie einen Aufruf einer variadischen Funktion nicht weiterleiten.

... um den genannten Wrapping-Trick anzuwenden , müssen Sie die Schnittstellendokumentation von fcntl () Zeile für Zeile durchgehen, die Variadic wie gewohnt entpacken und dann die Wrapped-Version mit einem neuen Variadic-Aufruf aufrufen.

Glücklicherweise ist es kein so schwieriger Fall (fcntl akzeptiert 0 oder 1 Argumente mit dokumentierten Typen). Hier ist der Code dafür, um anderen Ärger zu ersparen. Stellen Sie sicher, dass Sie --wrap = fcntl64 an den Linker übergeben ( -Wl, - wrap = fcntl64, wenn Sie ld nicht direkt aufrufen):

asm (".symver fcntl64, fcntl@GLIBC_2.2.5");

extern "C" int __wrap_fcntl64(int fd, int cmd, ...)
{
    int result;
    va_list va;
    va_start(va, cmd);

    switch (cmd) {
      //
      // File descriptor flags
      //
      case F_GETFD: goto takes_void;
      case F_SETFD: goto takes_int;

      // File status flags
      //
      case F_GETFL: goto takes_void;
      case F_SETFL: goto takes_int;

      // File byte range locking, not held across fork() or clone()
      //
      case F_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
      case F_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
      case F_GETLK: goto takes_flock_ptr_INCOMPATIBLE;

      // File byte range locking, held across fork()/clone() -- Not POSIX
      //
      case F_OFD_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
      case F_OFD_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
      case F_OFD_GETLK: goto takes_flock_ptr_INCOMPATIBLE;

      // Managing I/O availability signals
      //
      case F_GETOWN: goto takes_void;
      case F_SETOWN: goto takes_int;
      case F_GETOWN_EX: goto takes_f_owner_ex_ptr;
      case F_SETOWN_EX: goto takes_f_owner_ex_ptr;
      case F_GETSIG: goto takes_void;
      case F_SETSIG: goto takes_int;

      // Notified when process tries to open or truncate file (Linux 2.4+)
      //
      case F_SETLEASE: goto takes_int;
      case F_GETLEASE: goto takes_void;

      // File and directory change notification
      //
      case F_NOTIFY: goto takes_int;

      // Changing pipe capacity (Linux 2.6.35+)
      //
      case F_SETPIPE_SZ: goto takes_int;
      case F_GETPIPE_SZ: goto takes_void;

      // File sealing (Linux 3.17+)
      //
      case F_ADD_SEALS: goto takes_int;
      case F_GET_SEALS: goto takes_void;

      // File read/write hints (Linux 4.13+)
      //
      case F_GET_RW_HINT: goto takes_uint64_t_ptr;
      case F_SET_RW_HINT: goto takes_uint64_t_ptr;
      case F_GET_FILE_RW_HINT: goto takes_uint64_t_ptr;
      case F_SET_FILE_RW_HINT: goto takes_uint64_t_ptr;

      default:
        fprintf(stderr, "fcntl64 workaround got unknown F_XXX constant")
    }

  takes_void:
    va_end(va);
    return fcntl64(fd, cmd);

  takes_int:
    result = fcntl64(fd, cmd, va_arg(va, int));
    va_end(va);
    return result;

  takes_flock_ptr_INCOMPATIBLE:
    //
    // !!! This is the breaking case: the size of the flock
    // structure changed to accommodate larger files.  If you
    // need this, you'll have to define a compatibility struct
    // with the older glibc and make your own entry point using it,
    // then call fcntl64() with it directly (bear in mind that has
    // been remapped to the old fcntl())
    // 
    fprintf(stderr, "fcntl64 hack can't use glibc flock directly");
    exit(1);

  takes_f_owner_ex_ptr:
    result = fcntl64(fd, cmd, va_arg(va, struct f_owner_ex*));
    va_end(va);
    return result;

  takes_uint64_t_ptr:
    result = fcntl64(fd, cmd, va_arg(va, uint64_t*));
    va_end(va);
    return result;
}

Beachten Sie, dass Sie abhängig von der Version, auf der Sie tatsächlich aufbauen, möglicherweise einige dieser Flaggenabschnitte #ifdef müssen, wenn sie nicht verfügbar sind.

Dies betrifft eine Vielzahl von Anwendungen ... Die Handbuchseite für fcntl () zeigt, dass dies der Einstiegspunkt für ein kleines Universum von Unterfunktionen ist

... und es sollte wahrscheinlich eine Lehre für die Menschen sein: Vermeiden Sie es, solche "Küchenspülen" -Funktionen durch unterschiedlichen Missbrauch zu schaffen.

HostileFork sagt, vertraue SE nicht
quelle
Für diejenigen, die diskutieren möchten, was diesen Beitrag motiviert hat ... hier ist ein Diskurs-Thread über die Rolle der binären Übertragbarkeit in der heutigen Welt
HostileFork sagt, dass man SE
Und was würde das brechen? Werden fcntl64()jetzt Fehler beim Zugriff auf Dateien mit mehr als 2 GB auftreten, wenn die Verwendung von gelöscht wird? Die einzige Möglichkeit, dies zu wissen, besteht darin, einen vollständigen Regressionstest für alle fcntl()Verwendungszwecke durchzuführen . Halten Sie eine virtuelle Maschine mit der ältesten OS + -Toolchain bereit, die Ihr Projekt erstellt. DAS ist die eigentliche Antwort, und IMO sollte ganz vorne mit dabei sein.
Andrew Henle
1
"Das Targeting von Binärdateien für ältere Versionen ihrer Systeme von einer neueren Version hat keine hohe Priorität." - Es ist keine "nicht hohe Priorität", es ist ein exaktes Nichtziel .
Angestellt Russisch
1
@AndrewHenle "In Zukunft wird nie etwas auftauchen, das nicht gegen diese Bibliothek / dieses Betriebssystem läuft"? Niemand kann dieses Versprechen geben, und darauf können Sie sich nicht verlassen. " => Dies ist offensichtlich falsch - in dem Sinne, dass, wenn man eine ältere Version des Betriebssystems / der Toolchain für ein Ergebnis verwenden kann, neue Versionen der Toolchain könnte vor nicht allzu langer Zeit als inakzeptabel angesehen werden, wenn ein Compiler veröffentlicht würde, der keine Binärdateien erstellen könnte, die auf einem System ausgeführt würden, das erst am Vortag als "das neueste" angesehen wurde. Ich verstehe
Dieses
2

Wie erzwinge ich die Verknüpfung mit älteren libc fcntlanstelle von fcntl64?

Kompilieren Sie gegen eine ältere Version von libc. Zeitraum.

Da glibc nicht vorwärtskompatibel ist , ist es nur abwärtskompatibel :

Die GNU C-Bibliothek ist als abwärtskompatible , tragbare und leistungsstarke ISO C-Bibliothek konzipiert. Ziel ist es, alle relevanten Standards einschließlich ISO C11, POSIX.1-2008 und IEEE 754-2008 zu befolgen.

Ohne Garantie für die Vorwärtskompatibilität wissen Sie nicht, was sonst nicht richtig funktioniert .

Andrew Henle
quelle
So fügen Sie auf , dass, wenn möglich , nur normalen Paket Build - Prozess verwenden, in der Regel die Schritte zum Build - Paket in den meisten Distributionen enthalten Version von Bibliotheken in dieser Distribution verwendet installieren, auf diese Weise , wenn Ihr Build - Prozess Tests läuft man keine Probleme mit ihm früh fangen
XANi