সরাসরি পর্বে যান
পর্ব ৪২ · ৬৪-এর মধ্যে · মডিউল ৬

Directory, link আর mounting — VFS

Directories, Hard and Symbolic Links, Path Resolution, Mounting and the Virtual File System

Taught in Bangla, this lesson explains directories as name-to-inode lists, five directory structures from single-level to general graph, hard and symbolic links, Windows shortcuts, junctions and macOS aliases, directory implementation with linear lists, hash tables and B-trees, path-name resolution, mounting, the virtual file system across ext4, NFS, procfs and FUSE, and the Filesystem Hierarchy Standard.

এই পর্বে যা শিখবেন

  • Directory-র ছয়টি কাজ আর পাঁচ রকম গঠনের সুবিধা-অসুবিধা, cycle কেন বিপজ্জনক
  • Hard link আর symlink-এর ভেতরের পার্থক্য — মোছা, নাম বদলানো, অন্য file system-এ সরানোর সময় কী ঘটে
  • Directory-র ভেতরের গঠন (linear list, hash, B-tree) আর তার খরচের হিসাব
  • Kernel কীভাবে একটি path-কে inode-এ পৌঁছে দেয়, আর mounting কীভাবে অনেক ডিস্ককে একটি গাছ বানায়
  • VFS-এর চারটি বস্তু, আর Linux-এর ফোল্ডার-মানচিত্র (FHS)

১ · Directory আসলে কী — নাম থেকে inode-এর একটি তালিকা

পর্ব ৪১-এ দেখেছেন একটি ফাইল খোলা, পড়া আর লেখা কীভাবে হয়। কিন্তু open("/home/rahim/notes.txt") লিখলে kernel ফাইলটি খুঁজে পায় কীভাবে? উত্তর হলো directory — যাকে Windows-এ সাধারণত “ফোল্ডার” বলা হয়। আর cd, ls, absolute আর relative path ব্যবহারের নিয়ম আপনি পর্ব ০৩-এ (Linux টার্মিনাল) শিখেছেন; এখানে আমরা দেখব পর্দার পেছনে কী ঘটে।

একটি বড় লাইব্রেরির কথা ভাবুন। বইগুলো তাকে সাজানো, প্রতিটি তাকের একটি নম্বর আছে। প্রবেশপথে আছে ক্যাটালগ — প্রতিটি কার্ডে লেখা “বইয়ের নাম → তাক নম্বর”. বইয়ের ভেতরে কিন্তু ক্যাটালগের কথা লেখা থাকে না। Directory হলো এই ক্যাটালগ, তাক নম্বর হলো inode নম্বর, আর বই হলো ফাইলের আসল তথ্য। একই বইয়ের জন্য দুটি কার্ড রাখা যায় (“বাংলা ব্যাকরণ” আর “ব্যাকরণ, বাংলা”) — এটিই পরে দেখবেন hard link.

Unix-ধাঁচের file system-এ (ext4, XFS, APFS…) একটি ফাইলের সব তথ্য — মালিক, permission, আকার, সময়, ডেটা-ব্লকের ঠিকানা — থাকে তার inode-এ (inode-এর ভেতরের গঠন পর্ব ৪৩-এ)। Directory নিজেও একটি বিশেষ ধরনের ফাইল, যার ডেটা হলো কয়েকটি directory entry. ext4-এ প্রতিটি entry-তে থাকে inode নম্বর (৪ বাইট), entry-র দৈর্ঘ্য, নামের দৈর্ঘ্য, ফাইলের ধরন আর সর্বোচ্চ ২৫৫ বাইটের নাম। প্রতিটি directory-তে দুটি বিশেষ entry থাকে: . (নিজে) আর .. (মা-directory).

/home/rahim  (inode 2050)  — ডেটা-ব্লকে এই entry-গুলো:
  inode   type  name
  2050    dir   .
  131     dir   ..
  2101    dir   docs
  2207    file  notes.txt
  2300    link  latest

সব file system এমন নয়। FAT32 (পুরোনো USB stick, memory card) কোনো inode রাখে না — নাম, আকার, সময় আর প্রথম cluster-এর ঠিকানা সবই directory entry-র ৩২ বাইটের ভেতরে। তাই FAT-এ একই ফাইলের দুটি নাম (hard link) রাখা যায় না: তথ্য দুই জায়গায় থাকলে একটি বদলালে অন্যটি পুরোনো থেকে যেত। NTFS প্রতিটি ফাইলের জন্য Master File Table (MFT)-এ একটি রেকর্ড রাখে — ভূমিকায় inode-এর কাছাকাছি।

Directory-র ওপর ছয়টি মৌলিক কাজ

OS-এর বইগুলো সাধারণত directory-র ওপর এই কাজগুলোর কথা বলে। প্রতিটির পেছনে একটি বা দুটি system call আছে:

কাজমানেLinux system callকমান্ডWindows API (মোটামুটি)
Search (খোঁজা)একটি নামের inode বের করাপথ খোঁজার অংশ হিসেবে, যেমন stat, openls nameGetFileAttributes
Create (তৈরি)নতুন entry যোগopen(O_CREAT), mkdir, link, symlinktouch, mkdir, lnCreateFile, CreateDirectory
Delete (মোছা)entry সরানোunlink, rmdirrm, rmdirDeleteFile, RemoveDirectory
List (তালিকা)সব entry পড়াgetdents64 (libc-তে readdir)lsFindFirstFile/FindNextFile
Rename (নাম বদল)entry-র নাম বা জায়গা বদলrename, renameat2mvMoveFileEx
Traverse (পুরো গাছ ঘোরা)প্রতিটি sub-directory-তে ঢুকে সব দেখাopenat + getdents64 বারবারfind, du, tarপুনরাবৃত্ত FindFirstFile

একটি খুঁটিনাটি: rmdir শুধু খালি directory মোছে (. আর .. ছাড়া কিছু না থাকলে)। rm -r আসলে user-space-এ ভেতরের সবকিছু একে একে unlink করে, শেষে rmdir. পুরো গাছ একবারে মোছার কোনো system call kernel-এ নেই।

২ · Directory-র পাঁচ রকম গঠন — single-level থেকে general graph

Directory কেমন আকৃতির হবে, তা নিয়ে OS-এর ইতিহাসে কয়েকটি ধাপ এসেছে। বিশ্ববিদ্যালয় আর চাকরির পরীক্ষায় এই পাঁচটির তুলনা প্রায়ই আসে।

১. Single-level

পুরো ডিস্কে একটিই directory, সব ফাইল সেখানে। খুব সহজ, খোঁজাও সহজ। কিন্তু সব নাম আলাদা হতে হবে — দুজন user-ই report.txt রাখতে পারবেন না, আর হাজার ফাইলে গুছিয়ে রাখার উপায় নেই। প্রথম দিকের ছোট সিস্টেমে, আর আজও কিছু ক্যামেরা বা embedded যন্ত্রের সমতল ফাইল-তালিকায় এমন দেখা যায়।

সুবিধা: সরলতা · অসুবিধা: নামের সংঘাত, দলবদ্ধতা নেই

২. Two-level (MFD/UFD)

ওপরে একটি Master File Directory (MFD), যেখানে প্রতিটি user-এর নামের পাশে তাঁর নিজস্ব User File Directory (UFD)-র ঠিকানা। দুই user একই নাম রাখতে পারেন, একে অপরের ফাইল থেকে আলাদাও থাকেন। কিন্তু ভাগাভাগি কঠিন, আর এক user-এর ভেতরে আবার ভাগ করা যায় না। সাধারণ প্রোগ্রাম (compiler ইত্যাদি) রাখতে একটি বিশেষ “system user” আর search path লাগে।

সুবিধা: user আলাদা · অসুবিধা: ভাগাভাগি আর গোছানো কঠিন

৩. Tree-structured

যেকোনো গভীরতার গাছ — আজকের সব OS-এর ভিত্তি। প্রতিটি ফাইলের একটিই পূর্ণ ঠিকানা (absolute path), আর প্রতিটি process-এর নিজস্ব current working directory, যেখান থেকে relative path হিসাব হয়। এক process cd করলে অন্যটির cwd বদলায় না — তাই shell-এর cd একটি builtin, আলাদা প্রোগ্রাম নয়।

সুবিধা: গোছানো, দক্ষ খোঁজা · অসুবিধা: একটি ফাইল দুই জায়গায় রাখা যায় না

৪. Acyclic graph

গাছ, কিন্তু একটি ফাইল বা sub-directory একাধিক directory থেকে দেখা যায় — link দিয়ে। দুই সহকর্মী একই প্রজেক্ট-ফোল্ডার নিজের নিজের জায়গা থেকে খুলতে পারেন। শর্ত একটাই: কোনো চক্র (cycle) থাকবে না।

সুবিধা: ভাগাভাগি · অসুবিধা: aliasing, ঝুলে থাকা link, মোছার হিসাব

৫. General graph

Link দিয়ে cycle-ও তৈরি করা যায় — একটি directory তার নিজের পূর্বপুরুষকে ধরে রাখতে পারে। সবচেয়ে নমনীয়, কিন্তু গাছ ঘোরা অসীম লুপে পড়তে পারে, আর কোন ফাইল আর কারও কাজে লাগে না তা বুঝতে garbage collection লাগে।

সুবিধা: পূর্ণ নমনীয়তা · অসুবিধা: লুপ, GC-র খরচ

Acyclic graph-এর তিনটি সমস্যা

  • Aliasing: একই ফাইলের একাধিক নাম। du বা backup প্রোগ্রাম সাবধান না হলে একই ফাইল দুবার গুনবে বা দুবার কপি করবে।
  • Dangling pointer: link যদি শুধু একটি path মনে রাখে (symlink), আর আসল ফাইল মুছে যায় — link এখন এমন কিছুকে দেখায় যা নেই। একে বলে dangling link. আরও খারাপ: পরে একই নামে অন্য কোনো ফাইল তৈরি হলে link চুপচাপ সেটিকে দেখাবে।
  • মোছার হিসাব: একটি নাম মুছলে ফাইলটি কি সত্যিই মুছবে? সমাধান reference count: inode গোনে তাকে কতগুলো নাম দেখায় (link count). একটি নাম মুছলে গণনা এক কমে; শূন্য হলে (আর কোনো process ফাইলটি খোলা না রাখলে) তবেই ডেটা মুক্ত হয়। Unix এই পথই নিয়েছে — rm আসলে unlink, অর্থাৎ “একটি নাম সরানো”.

General graph: কেন reference count আর যথেষ্ট নয়

ধরুন /a/b একটি directory, আর তার ভেতরে loop নামে একটি hard link আছে যা আবার /a-কে দেখায়। এবার / থেকে a নামটি মুছে দিলেন। এখন a আর b একে অপরকে ধরে রেখেছে — দুজনের link count শূন্য নয়, অথচ মূল / থেকে কোনো পথে আর তাদের কাছে পৌঁছানো যায় না। Reference count দিয়ে এই জায়গা কখনো ফেরত আসবে না।

যুক্তিটি সাধারণভাবে বলা যায়: reference count একটি বস্তু মুক্ত করে তখনই, যখন তার দিকে আসা সব তীর চলে যায়। একটি cycle-এর প্রতিটি সদস্যের দিকে অন্তত একটি তীর cycle-এর ভেতর থেকেই আসে, তাই বাইরের সব নাম মুছলেও কোনো সদস্যের গণনা শূন্য হয় না। এমন জায়গা উদ্ধারের একমাত্র উপায় garbage collection: / থেকে পৌঁছানো যায় এমন সবকিছুতে দাগ দেওয়া (mark), তারপর দাগহীন সবকিছু মুক্ত করা (sweep). ডিস্কের লক্ষ লক্ষ inode-এ এটি চালানো অত্যন্ত ধীর। আরেকটি সমস্যা: cycle থাকলে ..-র মানে অস্পষ্ট — একাধিক মা থাকলে কোনটি?

Unix-এর সমাধান

(১) সাধারণ user directory-র hard link বানাতে পারেন না — link() directory-তে EPERM দেয়; শুধু mkdir নিজে . আর .. বানায়। তাই hard link-এর গ্রাফ সবসময় acyclic থাকে। (২) Cycle শুধু symlink দিয়ে সম্ভব, আর symlink link count বাড়ায় না — মুছলে শুধু ঝুলে থাকে, জায়গা আটকে রাখে না। (৩) গাছ-ঘোরা টুল ডিফল্টভাবে symlink অনুসরণ করে না (find-এর -P, du); find -L দিলে GNU find প্রতিটি পূর্বপুরুষ directory-র (device, inode) জোড়া মনে রাখে, একই জোড়া আবার এলে “File system loop detected” বলে থামে। du-ও (device, inode) মনে রেখে একাধিক নামের ফাইল একবারই গোনে।

৩ · Hard link আর symbolic link — একই ফাইলের দুই রকম দ্বিতীয় নাম

এখন আসল মজার অংশ। দুই রকম link-এর পার্থক্য বুঝলে আপনি backup, deployment, package manager আর container-এর অনেক রহস্য বুঝবেন।

🔗 Hard link

Hard link হলো একই inode-এর দিকে আরেকটি directory entry. কোনটি “আসল” আর কোনটি “link” — এমন কোনো পার্থক্যই নেই; দুটি নামই সমান। inode-এর link count বাড়ে। একই file system-এর ভেতরেই শুধু সম্ভব, কারণ inode নম্বরের মানে শুধু নিজের file system-এ।

ln target name

↪️ Symbolic (soft) link

Symlink একটি আলাদা, ছোট ফাইল — নিজের inode — যার ডেটা হলো একটি path লেখা। খোলার সময় kernel সেই path ধরে আবার খোঁজে। Directory, অন্য file system, এমনকি না-থাকা ফাইলকেও দেখাতে পারে।

ln -s target name · readlink name
পরিস্থিতিHard linkSymbolic link
নিজের inode?না — লক্ষ্যের inode-ইহ্যাঁ, আলাদা (ধরন l)
link countবাড়েলক্ষ্যের গণনা বদলায় না
আসল নামটি মুছলেডেটা টিকে থাকেঝুলে যায় (dangling), No such file
লক্ষ্যের নাম বদলালে বা সরালে (একই FS)কিছুই হয় নাভেঙে যায়
link-টিকে নিজেকেই অন্য directory-তে সরালেকিছুই হয় নাrelative path থাকলে ভাঙতে পারে; absolute হলে না
অন্য file system-এঅসম্ভব — EXDEV (“Invalid cross-device link”)সম্ভব
Directory-কেনিষেধ (cycle এড়াতে)সম্ভব
permissioninode-এর, তাই সব নামের জন্য একlink-এর নিজের permission সাধারণত অর্থহীন (lrwxrwxrwx); লক্ষ্যের permission খাটে

নিচের ডায়াগ্রামে একটি ফাইল, তার একটি hard link আর একটি symlink — তারপর মোছা আর নতুন করে তৈরি। inode নম্বর কাল্পনিক; “পরে” চেপে প্রতিটি ঘর খেয়াল করুন।

একটি directory আর তার inode-গুলো
কমান্ড
directory-র entry
inode 501 (ডেটা “hi”)
inode 502 (symlink)
inode 503 (ডেটা “new”)
cat hard.txt
cat soft.txt
  1. একটি ফাইল, একটি নাম। original.txt তৈরি হলো। kernel একটি নতুন inode (501) দিল, ডেটা “hi”, আর directory-তে entry বসাল original.txt → 501. link count ১.
  2. Hard link — নতুন inode নয়। ln শুধু directory-তে আরেকটি entry লিখল, hard.txt → 501, আর inode 501-এর link count হলো ২. এখন দুটি নাম সমান — কে “আগে” তৈরি, inode তা জানেই না।
  3. Symlink — আলাদা inode. ln -s নতুন inode 502 বানাল, ধরন symlink, যার ভেতরে শুধু ১২ বাইটের লেখা “original.txt”. inode 501-এর link count বদলায়নি — ২-ই আছে। cat soft.txt লিখলে kernel ঐ লেখাটি পড়ে আবার খোঁজে, পৌঁছায় 501-এ।
  4. rm original.txt — একটি নাম গেল। rm আসলে unlink: entry মুছল, 501-এর link count হলো ১. ডেটা অক্ষত, hard.txt দিয়ে এখনো “hi” পড়া যায়। কিন্তু soft.txt-এর ভেতরের লেখা “original.txt” নামে আর কিছু নেই — symlink এখন dangling.
  5. একই নামে নতুন ফাইল। original.txt আবার তৈরি হলো — কিন্তু এটি নতুন inode 503, ডেটা “new”. symlink শুধু নাম মনে রাখে, তাই এখন চুপচাপ 503 দেখায়: cat soft.txt দেয় “new”. আর hard link মনে রাখে inode, তাই hard.txt এখনো পুরোনো “hi”. এটিই দুই link-এর মূল দর্শন: নাম বনাম পরিচয়।
  6. শেষ নামও গেল — inode মুক্ত। rm hard.txt-এ 501-এর link count ০. কোনো process ফাইলটি খোলা না রাখলে kernel এখন inode আর তার ডেটা-ব্লক ফাঁকা হিসেবে চিহ্নিত করে। (কেউ খোলা রাখলে মুক্তি হয় শেষ close-এর পর — log ফাইল মুছেও ডিস্ক না খালি হওয়ার কারণ এটাই।)

এবার আসল Linux-এ একই পরীক্ষা। inode নম্বর প্রতি রানে আলাদা হবে, তাই script নম্বরের বদলে সম্পর্ক ছাপে — “একই inode” নাকি “আলাদা”. শেষে দুটি সীমাও দেখা যাবে: symlink-এর লুপ আর directory-র hard link.

rm -rf original.txt hard.txt soft.txt loopA loopB dir1 dirlink
echo "hi" > original.txt
ln original.txt hard.txt            # hard link: a second name for the same inode
ln -s original.txt soft.txt         # symlink: a tiny file that stores a path
ls -li --time-style=+%F original.txt hard.txt soft.txt
ino() { stat -c %i "$1"; }          # stat does not follow a symlink
[ "$(ino original.txt)" = "$(ino hard.txt)" ] && echo "hard.txt: SAME inode, link count $(stat -c %h hard.txt)"
[ "$(ino original.txt)" != "$(ino soft.txt)" ] && echo "soft.txt: OWN inode, stores '$(readlink soft.txt)' ($(stat -c %s soft.txt) bytes)"
echo "--- rm original.txt ---"
rm original.txt
echo "hard.txt: $(cat hard.txt) (link count now $(stat -c %h hard.txt))"
cat soft.txt 2>&1 || echo "soft.txt is dangling (cat exit $?)"
echo "--- create a NEW original.txt ---"
echo "new" > original.txt
echo "soft.txt: $(cat soft.txt)   hard.txt: $(cat hard.txt)"
echo "--- limits ---"
ln -s loopB loopA; ln -s loopA loopB
cat loopA 2>&1 || true
mkdir dir1; ln dir1 dirlink 2>&1 || true
echo "dir1 link count: $(stat -c %h dir1)"

আউটপুট (এক রানের; inode নম্বর আর তারিখ প্রতি রানে আলাদা):

4705992 -rw-r--r-- 2 wandbox wandbox  3 2026-10-25 hard.txt
4705992 -rw-r--r-- 2 wandbox wandbox  3 2026-10-25 original.txt
4705993 lrwxrwxrwx 1 wandbox wandbox 12 2026-10-25 soft.txt -> original.txt
hard.txt: SAME inode, link count 2
soft.txt: OWN inode, stores 'original.txt' (12 bytes)
--- rm original.txt ---
hard.txt: hi (link count now 1)
cat: soft.txt: No such file or directory
soft.txt is dangling (cat exit 1)
--- create a NEW original.txt ---
soft.txt: new   hard.txt: hi
--- limits ---
cat: loopA: Too many levels of symbolic links
ln: dir1: hard link not allowed for directory
dir1 link count: 2

(১) ls -li-এর প্রথম কলাম inode নম্বর: hard.txt আর original.txt একই (এই রানে 4705992; আপনার রানে অন্য সংখ্যা আসবে, কিন্তু দুটি মিলবে), তৃতীয় কলামে link count 2. soft.txt-এর inode আলাদা, ধরন l, link count 1, আর আকার ১২ বাইট — “original.txt” লেখাটির ঠিক ১২টি অক্ষর; symlink-এর “ডেটা” মানে শুধু এই path. (ls নাম বর্ণানুক্রমে সাজায়, তাই hard.txt আগে।)

(২) rm-এর পর hard link দিয়ে এখনো “hi” পড়া যায়, link count 1. symlink-এর cat ব্যর্থ — stepper-এর ধাপ ৪. (৩) একই নামে নতুন ফাইল আসতেই symlink “new” দেখায়, hard link পুরোনো “hi” — ধাপ ৫. (৪) দুটি symlink একে অপরকে দেখালে kernel সীমা পার হয়ে ELOOP দেয়। directory-র hard link-এ coreutils-এর ln নিজেই আগে থামায়; সরাসরি link() ডাকলেও kernel EPERM দিত। নতুন খালি dir1-এর link count 2 — মা-directory-তে তার নাম আর তার নিজের ..

নাম বদল, সরানো আর অন্য file system

একই file system-এর ভেতরে mv আসলে একটি rename() system call: পুরোনো entry সরিয়ে নতুন entry বসানো, inode আর ডেটা একটুও নড়ে না — তাই ২০ GB-র ভিডিও ফাইলও চোখের পলকে “সরে”. POSIX বলে rename() atomic: অন্য কেউ হয় পুরোনো নামটি দেখবেন, নয়তো নতুনটি — মাঝামাঝি কিছু নয়।

কিন্তু /home থেকে USB stick-এ (/media/usb) সরালে rename() EXDEV ফেরত দেয়, কারণ inode নম্বর অন্য file system-এ অর্থহীন। তখন mv নিজে থেকে পুরো ডেটা কপি করে, তারপর পুরোনোটি মোছে। ফলাফল: সময় লাগে, কাজটি আর atomic নয় (মাঝপথে বিদ্যুৎ গেলে দুই জায়গায় অর্ধেক থাকতে পারে), আর ফাইলের অন্য hard link-গুলোর সাথে সম্পর্ক ছিন্ন হয় — নতুন জায়গায় এটি এক-নামের নতুন ফাইল। Symlink কপি হয় symlink হিসেবেই, ভেতরের path না বদলে; relative path হলে নতুন জায়গায় ভেঙে যেতে পারে।

Symlink loop আর ELOOP: loopA → loopB আর loopB → loopA হলে খোঁজা কখনো শেষ হতো না। তাই kernel একটি path খোঁজার সময় symlink অনুসরণের সংখ্যা গোনে; Linux-এ সীমা ৪০, পার হলে ELOOP (“Too many levels of symbolic links”). এটি cycle ধরার বুদ্ধিমান পদ্ধতি নয়, শুধু একটি গণনার সীমা — ৪১টি বৈধ symlink-এর শিকলও একই error দেবে।

৪ · Windows আর macOS-এ link — shortcut, junction আর alias

“Windows-এ তো shortcut আছে, ওটাই কি symlink?” — না, আর পার্থক্যটা গুরুত্বপূর্ণ। Windows-এ চার রকম জিনিস আছে, আর এদের আচরণ সংস্করণ ও সেটিংসভেদে একটু বদলায়; নিচের সারাংশ সাধারণ ক্ষেত্রের জন্য:

জিনিসকে বোঝেকী দেখাতে পারেতৈরি
Shortcut (.lnk)শুধু Explorer/shell — file system-এর কাছে এটি একটি সাধারণ ফাইলযেকোনো ফাইল, ফোল্ডার, এমনকি নেটওয়ার্কডান-ক্লিক → Create shortcut
NTFS hard linkfile systemশুধু ফাইল, একই volume-এmklink /H new target
Junctionfile system (reparse point)শুধু directory, স্থানীয় volume-এর absolute pathmklink /J new target
Symbolic linkfile system (reparse point, Vista থেকে)ফাইল বা directory, relative বা অন্য volume/নেটওয়ার্কmklink বা mklink /D; সাধারণত admin অধিকার বা Developer Mode লাগে

একটি C প্রোগ্রাম CreateFile দিয়ে .lnk খুললে shortcut-এর লক্ষ্য নয়, ঐ .lnk ফাইলের বাইনারি ডেটাই পাবে — kernel এটিকে link বলে চেনে না। পরিচিত একটি junction-এর উদাহরণ: Vista থেকে পুরোনো প্রোগ্রামের সুবিধার জন্য C:\Documents and Settings একটি junction, যা C:\Users-কে দেখায়। macOS-এ symlink আর hard link দুই-ই আছে (BSD ঐতিহ্য), আর Finder-এর “alias” Windows shortcut-এর মতো একটি আলাদা ফাইল — তবে সেটি ফাইলের ভেতরের পরিচয়ও মনে রাখে, তাই লক্ষ্য সরালেও অনেক সময় খুঁজে পায়।

PS> cmd /c mklink /H copy2.txt notes.txt
Hardlink created for copy2.txt <<===>> notes.txt
PS> cmd /c mklink /J proj D:\work\project
Junction created for proj <<===>> D:\work\project
PS> fsutil hardlink list notes.txt
\Users\rahim\notes.txt
\Users\rahim\copy2.txt

৫ · Directory-র ভেতরের বাস্তবায়ন — linear list, hash table আর B-tree

একটি directory-তে নাম খোঁজার গতি নির্ভর করে entry-গুলো কীভাবে সাজানো তার ওপর। ছোট directory-তে কোনো পার্থক্য চোখে পড়ে না; কিন্তু একটি mail server বা ছবির সার্ভারে এক directory-তে লাখো ফাইল থাকতে পারে।

পদ্ধতিনাম খোঁজাযোগ / মোছাসাজানো তালিকাকোথায়
Linear listO(n) — শুরু থেকে একে একেযোগ সস্তা, কিন্তু আগে নামটি আছে কি না খুঁজতে O(n)আলাদা করে সাজাতে হয়ext2, dir_index ছাড়া ext3/ext4-এর ছোট directory, FAT
Hash tableগড়ে O(1)গড়ে O(1); collision আর আকার বাড়ানো সামলাতে হয়নেই — hash-এর ক্রম এলোমেলোext3/ext4-এর htree (hash-কে চাবি ধরে একটি অগভীর গাছ)
B-tree / B+ treeO(log n), প্রতিটি node একটি ডিস্ক-ব্লকO(log n), গাছ নিজে ভারসাম্য রাখেনামের ক্রমে বিনামূল্যেNTFS-এর directory index, XFS-এর বড় directory, Btrfs, APFS

ext4-এর htree আসলে দুটির মিশ্রণ: নামের hash হিসাব করে, hash-কে চাবি ধরে একটি অগভীর B-tree-ধাঁচের index-এ খোঁজে, আর শেষ ধাপের leaf block-গুলো আগের মতোই সাধারণ linear entry. ফল: index-এর গভীরতা সাধারণত ১ বা ২ স্তর (বড় directory-র জন্য large_dir feature-এ ৩), তাই এক লাখ ফাইলেও কয়েকটি ব্লক পড়লেই নাম মেলে। একটি পার্শ্বফল: ext4-এ getdents64 entry দেয় hash-এর ক্রমে, তাই ls -f (না-সাজানো) এলোমেলো দেখায়; সাধারণ ls নিজেই নামগুলো সাজিয়ে দেখায়।

হিসাব করে দেখুন

একটি directory-তে ১০,০০,০০০ (দশ লাখ) entry, প্রতিটি ৪ KiB ব্লকে গড়ে ২০০টি entry ধরে নিই (ছোট নাম)। মোট leaf block = ১০,০০,০০০ ÷ ২০০ = ৫,০০০.

  • Linear list: আছে এমন নাম খুঁজতে গড়ে অর্ধেক পড়তে হয় — ২,৫০০ ব্লক; নেই এমন নাম (যেমন নতুন ফাইল তৈরির আগে যাচাই) — পুরো ৫,০০০ ব্লক।
  • Hash-ভিত্তিক গাছ (htree-ধাঁচ), প্রতিটি index ব্লকে ~৫০০ চাবি: এক স্তরে ৫০০ leaf চেনা যায়, দুই স্তরে ৫০০ × ৫০০ = ২,৫০,০০০. ৫,০০০ leaf-এর জন্য দুই স্তর যথেষ্ট — মোট পড়া = ২ index ব্লক + ১ leaf = ৩ ব্লক.
  • অনুপাত: গড়ে ২,৫০০ ÷ ৩ ≈ ৮৩৩ গুণ কম ডিস্ক-ব্লক। (সংখ্যাগুলো ধারণার জন্য; আসল entry-সংখ্যা নামের দৈর্ঘ্যের ওপর নির্ভর করে, আর dentry cache-এ থাকলে ডিস্কই লাগে না।)

৬ · Path-name resolution — kernel কীভাবে একটি path খোঁজে

প্রতিটি open, stat, execve-এর শুরুতে kernel-কে একটি লেখা path থেকে একটি inode-এ পৌঁছাতে হয়। ক্লাসিক Unix-এ এই function-এর নাম ছিল namei (“name to inode”); Linux-এ কাজটি করে fs/namei.c-এর link_path_walk আর সঙ্গী function-গুলো। নিয়মগুলো:

  1. Path / দিয়ে শুরু হলে খোঁজা শুরু process-এর root directory থেকে, নইলে তার cwd থেকে (দুটোই process-এর নিজস্ব, chroot আর container এ কারণেই কাজ করে)।
  2. Path-কে / দিয়ে component-এ ভাঙা; একাধিক / একটি হিসেবে ধরা।
  3. প্রতিটি মাঝের directory-তে execute (x) বা “search” permission লাগে; শুধু read (r) থাকলে নামের তালিকা দেখা যায়, কিন্তু ভেতরে ঢোকা যায় না (খুঁটিনাটি পর্ব ৪৭-এ)।
  4. . মানে একই জায়গা, .. মানে মা-directory; root-এর .. root নিজেই।
  5. কোনো component symlink হলে তার ভেতরের path পড়ে বাকি path-এর আগে বসিয়ে খোঁজা চলতে থাকে (শেষ component-এ অনুসরণ হবে কি না, তা system call আর flag-এর ওপর নির্ভর করে, যেমন lstat বা O_NOFOLLOW).
  6. কোনো component একটি mount point হলে খোঁজা ঐ জায়গায় mount করা file system-এর root-এ লাফ দেয় (অংশ ৭)।

এত কাজ প্রতিবার ডিস্কে করলে কম্পিউটার অচল হতো। তাই Linux রাখে dentry cache: (মা-directory, নাম) → inode-এর একটি বড় hash table, RAM-এ। এমনকি “এই নামে কিছু নেই” ফলটিও মনে রাখা হয় (negative dentry) — PATH-এর প্রতিটি ফোল্ডারে একটি কমান্ড খোঁজার সময় এটি খুব কাজে লাগে।

open("/home/rahim/docs/notes.txt", O_RDONLY) — rahim-এর process (inode নম্বর কাল্পনিক, root ছাড়া)
এখন খোঁজা হচ্ছে
কোন directory-তে
permission যাচাই
dentry cache
হাতে এখন
  1. শুরুর বিন্দু। Path / দিয়ে শুরু, তাই kernel process-এর root directory নেয় (task-এর fs->root). ext4-এ root directory-র inode নম্বর সবসময় ২ — ০ “কিছু নেই” বোঝায়, আর ১ খারাপ ব্লকের তালিকার জন্য সংরক্ষিত। বাকি path: home/rahim/docs/notes.txt, চারটি component.
  2. “home” খোঁজা। আগে /-এ x permission যাচাই (সবার আছে)। তারপর dentry cache-এ (inode 2, “home”) খোঁজা — hit, সাথে সাথে inode 131. ডিস্ক ছোঁয়াই লাগেনি।
  3. “rahim” খোঁজা। /home-এ x যাচাই ✓, cache hit → inode 2050. কোনো component mount point হলে এখানেই অন্য file system-এ লাফ হতো; এখানে তা নয়।
  4. “docs” — cache miss. /home/rahim-এর permission drwx------: শুধু মালিক ঢুকতে পারেন। process-টি rahim-এর, তাই x ✓ (অন্য user হলে এখানেই EACCES). Cache-এ নেই, তাই ext4-এর lookup ডাকা হলো — htree দিয়ে directory ব্লক পড়ে inode 2101 পাওয়া গেল, আর নতুন dentry cache-এ বসল। পরের বার এটি hit.
  5. শেষ component “notes.txt”. docs-এ x ✓, cache hit → inode 2207, একটি regular file. এটি শেষ component, তাই এখন open-এর অনুরোধ অনুযায়ী ফাইলটির নিজের permission দেখা হয় — O_RDONLY, তাই r ✓. মোট চারটি directory-তে x আর একটি ফাইলে r যাচাই হলো।
  6. ফল — একটি file descriptor. kernel একটি struct file বানায় (offset ০, mode পড়া, আর inode-এর file_operations — অংশ ৮), process-এর fd টেবিলের প্রথম ফাঁকা ঘরে বসায় (পর্ব ৪১), আর ফেরত দেয় ৩. এরপর read(3, …) আর path খোঁজে না।

এবার user-space থেকে একই কাজ নিজ হাতে: প্রোগ্রামটি দুটি directory আর একটি symlink বানায়, তারপর প্রতিটি component fstatat(…, AT_SYMLINK_NOFOLLOW) দিয়ে পরীক্ষা করে (এটিই lstat-এর আচরণ — symlink নিজেকে দেখায়), আর openat দিয়ে এক ধাপ এগোয়, ঠিক kernel-এর মতো এক directory থেকে পরেরটিতে।

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static ino_t walk(const char *path) {          /* resolve one component at a time */
    char buf[256], tgt[256];
    struct stat st;
    int n = 0, dfd = open(".", O_PATH | O_DIRECTORY);
    printf("walk %s\n", path);
    snprintf(buf, sizeof buf, "%s", path);
    for (char *c = strtok(buf, "/"); c; c = strtok(NULL, "/"), n++) {
        if (fstatat(dfd, c, &st, AT_SYMLINK_NOFOLLOW) != 0) { perror(c); return 0; }
        ssize_t k = readlinkat(dfd, c, tgt, sizeof tgt - 1);   /* -1 if not a symlink */
        tgt[k > 0 ? k : 0] = '\0';
        printf("  %d. %-6s %s%s\n", n + 1, c, S_ISLNK(st.st_mode) ? "symlink, follow -> " :
               S_ISDIR(st.st_mode) ? "directory" : "regular file", tgt);
        int next = openat(dfd, c, O_PATH);       /* O_PATH without O_NOFOLLOW follows links */
        close(dfd);
        dfd = next;
    }
    fstat(dfd, &st);
    close(dfd);
    printf("  components looked up: %d\n", n);
    return st.st_ino;
}

int main(void) {
    mkdir("d1", 0755); mkdir("d1/d2", 0755); unlink("lnk");
    close(open("d1/d2/f.txt", O_CREAT | O_WRONLY, 0644));
    if (symlink("d1/d2", "lnk") != 0) perror("symlink");
    ino_t a = walk("d1/d2/f.txt"), b = walk("lnk/f.txt");
    printf("same file at the end? %s\n", a == b ? "yes, same inode" : "no");
    return 0;
}

আউটপুট:

walk d1/d2/f.txt
  1. d1     directory
  2. d2     directory
  3. f.txt  regular file
  components looked up: 3
walk lnk/f.txt
  1. lnk    symlink, follow -> d1/d2
  2. f.txt  regular file
  components looked up: 2
same file at the end? yes, same inode

(১) প্রথম path-এ তিনটি component, প্রতিটির জন্য একটি fstatat আর একটি openat — kernel-এর namei-ও ঠিক এভাবে এক directory থেকে পরেরটিতে যায়, প্রতি ধাপে permission দেখে। (২) দ্বিতীয় path-এ লেখা আছে মাত্র দুটি component, কিন্তু lnk একটি symlink: AT_SYMLINK_NOFOLLOW (অর্থাৎ lstat-এর আচরণ) থাকায় আমরা link-টিকেই দেখলাম, আর readlinkat তার ভেতরের “d1/d2” পড়ল। তারপর openat(…, O_PATH) link অনুসরণ করল — kernel ভেতরে ভেতরে d1 আর d2 খুঁজে নিল। (৩) দুই পথে শেষ inode একই: একই ফাইলে পৌঁছানোর দুটি নাম — aliasing. (৪) সাধারণ ফাইল বা directory-তে readlinkat −1 দেয় (EINVAL), তাই সেখানে লক্ষ্যের লেখা খালি। O_PATH fd শুধু “জায়গা” চিহ্নিত করে, পড়ার অনুমতি লাগে না — container runtime-এর মতো প্রোগ্রাম নিরাপদে path খুঁজতে এই ধরনের fd ব্যবহার করে।

শেষে আরেকটি সূক্ষ্ম বিষয়, যা অভিজ্ঞদেরও ধোঁকা দেয়: kernel-এর .. “physical” — symlink অনুসরণের পর আসল জায়গার মা-directory. ধরুন ~/proj একটি symlink যা /etc দেখায়। ls ~/proj/.. দেখাবে /-এর ভেতর, আপনার home নয়! অথচ bash-এর cd ~/proj/.. আপনাকে home-এ ফেরত আনে, কারণ shell নিজে “logical” path মনে রাখে (cd -P দিলে physical). নিচের ছোট simulator একটি কাল্পনিক file system-এ namei-র সব নিয়ম — symlink, .., mount আর ELOOP — একসাথে দেখায়:

// A tiny namei: inode table, symlinks, "..", and one mounted USB file system
const I = {
  2:  { e: { home: 10, etc: 30, mnt: 40 } },  10: { e: { rahim: 11 } },    // "/" and /home
  11: { e: { docs: 12, latest: 14, proj: 15, loop: 16 } },  12: { e: { 'notes.txt': 13 } },
  14: { to: 'docs/notes.txt' },  15: { to: '/etc' },  16: { to: 'loop' },   // symlinks
  30: { e: { hosts: 31 } },  40: { e: { 'old.txt': 41 } },                 // /etc and /mnt
  900: { e: { 'photo.jpg': 901 } },                                        // root of the USB fs
};                                    // inodes not listed (13, 31, 41, 901) are regular files
const mounts = { 40: 900 };           // as if: mount /dev/sdb1 /mnt
let follows = 0;
function walk(path, stack) {
  stack = path.startsWith('/') ? [2] : stack.slice();
  const parts = path.split('/').filter(Boolean);
  for (let i = 0; i < parts.length; i++) {
    const name = parts[i], dir = I[stack[stack.length - 1]];
    if (!dir || !dir.e) return 'ENOTDIR';
    if (name === '.') continue;
    if (name === '..') { if (stack.length > 1) stack.pop(); continue; }
    let ino = dir.e[name];
    if (ino === undefined) return 'ENOENT';
    if (mounts[ino]) ino = mounts[ino];                 // cross into the mounted fs
    if (I[ino] && I[ino].to) {                          // a symlink: splice its path in
      if (++follows > 40) return 'ELOOP';               // Linux gives up after 40
      return walk([I[ino].to, ...parts.slice(i + 1)].join('/'), stack);
    }
    stack.push(ino);
  }
  return 'inode ' + stack[stack.length - 1];
}
for (const p of ['/home/rahim/docs/notes.txt', '/home/rahim/latest', '/home/rahim/proj/hosts',
    '/home/rahim/proj/..', '/mnt/photo.jpg', '/mnt/old.txt', '/mnt/..', '/home/rahim/loop',
    '/home/rahim/docs/notes.txt/x', '//home/./rahim/../rahim/docs']) {
  follows = 0;
  console.log(p.padEnd(30), '->', walk(p, [2]).padEnd(10), 'symlinks followed:', follows);
}

আউটপুট:

/home/rahim/docs/notes.txt     -> inode 13   symlinks followed: 0
/home/rahim/latest             -> inode 13   symlinks followed: 1
/home/rahim/proj/hosts         -> inode 31   symlinks followed: 1
/home/rahim/proj/..            -> inode 2    symlinks followed: 1
/mnt/photo.jpg                 -> inode 901  symlinks followed: 0
/mnt/old.txt                   -> ENOENT     symlinks followed: 0
/mnt/..                        -> inode 2    symlinks followed: 0
/home/rahim/loop               -> ELOOP      symlinks followed: 41
/home/rahim/docs/notes.txt/x   -> ENOTDIR    symlinks followed: 0
//home/./rahim/../rahim/docs   -> inode 12   symlinks followed: 0

লাইন ধরে: (১) সরাসরি path আর latest symlink — দুটোই inode 13, দ্বিতীয়টিতে ১টি symlink অনুসরণ (relative লক্ষ্য docs/notes.txt হিসাব হলো link-টি যে directory-তে আছে, /home/rahim থেকে)। (২) proj → /etc (absolute), তাই proj/hosts = /etc/hosts, আর proj/.. = inode 2, অর্থাৎ / — /home/rahim নয়; kernel-এর physical ... (৩) /mnt/photo.jpg USB-র file system থেকে (inode 901), আর /mnt/old.txt এখন ENOENT — mount-এর নিচে চাপা পড়েছে, মোছেনি। /mnt/.. আবার root fs-এর /. (৪) loop নিজেকে দেখায়: ৪০টি অনুসরণের পর ৪১তম-তে ELOOP. (৫) একটি সাধারণ ফাইলের “ভেতরে” x খুঁজলে ENOTDIR. (৬) একাধিক /, . আর .. মিলেও শেষে সঠিক docs (inode 12). আসল kernel-এ ঠিক এই নিয়মগুলোই, সাথে প্রতি ধাপে permission আর dentry cache.

৭ · Mounting — অনেক file system, একটিই গাছ

আপনার ল্যাপটপে হয়তো একটি SSD, একটি USB stick, আর kernel-এর তৈরি কয়েকটি “কাল্পনিক” file system (/proc, /sys) আছে। Windows প্রতিটি volume-কে একটি আলাদা অক্ষর দেয় — C:, D:, E:. Unix-এর দর্শন ভিন্ন: একটিই গাছ, যার শিকড় /, আর প্রতিটি নতুন file system গাছের কোনো একটি directory-তে “জুড়ে দেওয়া” হয়। এই জুড়ে দেওয়ার নাম mount, আর ঐ directory হলো mount point.

অফিসের একটি বড় ফাইল ক্যাবিনেট ভাবুন, যার একটি ড্রয়ারে লেবেল “শাখা অফিস”. শাখা অফিস থেকে পুরো একটি ছোট ক্যাবিনেট এলে সেটি ঐ ড্রয়ারের জায়গায় বসিয়ে দেওয়া হলো। এখন “শাখা অফিস” ড্রয়ার খুললেই নতুন ক্যাবিনেটের ফাইল — আর ড্রয়ারে আগে যা ছিল, তা নতুন ক্যাবিনেটের নিচে চাপা পড়ে, মোছে না। ক্যাবিনেট সরিয়ে নিলে পুরোনো জিনিস আবার দেখা যায়।

Kernel প্রতিটি mount-এর রেকর্ড রাখে একটি mount table-এ (Linux-এ প্রতিটি mount namespace-এর নিজস্ব; container এ কারণেই আলাদা দৃশ্য পায় — পর্ব ৫১)। নিচে একটি USB stick-কে /mnt-এ mount করার পুরো গল্প:

Mount table আর /mnt-এর দৃশ্য
ঘটনা
mount table
/mnt-এ ঢুকলে পৌঁছায়
ls /mnt
kernel-এর কাজ
  1. শুরু। mount table-এ একটিই সারি: SSD-র partition /-এ, ext4. /mnt একটি সাধারণ directory (inode 40), যার ভেতরে old.txt আছে।
  2. USB ঢোকানো — শুধু ডিভাইস। USB driver যন্ত্রটি চিনল, udev /dev/sdb1 নামের device node বানাল। কিন্তু ব্লকগুলো এখনো ফাইল হিসেবে দেখা যায় না — কেউ বলেনি কোন file system আর কোথায় বসবে। (ডেস্কটপ Linux-এ udisks নামের সার্ভিস এখানেই নিজে থেকে mount করে দেয়, সাধারণত /media/<user>/… বা /run/media/<user>/…-এ।)
  3. mount — জোড়া লাগল। kernel vfat-এর driver দিয়ে stick-এর boot sector/superblock পড়ল, একটি struct super_block আর একটি struct mount বানাল, mount table-এ নতুন সারি যোগ করল, আর /mnt-এর dentry-তে চিহ্ন দিল “এখানে কিছু mount করা”. old.txt মোছেনি — শুধু চাপা পড়েছে।
  4. Path খোঁজা mount পার হয়। /mnt/photo.jpg খুঁজতে গিয়ে namei /mnt-এ পৌঁছে চিহ্নটি দেখে, তারপর mount table থেকে USB-র root dentry-তে লাফ দেয়, আর সেখানে photo.jpg খোঁজে। এখান থেকে .. করলে আবার /-এ — mount-এর মা-directory.
  5. Busy — umount নিষেধ। একটি shell-এর cwd /mnt-এ (বা কোনো প্রোগ্রাম সেখানে ফাইল খোলা রেখেছে)। kernel EBUSY দেয়: “target is busy”. কে আটকে রেখেছে দেখতে fuser -vm /mnt বা lsof /mnt.
  6. নিরাপদে umount. shell সরে গেল, umount সফল: cache-এ থাকা লেখা (dirty page) ডিস্কে লেখা হলো, superblock ছাড়া হলো, সারিটি mount table থেকে গেল। ls /mnt আবার পুরোনো old.txt দেখায়। “Safely remove” না করে stick টেনে খুললে এই লেখাগুলো হারানোর ঝুঁকি থাকে।

আরও কয়েক রকম mount

  • Bind mount: mount --bind /srv/data /home/rahim/data — নতুন file system নয়, একটি বিদ্যমান directory-কে আরেক জায়গায় আবার দেখানো। symlink-এর মতো শোনায়, কিন্তু kernel-এর mount স্তরে হয়, তাই .., chroot আর container-এর ভেতরেও ঠিক থাকে। Docker-এর -v /host/dir:/app/data আসলে একটি bind mount.
  • Automount: path প্রথমবার ছোঁয়ার মুহূর্তে mount, অনেকক্ষণ ব্যবহার না হলে খুলে ফেলা — autofs বা systemd-এর .automount unit. অফিসের নেটওয়ার্ক-শেয়ার (NFS) বা কম-ব্যবহৃত ডিস্কের জন্য উপযোগী।
  • কাল্পনিক file system: procfs (/proc), sysfs (/sys), tmpfs (RAM-এ থাকা ফাইল, যেমন /dev/shm, অনেক distro-য় /tmp), cgroup2 — এদের পেছনে কোনো ডিস্ক নেই, kernel পড়ার মুহূর্তে উত্তর বানায়।
  • Windows-এ: drive letter-ই মূল পদ্ধতি, তবে NTFS একটি volume-কে অন্য volume-এর একটি খালি ফোল্ডারেও mount করতে পারে (Disk Management-এ “Mount in the following empty NTFS folder”, বা mountvol) — Unix-এর mount point-এর কাছাকাছি।

এবার এই অনলাইন রানারের container নিজেই কী দেখে, দেখি। /proc/mounts হলো এই process-এর mount namespace-এর mount table, আর findmnt সেটি গাছ বা কলাম আকারে সুন্দর করে দেখায় (না থাকলে script জানাবে):

echo "== first 6 lines of /proc/mounts (device  mount-point  type  options ...) =="
head -n 6 /proc/mounts | cut -c1-90
echo "== mounts per file-system type =="
awk '{print $3}' /proc/mounts | sort | uniq -c | sort -rn
echo "== which file system holds these paths? =="
for d in / /proc /sys /dev /tmp "$PWD"; do
  printf '%-22s %s\n' "$d" "$(stat -f -c %T "$d")"
done
echo "total mounts visible to this process: $(wc -l < /proc/self/mounts)"
if command -v findmnt >/dev/null; then findmnt -n -o TARGET,FSTYPE,SOURCE /; else echo "findmnt not installed"; fi

আউটপুট:

== first 6 lines of /proc/mounts (device  mount-point  type  options ...) ==
/dev/vda2 /home/wandbox ext4 rw,relatime 0 0
/dev/vda2 /run/podman-init ext4 ro,relatime 0 0
/dev/vdb1 /opt/wandbox ext4 ro,noatime 0 0
/dev/vda2 /etc/resolv.conf ext4 rw,relatime 0 0
/dev/vda2 /etc/hosts ext4 rw,relatime 0 0
shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=64000k,uid=1000,gid=1000,inode64 0
== mounts per file-system type ==
     10 devtmpfs
      7 proc
      7 ext4
      6 tmpfs
      1 sysfs
      1 overlay
      1 mqueue
      1 devpts
      1 cgroup2
== which file system holds these paths? ==
/                      overlayfs
/proc                  proc
/sys                   sysfs
/dev                   tmpfs
/tmp                   overlayfs
/home/wandbox          ext2/ext3
total mounts visible to this process: 35
/      overlay overlay

এক রানের আউটপুট (প্রথম ছয় লাইনের ক্রম রানভেদে বদলাতে দেখা গেছে, আর অন্য সার্ভারে সংখ্যাগুলো আলাদা হতে পারে)। কয়েকটি শিক্ষা:

  • / হলো overlay: container image-এর শুধু-পড়ার স্তরগুলোর ওপর একটি লেখার স্তর মিলিয়ে একটি গাছ — Docker আর Podman-এর স্বাভাবিক root (পর্ব ৫১)।
  • ফাইল-স্তরের bind mount: /etc/hosts, /etc/resolv.conf আর /home/wandbox একই host ডিস্ক /dev/vda2 থেকে — container runtime host-এর ফাইলগুলো এক এক করে ভেতরে bind mount করেছে। /opt/wandbox (সম্ভবত compiler-গুলো) আলাদা ডিস্ক থেকে ro, অর্থাৎ শুধু-পড়ার mount.
  • ১০টি devtmpfs: /dev/null, /dev/zero-এর মতো device file-গুলো আলাদা আলাদা করে host থেকে আনা; আর একাধিক proc আর tmpfs সম্ভবত /proc-এর স্পর্শকাতর অংশ ঢাকতে বা শুধু-পড়ার করতে — container runtime-গুলো সাধারণত এমন করে। এখানে mount ব্যবহার হচ্ছে নিরাপত্তার পর্দা হিসেবে।
  • ext2/ext3? stat -f file system চেনে superblock-এর magic number দিয়ে, আর ext2, ext3, ext4-এর magic একই (0xEF53) — তাই আলাদা করতে পারে না; /proc/mounts সঠিকভাবে ext4 বলছে।
  • ৩৫টি mount একটি ছোট container-এ; একটি সাধারণ ডেস্কটপ Linux-এও findmnt কয়েক ডজন দেখায় — বেশিরভাগই কাল্পনিক file system. findmnt এখানে আছে, আর root-এর উৎস ও ধরন দুটোই overlay.

৮ · VFS — একই read() কীভাবে ext4, NFS, procfs আর FUSE-এ চলে

আপনার প্রোগ্রাম read(fd, buf, 100) লেখে। fd-টি হতে পারে SSD-র ext4 ফাইল, অফিসের সার্ভারের NFS ফাইল, /proc/cpuinfo (যার কোনো ডিস্কই নেই), অথবা FUSE দিয়ে বানানো Google Drive-এর একটি ফাইল। প্রোগ্রাম কোনো পার্থক্য জানে না। এই জাদুর নাম VFS — virtual file system স্তর।

বাংলাদেশে এক ব্যাংকের ATM কার্ড দিয়ে অনেক সময় অন্য ব্যাংকের বুথ থেকেও টাকা তোলা যায়। বুথ জানে না আপনার ব্যাংকের ভেতরের হিসাব কীভাবে চলে; সে শুধু একটি সাধারণ নিয়মে অনুরোধ পাঠায়, আর আপনার ব্যাংক নিজের পদ্ধতিতে উত্তর দেয়। VFS হলো সেই সাধারণ নিয়ম; ext4, NFS, procfs হলো আলাদা আলাদা ব্যাংক।

VFS কয়েকটি সাধারণ বস্তু সংজ্ঞায়িত করে, আর প্রতিটির সাথে একটি operation table — function pointer-এর একটি struct, যা প্রতিটি file system নিজের function দিয়ে ভরে (C-তে object-oriented নকশা):

বস্তুকী বোঝায়কতগুলোoperation table
superblockএকটি mount করা file system — আকার, block size, root, ফাঁকা জায়গাপ্রতি file system একটিsuper_operations: inode বানানো/লেখা, sync_fs, statfs
inodeএকটি ফাইল বা directory-র মেটাডেটাপ্রতি ফাইল একটি (নাম যত-ই থাক)inode_operations: lookup, create, link, unlink, symlink, mkdir, rename
dentrypath-এর একটি component — (মা, নাম) → inode; শুধু RAM-এপ্রতিটি নামে একটি (hard link-এ একাধিক dentry, একটি inode)dentry_operations: নাম তুলনা, পুনঃযাচাই (NFS-এর জন্য জরুরি)
fileএকটি খোলা ফাইল — offset, mode (পর্ব ৪১-এর open file description)প্রতিটি open()-এ একটিfile_operations: read_iter, write_iter, mmap, iterate_shared, fsync
struct file_operations {                 /* one table per kind of file */
    loff_t  (*llseek)    (struct file *, loff_t, int);
    ssize_t (*read)      (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)     (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
    int     (*iterate_shared)(struct file *, struct dir_context *);  /* readdir */
    int     (*mmap)      (struct file *, struct vm_area_struct *);
    int     (*open)      (struct inode *, struct file *);
    int     (*fsync)     (struct file *, loff_t, loff_t, int datasync);
    /* ... many more ... */
};

const struct file_operations ext4_file_operations = {   /* ext4 fills it in */
    .llseek     = ext4_llseek,
    .read_iter  = ext4_file_read_iter,
    .write_iter = ext4_file_write_iter,
    .mmap       = ext4_file_mmap,
    .open       = ext4_file_open,
    .fsync      = ext4_sync_file,
};

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    /* (permission and range checks omitted) */
    if (file->f_op->read)
        return file->f_op->read(file, buf, count, pos);      /* jump into the fs */
    if (file->f_op->read_iter)
        return new_sync_read(file, buf, count, pos);
    return -EINVAL;
}

অর্থাৎ read() system call → vfs_read() → file->f_op->read_iter — আর কোন function সেখানে বসানো, তা ঠিক হয়েছিল open()-এর সময়, ফাইলটি কোন file system-এর তা দেখে। এভাবে:

  • ext4: page cache দেখে, না থাকলে ডিস্কের ব্লক পড়ার অনুরোধ (পর্ব ৩৯-৪০-এর I/O পথ)।
  • NFS: নেটওয়ার্কে সার্ভারকে RPC পাঠায় (পর্ব ৫৩)।
  • procfs: kernel-এর ভেতরের তথ্য দিয়ে সেই মুহূর্তে লেখা তৈরি করে — এ জন্যই ls -l /proc/cpuinfo আকার ০ দেখায়, অথচ cat করলে অনেক লাইন আসে।
  • FUSE: kernel-এর FUSE module অনুরোধটি /dev/fuse দিয়ে একটি সাধারণ user-space প্রোগ্রামের কাছে পাঠায় (যেমন sshfs), উত্তর এলে ফেরত দেয়। ফলে root অধিকার বা kernel কোড ছাড়াই নতুন file system লেখা যায় — বিনিময়ে প্রতিটি অনুরোধে বাড়তি context switch.

Windows-এও একই চিন্তা, ভিন্ন রূপে: I/O Manager একটি অনুরোধকে IRP (I/O request packet) বানিয়ে সঠিক file system driver-এর (NTFS, FAT, ReFS) কাছে পাঠায়, আর filter driver (যেমন antivirus) মাঝখানে বসতে পারে (পর্ব ৫৬)।

৯ · Filesystem Hierarchy Standard — Linux-এর ফোল্ডার-মানচিত্র

প্রতিটি Linux distro-তে /etc-এ configuration আর /home-এ user-দের ফাইল কেন? কারণ একটি সাধারণ চুক্তি আছে — Filesystem Hierarchy Standard (FHS), যার সর্বশেষ বড় সংস্করণ ৩.০ (২০১৫, Linux Foundation). পর্ব ০৩-এ মানচিত্রটি এক নজরে দেখেছেন; এখানে প্রতিটি directory কোন file system বা mount-এর সাথে জড়িত, সেই চোখে দেখি:

Directoryকী থাকেmount-এর দিক থেকে
/গাছের শিকড়root file system, বুটের সময় প্রথম mount (পর্ব ০৭)
/bin, /sbinমৌলিক কমান্ডঅনেক নতুন distro-তে আসলে /usr/bin-এর symlink (“usr merge”)
/etcসিস্টেমের configuration লেখা-ফাইলroot fs-এর অংশ
/homeuser-দের ফাইলপ্রায়ই আলাদা partition — OS নতুন করে বসালেও ডেটা থাকে
/usrপ্রোগ্রাম, library, ডকুমেন্টেশন (মূলত শুধু-পড়া)কোনো কোনো সিস্টেমে আলাদা, এমনকি read-only mount
/varবদলাতে থাকা ডেটা — log, mail, cache, ডেটাবেসসার্ভারে প্রায়ই আলাদা, যাতে log ভরে গেলে / না ভরে
/tmpঅস্থায়ী ফাইল, সবাই লিখতে পারেন (sticky bit)অনেক distro-য় tmpfs — reboot-এ মুছে যায়
/procprocess আর kernel-এর তথ্যprocfs, কোনো ডিস্ক নেই
/sysডিভাইস, driver, kernel-এর সেটিংsysfs
/devdevice file (/dev/sda, /dev/null)devtmpfs, udev পরিচালনা করে
/mnt, /mediaঅস্থায়ী আর অপসারণযোগ্য mountখালি mount point

Windows-এ তুলনীয় জায়গা: C:\Windows (OS), C:\Program Files (প্রোগ্রাম, অনেকটা /usr), C:\Users (/home), C:\ProgramData (কিছুটা /var আর /etc-এর মিশ্রণ), আর configuration-এর বড় অংশ registry-তে (পর্ব ৫৬)। macOS-এ user-দের ফাইল /Users-এ, আর ভেতরে BSD-ধাঁচের /etc, /usr, /var আছে।

আরও গভীরে: Linux ২.৬.৩৮ (২০১১) থেকে path খোঁজা প্রথমে “RCU-walk” পদ্ধতিতে হয় — কোনো lock বা reference count না নিয়ে, sequence counter দিয়ে যাচাই করে dentry cache-এ দৌড়ে যাওয়া (Nick Piggin-এর কাজ); মাঝপথে কিছু বদলালে ধীর “ref-walk”-এ ফিরে আসে। এজন্যই বহু-core সার্ভারে লাখো stat() একসাথে চললেও lock-এর লড়াই কম। Linux ৫.৬-এ আসা openat2() system call RESOLVE_BENEATH, RESOLVE_NO_SYMLINKS, RESOLVE_IN_ROOT flag দিয়ে প্রোগ্রামকে নিশ্চিত হতে দেয় যে খোঁজা একটি directory-র বাইরে যাবে না — container runtime আর web server-এর জন্য symlink-ভিত্তিক বিপদ থেকে সুরক্ষা। Mount-এরও propagation আছে (shared, slave, private), যা ঠিক করে এক namespace-এ নতুন mount অন্যটিতে দেখা যাবে কি না।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Hard link আর soft link-এর পার্থক্য? — hard link একই inode-এর আরেকটি নাম (link count বাড়ে, একই FS, directory নয়); soft link নিজস্ব inode-এ একটি path রাখে, অন্য FS আর directory-কেও দেখাতে পারে, লক্ষ্য মুছলে ঝুলে যায়।
  • Directory-র hard link কেন নিষেধ? — cycle তৈরি হতো: গাছ-ঘোরা অসীম লুপে পড়ত, reference count শূন্য না হওয়ায় জায়গা ফেরত আসত না (GC লাগত), আর ..-র মানে অস্পষ্ট হতো।
  • Acyclic আর general graph directory-র পার্থক্য? — প্রথমটিতে ভাগাভাগি আছে কিন্তু cycle নেই, তাই reference count যথেষ্ট; দ্বিতীয়টিতে cycle সম্ভব, তাই traversal-এ লুপ ঠেকানো আর garbage collection লাগে।
  • Mounting কী? — একটি file system-কে বিদ্যমান গাছের একটি directory-তে (mount point) জুড়ে দেওয়া; সেখানকার আগের জিনিস ঢাকা পড়ে, umount-এ ফিরে আসে।
  • VFS-এর প্রধান বস্তুগুলো? — superblock (একটি mount করা FS), inode (একটি ফাইল), dentry (path-এর একটি নাম), file (একটি খোলা instance); প্রতিটির operation table প্রতিটি FS নিজে ভরে।

১০ · AI-এর যুগে file system-এর প্রশ্ন — উত্তর যাচাই

“Linux-এ একটি ফোল্ডার অন্য জায়গায় দেখাতে চাই” — AI খুব দ্রুত ln -s বা mount --bind লিখে দেবে। কিন্তু link আর mount-এর ভুল সাধারণত সাথে সাথে ধরা পড়ে না; কয়েক মাস পরে backup-এ, deploy-এ বা কারও ফাইল মোছার সময় ধরা পড়ে। AI-এর সাধারণ ভুলগুলো:

AI-এর ভুলযা ঘটেকীভাবে ধরবেন
ln -s-এর argument-এর ক্রম উল্টো (লক্ষ্য আগে, link-এর নাম পরে)ভুল জায়গায় link, বা বিদ্যমান ফোল্ডারের ভেতরে নতুন linkls -l আর readlink -f দিয়ে দেখুন link আসলে কোথায় দেখায়
relative symlink-কে অন্য directory থেকে তৈরিলক্ষ্যের path link-এর জায়গা থেকে হিসাব হয়, তাই danglingfind . -xtype l দিয়ে ঝুলে থাকা link খুঁজুন
“USB-তে hard link করুন” বা “ডিরেক্টরির hard link বানান”EXDEV বা hard link not allowed for directorylink-এর নিয়ম মনে রাখুন; stat -f -c %T দিয়ে দুই জায়গার FS দেখুন
permission সমস্যায় chmod -R 777 বা sudoসবাই লিখতে পারে — নিরাপত্তা-ঝুঁকি; আসল কারণ (মাঝের directory-তে x নেই) থেকেই যায়namei -l /full/path দিয়ে প্রতিটি component-এর permission দেখুন
symlink-ওয়ালা ফোল্ডারে rm -rf link/ (শেষে /)link নয়, লক্ষ্যের ভেতরের সবকিছু মুছে যেতে পারেআগে ls -ld; link সরাতে rm link (স্ল্যাশ ছাড়া) বা unlink link
পুরোনো বা ভুল OS-এর তথ্য — /etc/mtab-কে আসল ফাইল ধরা, Windows-এ ln, macOS-এ GNU-র flagকমান্ড চলে না বা ভুল ব্যাখ্যাuname -a, man ln, ls -l /etc/mtab (নতুন সিস্টেমে এটি /proc/self/mounts-এর symlink)

AI-কে প্রশ্ন করার সময় পরিবেশ, উদ্দেশ্য আর সীমা বলে দিন, আর আগে শুধু-দেখার কমান্ড চান:

আমার Ubuntu 24.04 সার্ভারে /var/www/app/uploads ফোল্ডারটি আসলে /data ডিস্কে রাখতে চাই (/data একটি আলাদা ext4 partition). অ্যাপটি www-data user হিসেবে চলে, আর আমি Docker ব্যবহার করি না। ১. symlink আর bind mount — দুটির সুবিধা-অসুবিধা এই ক্ষেত্রে তুলনা করো: backup (tar, rsync), অ্যাপের path যাচাই, reboot-এর পর কী হবে। ২. প্রথমে শুধু পড়ার কমান্ড দাও (findmnt, stat -f, namei -l, ls -ld) যাতে বর্তমান অবস্থা দেখতে পারি। ৩. কোনো chmod 777 বা অপ্রয়োজনীয় sudo দিও না; প্রতিটি পরিবর্তনের কমান্ডের পরে কীভাবে যাচাই করব আর কীভাবে আগের অবস্থায় ফেরাব সেটাও লেখো। ৪. /etc/fstab-এ কিছু যোগ করতে বললে ভুল হলে বুট আটকে যাবে কি না, আর কীভাবে আগেই পরীক্ষা করব (mount -a, findmnt --verify) তা বলো। ৫. কোন তথ্য তুমি নিশ্চিত নও, স্পষ্ট বলো।

AI-এর দেওয়া link বা mount-এর পরামর্শ ব্যবহারের আগে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Unix সাধারণ user-কে directory-র hard link বানাতে দেয় না — অথচ macOS-এর Time Machine একসময় ঠিক এটাই করত। কেন, আর কীভাবে নিরাপদ রাখা হতো?

পুরোনো Unix-এ mkdir কোনো system call ছিল না। mkdir কমান্ডটি ছিল একটি setuid-root প্রোগ্রাম, যা mknod দিয়ে একটি ফাঁকা directory inode বানিয়ে তারপর link() দিয়ে . আর .. জুড়ত — অর্থাৎ superuser directory-র hard link বানাতে পারতেন। ভুল করে একটি directory-কে তার নিজের ভেতরে link করলে fsck-এর মতো টুলও বিভ্রান্ত হতো। 4.2BSD (১৯৮৩) mkdir, rmdir আর rename-কে kernel-এর atomic system call বানায়, আর ধীরে ধীরে directory-র hard link কার্যত নিষিদ্ধ হয়। আজ Linux-এ link() directory পেলে root-এর জন্যও EPERM দেয়।

কারণগুলো অংশ ২-এর যুক্তি: cycle হলে reference count দিয়ে জায়গা ফেরত আসে না, find/du/backup অসীম লুপে পড়ে, আর একাধিক মা থাকলে .. কাকে দেখাবে তা অস্পষ্ট।

তবু Apple ২০০৭ সালে Mac OS X 10.5-এ Time Machine আনার সময় HFS+-এ directory hard link চালু করে, শুধু Time Machine-এর জন্য। প্রতি ঘণ্টার backup-এ যে ফোল্ডার বদলায়নি, সেটি নতুন করে কপি না করে আগের backup-এর ফোল্ডারের দিকে একটি hard link — ফলে প্রতিটি snapshot পুরো ডিস্কের মতো দেখায়, অথচ জায়গা লাগে শুধু বদলের। নিরাপত্তার জন্য কয়েকটি কঠোর শর্ত দেওয়া হয়, যেমন দুটি নাম একই মা-directory-তে থাকতে পারবে না, আর একটি directory-কে তার নিজের ভেতরের (বংশধর) কোনো directory-তে link করা যাবে না — অর্থাৎ গ্রাফ acyclic থাকবে, এটি kernel নিজে নিশ্চিত করে। পরে APFS-এ (macOS 11 Big Sur থেকে নতুন Time Machine ডিস্কে) এই কৌশলের বদলে file system snapshot ব্যবহার হয়, যা copy-on-write file system-এর স্বাভাবিক ক্ষমতা (পর্ব ৪৪)।

Linux-এর জগতে একই প্রয়োজন মেটায় rsync --link-dest: বদলায়নি এমন প্রতিটি ফাইলের hard link, directory নতুন করে তৈরি। নিয়ম ভাঙতে হয় না।

মূল উপলব্ধি: নিষেধটি কোনো গাণিতিক অসম্ভবতা নয়, একটি নকশার সিদ্ধান্ত: cycle-মুক্ত গ্রাফ নিশ্চিত করার সবচেয়ে সস্তা উপায় হলো directory-র link-ই বন্ধ করা। বিশেষ প্রয়োজনে কেউ নিয়ম শিথিল করলে, cycle ঠেকানোর দায় নিজেকে নিতে হয়।

প্র ০২rename() atomic — এই একটি নিশ্চয়তা দিয়ে editor, package manager আর ডেটাবেস কীভাবে নিরাপদে ফাইল বদলায়? আর এই কৌশলে কী ভুল হতে পারে?

ধরুন একটি editor config.json সংরক্ষণ করছে। সরাসরি পুরোনো ফাইলের ওপর লিখলে, অর্ধেক লেখার সময় বিদ্যুৎ গেলে (বাংলাদেশে লোডশেডিং পরিচিত ঘটনা) ফাইলটি অর্ধেক পুরোনো, অর্ধেক নতুন — নষ্ট। তাই প্রচলিত কৌশল:

  1. একই directory-তে নতুন ফাইল লেখা, যেমন config.json.tmp;
  2. fsync() দিয়ে নিশ্চিত হওয়া যে ডেটা সত্যিই ডিস্কে পৌঁছেছে;
  3. rename("config.json.tmp", "config.json") — atomic, তাই যেকোনো মুহূর্তে কেউ হয় পুরো পুরোনো, নয় পুরো নতুন ফাইল দেখবেন;
  4. সবচেয়ে সাবধানী প্রোগ্রাম directory-টিও fsync করে, যাতে নাম বদলের রেকর্ডটিও টেকে।

একই directory কেন? অংশ ৩-এ দেখেছেন — অন্য file system-এ rename EXDEV দেয়, তাই tmp ফাইল /tmp-এ (যা প্রায়ই tmpfs) লিখলে কৌশলটি ভেঙে যায়। dpkg, rpm, git, SQLite-এর কিছু মোড — সবাই এই নীতিতে কাজ করে।

ইতিহাসের একটি শিক্ষণীয় ঘটনা: ২০০৯ সালের দিকে ext4 নতুন হলে অনেক user অভিযোগ করেন, crash-এর পর কিছু configuration ফাইল শূন্য বাইটের হয়ে যাচ্ছে। কারণ প্রোগ্রামগুলো ধাপ ২ (fsync) বাদ দিয়ে শুধু লিখে rename করত; ext4-এর delayed allocation ডেটা কয়েক সেকেন্ড পরে লিখত, কিন্তু rename-এর মেটাডেটা আগেই journal-এ চলে যেত। ফল: নতুন নাম আছে, ডেটা নেই। POSIX অনুযায়ী প্রোগ্রামগুলোরই ভুল ছিল, তবু ব্যবহারিক কারণে ext4-এর রক্ষণাবেক্ষণকারী Ted Ts'o একটি heuristic যোগ করেন (auto_da_alloc): পুরোনো ফাইলের ওপর rename হলে আগে ডেটা লেখা শুরু করা। Crash consistency-র এই বিষয় পর্ব ৪৪-এ বিস্তারিত।

আরেকটি সূক্ষ্মতা: rename-এ inode বদলায়, তাই যে ফাইলের hard link ছিল সেটি নতুন ফাইলে আর যুক্ত থাকে না, আর কোনো প্রোগ্রাম পুরোনো ফাইলটি খোলা রাখলে সে পুরোনো inode-ই পড়তে থাকে। Log rotation টুল এ জন্যই প্রোগ্রামকে signal পাঠিয়ে ফাইল আবার খোলায়।

মূল উপলব্ধি: নিরাপদ ফাইল-আপডেট তিনটি আলাদা নিশ্চয়তার ওপর দাঁড়ায় — ডেটা টেকসই (fsync), নাম বদল atomic (rename), আর দুটো একই file system-এ। যেকোনো একটি বাদ দিলে “বেশিরভাগ সময় কাজ করে” এমন কোড পাবেন, যা ঠিক crash-এর দিনেই ব্যর্থ হয়।

প্র ০৩Symlink কেন নিরাপত্তার দিক থেকে ঝুঁকিপূর্ণ, আর আধুনিক OS কীভাবে এর থেকে রক্ষা করে?

Symlink-এর শক্তিই তার বিপদ: নামটি দেখতে এক জায়গার, কিন্তু খোলা হয় অন্য জায়গার ফাইল। সমস্যা হয় যখন বেশি অধিকারের একটি প্রোগ্রাম কম অধিকারের user-দের লেখার জায়গায় কাজ করে — ক্লাসিক উদাহরণ /tmp, যেখানে সবাই ফাইল তৈরি করতে পারেন। পুরোনো দিনে কোনো root-চালিত script যদি অনুমেয় নামে, যেমন /tmp/report.txt-এ লিখত, অন্য কেউ আগে থেকেই ঐ নামে একটি symlink রেখে দিলে script অজান্তে একটি জরুরি সিস্টেম ফাইলের ওপর লিখে ফেলত। এটি “time-of-check to time-of-use” (TOCTOU) ধরনের ত্রুটি: পরীক্ষা আর ব্যবহারের মাঝের মুহূর্তে নামের অর্থ বদলে যায়।

প্রতিরক্ষার স্তরগুলো (সবই কনফিগার আর যাচাই করার মতো):

  • Kernel-এর নীতি: Linux ৩.৬ (২০১২) থেকে fs.protected_symlinks আর fs.protected_hardlinks sysctl; বেশিরভাগ distro এগুলো ডিফল্টভাবে ১ রাখে। তখন sticky bit-ওয়ালা, সবার-লেখার directory-তে অন্য user-এর symlink অনুসরণ করা হয় না, আর নিজের নয় এমন ফাইলের hard link বানানো যায় না। যাচাই: sysctl fs.protected_symlinks.
  • নিরাপদ API: mkstemp() অনুমান-অযোগ্য নামে আর O_CREAT | O_EXCL দিয়ে ফাইল তৈরি করে, যা আগে থেকে থাকা symlink-এ ব্যর্থ হয়; O_NOFOLLOW শেষ component-এ symlink হলে খুলতে অস্বীকার করে; openat2()-এর RESOLVE_NO_SYMLINKS/RESOLVE_BENEATH পুরো path-এ নিয়ন্ত্রণ দেয়।
  • নকশা: প্রতিটি service-এর নিজস্ব private tmp (systemd-এর PrivateTmp=yes), আর web server-এ “symlink অনুসরণ নিষেধ, বা শুধু একই মালিকের হলে অনুমতি” ধরনের সেটিং।
  • Windows: ঠিক এই ঝুঁকির কারণে Vista থেকে symlink তৈরি করতে আলাদা অধিকার (SeCreateSymbolicLinkPrivilege) লাগে, যা ডিফল্টভাবে admin-দের থাকে; Windows 10-এর একটি আপডেট থেকে Developer Mode চালু করলে সাধারণ user-ও বানাতে পারেন।

মূল উপলব্ধি: একটি path একটি “প্রতিশ্রুতি”, পরিচয় নয় — খোঁজার মুহূর্তে তার অর্থ ঠিক হয়। অধিকারপ্রাপ্ত কোড যত কম path আর যত বেশি খোলা fd (openat দিয়ে) নিয়ে কাজ করে, তত নিরাপদ।

প্র ০৪VFS-এর ধারণা কোথা থেকে এলো, আর “সব কিছুই ফাইল” abstraction কোথায় ফুটো হয়ে যায়?

প্রথম দিকের Unix-এ একটিই file system ছিল, কোড সরাসরি তার গঠনের সাথে বাঁধা। ১৯৮০-র দশকের মাঝামাঝি Sun Microsystems নেটওয়ার্কে ফাইল ভাগাভাগির জন্য NFS বানানোর সময় সমস্যায় পড়ে: দূরের সার্ভারের ফাইলকে স্থানীয় ফাইলের মতো দেখাতে হলে kernel-এর ফাইল-কোডকে file system-নিরপেক্ষ করতে হবে। Steve Kleiman-এর নকশা “vnode/VFS” (SunOS-এ, ১৯৮৫ সালের দিকে; ১৯৮৬-র USENIX-এ প্রকাশিত প্রবন্ধ) প্রতিটি file system-কে একটি operation table দিয়ে প্লাগ-ইন হিসেবে বসানোর পথ দেখায়। পরে প্রায় সব Unix, আর Linux-ও একই ধারণা নেয়। Bell Labs-এর Plan 9 আরও এগিয়ে যায় — নেটওয়ার্ক, window, process সবই ফাইল, আর প্রতিটি process-এর নিজস্ব namespace; Linux-এর /proc আর mount namespace (২.৪.১৯, ২০০২) এই ধারার উত্তরাধিকার। FUSE ২০০৫ সালে Linux ২.৬.১৪-এ যুক্ত হয়ে user-space file system-এর দরজা খোলে।

কিন্তু abstraction “ফুটো” হয় (leaky abstraction) যেখানে নিচের file system-এর আচরণ সত্যিই ভিন্ন:

  • NFS: অনেক NFS সংস্করণ “close-to-open” consistency দেয় — একজন ফাইল বন্ধ করার পর আরেকজন খুললে তবেই নতুন ডেটা নিশ্চিত। স্থানীয় ফাইলের মতো মুহূর্তে মুহূর্তে মিল আশা করলে বাগ। সার্ভার না পেলে read() মিনিটের পর মিনিট আটকে থাকতে পারে।
  • procfs: আকার ০, অথচ ডেটা আছে; প্রতিবার পড়লে ভিন্ন উত্তর। ফাইলের আকার দেখে বাফার বানায় এমন কোড এখানে ভুল করে।
  • FAT/vfat: permission, owner, symlink, hard link — কিছুই নেই; mount-এর সময় সবার জন্য একটি কাল্পনিক মালিক আর permission বসানো হয়। ছোট-বড় হাতের অক্ষর আলাদা নয়।
  • FUSE: প্রতিটি অনুরোধে user-space-এ যাতায়াত, তাই ছোট ছোট অনেক ফাইলে ধীর; আর সার্ভিসটি crash করলে mount “Transport endpoint is not connected” দেখায়।

মূল উপলব্ধি: VFS একটি চুক্তি দেয় — কোন function আছে — কিন্তু প্রতিটি function-এর গতি, consistency আর ব্যর্থতার ধরন নিচের file system-এর ওপর নির্ভর করে। ভালো systems প্রোগ্রামার interface দেখে নিশ্চিন্ত হন না, জানতে চান “এর নিচে কে?” — stat -f আর findmnt এই প্রশ্নের উত্তর দেয়।

প্র ০৫Windows-এ C:, D: আর Unix-এ একটিই গাছ — দুটি নকশা কোথা থেকে এলো, আর কোনটি কখন সুবিধাজনক?

Drive letter-এর শিকড় ১৯৭০-এর দশকের CP/M-এ, যেখানে প্রতিটি floppy drive একটি অক্ষর: A:, B:. MS-DOS (১৯৮১) এই ধারণা নেয়; hard disk এলে তার জায়গা হয় C: — এজন্যই আজও Windows সাধারণত C:-এ থাকে, যদিও floppy আর নেই। প্রতিটি অক্ষরের নিজস্ব গাছ আর নিজস্ব current directory. Unix (১৯৭০-এর দশক, Bell Labs) শুরু থেকেই একটি গাছ রেখেছে, যেখানে /etc/fstab বা বুটের সময় বিভিন্ন ডিস্ক বিভিন্ন ডালে জোড়া হয়।

Drive letter-এর সুবিধা: সাধারণ user-এর কাছে সহজ — “পেনড্রাইভ E:-এ”. কোন ফাইল কোন ডিস্কে, তা নামেই বোঝা যায়, আর এক ডিস্ক থেকে আরেক ডিস্কে সরানো যে কপি (সময়সাপেক্ষ), তা স্পষ্ট। অসুবিধা: মাত্র ২৬টি অক্ষর; ডিস্ক যোগ বা অক্ষর বদলালে path ভেঙে যায়; একটি ভরে যাওয়া ফোল্ডারকে নতুন ডিস্কে সরাতে চাইলে প্রোগ্রামের সব path বদলাতে হয়।

একক গাছের সুবিধা: path-গুলো হার্ডওয়্যার-নিরপেক্ষ — /var/log ভরে গেলে নতুন ডিস্ক এনে /var-এ mount করলেই চলে, কোনো প্রোগ্রাম বদলাতে হয় না। নেটওয়ার্ক file system, RAM-এর tmpfs, kernel-এর procfs — সব একই নিয়মে। আর প্রতি process-এর আলাদা mount namespace থাকায় container নিজের মতো গাছ পায়। অসুবিধা: কোন path কোন ডিস্কে তা নাম দেখে বোঝা যায় না — তাই mv কখনো তাৎক্ষণিক, কখনো ঘণ্টাব্যাপী কপি (অংশ ৩-এর EXDEV).

বাস্তবে দুটো দর্শন কাছাকাছি এসেছে। Windows NT-র ভেতরে আসলে একটি একক object namespace আছে — C: শুধু \Device\HarddiskVolume3-এর মতো একটি নামের প্রতীকী link; NTFS volume mount point দিয়ে একটি ডিস্ক একটি ফোল্ডারে বসানো যায়। আবার WSL-এ Windows-এর C: দেখা যায় /mnt/c-এ — একটি mount point হিসেবে।

মূল উপলব্ধি: ব্যবহারকারীর কাছে কী দেখানো হবে (অক্ষর না গাছ) আর kernel ভেতরে কী রাখে (একটি namespace) — দুটি আলাদা স্তর। ভালো নকশা নিচের স্তরকে নমনীয় রাখে, ওপরের স্তরকে পরিচিত।

অনুশীলন

প্রথমে নিজে চেষ্টা করুন, তারপর উত্তর মিলিয়ে নিন।

  1. হিসাব (numerical): একটি directory-তে ৫০,০০০টি entry, প্রতিটি ডিস্ক-ব্লকে ১০০টি entry ধরে। (ক) Linear list-এ আছে এমন একটি নাম খুঁজতে গড়ে আর সবচেয়ে খারাপ ক্ষেত্রে কতগুলো ব্লক পড়তে হবে? (খ) নেই এমন নাম? (গ) প্রতিটি index ব্লকে ৫০০টি চাবি রাখা যায় এমন hash-ভিত্তিক গাছে কতগুলো?

    মোট leaf block = ৫০,০০০ ÷ ১০০ = ৫০০.

    (ক) আছে এমন নাম: গড়ে অর্ধেক — ৫০০ ÷ ২ = ২৫০ ব্লক (আরও নিখুঁতভাবে (৫০০ + ১) ÷ ২ ≈ ২৫০.৫); সবচেয়ে খারাপ ক্ষেত্রে ৫০০ ব্লক.

    (খ) নেই এমন নাম: নিশ্চিত হতে পুরোটাই — ৫০০ ব্লক. প্রতিটি নতুন ফাইল তৈরির আগে এই যাচাই লাগে, তাই linear list-এ বড় directory-তে ফাইল তৈরিও ধীর।

    (গ) এক স্তরের index ৫০০টি leaf চেনাতে পারে, আর leaf ঠিক ৫০০টি — তাই ১ index ব্লক + ১ leaf ব্লক = ২ ব্লক, নাম থাকুক বা না থাকুক। অনুপাত গড়ে ২৫০ ÷ ২ = ১২৫ গুণ, নেই এমন নামে ৫০০ ÷ ২ = ২৫০ গুণ কম।

  2. আউটপুট অনুমান করুন: একটি খালি directory-তে এই কমান্ডগুলো চালালে শেষ চারটি লাইনে কী ছাপা হবে?
    echo A > f; ln f g; ln g h; ln -s f s
    rm f
    stat -c %h g
    cat s 2>&1 | head -c 20; echo
    echo B > f
    cat s h

    f, g, h তিনটি নামই একই inode (link count ৩); s একটি symlink যার ভেতরে “f”. rm f-এর পর link count ২, তাই প্রথম লাইন 2. cat s ব্যর্থ — দ্বিতীয় লাইন cat: s: No such file (প্রথম ২০ বাইট)। তারপর নতুন f তৈরি হলো — নতুন inode, ডেটা B. cat s h প্রথমে symlink অনুসরণ করে নতুন f পড়ে: B, তারপর h পুরোনো inode পড়ে: A. অর্থাৎ শেষ চার লাইন: 2, cat: s: No such file, B, A.

  3. Path হাতে ট্রেস করুন: /var/www একটি symlink যা /srv/www দেখায়। একটি প্রোগ্রাম open("/var/www/../log/./app.log") করল। kernel আসলে কোন ফাইল খুলবে? কয়টি symlink অনুসরণ হলো, আর কোন কোন directory-তে x permission লাগবে?

    ধাপ: / → var → www একটি symlink, ভেতরে /srv/www (absolute) — তাই আবার / থেকে: srv → www. এখন বাকি ../log/./app.log: kernel-এর .. physical, তাই /srv/www-এর মা /srv; তারপর log, . (একই জায়গা), app.log. খোলা হবে /srv/log/app.log — /var/log/app.log নয়! symlink অনুসরণ ১টি. x permission লাগবে /, /var, /srv, /srv/www, /srv/log-এ (খোঁজার পথে যে directory-গুলোর ভেতরে নাম খোঁজা হয়েছে), তারপর app.log-এ open-এর মোড অনুযায়ী r বা w. (bash-এ cd /var/www/.. করলে কিন্তু /var-এ যাবেন — shell-এর logical path.)

  4. কমান্ড লিখুন: (ক) বর্তমান ফোল্ডারের নিচে সব ঝুলে থাকা (dangling) symlink খুঁজুন। (খ) /home file system-এ report.pdf-এর সব hard link (একই inode-এর সব নাম) খুঁজুন। (গ) /data কোন ডিভাইস থেকে, কোন ধরনের file system হিসেবে mount করা — দেখুন।

    (ক) find . -xtype l — -xtype l মানে “symlink অনুসরণ করার পরেও যা symlink থেকে যায়”, অর্থাৎ লক্ষ্য নেই। (GNU find; macOS-এর BSD find-এ এই flag নেই, সেখানে find . -type l ! -exec test -e {} \; -print)

    (খ) find /home -xdev -samefile /home/rahim/report.pdf — -xdev অন্য file system-এ ঢোকে না (সেখানে একই inode নম্বরের অর্থ ভিন্ন)। বিকল্প: ls -i দিয়ে inode নম্বর নিয়ে find /home -xdev -inum NUMBER. আগে stat -c %h দেখে নিন কতগুলো নাম পাওয়ার কথা।

    (গ) findmnt /data বা findmnt -T /data (path যেকোনো ফাইল হলেও তার mount দেখায়); বিকল্প df -T /data বা grep ' /data ' /proc/mounts.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি প্রোগ্রাম open("/home/rahim/notes.txt", O_RDONLY) ডাকল। kernel-এর ভেতরের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. System call-এ user mode থেকে kernel mode-এ প্রবেশ
  2. Path / দিয়ে শুরু, তাই process-এর root directory থেকে খোঁজা শুরু
  3. প্রতিটি মাঝের directory-তে x permission যাচাই আর dentry cache (না পেলে ডিস্ক) থেকে পরের inode
  4. শেষ component-এর inode পাওয়া আর তার r permission যাচাই
  5. struct file তৈরি, inode-এর file_operations জোড়া
  6. fd টেবিলের প্রথম ফাঁকা ঘরের নম্বর ফেরত

একটি ফাইলের একটি hard link আর একটি symbolic link আছে। এরপর ফাইলটির আসল নামটি মুছে ফেলা হলো। কোন কথাটি সঠিক?

  • দুটি link-ই এখন ব্যর্থ হবে
  • Hard link দিয়ে ডেটা পড়া যাবে, symlink ঝুলে যাবে
  • Symlink দিয়ে ডেটা পড়া যাবে, hard link ঝুলে যাবে
  • আসল নাম মুছলে kernel সব link-ও মুছে দেয়

ext4-এ একটি directory-র ভেতরে ৩টি sub-directory আর ১০টি সাধারণ ফাইল আছে। ঐ directory-র link count সাধারণত কত?

  • ১
  • ২
  • ৫
  • ১৫

Unix-ধাঁচের OS সাধারণ ব্যবহারে directory-র hard link তৈরি করতে দেয় না। প্রধান কারণ কোনটি?

  • Cycle তৈরি হতে পারে — traversal, reference count আর “..” তিনটিই ভেঙে পড়ে
  • Directory-র hard link অনেক ডিস্ক-জায়গা নেয়
  • Directory-র inode-এ link count রাখার জায়গা নেই
  • Hard link শুধু root ব্যবহার করতে পারেন

Linux-এ /home একটি ext4 partition আর /media/usb একটি আলাদা vfat USB stick. mv /home/rahim/video.mp4 /media/usb/ চালালে ভেতরে কী ঘটে?

  • rename() সফল হয়, শুধু directory entry সরে, ডেটা নড়ে না
  • mv ব্যর্থ হয়ে “Invalid cross-device link” দেখায়
  • একটি hard link তৈরি হয়, তারপর পুরোনো নাম মোছে
  • rename() EXDEV দেয়; mv ডেটা কপি করে তারপর মূল ফাইল মোছে

একটি process /home/rahim/docs/notes.txt পড়ার জন্য খুলতে চায়। কোন permission-গুলো লাগবে?

  • প্রতিটি directory-তে r, আর ফাইলে r
  • /, /home, /home/rahim আর docs-এ x, আর notes.txt-এ r
  • শুধু notes.txt-এ r
  • প্রতিটি directory-তে w, আর ফাইলে r

Linux-এর VFS-এ কোন বস্তুটি একটি নির্দিষ্ট open() থেকে আসা “খোলা ফাইল” বোঝায় — যার নিজস্ব offset আর mode আছে?

  • struct super_block
  • struct inode
  • struct dentry
  • struct file

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Directory হলো নাম → inode-এর তালিকা; গঠন single-level → two-level → tree → acyclic graph → general graph, আর cycle এড়াতে Unix directory-র hard link দেয় না।
  2. Hard link = একই inode-এর আরেকটি নাম (link count, একই FS); symlink = নিজের inode-এ একটি path (অন্য FS, directory, ঝুলে যেতে পারে, ৪০-এর পরে ELOOP).
  3. Directory-র ভেতর: linear list O(n), hash গড়ে O(1), B-tree O(log n) আর সাজানো — ext4-এর htree, NTFS-এর B+ tree.
  4. namei: root বা cwd থেকে শুরু, প্রতিটি component-এ x যাচাই, dentry cache, symlink আর mount point পার হয়ে শেষ inode.
  5. Mount একটি file system-কে গাছের একটি ডালে বসায়; VFS-এর superblock, inode, dentry, file আর operation table-এর জন্যই একই system call সব file system-এ চলে।

পরের পর্বে: নাম আর directory-র পেছনে ডিস্কের ভেতরে কী আছে — inode কীভাবে ডেটা-ব্লক খুঁজে পায়, contiguous, linked আর indexed allocation, FAT, ext4-এর extent আর ফাঁকা জায়গার হিসাব।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান