Memakai Simple File System Protocol di UEFI
Pada UEFI, setiap layanan yang disediakan dikelompokkan dalam apa yang disebut sebagai protocol. Untuk membaca dan menulis file pada partisi dengan file system yang didukung oleh UEFI, saya dapat menggunakan Simple File System Protocol. Bila partisi menggunakan file system yang tidak didukung secara bawaan atau saya ingin mengakses sektor (bukan dalam abstraksi file), saya dapat menggunakan Disk I/O Protocol. Bila ingin sesuatu yang lebih low level lagi, terdapat ATA Pass Thru Protocol untuk memberikan perintah ATA langsung ke perangkat keras media penyimpanan. Pada tulisan ini saya akan mencoba mengakses partisi dengan menggunakan Simple File System Protocol.
File System
Secara fisik, media penyimpanan sebenarnya hanya mendukung akses seperti LBA dalam bentuk sektor. Sektor pertama diakses dengan LBA 0, sektor kedua diakses lewat LBA 1, dan seterusnya. Sistem operasi menggunakan file system untuk melakukan translasi konsep yang lebih mudah dimengerti seperti file, folder dan permission ke alamat LBA. Pengguna sistem operasi tidak perlu tahu konsep sector atau alamat LBA untuk menyimpan atau membaca file karena data dikelompokkan berdasarkan nama yang mudah dimengerti.
Sistem operasi Windows menggunakan file sytem NTFS dan FAT32. Sementara itu, sistem operasi Linux menggunakan file system ext4. Masing-masing file system memiliki kelebihan dan kekurangannya. Sebagai contoh, FAT32 merupakan file system yang paling mudah dibuat implementasinya. FAT dibuat oleh Bill Gates yang terkenal dengan kutipan “I wrote FAT on an airplane”. NTFS dan ext4 menawarkan fitur yang lebih banyak dibandingkan FAT seperti dukungan ukuran file yang lebih besar, permission, dan sebagainya.
Pada era BIOS, pembuat bootstrap loader harus mencari cara untuk membaca file dari media penyimpanan dengan menyertakan kode file system yang minimal. UEFI membuat hal ini lebih sederhana dengan menyediakan dukungan file system FAT (FAT12, FAT16, dan FAT32) secara bawaan. Kode program yang ingin membaca dan menulis file di partisi dengan file system FAT cukup memanggil Simple File System Protocol yang disediakan oleh UEFI.
Mendapatkan Daftar Partisi
Karena setiap PC bisa memiliki jumlah perangkat penyimpan dan partisi yang berbeda, langkah pertama yang perlu saya lakukan adalah mendapatkan daftar Simple File System Protocol yang tersedia. Untuk itu, saya dapat menggunakan LocateHandleBuffer()
dari boot service. EDK2 sudah menyediakan variabel global gBS
untuk boot service dan gRT
untuk runtime service. Saya hanya perlu menyertakan header Library/UefiBootServicesTableLib.h
. Sebagai latihan, saya akan memanggil LocateHandleBuffer()
seperti pada contoh kode program berikut ini:
Kode program di atas menggunakan boot service LocateHandleBuffer
untuk mendapatkan daftar handle untuk protocol tertentu dalam bentuk array. Metode pencarian yang dipakai adalah ByProtocol
untuk mencari seluruh handle untuk protokol gEfiSimpleFileSystemProtocolGuid
(Simple File System Protoocol). Bila pada PC terdapat dua hardisk dimana masing-masing harddisk memiliki satu partisi yang didukung oleh UEFI, maka kode program di atas akan mendapatkan 2 handle ke Simple File System Protocol.
Walaupun kode program di atas bisa dijalankan tanpa kesalahan, pada EDK2, bila sebuah komponen menggunakan protocol, sebaiknya protocol yang dipakai ditambahkan pada file INF di bagian [Protocols]
untuk mempermudah deteksi kesalahan. Sebagai contoh, karena kode program di atas memakai Simple File System Protocol, saya akan menambahkan baris berikut ini pada file INF:
Setelah mendapatkan handle dengan LocateHandleBuffer
, langkah berikutnya adalah menggunakan HandleProtocol
untuk mendapatkan interface yang bisa digunakan untuk mengakses protocol. Sebagai contoh, saya akan menambahkan kode program berikut ini:
Sekarang, saya sudah mendapatkan sebuah EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
untuk setiap partisi di media penyimpanan yang ada di PC. Berdasarkan dokumentasi UEFI, Simple File System Protocol hanya memiliki sebuah function dengan nama OpenVolume
. Saya dapat menggunakan function ini untuk mendapatkan sebuah interface ke EFI_FILE_PROTOCOL
. Sebagai contoh, saya akan menambahkan program berikut ini:
Pada kode program di atas, saya mendapatkan EFI_FILE_PROTOCOL
dengan nama Root
. Selanjutnya, saya bisa menggunakan Root
untuk mendapatkan EFI_FILE_PROTOCOL
lain yang mewakili file dan folder yang akan dipakai. Saya kemudian bisa melakukan operasi pada EFI_FILE_PROTOCOL
tersebut seperti membaca, membuat dan menghapus file.
Melihat Informasi Partisi
Khusus untuk Root
, saya dapat memanggil GetInfo()
untuk mendapatkan informasi file system seperti label partisi, ukuran partisi dan ruang kosong pada partisi. Untuk itu, saya bisa membuat kode program seperti berikut ini:
Sama seperti protocol yang didefinisikan di file EFI, saya juga perlu menambahkan setiap GUID yang saya pakai ke file INF. Sebagai contoh, karena kode program di atas menggunakan gEfiFileSystemInfoGuid
, saya akan menambahkan baris berikut ini pada file INF:
Membuat File Baru
Untuk membuat file baru, saya dapat menggunakan function Open()
untuk mendapatkan EFI_FILE_PROTOCOL
baru dengan nama dan lokasi sesuai dengan yang telah ditentukan. Setelah itu, saya akan menggunakan function Write()
untuk menulis isi file dan tidak lupa memanggil Close()
untuk menyimpan perubahan. Sebagai contoh, saya akan membuat kode program seperti berikut ini:
Kode program di atas akan membuat sebuah file baru di partisi dengan nama readme.txt
dengan isi seperti File ini dibuat langsung dari UEFI tanpa lewat OS!
. Untuk menulis isi file per byte dalam bentuk ASCII sehingga lebih mudah ditampilkan oleh teks editor, saya menggunakan CHAR8*
dan bukan CHAR16*
seperti biasanya yang dipakai di UEFI. Library string yang disediakan oleh EDK2 seperti StrLen()
mengharapkan input dalam bentuk CHAR16*
. Bila ingin bekerja dengan CHAR8*
, saya perlu menggunakan function yang diawali dengan Ascii
seperti AsciiStrLen()
.
Untuk kode yang lebih singkat, saya juga dapat menggunakan library FileHandleLib
yang telah disediakan oleh EDK2. Namun sebelumnya, saya perlu menambahkan baris berikut ini pada file DSC:
Saya juga perlu menambahkan baris ini pada file INF untuk aplikasi:
Sekarang, saya dapat mengubah kode program menjadi seperti berikut ini:
Membaca File
Masih memakai FileHandleLib
, bila ingin membaca satu baris dari sebuah file, saya dapat menggunakan FileHandleReadLine()
atau FileHandleReturnLine()
. Sebagai contoh, kode program berikut ini akan mencoba membaca file \ProgramData\Microsoft\Diagnosis\osver.txt
yang berisi informasi versi Windows dan mencetaknya ke layar bila file tersebut ada:
FileHandleReturnLine()
akan membaca sebuah baris dari file dan mengembalikannya dalam bentuk string CHAR16*
. Walaupun kode program di atas berjalan dengan lancar di emulator, saya menemukan bahwa pada PC dengan driver NTFS bawaan, kode program di atas hanya mencetak satu krakter saja. Implementasi FileHandleReturnLine()
akan membaca file dengan FileHandleRead()
berulang kali (per karakter). Sementara itu, perilaku posisi file tidak sesuai dengan yang diharapkan saat file dibaca berulang kali, bahkan menyebabkan kesalahan bila FileHandleRead()
dipanggil kembali walaupun belum mencapai EOF. Untuk mengatasinya, saya dapat membaca file hanya sekali saja, misalnya dengan menggunakan kode program seperti berikut ini: