1 पॉइंट द्वारा GN⁺ 2024-09-30 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 32-बिट glibc सिस्टम में 2038 के बाद वर्तमान समय प्राप्त करना या stat() कॉल विफल हो सकती है, इसलिए Gentoo को 64-बिट time_t पर सुरक्षित रूप से जाने का रास्ता चाहिए
  • glibc का time64 Large File Support(LFS) के साथ ही इस्तेमाल होना चाहिए, और 32-बिट वातावरण में मौजूदा ABI, LFS ABI, और LFS+time64 ABI साथ-साथ मौजूद रहते हैं
  • जब time_t API, struct, या function arguments में आता है, तो type width में बदलाव ABI breakage बन जाता है, जिससे time32 और time64 binaries को मिलाने पर runtime malfunction और security risk पैदा होते हैं
  • source-based distribution होने के कारण Gentoo में @world rebuild के दौरान failure या circular dependency की वजह से आधा-परिवर्तित सिस्टम बच सकता है
  • 2024-09-30 के correction के बाद यह साफ हुआ कि सिर्फ libdir transition काफ़ी नहीं है, और dynamic loader व कई language toolchains तक फैला time64 marker एक मुख्य constraint बना हुआ है

2038 समस्या और time64 ट्रांज़िशन का दायरा

  • 32-बिट time_t इस्तेमाल करने वाले 32-बिट applications को 2038 में वर्तमान समय की जगह -1 error मिल सकती है, या वे फ़ाइलों पर stat() नहीं कर पाएँगे
  • ट्रांज़िशन की बुनियादी दिशा time_t को 64-बिट type में बदलना है
    • musl यह बदलाव पहले ही कर चुका है
    • glibc इसे एक option के रूप में support करता है
    • Debian जैसी कुछ distributions यह बदलाव पहले ही कर चुकी हैं
  • Gentoo जैसी source-based distributions में users सिस्टम को खुद rebuild करते हैं, इसलिए packages के अलग-अलग ABI state में रहने का समय कम करना ज़रूरी है
  • सबसे बड़ा जोखिम यह है कि time_t की width बदलने से ABI टूटता है
    • अगर किसी library API में time_t है, तो उस library से link होने वाला सारा code वही type width इस्तेमाल करे
    • केवल कुछ हिस्सों को time64 में बदलना सुरक्षित नहीं है

LFS और 32-बिट के तीन sub-ABI

  • 32-बिट architectures में फ़ाइल-संबंधित type width की समस्या पहले से मौजूद थी
    • off_t फ़ाइल offset के लिए इस्तेमाल होता है
    • ino_t inode number के लिए इस्तेमाल होता है
    • ये मूल रूप से 32-बिट थे, इसलिए 2GiB से बड़ी फ़ाइलें या 32-बिट range से बाहर inode numbers समस्या बनते थे
  • इसे हल करने के लिए Large File Support(LFS) लाया गया
    • off_t और ino_t को 64-बिट variants में बदला गया
    • glibc में यह आज भी optional है
    • कई packages ने upstream में LFS enable करके ABI breakage संभाला, लेकिन समस्या पूरी तरह खत्म नहीं हुई
  • glibc का time64 support LFS के उपयोग की मांग करता है, इसलिए फ़ाइल size और समय की समस्या एक साथ हल करने वाली संरचना बनती है
  • 32-बिट सिस्टम में तीन sub-ABI मौजूद होते हैं
    • मौजूदा ABI: 32-बिट types
    • LFS: 64-बिट off_t, 64-बिट ino_t, 32-बिट time_t
    • time64: LFS + 64-बिट time_t
  • एक glibc build इन तीनों variants के साथ compatible हो सकता है, लेकिन जो libraries इन types को API में इस्तेमाल करती हैं, वे इन variants को आपस में mix नहीं कर सकतीं

ABI बदलाव वास्तव में कैसे तोड़ता है

  • जब time_t 32-बिट से 64-बिट होता है, तो struct layout बदल जाता है
    • उदाहरण के लिए अगर किसी struct में int a, time_t b, int c इसी क्रम में हों, तो c का offset 32-बिट time_t और 64-बिट time_t में अलग होगा
    • time32 और time64 binaries को मिलाने पर गलत fields पढ़े या लिखे जा सकते हैं, और out-of-bounds access भी संभव है
  • struct stat का size भी ABI के अनुसार बदलता है
    • 32-बिट x86 glibc default: 88 bytes
    • LFS: 96 bytes
    • LFS + time64: 108 bytes
  • केवल structs ही नहीं, function arguments में भी समस्या आती है
    • x86 में function arguments stack के जरिए pass होते हैं
    • अगर किसी argument में time_t है, तो उसके बाद आने वाले arguments की stack position बदल जाती है
  • एक example experiment में जब time32 program को time64 के साथ rebuild की गई library से link किया गया, तो values बिगड़ गईं
    • मूल output था a = 1, एक सामान्य time value, c = 3
    • लेकिन सिर्फ library को -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 के साथ rebuild करने पर b और c गलत तरह से interpret हुए
  • अभी ऐसे ABI mixing को रोकने के लिए कोई ठोस protection mechanism नहीं है, इसलिए runtime breakage और security issues पैदा हो सकते हैं

Gentoo में ट्रांज़िशन ज़्यादा कठिन क्यों है

  • binary distributions सभी packages को rebuild करने के बाद users को अपेक्षाकृत atomic step में upgrade कराती हैं
    • third-party repositories या local-built programs हों तो समस्या आ सकती है, लेकिन कुल प्रक्रिया अपेक्षाकृत सुरक्षित रहती है
  • Gentoo में @world rebuild करते समय ABI को उसी जगह पर बदलना पड़ता है
    • दो packages के अलग-अलग rebuild होने के बीच incompatible ABI मिल सकते हैं
    • कुछ rebuild failures की वजह से सिस्टम आधा-परिवर्तित स्थिति में रह सकता है
    • circular dependencies की वजह से dependency packages rebuild करते-करते build tools ही टूट सकते हैं, और उसके बाद आगे rebuild संभव नहीं रह सकता

विचाराधीन mitigation उपाय

  • चर्चा में तीन दिशाएँ हैं
    • नए ABI को मौजूदा 32-बिट ABI से अलग दिखाने के लिए platform tuple CHOST बदलना
    • नए ABI का libdir बदलकर rebuilt libraries को पुरानी libraries से अलग install करना
    • अलग sub-ABI की binaries को link होने से रोकने के लिए binary-level ABI distinction लाना
  • इन तीनों तरीकों को कुछ हद तक स्वतंत्र रूप से लागू किया जा सकता है, लेकिन कुछ मामलों में वे एक-दूसरे पर निर्भर हो सकते हैं
  • लेख में दिए गए example strings अंतिम समाधान के वास्तविक strings हों, यह ज़रूरी नहीं है

CHOST के ज़रिए ABI अलग करना

  • platform tuple उस platform की पहचान करता है जिसे toolchain target करता है, और Gentoo में यह multilib support के लिए ABI को uniquely अलग करने में भी काम आता है
  • tuple चार हिस्सों से बना होता है: architecture, vendor, operating system, libc
    • उदाहरण: i386-pc-linux-gnu
    • उदाहरण: i686-pc-linux-gnu
    • उदाहरण: i686-unknown-linux-gnu
  • नया ABI लाते समय vendor field बदलने या libc field में ABI notation जोड़ने का तरीका पहले इस्तेमाल हुआ है
    • ARM hardfloat ABI में पहले armv7a-hardfloat-linux-gnueabi और armv7a-unknown-linux-gnueabihf जैसे रूप इस्तेमाल हुए थे
  • time64 ABI के लिए भी ऐसे विकल्प हैं
    • i686-gentoo_t64-linux-gnu
    • i686-pc-linux-gnut64
    • armv7a-gentoo_t64-linux-gnueabihf
    • armv7a-unknown-linux-gnueabihft64
  • tuple बदलने के लिए बहुत ज़्यादा patches की ज़रूरत नहीं लगती
    • GNU toolchain और GNU build system, libc field में gnu के बाद आने वाले हिस्से को ignore करते हैं
    • Clang को tuple के आधार पर सही ABI अपने-आप चुनने के लिए patch करना होगा

libdir बदलाव और preserved-libs

  • libdir library installation directory का default नाम होता है
    • सामान्य default lib है
    • 64-बिट capable architectures में परंपरागत रूप से lib64 अक्सर इस्तेमाल होता है
    • x86 का x32 ABI libx32 और MIPS n32 ABI lib32 इस्तेमाल करते हैं
  • time64 वाले 32-बिट ABI के लिए libdir को lib से libt64 जैसे नाम में बदलने का प्रस्ताव विचाराधीन है
  • अलग libdir ट्रांज़िशन के दौरान ABI mixing कम करने का एक उपाय बनता है
    • इससे time64 executables के गलती से time32 libraries से link होने का जोखिम घटता है
    • Portage की preserved-libs सुविधा time32 libraries को preserve कर सकती है
    • विकल्प के रूप में time32 + time64 multilib profile देकर पुराने time32 prebuilt applications के साथ compatibility रखी जा सकती है
  • preserved-libs होने पर पुराने executables rebuild होने तक time32 libraries इस्तेमाल करते रहेंगे, और time64 में rebuilt libraries नए libdir में install होंगी
  • libdir बदलने के लिए toolchain patches चाहिए
    • glibc को special handling दी जा सकती है, क्योंकि वही library set कई sub-ABI के लिए valid हो सकता है
    • time64 executables की .interp को time64 वाले ld.so की ओर point कराने के लिए अलग ld.so की ज़रूरत पड़ सकती है
  • proper multilib support के लिए उस ABI का एक unique platform tuple भी चाहिए

binary स्तर पर incompatibility को चिह्नित करना

  • अलग ABI की binaries को मिलाने पर सामान्यतः linker या dynamic loader को उसे रोकना चाहिए
    • अगर 64-बिट program को 32-बिट library से link किया जाए, तो linker file in wrong format कहकर मना कर देता है
    • dynamic loader भी wrong ELF class: ELFCLASS32 जैसी error देकर मना कर देता है
  • मौजूदा ABI distinction के लिए कई mechanisms इस्तेमाल होते हैं
    • ELFCLASS32 और ELFCLASS64
    • EM_386 और EM_X86_64 जैसे machine identifier
    • ARM और MIPS के flags field
    • architecture-specific attribute section
  • time32 और time64 के लिए भी ऐसा ही mechanism चाहिए, लेकिन यह आसान नहीं है
    • कोई reusable general mechanism दिखता नहीं है
    • कई architectures के लिए काम करने वाला समाधान चाहिए
    • नया ELF note section जोड़ना और उसके लिए toolchain support implement करना एक व्यावहारिक विकल्प लगता है
  • यह भी ध्यान रखना होगा कि users इस protection को disable कर सकते हैं
    • अगर source के बिना मिलने वाला prebuilt software time_t इस्तेमाल करने वाले API को call नहीं करता, तो वह system libraries के साथ चलता रह सकता है
    • ऐसे में इसे हर हाल में रोकने वाला उपाय समस्या से भी बुरा साबित हो सकता है
  • अलग libdir का उपयोग करके अपेक्षाकृत आसान non-fatal QA checks बनाए जा सकते हैं
    • .interp से time64 executables पहचाने जा सकते हैं
    • जाँचा जा सकता है कि time32 programs libt64 से libraries load तो नहीं कर रहे
    • और time64 programs सीधे lib से libraries load तो नहीं कर रहे

prebuilt 32-बिट applications की सीमाएँ

  • source से build होने वाले packages से अलग, x86 और PowerPC के लिए कुछ applications केवल पुराने prebuilt binaries के रूप में उपलब्ध हैं
    • खासकर proprietary software और पुराने games इस श्रेणी में आते हैं
  • इन्हें system libraries के साथ compatibility की समस्या भी होती है और 2038 समस्या भी
  • compatibility समस्या के लिए मौजूदा multilib structure कुछ हद तक समाधान देता है
    • amd64 पर 32-बिट software support के लिए पहले से multilib layout और कई library versions build करने की व्यवस्था मौजूद है
    • इसे abi_x86_32 और abi_x86_t64 अलग करने तक बढ़ाया जा सकता है
    • दोनों ABI को support करने वाला नया multilib x86 profile बनाया जा सकता है
  • लेकिन 2038 के बाद 32-बिट program के अपने failure की समस्या बनी रहती है
    • faketime से system time को नियंत्रित करने का तरीका संभव है
    • या समय को पीछे सेट किए हुए VM चलाने का तरीका भी संभव है

2024-09-30 correction: केवल libdir काफ़ी नहीं

  • शुरुआती योजना ज़रूरत से ज़्यादा आशावादी थी, और केवल libdir बदलकर स्थिर separation पाना मुश्किल है
  • क्योंकि सभी libdir ld.so.conf में सूचीबद्ध होते हैं, इसलिए ld.so के अंदर libdir path hardcode करने वाले तरीके पर निर्भर नहीं रहा जा सकता
    • custom LLVM prefix में पहले से path adjustments होते हैं, और इस मामले में भी special handling चाहिए
  • इसके कारण libdir बदलाव को binary incompatibility distinction पर निर्भर होना पड़ सकता है
  • तीन बुनियादी लक्ष्य पूरे करने होंगे
    • dynamic loader को time32 और time64 binaries में फर्क करना चाहिए
    • जिन binaries पर explicit time64 marker न हो, उन्हें backward compatibility के लिए time32 माना जाना चाहिए
    • नई build होने वाली सभी binaries पर explicit time64 marker होना चाहिए, जिनमें Rust जैसे non-C environments में बनी binaries भी शामिल हैं
  • यह कई भाषाओं के कई toolchains में patching माँगने वाला काम है
    • Gentoo के लिए इसे केवल local patch set के रूप में बनाए रखना मुश्किल होगा, और कई पक्षों के सहयोग की ज़रूरत होगी
    • target architectures को अक्सर legacy माना जाता है या उन्हें अब पर्याप्त support नहीं मिलता
  • एक अलग समस्या यह भी है कि क्या दूसरे toolchains सही time64 executables बनाएँगे
    • अगर उन्हें C programs की तरह _TIME_BITS follow करने के लिए adjust नहीं किया गया, तो वे किसी खास time_t width को hardcode करके टूट सकते हैं
  • जिन binaries पर explicit time64 marker नहीं होगा, वे सभी time32 libraries का उपयोग करेंगी, इसलिए Gentoo ऐसे third-party executables नहीं चला पाएगा जिन्हें सही marker देने के लिए patch नहीं किया गया है
  • कम महत्वाकांक्षी विकल्पों पर भी विचार हो रहा है
    • सभी time64 executables में RPATH inject करके time64 libdir को सीधे enforce किया जाए
    • यह तरीका dynamic loader को time32 libraries उपयोग करने से पूरी तरह नहीं रोकेगा, लेकिन बड़े compatibility issues के बिना ट्रांज़िशन में मदद कर सकता है
  • उल्टा तरीका भी संभव है, जिसमें time64 libdir को स्थायी रूप से नहीं बदला जाए बल्कि time32 libdir को अस्थायी रूप से बदला जाए
    • पुराने programs में RPATH inject किया जाए और libdir का नाम बदला जाए
    • नई time64 libraries मौजूदा libdir में install हों
    • नए time64 programs में time32 libraries को force करने वाला RPATH न हो
    • इसका फायदा यह है कि पहले से ट्रांज़िशन कर चुकी दूसरी distributions के साथ compatibility बनी रह सकती है

बाकी बची चुनौतियाँ

  • अगर इन तीनों समाधानों को लागू किया जाए, तो glibc इस्तेमाल करने वाले 32-बिट Gentoo सिस्टम के लिए एक ज़्यादा साफ़ और अपेक्षाकृत सुरक्षित ट्रांज़िशन path दिया जा सकता है
  • फिर भी ये समाधान मुख्यतः source से build होने वाले packages पर लागू होते हैं
  • prebuilt 32-बिट applications में ABI compatibility बनी रहे, तब भी 2038 समस्या बनी रहती है
  • पूरी design अभी draft में है, और experiments, discussions, और patch submissions के साथ बदलती रह सकती है

1 टिप्पणियां

 
GN⁺ 2024-09-30
Hacker News की राय
  • Gentoo में कुछ विकल्प हैं जिन पर लेख में बात नहीं की गई, और शायद Gentoo के सिस्टम डिज़ाइन के कारण काम की मात्रा बड़ी होने से वे छूट गए

    1. किसी पैकेज को इंस्टॉल किए बिना ही उसे target करके build कर पाना। मुख्य बात यह है कि Gentoo में package build और installation एक ही चरण हैं, इसलिए आपसी निर्भरता वाली कई चीज़ों को पहले build करके फिर परिणामों को atomically install नहीं किया जा सकता। ABI बदलाव वाले updates के दौरान सिस्टम आंशिक रूप से टूटने के लिए संवेदनशील हो जाता है
    2. सामान्य .so versioning को बढ़ाकर dependent packages के ABI बदलावों को भी दर्शाना। आम तौर पर shared libraries में libfoo.so.1.0.0 जैसे filename और internal version में version number होता है, और package अपने ABI breakage को track करता है। 64-bit time_t को support करने के लिए हर .so के dependency ABI द्वारा नियंत्रित version element जोड़ना होगा। लेख में “अलग libdir इस्तेमाल करना” जैसा ही परिणाम होगा, लेकिन भविष्य के ABI बदलावों के लिए reusable आधार बन सकता है—हालांकि यह कहीं ज़्यादा invasive होने की संभावना रखता है
    • “इंस्टॉल किए बिना build” करने के लिए partial staged updates सबसे उपयुक्त लगते हैं
      कई नए packages की build schedule करें, उन्हें sandbox में build करें, और फिर नई compilation को union के ज़रिए पहले sandbox देखने और फिर system पर fallback करने दें। सब build हो जाने पर परिणामों को package करके sandbox से real system में ले जाया जा सकता है। इससे Gentoo updates को transaction जैसा बनाया जा सकता है, जिससे अन्य पहलुओं में भी बड़े फायदे मिलेंगे
    • Gentoo पहले से ही अंतिम installation के लिए तैयार image को target directory के रूप में specify करने के तरीके से नंबर 1 को support करता है। आम तौर पर / पर set होने वाले ROOT को बदलना होता है
      @system और @world पूरे को फिर से build करके निर्दिष्ट subdirectory में install किया जा सकता है, और फिर एक बार में sync किया जा सकता है। संभव हो तो यह live session में करना बेहतर है; सिद्धांत रूप में नए install किए गए स्थान की subdirectory में / को bind mount करके chroot में जाकर वास्तविक parent / में sync करना भी संभव है
      https://devmanual.gentoo.org/ebuild-writing/variables/#root
    • Gentoo पहले से ही सभी dependencies के update होने तक पुरानी libraries को सुरक्षित रखता है, इसलिए ABI बदलाव को architecture और SONAME में encode कर दें—यही वह जगह है जहां original ABI changes दर्ज होने चाहिए—तो यह समस्या हल हो सकती है
  • Mac OS X ने off_t और ino_t को जिस तरह handle किया, वह एक hint हो सकता है। मौजूदा calls और structs का behavior बनाए रखा गया, 64 suffix वाली नई calls और types जोड़ी गईं, और preprocessor macros से actual reference target चुना जा सकता था, हालांकि सीधे ऐसा करना दुर्लभ था
    इसके बजाय OS और SDK versioned थे, और build time पर यह specify किया जा सकता था कि binary को सबसे पुराने किस OS version पर चलना है। headers इसी के आधार पर उचित macros अपने-आप चुनते थे, और नए API/deprecated API annotations भी इसी mechanism से weak links या warnings बनाते थे। शुरू में यह preprocessor से किया गया था, लेकिन अब compiler Apple द्वारा कही जाने वाली API availability को और बारीकी से समझता है, इसलिए दूसरे platforms पर भी यही तरीका संभव दिखता है

    • TFA में बताए गए मुख्य problem को यह हल नहीं करता। अलग-अलग “compile target OS version” इस्तेमाल करने वाले applications अब एक-दूसरे से link नहीं कर पाएंगे
      OS v.B पर चल रहे होने पर भी, OS v.B को target करके घोषित application X, OS v.A को target करके घोषित application Y से link नहीं कर सकेगा। दरअसल यह तरीका लगभग वही है जो लगभग सभी platforms पहले से करते हैं, और अलग तरीके से करने पर existing binary compatibility तुरंत टूट जाएगी
    • glibc के अलावा auxiliary libraries off_t size के हिसाब से functions के कई sets define नहीं करेंगी, और client program जिस type size को चाहता है उसके आधार पर सही function set को headers में transparently चुनने वाला कोई switch भी नहीं होगा
      फिर भी लेख ज़ोर देता है कि time_t, off_t से बड़ी समस्या है। इसकी संभावित वजह यह है कि time_t कहीं अधिक व्यापक रूप से फैला हुआ हैoff_t अपेक्षाकृत कम interfaces में शामिल POSIX type है, जबकि time_t ISO C का हिस्सा है और जगह-जगह इस्तेमाल होता है। इसके अलावा बहुत-सा C code मानता है कि time_t, int जितनी ही width वाला integer type है, जबकि off_t के बारे में ऐसी धारणा कम आम है
    • सुनने में elegant solution लगता है, लेकिन वास्तव में यह भयानक hack जैसा पढ़ता है। type-less macros वह nightmare हैं जिससे मैं फिर कभी नहीं निपटना चाहता
    • यह तभी काम करता है जब आप पूरे platform को साथ चलने के लिए मजबूर कर सकें। अच्छा solution है, लेकिन C library पर नियंत्रण चाहिए। Gentoo यह control नहीं करता कि libc क्या करती है, और users GNU libc, musl या कुछ और इस्तेमाल कर सकते हैं
  • Debian के लिए भी यह बहुत दर्दनाक था। कुछ लोगों के burnout होने की संभावना है, और कई लोग source-based distribution की ओर इशारा करते हुए कहते थे, “उनके लिए तो यह बहुत आसान होगा”

    • Debian का time64 transition कैसे painful था, इसके बारे में detail में बताने वाला material जानना चाहूंगा। बाहर से देखने पर यह अपेक्षाकृत कम विवाद वाला और smooth लगा, और मसलन /usr merge से कहीं बेहतर दिखा
    • m68k, powerpc, sh4 transition किया और hppa में भी कुछ मदद की; दूसरे Debian developers की मदद भी मिली, इसलिए अभी तक ज़िंदा हूं
    • अगर “आसान” का मतलब है “users से कह दो कि एक बार में सब कुछ फिर से build कर लें”, तो शायद सही है
  • जब भी लोगों को इस तरह की समस्या से जूझते देखता हूँ, तो लगता है कि FreeBSD के शुरुआती amd64 port के समय इस मुद्दे को आगे बढ़ाना वाकई अच्छा फैसला था। हम ABI के basic types तय कर सकते थे, और तय किया कि अतीत से ज़्यादा भविष्य को देखें
    amd64 में एक दिलचस्प विशेषता थी जिसने यह काम आसान बना दिया। function call के दौरान 32-bit function arguments अपने-आप 64-bit में cast हो जाते थे, इसलिए 64-bit time_t की उम्मीद करने वाले function को 32-bit time integer देने पर भी platform के शुरुआती काम के दौरान ज़्यादातर चीज़ें बस चल जाती थीं। इसलिए छोटी-मोटी finishing बाद के लिए टाली जा सकती थी
    उस समय दूसरे 64-bit platforms भी थे, लेकिन उनमें 64-bit time_t नहीं था, और FreeBSD/amd64 करीब 2003~2005 के आसपास उस श्रेणी में पहला था। मेरी याद में sparc64 भी 64-bit time_t पर गया था
    सबसे बड़ी समस्या यह थी कि उस समय tzcode 64-bit safe नहीं थाstruct tm normalization algorithm ऐसी degenerate condition में फँस जाता था जहाँ वह time_t(2^62) के दिन/महीना/साल की बार-बार गणना करने की कोशिश करता था। tzcode में बड़ा बदलाव करने के बजाय, मेरी याद में हमने लगभग 1900 से पहले या 10000 के बाद की dates को fail कर दिया था। अब संभव है कि upstream में यह बहुत पहले ठीक हो चुका हो
    कुछ सालों तक third-party code में files या network की data structures में int/long/time_t को ढीले-ढाले तरीके से handle करने से पैदा हुए 32/64-bit time confusion को whack-a-mole की तरह ठीक करना पड़ा, लेकिन कुल मिलाकर यह कोई बहुत बड़ी समस्या नहीं थी। पहले दिन से 64-bit time_t इस्तेमाल करने से ज़्यादातर समस्याएँ बच गईं, और शुरुआत से करना आसान था। Linux ने amd64/x86_64 port शुरू करते समय वही काम करने का बड़ा मौका गंवा दिया
    साथ में, उस समय 64-bit ino_t पूरा नहीं हो पाया था। 32-bit inode numbers file system on-disk structures, UFS directory structures वगैरह बहुत सारी जगहों पर exposed थे। जब FreeBSD/amd64 lower-tier platform था, तब बाकी tier-1 architectures को बहुत हिलाए बिना इसे शुरू से handle करने का कोई practical तरीका नहीं था। काम दो बार किया गया, लेकिन आखिर किसी और ने इसे finish किया, और mountpoint path length जैसे बहुत छोटे constants भी साथ में ठीक किए

    • मेरी समझ में सभी 64-bit Linux ports ने शुरुआत से ही 64-bit time_t, off_t, ino_t इस्तेमाल किए थे। अभी समस्या 32-bit Linux को 64-bit time_t पर migrate करने की है
    • FreeBSD ने off_t को भी ज़्यादा आक्रामक तरीके से handle किया, इसलिए 2.0 से ही वह 64-bit हो गया। Linux के 32-bit version में अब भी पुराने size के निशान बचे हैं
      function call के दौरान 32-bit arguments अपने-आप 64-bit में cast होते हैं—इस हिस्से को मैं unsigned arguments के लिए ही काम करने वाला समझता हूँ, क्योंकि %edi में load करने पर %rdi का upper part clear हो जाता है। x86-64 के लिए SysV ABI spec यह नहीं कहता कि registers या stack की सभी values को पूरे 64-bit value में extend किया जाता है, और boolean पर note भी कहता है कि सिर्फ lower 1 byte meaningful है, जिससे संकेत मिलता है कि यही general rule है
    • अगर मतलब यह है कि amd64 आने पर FreeBSD ने i386 के time_t को भी 64-bit पर port किया था, तो यह काफी चौंकाने वाला है। जानना चाहूँगा कि Motorola 68000 या sparc32 जैसी दूसरी 32-bit architectures भी 64-bit time_t पर गई थीं या नहीं
  • पुराने बड़े 32-bit Unix system में future dates handle करने के लिए, signed 32-bit time_t libc functions को unsigned 32-bit time_t compatible functions से बदला था। इससे 2038 के बाद 68 साल और मिल गए, और तब तक मैं तो रहूँगा नहीं
    कमी यह है कि Unix epoch यानी 1970 से पहले की dates represent नहीं की जा सकतीं, लेकिन यह scheduling system था इसलिए समस्या नहीं थी। अगर past dates important हों, तो epoch को कुछ दशक shift किया जा सकता है, या time resolution को 1 second से घटाकर 2 seconds किया जा सकता है। हर approach में subtle समस्याएँ हैं, इसलिए use case पर निर्भर करता है

    • अगर पूरा system signed से unsigned में बदल सकते हैं, तो जिज्ञासा है कि 64-bit में क्यों नहीं बदला
  • असली BSD manual page में tunefs के “Bugs” section में मशहूर मज़ाक था: “You can tune a file system, but you can't tune a fish.”, और “Expert C Programming” के मुताबिक उस manual page source code में उस मज़ाक के पास यह comment था
    “अगर इसे हटाया, तो UNIX daemon अभी से लेकर time_t के wrap around होने तक तुम्हारा चार कदम पीछा करेगा।”
    70 के दशक में जब यह वाक्य लिखा गया था, तब 2038 सचमुच अकल्पनीय रूप से दूर भविष्य रहा होगा
    https://progforperf.github.io/Expert_C_Programming.pdf

  • मेरी सबसे बड़ी takeaway यह है कि effort की कद्र है, लेकिन user के नज़रिए से मैं Debian जैसी non-source-based distribution पर जाकर इस समस्या को खत्म करना चाहूँगा

    • source-based distribution की कठिनाई शायद ABI-incompatible changes करते हुए in-place upgrade की कोशिश से आती है। इसलिए पूरी तरह अलग distribution पर जाना, नए ABI वाले Gentoo को clean install करने जितना ही disruptive हो सकता है—शायद समय कम लगे, लेकिन कम से कम उसी level का
    • Gentoo में भी इसे आसानी से handle करने का तरीका है। USB जैसी किसी चीज़ से boot करें, / और /usr partitions पर mkfs.ext4 या जो file system इस्तेमाल करते हों वह चलाएँ, फिर mount करें, stage3 extract करें, chroot में जाएँ और emerge $all-my-packages-that-where-installed-before-mkfs चलाएँ
      incremental upgrade करने के बजाय Gentoo की नई copy install की जा सकती है
    • non-source-based distribution पर जाने से काम हो जाएगा—यह distinction थोड़ा ज्यादा subtle है। NixOS जैसी source-based distribution में वही समस्या नहीं है। मुख्य बात source से build करना नहीं, बल्कि Gentoo का packages को build और install करने का तरीका है
      अगर third-party closed-source software है, तो binary system में भी समस्या आ सकती है। अलग step के रूप में separately install किए जाने वाले first-party packages में भी समस्या हो सकती है
  • मैं C विशेषज्ञ नहीं हूं, लेकिन मुझे लगा था कि off_t जैसे type alias इसलिए लाए गए थे ताकि उन्हें बाद में बदला जा सके। मगर लगता है कि यह साफ़ तौर पर काम नहीं करता; सोच रहा हूं कि क्या मेरी समझ गलत है

    • यह source compatibility और binary compatibility का फर्क है। off_t जैसे typedef का इस्तेमाल करने पर आम तौर पर code फिर से लिखने की जरूरत नहीं पड़ती, लेकिन उस type का इस्तेमाल करने वाली हर चीज़ को फिर से compile करना पड़ता है
    • कुछ हद तक यह काम करता है, लेकिन source-based distributions में यह ठीक से fit नहीं बैठता। अगर off_t की definition बदलने के बाद @world को atomically फिर से build किया जा सके तो समस्या नहीं होगी, लेकिन source-based distributions @world को atomically rebuild नहीं करतीं, बल्कि packages को एक-एक करके फिर से build करती हैं
      तब libc.so 64-bit off_t का इस्तेमाल कर रहा होता है, जबकि gcc 32-bit off_t के आधार पर build हुआ होता है, और इस वजह से gcc रुक सकता है। bash, coreutils, make, binutils जैसे @world rebuild के लिए जरूरी packages भी टूट सकते हैं, और उस point पर आप फंस जाते हैं। इसलिए ऐसे upgrades में सावधानी चाहिए
    • वह puzzle का पहला कदम, शायद आधा कदम भर है। लेख में जैसा कहा गया है, जैसे ही off_t किसी struct में जाता है, function call में इस्तेमाल होता है, या protocol में integrate होता है, abstraction गायब हो जाता है और असली size मायने रखने लगता है
      library load करते समय या protocol से communication करते हुए पुराने और नए code को मिलाने पर offsets गलत हो जाते हैं और crashes शुरू हो जाते हैं। आखिरकार transition के लिए सबको programs को “legacy” और “ported या कम-से-कम reviewed” में बांटना पड़ता है, इसलिए यह बहुत दर्दनाक है
    • type alias सिर्फ source code level पर चीज़ों को आसान बनाते हैं। सच में, खासकर पूरी abstraction तो बिल्कुल नहीं हैं। उदाहरण के लिए, internal type को floating-point type में बदल दें तो semantics काफी बदल जाते हैं और user code में पूरी तरह दिख जाते हैं
      समान semantics वाले बड़े type में बदलने पर भी चीजें टूट सकती हैं। एक सरल उदाहरण struct padding है, और pointer को integer में बदलकर वापस लाने वाले use cases भी बहुत हैं, इसलिए internal representation बदलने पर टूटना तय है। यह अच्छी practice है या नहीं, यह अलग बात है, लेकिन यह दुर्लभ नहीं है। मुख्य बात ABI compatibility है
    • यह काम तो करता है, लेकिन ABI change की समस्या यह है कि बदलाव करते समय हर जगह एक साथ बदलना पड़ता है। मूल रूप से ऐसा कोई guard नहीं है जो 32-bit off_t से build हुई library और 64-bit off_t से build हुई library को link करने से रोके, और नतीजे का behavior बहुत unpredictable हो सकता है
  • example struct में कहा गया कि 32-bit time_t होने पर c का offset 8 है और 64-bit type होने पर 12, लेकिन मुझे लगता है कि असल में यह 16 होना चाहिए, है ना। क्योंकि b को 64-bit aligned होना चाहिए, इसलिए a और b के बीच padding आनी चाहिए। बल्कि यह लेखक के तर्क को और मजबूत करता है

    • उस समय ज्यादातर x86 ABI में 64-bit load नहीं थे, इसलिए वे 64-bit types के लिए padding force नहीं करते
  • यह सब देखकर Windows के अजीब time representation में भी थोड़ा फायदा दिखता है: Gregorian calendar के हिसाब से 1 जनवरी 1601, 00:00 GMT से 100ns units में 64-bit गिनती। Resolution भी शानदार है, और पूरी galaxy conquer हो जाने तक भी यह काम करता रहेगा