diff --git a/.vscode/launch.json b/.vscode/launch.json index ca6c8fc..b2a6d18 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,12 +31,45 @@ "text": "symbol-file kernel", "description": "Get kernel symbols" }, + { + "text": "add-symbol-file shell_elf", + "description": "Get shell symbols" + }, { "text": "set output-radix 16", "description": "Use hexadecimal output" } - ], + ], "avoidWindowsConsoleRedirection": true }, + { + "name": "Inserter", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/inserter", + "args": ["shell", "2", "storage.bin"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "environment": [], + "preLaunchTask": "Build Inserter", + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + }, + { + "text": "set output-radix 16", + "description": "Use hexadecimal output" + }, + ] + } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index dec631f..44f5321 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { "debug.onTaskErrors": "debugAnyway", + "files.associations": { + "mkdir.h": "c" + }, } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2e633d6..c7aadf9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -19,7 +19,7 @@ { "type": "shell", "label": "Launch QEMU", - "command": "echo Starting QEMU&qemu-system-i386 -s -S -cdrom os2023.iso", + "command": "echo Starting QEMU&qemu-system-i386 -s -S -drive file=storage.bin,format=raw,if=ide,index=0,media=disk -cdrom os2023.iso", "isBackground": true, "dependsOn": "Build OS", "options": { @@ -45,6 +45,21 @@ "type": "shell", "label": "Exit QEMU", "command": "pkill -f qemu || test $? -eq 1" + }, + { + "type": "cppbuild", + "label": "Build Inserter", + "command": "make", + "args": [ + "inserter", + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build", + "isDefault": true + } } ], } \ No newline at end of file diff --git a/README.md b/README.md index 56af011..0d9391e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,89 @@ -# kit-OS-2023 -Kit untuk IF2230 - Sistem Operasi 2023 +# Create an 32-bit x86 Architecture Operating System +> *Source Code* ini dibuat oleh kami, Kelompok ApaGaKeOS, untuk memenuhi Tugas Besar Sistem Operasi yaitu membuat +> Sistem operasi yang akan dibuat akan berjalan pada arsitektur x86 32-bit + +## Daftar Isi +- [Author](#author) +- [Deskripsi Singkat](#deskripsi-singkat) +- [Sistematika File](#sistematika-file) +- [Requirements](#requirements) +- [Cara Mengkompilasi dan Menjalankan Program](#cara-mengkompilasi-dan-menjalankan-program) + +## Author +| NIM | Nama | Github Profile | +| -------- | ---------------------------|-----------------------------------------------------------| +| 13521062 | Go Dillon Audris | [GoDillonAudris512](https://github.com/GoDillonAudris512) | +| 13521084 | Austin Gabriel Pardosi | [AustinPardosi](https://github.com/AustinPardosi) | +| 13521108 | Michael Leon Putra Widhi | [mikeleo03](https://github.com/mikeleo03) | +| 13521172 | Nathan Tenka | [Nat10k](https://github.com/Nat10k) | + +## Deskripsi Singkat +Tugas ini akan membuat sebuah program *mistis* yang umumnya tidak diketahui orang awam bernama sistem operasi. Sistem operasi yang akan dibuat akan berjalan pada arsitektur x86 32-bit yang nanti akan dijalankan dengan emulator QEMU. Tugas ini akan dibagi menjadi beberapa milestone. + +### Milestone 1 - Booting, Kernel, 32 bit Protected Mode +Waktu implementasi : Jumat, 10 Februari 2023 - Kamis, 2 Maret 2023 +1. Menyiapkan alat dan *repository* +2. Pembuatan *build script* +3. Menjalankan sistem operasi +4. Membuat *output* dengan *text* +5. Memasuki *Protected Mode* + +### Milestone 2 - Interrupt, Driver, dan Filesystem +Waktu implementasi : Jumat, 3 Maret 2023 - Kamis, 30 Maret 2023 +1. Interrupt dan IDT +2. Keyboard driver +3. Disk driver +4. *File System* FAT32 +5. [BONUS] Dukungan CMOS time untuk *file system* + +### Milestone 3 - Paging, User Mode, dan Shell +Waktu implementasi : Jumat, 31 Maret 2023 - Sabtu, 29 April 2023 +1. Paging +2. User Mode +3. Shell +4. [BONUS] Shell mendukung *Relative pathing* +5. [BONUS] Kreativitas, diantaranya + 1. *Splash screen* + 2. Pemrosesan perintah ganda (lebih dari 2 argumen) untuk mkdir, cat, dan whereis + 3. Pemrosesan perintah ls lebih dari 1 argumen + 4. Penanganan terhadap masukan dengan maupun tanpa ekstensi untuk *file* + 5. Menambah *command* baru, clear, untuk membersihkan isi buffer pada layar + +## Sistematika File +```bash +. +├─── bin +├─── other +├─── src +│ ├─── filesystem +│ ├─── framebuffer +│ ├─── gdt +│ ├─── inserter +│ ├─── interrupt +│ ├─── kernel +│ ├─── keyboard +│ ├─── paging +│ ├─── portio +│ ├─── std +│ ├─── user +│ ├─── linker.ld +│ └─── menu.lst +├─── makefile +└─── README.md +``` + +## Requirements +- GCC compiler (versi 11.2.0 atau yang lebih baru) +- Visual Studio Code +- Windows Subsystem for Linux (WSL2) dengan distribusi minimal Ubuntu 20.04 +- Emulator QEMU + +## Cara Mengkompilasi dan Menjalankan Program +1. Lakukan *clone repository* melalui terminal dengan *command* berikut + ``` bash + $ git clone https://github.com/Sister20/if2230-2023-apagakeos.git + ``` +2. Lakukan eksekusi pada makefile dengan memasukkan *command* `make all` pada terminal. Jika berhasil maka akan tercipta beberapa file pada folder `bin` +3. Jalan sistem oprerasi dengan membuka Visual Studio Code dan jalankan `Shift + F5`. Pastikan QEMU yang digunakan sudah aktif sebelumnya. Jika proses aktivasi tidak berhasil, maka gunakan [Panduan Debugger dan WSL](https://docs.google.com/document/d/1Zt3yzP_OEiFz8g2lHlpBNNr9qUyXghFNeQlAeQpAaII/edit#). + +Jika berhasil, maka sistem operasi akan muncul pada layar. \ No newline at end of file diff --git a/makefile b/makefile index b0c0033..e3dc23f 100644 --- a/makefile +++ b/makefile @@ -3,10 +3,13 @@ ASM = nasm LIN = ld CC = gcc +# Disk +DISK_NAME = storage + # Directory SOURCE_FOLDER = src OUTPUT_FOLDER = bin -ISO_NAME = os2023 +ISO_NAME = OS2023.iso # Flags WARNING_CFLAG = -Wall -Wextra -Werror @@ -15,28 +18,75 @@ STRIP_CFLAG = -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfi CFLAGS = $(DEBUG_CFLAG) $(WARNING_CFLAG) $(STRIP_CFLAG) -m32 -c -I$(SOURCE_FOLDER) AFLAGS = -f elf32 -g -F dwarf LFLAGS = -T $(SOURCE_FOLDER)/linker.ld -melf_i386 - +ISOFLAGS = -no-emul-boot -boot-load-size 4 -A os -input-charset utf8 -quiet -boot-info-table run: all - @qemu-system-i386 -s -S -cdrom $(OUTPUT_FOLDER)/$(ISO_NAME).iso + @qemu-system-i386 -s -S -drive file=bin/storage.bin,format=raw,if=ide,index=0,media=disk -cdrom bin/os2023.iso + all: build + build: iso + clean: rm -rf *.o *.iso $(OUTPUT_FOLDER)/kernel - - kernel: - @$(ASM) $(AFLAGS) $(SOURCE_FOLDER)/kernel_loader.s -o $(OUTPUT_FOLDER)/kernel_loader.o -# TODO: Compile C file with CFLAGS + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/portio/portio.c -o $(OUTPUT_FOLDER)/portio.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/std/stdmem.c -o $(OUTPUT_FOLDER)/stdmem.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/std/string.c -o $(OUTPUT_FOLDER)/string.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/gdt/gdt.c -o $(OUTPUT_FOLDER)/gdt.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/framebuffer/framebuffer.c -o $(OUTPUT_FOLDER)/framebuffer.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/interrupt/interrupt.c -o $(OUTPUT_FOLDER)/interrupt.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/interrupt/idt.c -o $(OUTPUT_FOLDER)/idt.o + @$(ASM) $(AFLAGS) $(SOURCE_FOLDER)/interrupt/intsetup.s -o $(OUTPUT_FOLDER)/intsetup.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/keyboard/keyboard.c -o $(OUTPUT_FOLDER)/keyboard.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/filesystem/disk.c -o $(OUTPUT_FOLDER)/disk.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/filesystem/cmos.c -o $(OUTPUT_FOLDER)/cmos.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/filesystem/fat-32-no-cmos.c -o $(OUTPUT_FOLDER)/fat-32-no-cmos.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/paging/paging.c -o $(OUTPUT_FOLDER)/paging.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/paging/paging.c -o $(OUTPUT_FOLDER)/paging.o + @$(ASM) $(AFLAGS) $(SOURCE_FOLDER)/kernel/kernel_loader.s -o $(OUTPUT_FOLDER)/kernel_loader.o + @$(CC) $(CFLAGS) $(SOURCE_FOLDER)/kernel/kernel.c -o $(OUTPUT_FOLDER)/kernel.o @$(LIN) $(LFLAGS) bin/*.o -o $(OUTPUT_FOLDER)/kernel @echo Linking object files and generate elf32... @rm -f *.o -iso: kernel +iso: kernel disk insert-shell @mkdir -p $(OUTPUT_FOLDER)/iso/boot/grub @cp $(OUTPUT_FOLDER)/kernel $(OUTPUT_FOLDER)/iso/boot/ @cp other/grub1 $(OUTPUT_FOLDER)/iso/boot/grub/ @cp $(SOURCE_FOLDER)/menu.lst $(OUTPUT_FOLDER)/iso/boot/grub/ -# TODO: Create ISO image - @rm -r $(OUTPUT_FOLDER)/iso/ + @cd bin && genisoimage -R -b boot/grub/grub1 $(ISOFLAGS) -o $(ISO_NAME) iso + +disk: + @qemu-img create -f raw $(OUTPUT_FOLDER)/$(DISK_NAME).bin 4M + +inserter: + @$(CC) -Wno-builtin-declaration-mismatch -g \ + $(SOURCE_FOLDER)/std/stdmem.c $(SOURCE_FOLDER)/filesystem/fat-32-no-cmos.c \ + $(SOURCE_FOLDER)/inserter/external-inserter.c \ + -o $(OUTPUT_FOLDER)/inserter + +user-shell: + @$(ASM) $(AFLAGS) $(SOURCE_FOLDER)/user/user-entry.s -o user-entry.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/user-shell.c -o user-shell.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/cd.c -o cd.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/ls.c -o ls.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/mkdir.c -o mkdir.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/cat.c -o cat.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/cp.c -o cp.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/rm.c -o rm.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/mv.c -o mv.o + @$(CC) $(CFLAGS) -fno-pie $(SOURCE_FOLDER)/user/whereis.c -o whereis.o + @$(LIN) -T $(SOURCE_FOLDER)/user/user-linker.ld -melf_i386 \ + user-entry.o user-shell.o $(OUTPUT_FOLDER)/string.o $(OUTPUT_FOLDER)/stdmem.o cd.o ls.o mkdir.o cat.o cp.o rm.o mv.o whereis.o -o $(OUTPUT_FOLDER)/shell + @echo Linking object shell object files and generate flat binary... + @$(LIN) -T $(SOURCE_FOLDER)/user/user-linker.ld -melf_i386 --oformat=elf32-i386\ + user-entry.o user-shell.o $(OUTPUT_FOLDER)/string.o $(OUTPUT_FOLDER)/stdmem.o cd.o ls.o mkdir.o cat.o cp.o rm.o mv.o whereis.o -o $(OUTPUT_FOLDER)/shell_elf + @echo Linking object shell object files and generate ELF32 for debugging... + @size --target=binary bin/shell + @rm -f *.o + +insert-shell: inserter user-shell + @echo Inserting shell into root directory... + @cd $(OUTPUT_FOLDER); ./inserter shell 2 $(DISK_NAME).bin \ No newline at end of file diff --git a/src/filesystem/cmos.c b/src/filesystem/cmos.c new file mode 100644 index 0000000..f0cc5e6 --- /dev/null +++ b/src/filesystem/cmos.c @@ -0,0 +1,92 @@ +#include "cmos.h" + +int century_register = 0x00; + +int get_update_in_progress_flag() { + out(CMOS_ADDRESS, 0x0A); + return (in(CMOS_DATA) & 0x80); +} + +unsigned char get_RTC_register(int reg) { + out(CMOS_ADDRESS, reg); + return in(CMOS_DATA); +} + +void read_rtc() { + // local variable to ensure the validity and persistence of date and time gotten + unsigned char century; + unsigned char last_second; + unsigned char last_minute; + unsigned char last_hour; + unsigned char last_day; + unsigned char last_month; + unsigned char last_year; + unsigned char last_century; + unsigned char registerB; + + while (get_update_in_progress_flag()); + second = get_RTC_register(0x00); + minute = get_RTC_register(0x02); + hour = get_RTC_register(0x04); + day = get_RTC_register(0x07); + month = get_RTC_register(0x08); + year = get_RTC_register(0x09); + + if(century_register != 0) { + century = get_RTC_register(century_register); + } + + do { + last_second = second; + last_minute = minute; + last_hour = hour; + last_day = day; + last_month = month; + last_year = year; + last_century = century; + + while (get_update_in_progress_flag()); + second = get_RTC_register(0x00); + minute = get_RTC_register(0x02); + hour = get_RTC_register(0x04); + day = get_RTC_register(0x07); + month = get_RTC_register(0x08); + year = get_RTC_register(0x09); + + if(century_register != 0) { + century = get_RTC_register(century_register); + } + + } while( (last_second != second) || (last_minute != minute) || (last_hour != hour) || + (last_day != day) || (last_month != month) || (last_year != year) || + (last_century != century) ); + + registerB = get_RTC_register(0x0B); + + // Convert BCD to binary values + + if (!(registerB & 0x04)) { + second = (second & 0x0F) + ((second / 16) * 10); + minute = (minute & 0x0F) + ((minute / 16) * 10); + hour = ( (hour & 0x0F) + (((hour & 0x70) / 16) * 10) ) | (hour & 0x80); + day = (day & 0x0F) + ((day / 16) * 10); + month = (month & 0x0F) + ((month / 16) * 10); + year = (year & 0x0F) + ((year / 16) * 10); + if(century_register != 0) { + century = (century & 0x0F) + ((century / 16) * 10); + } + } + + // Convert 12 hour clock to 24 hour clock and add 7 to synchronize with WIB + if (!(registerB & 0x02) && (hour & 0x80)) { + hour = ((hour & 0x7F) + 12) % 24; + } + + // Calculate the full (4-digit) year + if(century_register != 0) { + year += century * 100; + } else { + year += (CURRENT_YEAR / 100) * 100; + if(year < CURRENT_YEAR) year += 100; + } +} \ No newline at end of file diff --git a/src/filesystem/cmos.h b/src/filesystem/cmos.h new file mode 100644 index 0000000..be51af2 --- /dev/null +++ b/src/filesystem/cmos.h @@ -0,0 +1,30 @@ +#ifndef _CMOS_H +#define _CMOS_H + +#include "portio/portio.h" + +// macro to define current year +#define CURRENT_YEAR 2023 + +// macro of CMOS IO port +#define CMOS_ADDRESS 0x70 +#define CMOS_DATA 0x71 + +// global variables needed to save the date and time readed from register +unsigned char second; +unsigned char minute; +unsigned char hour; +unsigned char day; +unsigned char month; +unsigned int year; + +// return true if update in register is happening +int get_update_in_progress_flag(); + +// get the date time value from CMOS register +unsigned char get_RTC_register(int reg); + +// read the current time and update the global variables (second, minute, etc) +void read_rtc(); + +#endif \ No newline at end of file diff --git a/src/filesystem/disk.c b/src/filesystem/disk.c new file mode 100644 index 0000000..58539a0 --- /dev/null +++ b/src/filesystem/disk.c @@ -0,0 +1,51 @@ +#include "disk.h" +#include "portio/portio.h" + +static void ATA_busy_wait() { + while (in(0x1F7) & ATA_STATUS_BSY); +} + +static void ATA_DRQ_wait() { + while (!(in(0x1F7) & ATA_STATUS_RDY)); +} + +void read_blocks(void *ptr, uint32_t logical_block_address, uint8_t block_count) { + ATA_busy_wait(); + out(0x1F6, 0xE0 | ((logical_block_address >> 24) & 0xF)); + out(0x1F2, block_count); + out(0x1F3, (uint8_t) logical_block_address); + out(0x1F4, (uint8_t) (logical_block_address >> 8)); + out(0x1F5, (uint8_t) (logical_block_address >> 16)); + out(0x1F7, 0x20); + + uint16_t *target = (uint16_t*) ptr; + + for (uint32_t i = 0; i < block_count; i++) { + ATA_busy_wait(); + ATA_DRQ_wait(); + for (uint32_t j = 0; j < HALF_BLOCK_SIZE; j++) + target[j] = in16(0x1F0); + // Note : uint16_t => 2 bytes, HALF_BLOCK_SIZE*2 = BLOCK_SIZE with pointer arithmetic + target += HALF_BLOCK_SIZE; + } +} + +void write_blocks(const void *ptr, uint32_t logical_block_address, uint8_t block_count) { + ATA_busy_wait(); + out(0x1F6, 0xE0 | ((logical_block_address >> 24) & 0xF)); + out(0x1F2, block_count); + out(0x1F3, (uint8_t) logical_block_address); + out(0x1F4, (uint8_t) (logical_block_address >> 8)); + out(0x1F5, (uint8_t) (logical_block_address >> 16)); + out(0x1F7, 0x30); + + for (uint32_t i = 0; i < block_count; i++) { + ATA_busy_wait(); + ATA_DRQ_wait(); + /* Note : uint16_t => 2 bytes, i is current block number to write + HALF_BLOCK_SIZE*i = block_offset with pointer arithmetic + */ + for (uint32_t j = 0; j < HALF_BLOCK_SIZE; j++) + out16(0x1F0, ((uint16_t*) ptr)[HALF_BLOCK_SIZE*i + j]); + } +} diff --git a/src/filesystem/disk.h b/src/filesystem/disk.h new file mode 100644 index 0000000..fbe86c0 --- /dev/null +++ b/src/filesystem/disk.h @@ -0,0 +1,44 @@ +#ifndef _DISK_H +#define _DISK_H + +#include "../std/stdtype.h" + +/* -- ATA PIO status codes -- */ +#define ATA_STATUS_BSY 0x80 +#define ATA_STATUS_RDY 0x40 +#define ATA_STATUS_DRQ 0x08 +#define ATA_STATUS_DF 0x20 +#define ATA_STATUS_ERR 0x01 + +#define BLOCK_SIZE 512 +#define HALF_BLOCK_SIZE (BLOCK_SIZE/2) + +// Block buffer data type - @param buf Byte buffer with size of BLOCK_SIZE +struct BlockBuffer { + uint8_t buf[BLOCK_SIZE]; +} __attribute__((packed)); + +/** + * ATA PIO logical block address read blocks. Will blocking until read is completed. + * Note: ATA PIO will use 2-bytes per read/write operation. + * Recommended to use struct BlockBuffer + * + * @param ptr Pointer for storing reading data, this pointer should point to already allocated memory location. + * With allocated size positive integer multiple of BLOCK_SIZE, ex: buf[1024] + * @param logical_block_address Block address to read data from. Use LBA addressing + * @param block_count How many block to read, starting from block logical_block_address to lba-1 + */ +void read_blocks(void *ptr, uint32_t logical_block_address, uint8_t block_count); + +/** + * ATA PIO logical block address write blocks. Will blocking until write is completed. + * Note: ATA PIO will use 2-bytes per read/write operation. + * Recommended to use struct BlockBuffer + * + * @param ptr Pointer to data that to be written into disk. Memory pointed should be positive integer multiple of BLOCK_SIZE + * @param logical_block_address Block address to write data into. Use LBA addressing + * @param block_count How many block to write, starting from block logical_block_address to lba-1 + */ +void write_blocks(const void *ptr, uint32_t logical_block_address, uint8_t block_count); + +#endif \ No newline at end of file diff --git a/src/filesystem/fat-32-no-cmos.c b/src/filesystem/fat-32-no-cmos.c new file mode 100644 index 0000000..2351269 --- /dev/null +++ b/src/filesystem/fat-32-no-cmos.c @@ -0,0 +1,450 @@ +#include "../std/stdmem.h" +#include "fat32.h" +/*-----------------------------------------------------------------------------------*/ +/*-------------------------------------CONSTANT--------------------------------------*/ + +/* signature of file system */ +const uint8_t fs_signature[BLOCK_SIZE] = { + 'C', 'o', 'u', 'r', 's', 'e', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + 'D', 'e', 's', 'i', 'g', 'n', 'e', 'd', ' ', 'b', 'y', ' ', ' ', ' ', ' ', ' ', + 'L', 'a', 'b', ' ', 'S', 'i', 's', 't', 'e', 'r', ' ', 'I', 'T', 'B', ' ', ' ', + 'M', 'a', 'd', 'e', ' ', 'w', 'i', 't', 'h', ' ', '<', '3', ' ', ' ', ' ', ' ', + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '2', '0', '2', '3', '\n', + [BLOCK_SIZE-2] = 'O', + [BLOCK_SIZE-1] = 'k', +}; + +/* struct to save the file system driver state */ +struct FAT32DriverState driverState; + +/*-----------------------------------------------------------------------------------*/ +/*-------------------------------------HELPER----------------------------------------*/ + +uint32_t cluster_to_lba(uint32_t cluster) { + return cluster * CLUSTER_BLOCK_COUNT; +} + +void write_clusters(const void *ptr, uint32_t cluster_number, uint8_t cluster_count) { + write_blocks(ptr, cluster_to_lba(cluster_number), CLUSTER_BLOCK_COUNT*cluster_count); +} + +void read_clusters(void *ptr, uint32_t cluster_number, uint8_t cluster_count) { + read_blocks(ptr, cluster_to_lba(cluster_number), CLUSTER_BLOCK_COUNT*cluster_count); +} + +void init_directory_table(struct FAT32DirectoryTable *dir_table, char *name, uint32_t parent_dir_cluster) { + // create new entry of directory table, cluster number refer to the parent of this directory + struct FAT32DirectoryEntry dirEntry = { + .name = {name[0], name[1], name[2], name[3], name[4], name[5], name[6], name[7]}, + .attribute = ATTR_SUBDIRECTORY, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = parent_dir_cluster >> 16, + .cluster_low = parent_dir_cluster, + .filesize = 0, + }; + + // put the entry as the first entry + dir_table->table[0] = dirEntry; +} + +/*-----------------------------------------------------------------------------------*/ +/*-----------------------------------INITIALIZER-------------------------------------*/ + +bool is_empty_storage(void) { + // initiate buffer to contain boot sector content + uint8_t temp[BLOCK_SIZE]; + + // read the content of boot sector and put it to temp + read_blocks(temp,BOOT_SECTOR,1); + + // compare the buffer with filesystem signature, return true if equal + return memcmp(fs_signature, temp, BLOCK_SIZE) != 0; +} + +void create_fat32(void) { + // write the file system signature to the boot sector (cluster 0) + write_blocks(fs_signature, BOOT_SECTOR, 1); + + // initialize and write FAT to cluster 1 + driverState.fat_table.cluster_map[0] = CLUSTER_0_VALUE; + driverState.fat_table.cluster_map[1] = CLUSTER_1_VALUE; + driverState.fat_table.cluster_map[2] = FAT32_FAT_END_OF_FILE; + write_clusters(driverState.fat_table.cluster_map, 1, 1); + + // initialize root directory and write it to cluster 2 + struct FAT32DirectoryTable rootDir = { + .table = { + { + .name = {'r','o','o','t'}, + .attribute = ATTR_SUBDIRECTORY, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = 0x00, + .cluster_low = 0x02, + .filesize = 0 + } + } + }; + write_clusters(rootDir.table, 2, 1); +} + +void initialize_filesystem_fat32(void) { + // if storage empty then create new file system + // else load the FAT to driverState + if (is_empty_storage()) { + create_fat32(); + } + else { + read_clusters(&driverState.fat_table, 1, 1); + } +} + +/*-----------------------------------------------------------------------------------*/ +/*---------------------------------CRUD OPERATION------------------------------------*/ + +/** + * FAT32 Folder / Directory read + * + * @param request buf point to struct FAT32DirectoryTable, + * name is directory name, + * ext is unused, + * parent_cluster_number is target directory table to read, + * buffer_size must be exactly sizeof(struct FAT32DirectoryTable) + * @return Error code: 0 success - 1 not a folder - 2 not found - -1 unknown + */ +int8_t read_directory(struct FAT32DriverRequest request) { + // Read directory table from parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + if (driverState.dir_table_buf.table->user_attribute != UATTR_NOT_EMPTY) { // direktori kosong + return -1; + } + + // Search for directory with the same name + for (int i = 0; i < (int)(sizeof(driverState.dir_table_buf)/sizeof(struct FAT32DirectoryEntry)); i++) { + if (memcmp(driverState.dir_table_buf.table[i].name, request.name, 8) == 0) { // Check name of directory with request + if (driverState.dir_table_buf.table[i].attribute != ATTR_SUBDIRECTORY) { // Not a directory + return 1; + } else { + // Read directory + read_clusters(request.buf, ((driverState.dir_table_buf.table[i].cluster_high << 16) + driverState.dir_table_buf.table[i].cluster_low),1); + return 0; + } + } + } + // Folder not found + return 2; +} + + +/** + * FAT32 read, read a file from file system. + * + * @param request All attribute will be used for read, buffer_size will limit reading count + * @return Error code: 0 success - 1 not a file - 2 not enough buffer - 3 not found - -1 unknown + */ +int8_t read(struct FAT32DriverRequest request) { + // Read directory table from parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + if(driverState.dir_table_buf.table->user_attribute != UATTR_NOT_EMPTY) { // direktori kosong + return -1; + } + // Read FAT table + read_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + // Search for directory with the same name + for (int i=0; i<(int)(sizeof(driverState.dir_table_buf)/sizeof(struct FAT32DirectoryEntry)); i++) { + if (memcmp(driverState.dir_table_buf.table[i].name, request.name, 8) == 0 && + memcmp(driverState.dir_table_buf.table[i].ext, request.ext, 3) == 0) { // Check name of directory with request + if (driverState.dir_table_buf.table[i].attribute == ATTR_SUBDIRECTORY) { // Not a file + return 1; + } + else if (request.buffer_size < driverState.dir_table_buf.table[i].filesize) { // Buffer size not enough + return 2; + } + else { + int counter=0; + int cluster_num = (driverState.dir_table_buf.table[i].cluster_high << 16) + driverState.dir_table_buf.table[i].cluster_low; + + // Read until end of file + while(cluster_num != FAT32_FAT_END_OF_FILE) { + read_clusters(request.buf + CLUSTER_SIZE*counter, cluster_num, 1); + counter++; + cluster_num = driverState.fat_table.cluster_map[cluster_num]; + } + return 0; + } + } + } + // File not found + return 3; +} + + +/** + * FAT32 write, write a file or folder to file system. + * + * @param request All attribute will be used for write, buffer_size == 0 then create a folder / directory + * @return Error code: 0 success - 1 file/folder already exist - 2 invalid parent cluster - -1 unknown + */ +int8_t write(struct FAT32DriverRequest request) { + // read entries of directory from the parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // if parent cluster is not a directory, return with error code 2 + if (!(driverState.dir_table_buf.table[0].user_attribute == UATTR_NOT_EMPTY && + driverState.dir_table_buf.table[0].attribute == ATTR_SUBDIRECTORY)) { return 2;} + + // initialize value needed for subsequent checking + int entryRow = 0; + int entryChecked = 2; + bool valid = TRUE; + bool full = TRUE; + + // check the total entry in the directory and if there is entry with same name + while (entryChecked <= 64 && valid) { + if (driverState.dir_table_buf.table[entryChecked-1].user_attribute != UATTR_NOT_EMPTY && full) { + full = FALSE; + entryRow = entryChecked-1; + } + if (memcmp(driverState.dir_table_buf.table[entryChecked-1].name, request.name, 8) == 0 && + driverState.dir_table_buf.table[entryChecked-1].user_attribute == UATTR_NOT_EMPTY) { + valid = FALSE; + } + else { + entryChecked++; + } + } + + // if there is entry with same name and extension, return with error code 1 + if (!valid) { return 1;} + + // if the directory is full, return with error code -1 + if (full) { return -1; } + + // if the request buffer size is 0, then create a subdirectory, else write the file + if (request.buffer_size == 0) { + // find empty cluster in fat table + uint32_t clusterNumber = 0x0; + while (driverState.fat_table.cluster_map[clusterNumber] != FAT32_FAT_EMPTY_ENTRY && clusterNumber < 0x800) { + clusterNumber++; + } + + // if there is no cluster that can be allocated, return with error code -1 + if (clusterNumber == 0x800) { return -1; } + + // update the FAT table and write it to FAT cluster + driverState.fat_table.cluster_map[clusterNumber] = FAT32_FAT_END_OF_FILE; + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + + // update parent directory table and write it to parent cluster + struct FAT32DirectoryEntry dirEntry = { + .name = {request.name[0], request.name[1], request.name[2], request.name[3], request.name[4], request.name[5], request.name[6], request.name[7]}, + .ext = {request.ext[0], request.ext[1], request.ext[2]}, + .attribute = ATTR_SUBDIRECTORY, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = clusterNumber >> 16, + .cluster_low = clusterNumber, + .filesize = request.buffer_size + }; + driverState.dir_table_buf.table[entryRow] = dirEntry; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // create directory table for new directory and write it to directory cluster + read_clusters(driverState.dir_table_buf.table, clusterNumber, 1); + dirEntry.cluster_high = request.parent_cluster_number >> 16; + dirEntry.cluster_low = request.parent_cluster_number; + driverState.dir_table_buf.table[0] = dirEntry; + write_clusters(driverState.dir_table_buf.table, clusterNumber, 1); + } + else { + // determine number of cluster needed + int modulo = request.buffer_size % CLUSTER_SIZE; + int clusterNeeded = request.buffer_size / CLUSTER_SIZE; + if (modulo != 0) { clusterNeeded++; } + + // get the first cluster number that is empty + int clusterAvailable = 0; + bool firstClusterFound = FALSE; + uint32_t startClusterNumber = 0x0; + while (startClusterNumber != 0x800 && !firstClusterFound) { + if (driverState.fat_table.cluster_map[startClusterNumber] == FAT32_FAT_EMPTY_ENTRY) { + clusterAvailable++; + firstClusterFound = TRUE; + } + else { + startClusterNumber++; + } + } + + // if there is no cluster to put the file, return with error code -1 + if (!firstClusterFound) { return -1;} + + // get the rest of cluster number + uint32_t prevClusterNumber = startClusterNumber; + uint32_t currClusterNumber = startClusterNumber + 1; + while (currClusterNumber < 0x800 && clusterAvailable < clusterNeeded) { + if (driverState.fat_table.cluster_map[currClusterNumber] == FAT32_FAT_EMPTY_ENTRY) { + driverState.fat_table.cluster_map[prevClusterNumber] = currClusterNumber; + prevClusterNumber = currClusterNumber; + clusterAvailable++; + } + currClusterNumber++; + } + + // if there is not enough cluster to contain the file, reset the fat table and return with error code -1 + if (clusterAvailable != clusterNeeded) { + uint32_t tempClusterNumber; + while (driverState.fat_table.cluster_map[startClusterNumber] != FAT32_FAT_EMPTY_ENTRY) { + tempClusterNumber = driverState.fat_table.cluster_map[startClusterNumber]; + driverState.fat_table.cluster_map[startClusterNumber] = FAT32_FAT_EMPTY_ENTRY; + startClusterNumber = tempClusterNumber; + } + return -1; + } + else { + driverState.fat_table.cluster_map[prevClusterNumber] = FAT32_FAT_END_OF_FILE; + } + + // write the FAT table to FAT cluster + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + + // update parent directory table and write it to parent cluster + struct FAT32DirectoryEntry dirEntry = { + .name = {request.name[0], request.name[1], request.name[2], request.name[3], request.name[4], request.name[5], request.name[6], request.name[7]}, + .ext = {request.ext[0], request.ext[1], request.ext[2]}, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = startClusterNumber >> 16, + .cluster_low = startClusterNumber, + .filesize = request.buffer_size + }; + driverState.dir_table_buf.table[entryRow] = dirEntry; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // write the file to all of the cluster selected + int i = 0; + while (driverState.fat_table.cluster_map[startClusterNumber] != FAT32_FAT_END_OF_FILE) { + write_clusters((uint8_t*) request.buf + CLUSTER_SIZE*i, startClusterNumber, 1); + startClusterNumber = driverState.fat_table.cluster_map[startClusterNumber]; + i++; + } + write_clusters((uint8_t*) request.buf + CLUSTER_SIZE*i, startClusterNumber, 1); + } + + return 0; +} + + +/** + * FAT32 delete, delete a file or empty directory (only 1 DirectoryEntry) in file system. + * + * @param request buf and buffer_size is unused + * @return Error code: 0 success - 1 not found - 2 folder is not empty - -1 unknown + */ +int8_t delete(struct FAT32DriverRequest request) { + // read entries of directory from the parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // if parent is not a directory, return with error code -1 + if (!(driverState.dir_table_buf.table[0].user_attribute == UATTR_NOT_EMPTY && + driverState.dir_table_buf.table[0].attribute == ATTR_SUBDIRECTORY)) { return -1;} + + // check if name and extension to delete is valid + int entryRow = 0; + int entryChecked = 2; + bool found = FALSE; + bool isDirectory = FALSE; + uint32_t clusterNumber; + + while (entryChecked <= 64 && !found) { + if (memcmp(driverState.dir_table_buf.table[entryChecked-1].name, request.name, 8) == 0 && + memcmp(driverState.dir_table_buf.table[entryChecked-1].ext, request.ext, 3) == 0 && + driverState.dir_table_buf.table[entryChecked-1].user_attribute == UATTR_NOT_EMPTY) { + found = TRUE; + entryRow = entryChecked-1; + clusterNumber = ((uint32_t) driverState.dir_table_buf.table[entryChecked-1].cluster_high) << 16; + clusterNumber |= (uint32_t) driverState.dir_table_buf.table[entryChecked-1].cluster_low; + if (driverState.dir_table_buf.table[entryChecked-1].attribute == ATTR_SUBDIRECTORY) { + isDirectory = TRUE; + } + } + else { + entryChecked++; + } + } + + // if file / folder to delete not found, return with error code 1 + if (!found) { return 1; } + + // if the entry is a file, delete it. if the entry is a directory, check if it was empty + if (!isDirectory) { + // delete entry by removing its signature, and write it to parent cluster + for (int i = 0; i < 8; i++) { + driverState.dir_table_buf.table[entryRow].name[i] = 0; + if (i < 3) { + driverState.dir_table_buf.table[entryRow].ext[i] = 0; + } + } + driverState.dir_table_buf.table[entryRow].user_attribute = FAT32_FAT_EMPTY_ENTRY; + driverState.dir_table_buf.table[entryRow].attribute = FAT32_FAT_EMPTY_ENTRY; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // delete file and all its cluster from the FAT table, and write it to FAT table cluster + uint32_t currClusterNumber = clusterNumber; + uint32_t prevClusterNumber = 0; + while (driverState.fat_table.cluster_map[currClusterNumber] != FAT32_FAT_END_OF_FILE) { + prevClusterNumber = currClusterNumber; + currClusterNumber = driverState.fat_table.cluster_map[currClusterNumber]; + driverState.fat_table.cluster_map[prevClusterNumber] = FAT32_FAT_EMPTY_ENTRY; + } + driverState.fat_table.cluster_map[currClusterNumber] = 0; + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + } + else { + // load the directory table of directory to delete + struct FAT32DirectoryTable tempDir; + read_clusters(tempDir.table, clusterNumber, 1); + + // check if the directory is empty + bool empty = TRUE; + entryChecked = 2; + + while (entryChecked <= 64 && empty) { + if (tempDir.table[entryChecked-1].user_attribute == UATTR_NOT_EMPTY) { + empty = FALSE; + } + else { + entryChecked++; + } + } + + // if directory not empty, return with error code 2 + if (!empty) { return 2;} + + // delete entry by removing its signature, and write it to parent cluster + for (int i = 0; i < 8; i++) { + driverState.dir_table_buf.table[entryRow].name[i] = 0; + if (i < 3) { + driverState.dir_table_buf.table[entryRow].ext[i] = 0; + } + } + driverState.dir_table_buf.table[entryRow].user_attribute = FAT32_FAT_EMPTY_ENTRY; + driverState.dir_table_buf.table[entryRow].attribute = FAT32_FAT_EMPTY_ENTRY; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // delete the directory itself, write it to directory cluster + for (int i = 0; i < 8; i++) { + tempDir.table[0].name[i] = 0; + if (i < 3) { + tempDir.table[0].ext[i] = 0; + } + } + tempDir.table[0].user_attribute = FAT32_FAT_EMPTY_ENTRY; + tempDir.table[0].attribute = FAT32_FAT_EMPTY_ENTRY; + write_clusters(tempDir.table, clusterNumber, 1); + + // delete directory in FAT table, and write it to FAT cluster + driverState.fat_table.cluster_map[clusterNumber] = FAT32_FAT_EMPTY_ENTRY; + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + } + + return 0; +} \ No newline at end of file diff --git a/src/filesystem/fat32.c b/src/filesystem/fat32.c new file mode 100644 index 0000000..2b39e82 --- /dev/null +++ b/src/filesystem/fat32.c @@ -0,0 +1,474 @@ +#include "std/stdmem.h" +#include "cmos.h" +#include "fat32.h" +/*-----------------------------------------------------------------------------------*/ +/*-------------------------------------CONSTANT--------------------------------------*/ + +/* signature of file system */ +const uint8_t fs_signature[BLOCK_SIZE] = { + 'C', 'o', 'u', 'r', 's', 'e', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + 'D', 'e', 's', 'i', 'g', 'n', 'e', 'd', ' ', 'b', 'y', ' ', ' ', ' ', ' ', ' ', + 'L', 'a', 'b', ' ', 'S', 'i', 's', 't', 'e', 'r', ' ', 'I', 'T', 'B', ' ', ' ', + 'M', 'a', 'd', 'e', ' ', 'w', 'i', 't', 'h', ' ', '<', '3', ' ', ' ', ' ', ' ', + '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '2', '0', '2', '3', '\n', + [BLOCK_SIZE-2] = 'O', + [BLOCK_SIZE-1] = 'k', +}; + +/* struct to save the file system driver state */ +struct FAT32DriverState driverState; + +/*-----------------------------------------------------------------------------------*/ +/*-------------------------------------HELPER----------------------------------------*/ + +uint32_t cluster_to_lba(uint32_t cluster) { + return cluster * CLUSTER_BLOCK_COUNT; +} + +void write_clusters(const void *ptr, uint32_t cluster_number, uint8_t cluster_count) { + write_blocks(ptr, cluster_to_lba(cluster_number), CLUSTER_BLOCK_COUNT*cluster_count); +} + +void read_clusters(void *ptr, uint32_t cluster_number, uint8_t cluster_count) { + read_blocks(ptr, cluster_to_lba(cluster_number), CLUSTER_BLOCK_COUNT*cluster_count); +} + +void init_directory_table(struct FAT32DirectoryTable *dir_table, char *name, uint32_t parent_dir_cluster) { + // create new entry of directory table, cluster number refer to the parent of this directory + struct FAT32DirectoryEntry dirEntry = { + .name = {name[0], name[1], name[2], name[3], name[4], name[5], name[6], name[7]}, + .attribute = ATTR_SUBDIRECTORY, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = parent_dir_cluster >> 16, + .cluster_low = parent_dir_cluster, + .filesize = 0, + }; + + // put the entry as the first entry + dir_table->table[0] = dirEntry; +} + +/*-----------------------------------------------------------------------------------*/ +/*-----------------------------------INITIALIZER-------------------------------------*/ + +bool is_empty_storage(void) { + // initiate buffer to contain boot sector content + uint8_t temp[BLOCK_SIZE]; + + // read the content of boot sector and put it to temp + read_blocks(temp,BOOT_SECTOR,1); + + // compare the buffer with filesystem signature, return true if equal + return memcmp(fs_signature, temp, BLOCK_SIZE) != 0; +} + +void create_fat32(void) { + // write the file system signature to the boot sector (cluster 0) + write_blocks(fs_signature, BOOT_SECTOR, 1); + + // initialize and write FAT to cluster 1 + driverState.fat_table.cluster_map[0] = CLUSTER_0_VALUE; + driverState.fat_table.cluster_map[1] = CLUSTER_1_VALUE; + driverState.fat_table.cluster_map[2] = FAT32_FAT_END_OF_FILE; + write_clusters(driverState.fat_table.cluster_map, 1, 1); + + // initialize root directory and write it to cluster 2 + struct FAT32DirectoryTable rootDir = { + .table = { + { + .name = {'r','o','o','t'}, + .attribute = ATTR_SUBDIRECTORY, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = 0x00, + .cluster_low = 0x02, + .filesize = 0 + } + } + }; + write_clusters(rootDir.table, 2, 1); +} + +void initialize_filesystem_fat32(void) { + // if storage empty then create new file system + // else load the FAT to driverState + if (is_empty_storage()) { + create_fat32(); + } + else { + read_clusters(&driverState.fat_table, 1, 1); + } +} + +/*-----------------------------------------------------------------------------------*/ +/*---------------------------------CRUD OPERATION------------------------------------*/ + +/** + * FAT32 Folder / Directory read + * + * @param request buf point to struct FAT32DirectoryTable, + * name is directory name, + * ext is unused, + * parent_cluster_number is target directory table to read, + * buffer_size must be exactly sizeof(struct FAT32DirectoryTable) + * @return Error code: 0 success - 1 not a folder - 2 not found - -1 unknown + */ +int8_t read_directory(struct FAT32DriverRequest request) { + // read the time from CMOS + read_rtc(); + + // Read directory table from parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + if (driverState.dir_table_buf.table->user_attribute != UATTR_NOT_EMPTY) { // direktori kosong + return -1; + } + + // Search for directory with the same name + for (int i = 0; i < (int)(sizeof(driverState.dir_table_buf)/sizeof(struct FAT32DirectoryEntry)); i++) { + if (memcmp(driverState.dir_table_buf.table[i].name, request.name, 8) == 0) { // Check name of directory with request + if (driverState.dir_table_buf.table[i].attribute != ATTR_SUBDIRECTORY) { // Not a directory + return 1; + } else { + // Change access date + driverState.dir_table_buf.table[i].access_date = (((uint16_t) day) << 8) | ((uint16_t) month); + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + // Read directory + read_clusters(request.buf, ((driverState.dir_table_buf.table[i].cluster_high << 16) + driverState.dir_table_buf.table[i].cluster_low),1); + return 0; + } + } + } + // Folder not found + return 2; +} + + +/** + * FAT32 read, read a file from file system. + * + * @param request All attribute will be used for read, buffer_size will limit reading count + * @return Error code: 0 success - 1 not a file - 2 not enough buffer - 3 not found - -1 unknown + */ +int8_t read(struct FAT32DriverRequest request) { + // read the time from CMOS + read_rtc(); + + // Read directory table from parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + if(driverState.dir_table_buf.table->user_attribute != UATTR_NOT_EMPTY) { // direktori kosong + return -1; + } + // Read FAT table + read_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + // Search for directory with the same name + for (int i=0; i<(int)(sizeof(driverState.dir_table_buf)/sizeof(struct FAT32DirectoryEntry)); i++) { + if (memcmp(driverState.dir_table_buf.table[i].name, request.name, 8) == 0 && + memcmp(driverState.dir_table_buf.table[i].ext, request.ext, 3) == 0) { // Check name of directory with request + if (driverState.dir_table_buf.table[i].attribute == ATTR_SUBDIRECTORY) { // Not a file + return 1; + } + else if (request.buffer_size < driverState.dir_table_buf.table[i].filesize) { // Buffer size not enough + return 2; + } + else { + int counter=0; + int cluster_num = (driverState.dir_table_buf.table[i].cluster_high << 16) + driverState.dir_table_buf.table[i].cluster_low; + + // Change access date + driverState.dir_table_buf.table[i].access_date = (((uint16_t) day) << 8) | ((uint16_t) month); + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // Read until end of file + while(cluster_num != FAT32_FAT_END_OF_FILE) { + read_clusters(request.buf + CLUSTER_SIZE*counter, cluster_num, 1); + counter++; + cluster_num = driverState.fat_table.cluster_map[cluster_num]; + } + return 0; + } + } + } + // File not found + return 3; +} + + +/** + * FAT32 write, write a file or folder to file system. + * + * @param request All attribute will be used for write, buffer_size == 0 then create a folder / directory + * @return Error code: 0 success - 1 file/folder already exist - 2 invalid parent cluster - -1 unknown + */ +int8_t write(struct FAT32DriverRequest request) { + // read the time from CMOS + read_rtc(); + + // read entries of directory from the parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // if parent cluster is not a directory, return with error code 2 + if (!(driverState.dir_table_buf.table[0].user_attribute == UATTR_NOT_EMPTY && + driverState.dir_table_buf.table[0].attribute == ATTR_SUBDIRECTORY)) { return 2;} + + // initialize value needed for subsequent checking + int entryRow = 0; + int entryChecked = 2; + bool valid = TRUE; + bool full = TRUE; + + // check the total entry in the directory and if there is entry with same name + while (entryChecked <= 64 && valid) { + if (driverState.dir_table_buf.table[entryChecked-1].user_attribute != UATTR_NOT_EMPTY && full) { + full = FALSE; + entryRow = entryChecked-1; + } + if (memcmp(driverState.dir_table_buf.table[entryChecked-1].name, request.name, 8) == 0 && + driverState.dir_table_buf.table[entryChecked-1].user_attribute == UATTR_NOT_EMPTY) { + valid = FALSE; + } + else { + entryChecked++; + } + } + + // if there is entry with same name and extension, return with error code 1 + if (!valid) { return 1;} + + // if the directory is full, return with error code -1 + if (full) { return -1; } + + // if the request buffer size is 0, then create a subdirectory, else write the file + if (request.buffer_size == 0) { + // find empty cluster in fat table + uint32_t clusterNumber = 0x0; + while (driverState.fat_table.cluster_map[clusterNumber] != FAT32_FAT_EMPTY_ENTRY && clusterNumber < 0x800) { + clusterNumber++; + } + + // if there is no cluster that can be allocated, return with error code -1 + if (clusterNumber == 0x800) { return -1; } + + // update the FAT table and write it to FAT cluster + driverState.fat_table.cluster_map[clusterNumber] = FAT32_FAT_END_OF_FILE; + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + + // update parent directory table and write it to parent cluster + struct FAT32DirectoryEntry dirEntry = { + .name = {request.name[0], request.name[1], request.name[2], request.name[3], request.name[4], request.name[5], request.name[6], request.name[7]}, + .ext = {request.ext[0], request.ext[1], request.ext[2]}, + .attribute = ATTR_SUBDIRECTORY, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = clusterNumber >> 16, + .create_time = (((uint16_t) hour) << 8) | ((uint16_t) minute), + .create_date = (((uint16_t) day) << 8) | ((uint16_t) month), + .modified_time = (((uint16_t) hour) << 8) | ((uint16_t) minute), + .modified_date = (((uint16_t) day) << 8) | ((uint16_t) month), + .cluster_low = clusterNumber, + .filesize = request.buffer_size + }; + driverState.dir_table_buf.table[entryRow] = dirEntry; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // create directory table for new directory and write it to directory cluster + read_clusters(driverState.dir_table_buf.table, clusterNumber, 1); + dirEntry.cluster_high = request.parent_cluster_number >> 16; + dirEntry.cluster_low = request.parent_cluster_number; + driverState.dir_table_buf.table[0] = dirEntry; + write_clusters(driverState.dir_table_buf.table, clusterNumber, 1); + } + else { + // determine number of cluster needed + int modulo = request.buffer_size % CLUSTER_SIZE; + int clusterNeeded = request.buffer_size / CLUSTER_SIZE; + if (modulo != 0) { clusterNeeded++; } + + // get the first cluster number that is empty + int clusterAvailable = 0; + bool firstClusterFound = FALSE; + uint32_t startClusterNumber = 0x0; + while (startClusterNumber != 0x800 && !firstClusterFound) { + if (driverState.fat_table.cluster_map[startClusterNumber] == FAT32_FAT_EMPTY_ENTRY) { + clusterAvailable++; + firstClusterFound = TRUE; + } + else { + startClusterNumber++; + } + } + + // if there is no cluster to put the file, return with error code -1 + if (!firstClusterFound) { return -1;} + + // get the rest of cluster number + uint32_t prevClusterNumber = startClusterNumber; + uint32_t currClusterNumber = startClusterNumber + 1; + while (currClusterNumber < 0x800 && clusterAvailable < clusterNeeded) { + if (driverState.fat_table.cluster_map[currClusterNumber] == FAT32_FAT_EMPTY_ENTRY) { + driverState.fat_table.cluster_map[prevClusterNumber] = currClusterNumber; + prevClusterNumber = currClusterNumber; + clusterAvailable++; + } + currClusterNumber++; + } + + // if there is not enough cluster to contain the file, reset the fat table and return with error code -1 + if (clusterAvailable != clusterNeeded) { + uint32_t tempClusterNumber; + while (driverState.fat_table.cluster_map[startClusterNumber] != FAT32_FAT_EMPTY_ENTRY) { + tempClusterNumber = driverState.fat_table.cluster_map[startClusterNumber]; + driverState.fat_table.cluster_map[startClusterNumber] = FAT32_FAT_EMPTY_ENTRY; + startClusterNumber = tempClusterNumber; + } + return -1; + } + else { + driverState.fat_table.cluster_map[prevClusterNumber] = FAT32_FAT_END_OF_FILE; + } + + // write the FAT table to FAT cluster + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + + // update parent directory table and write it to parent cluster + struct FAT32DirectoryEntry dirEntry = { + .name = {request.name[0], request.name[1], request.name[2], request.name[3], request.name[4], request.name[5], request.name[6], request.name[7]}, + .ext = {request.ext[0], request.ext[1], request.ext[2]}, + .user_attribute = UATTR_NOT_EMPTY, + .cluster_high = startClusterNumber >> 16, + .create_time = (((uint16_t) hour) << 8) | ((uint16_t) minute), + .create_date = (((uint16_t) day) << 8) | ((uint16_t) month), + .modified_time = (((uint16_t) hour) << 8) | ((uint16_t) minute), + .modified_date = (((uint16_t) day) << 8) | ((uint16_t) month), + .cluster_low = startClusterNumber, + .filesize = request.buffer_size + }; + driverState.dir_table_buf.table[entryRow] = dirEntry; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // write the file to all of the cluster selected + int i = 0; + while (driverState.fat_table.cluster_map[startClusterNumber] != FAT32_FAT_END_OF_FILE) { + write_clusters((uint8_t*) request.buf + CLUSTER_SIZE*i, startClusterNumber, 1); + startClusterNumber = driverState.fat_table.cluster_map[startClusterNumber]; + i++; + } + write_clusters((uint8_t*) request.buf + CLUSTER_SIZE*i, startClusterNumber, 1); + } + + return 0; +} + + +/** + * FAT32 delete, delete a file or empty directory (only 1 DirectoryEntry) in file system. + * + * @param request buf and buffer_size is unused + * @return Error code: 0 success - 1 not found - 2 folder is not empty - -1 unknown + */ +int8_t delete(struct FAT32DriverRequest request) { + // read entries of directory from the parent cluster + read_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // if parent is not a directory, return with error code -1 + if (!(driverState.dir_table_buf.table[0].user_attribute == UATTR_NOT_EMPTY && + driverState.dir_table_buf.table[0].attribute == ATTR_SUBDIRECTORY)) { return -1;} + + // check if name and extension to delete is valid + int entryRow = 0; + int entryChecked = 2; + bool found = FALSE; + bool isDirectory = FALSE; + uint32_t clusterNumber; + + while (entryChecked <= 64 && !found) { + if (memcmp(driverState.dir_table_buf.table[entryChecked-1].name, request.name, 8) == 0 && + driverState.dir_table_buf.table[entryChecked-1].user_attribute == UATTR_NOT_EMPTY) { + found = TRUE; + entryRow = entryChecked-1; + clusterNumber = ((uint32_t) driverState.dir_table_buf.table[entryChecked-1].cluster_high) << 16; + clusterNumber |= (uint32_t) driverState.dir_table_buf.table[entryChecked-1].cluster_low; + if (driverState.dir_table_buf.table[entryChecked-1].attribute == ATTR_SUBDIRECTORY) { + isDirectory = TRUE; + } + } + else { + entryChecked++; + } + } + + // if file / folder to delete not found, return with error code 1 + if (!found) { return 1; } + + // if the entry is a file, delete it. if the entry is a directory, check if it was empty + if (!isDirectory) { + // delete entry by removing its signature, and write it to parent cluster + for (int i = 0; i < 8; i++) { + driverState.dir_table_buf.table[entryRow].name[i] = 0; + if (i < 3) { + driverState.dir_table_buf.table[entryRow].ext[i] = 0; + } + } + driverState.dir_table_buf.table[entryRow].user_attribute = FAT32_FAT_EMPTY_ENTRY; + driverState.dir_table_buf.table[entryRow].attribute = FAT32_FAT_EMPTY_ENTRY; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // delete file and all its cluster from the FAT table, and write it to FAT table cluster + uint32_t currClusterNumber = clusterNumber; + uint32_t prevClusterNumber = 0; + while (driverState.fat_table.cluster_map[currClusterNumber] != FAT32_FAT_END_OF_FILE) { + prevClusterNumber = currClusterNumber; + currClusterNumber = driverState.fat_table.cluster_map[currClusterNumber]; + driverState.fat_table.cluster_map[prevClusterNumber] = FAT32_FAT_EMPTY_ENTRY; + } + driverState.fat_table.cluster_map[currClusterNumber] = 0; + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + } + else { + // load the directory table of directory to delete + struct FAT32DirectoryTable tempDir; + read_clusters(tempDir.table, clusterNumber, 1); + + // check if the directory is empty + bool empty = TRUE; + entryChecked = 2; + + while (entryChecked <= 64 && empty) { + if (tempDir.table[entryChecked-1].user_attribute == UATTR_NOT_EMPTY) { + empty = FALSE; + } + else { + entryChecked++; + } + } + + // if directory not empty, return with error code 2 + if (!empty) { return 2;} + + // delete entry by removing its signature, and write it to parent cluster + for (int i = 0; i < 8; i++) { + driverState.dir_table_buf.table[entryRow].name[i] = 0; + if (i < 3) { + driverState.dir_table_buf.table[entryRow].ext[i] = 0; + } + } + driverState.dir_table_buf.table[entryRow].user_attribute = FAT32_FAT_EMPTY_ENTRY; + driverState.dir_table_buf.table[entryRow].attribute = FAT32_FAT_EMPTY_ENTRY; + write_clusters(driverState.dir_table_buf.table, request.parent_cluster_number, 1); + + // delete the directory itself, write it to directory cluster + for (int i = 0; i < 8; i++) { + tempDir.table[0].name[i] = 0; + if (i < 3) { + tempDir.table[0].ext[i] = 0; + } + } + tempDir.table[0].user_attribute = FAT32_FAT_EMPTY_ENTRY; + tempDir.table[0].attribute = FAT32_FAT_EMPTY_ENTRY; + write_clusters(tempDir.table, clusterNumber, 1); + + // delete directory in FAT table, and write it to FAT cluster + driverState.fat_table.cluster_map[clusterNumber] = FAT32_FAT_EMPTY_ENTRY; + write_clusters(driverState.fat_table.cluster_map, FAT_CLUSTER_NUMBER, 1); + } + + return 0; +} \ No newline at end of file diff --git a/src/filesystem/fat32.h b/src/filesystem/fat32.h new file mode 100644 index 0000000..a147127 --- /dev/null +++ b/src/filesystem/fat32.h @@ -0,0 +1,233 @@ +#ifndef _FAT32_H +#define _FAT32_H + +#include "disk.h" +#include "../std/stdtype.h" + +/** + * FAT32 - IF2230 edition - 2023 + * Check "IF2230 - Guidebook - Milestone 2" for more details + * https://docs.google.com/document/d/1IFyxHSYYpKgecHcS0T64oDc4bVElaq8tBcm1_mjjGGM/edit# + */ + +/* -- IF2230 File System constants -- */ +#define BOOT_SECTOR 0 +#define CLUSTER_BLOCK_COUNT 4 +#define CLUSTER_SIZE (BLOCK_SIZE*CLUSTER_BLOCK_COUNT) +#define CLUSTER_MAP_SIZE 512 + +/* -- FAT32 FileAllocationTable constants -- */ +// FAT reserved value for cluster 0 and 1 in FileAllocationTable +#define CLUSTER_0_VALUE 0x0FFFFFF0 +#define CLUSTER_1_VALUE 0x0FFFFFFF + +// EOF also double as valid cluster / "this is last valid cluster in the chain" +#define FAT32_FAT_END_OF_FILE 0x0FFFFFFF +#define FAT32_FAT_EMPTY_ENTRY 0x00000000 + +#define FAT_CLUSTER_NUMBER 1 +#define ROOT_CLUSTER_NUMBER 2 + +/* -- FAT32 DirectoryEntry constants -- */ +#define ATTR_SUBDIRECTORY 0b00010000 +#define UATTR_NOT_EMPTY 0b10101010 + +// Boot sector signature for this file system "FAT32 - IF2230 edition" +extern const uint8_t fs_signature[BLOCK_SIZE]; + +// Cluster buffer data type - @param buf Byte buffer with size of CLUSTER_SIZE +struct ClusterBuffer { + uint8_t buf[CLUSTER_SIZE]; +} __attribute__((packed)); + +/* -- FAT32 Data Structures -- */ + +/** + * FAT32 FileAllocationTable, for more information about this, check guidebook + * + * @param cluster_map Containing cluster map of FAT32 + */ +struct FAT32FileAllocationTable { + uint32_t cluster_map[CLUSTER_MAP_SIZE]; +} __attribute__((packed)); + +/** + * FAT32 standard 8.3 format - 32 bytes DirectoryEntry, Some detail can be found at: + * https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Directory_entry, and click show table. + * Attribute related to date and time is following the Real-Time Clock + * + * @param name Entry name + * @param ext File extension + * @param attribute Will be used exclusively for subdirectory flag / determining this entry is file or folder + * @param user_attribute If this attribute equal with UATTR_NOT_EMPTY then entry is not empty + * + * @param undelete Unused / optional + * @param create_time Unused / optional + * @param create_date Unused / optional + * @param access_time Unused / optional + * @param cluster_high Upper 16-bit of cluster number + * + * @param modified_time Unused / optional + * @param modified_date Unused / optional + * @param cluster_low Lower 16-bit of cluster number + * @param filesize Filesize of this file, if this is directory / folder, filesize is 0 + */ +struct FAT32DirectoryEntry { + char name[8]; + char ext[3]; + uint8_t attribute; + uint8_t user_attribute; + + bool undelete; + uint16_t create_time; + uint16_t create_date; + uint16_t access_date; + uint16_t cluster_high; + + uint16_t modified_time; + uint16_t modified_date; + uint16_t cluster_low; + uint32_t filesize; +} __attribute__((packed)); + +// FAT32 DirectoryTable, containing directory entry table - @param table Table of DirectoryEntry that span within 1 cluster +struct FAT32DirectoryTable { + struct FAT32DirectoryEntry table[CLUSTER_SIZE / sizeof(struct FAT32DirectoryEntry)]; +} __attribute__((packed)); + +/* -- FAT32 Driver -- */ + +/** + * FAT32DriverState - Contain all driver states + * + * @param fat_table FAT of the system, will be loaded during initialize_filesystem_fat32() + * @param dir_table_buf Buffer for directory table + * @param cluster_buf Buffer for cluster + */ +struct FAT32DriverState { + struct FAT32FileAllocationTable fat_table; + struct FAT32DirectoryTable dir_table_buf; + struct ClusterBuffer cluster_buf; +} __attribute__((packed)); + +/** + * FAT32DriverRequest - Request for Driver CRUD operation + * + * @param buf Pointer pointing to buffer + * @param name Name for directory entry + * @param ext Extension for file + * @param parent_cluster_number Parent directory cluster number, for updating metadata + * @param buffer_size Buffer size, CRUD operation will have different behaviour with this attribute + */ +struct FAT32DriverRequest { + void *buf; + char name[8]; + char ext[3]; + uint32_t parent_cluster_number; + uint32_t buffer_size; +} __attribute__((packed)); + +/* -- Driver Interfaces -- */ + +/** + * Convert cluster number to logical block address + * + * @param cluster Cluster number to convert + * @return uint32_t Logical Block Address + */ +uint32_t cluster_to_lba(uint32_t cluster); + +/** + * Initialize DirectoryTable value with parent DirectoryEntry and directory name + * + * @param dir_table Pointer to directory table + * @param name 8-byte char for directory name + * @param parent_dir_cluster Parent directory cluster number + */ +void init_directory_table(struct FAT32DirectoryTable *dir_table, char *name, uint32_t parent_dir_cluster); + +/** + * Checking whether filesystem signature is missing or not in boot sector + * + * @return True if memcmp(boot_sector, fs_signature) returning inequality + */ +bool is_empty_storage(void); + +/** + * Create new FAT32 file system. Will write fs_signature into boot sector and + * proper FileAllocationTable (contain CLUSTER_0_VALUE, CLUSTER_1_VALUE, + * and initialized root directory) into cluster number 1 + */ +void create_fat32(void); + +/** + * Initialize file system driver state, if is_empty_storage() then create_fat32() + * Else, read and cache entire FileAllocationTable (located at cluster number 1) into driver state + */ +void initialize_filesystem_fat32(void); + +/** + * Write cluster operation, wrapper for write_blocks(). + * Recommended to use struct ClusterBuffer + * + * @param ptr Pointer to source data + * @param cluster_number Cluster number to write + * @param cluster_count Cluster count to write, due limitation of write_blocks block_count 255 => max cluster_count = 63 + */ +void write_clusters(const void *ptr, uint32_t cluster_number, uint8_t cluster_count); + +/** + * Read cluster operation, wrapper for read_blocks(). + * Recommended to use struct ClusterBuffer + * + * @param ptr Pointer to buffer for reading + * @param cluster_number Cluster number to read + * @param cluster_count Cluster count to read, due limitation of read_blocks block_count 255 => max cluster_count = 63 + */ +void read_clusters(void *ptr, uint32_t cluster_number, uint8_t cluster_count); + + + + + +/* -- CRUD Operation -- */ + +/** + * FAT32 Folder / Directory read + * + * @param request buf point to struct FAT32DirectoryTable, + * name is directory name, + * ext is unused, + * parent_cluster_number is target directory table to read, + * buffer_size must be exactly sizeof(struct FAT32DirectoryTable) + * @return Error code: 0 success - 1 not a folder - 2 not found - -1 unknown + */ +int8_t read_directory(struct FAT32DriverRequest request); + + +/** + * FAT32 read, read a file from file system. + * + * @param request All attribute will be used for read, buffer_size will limit reading count + * @return Error code: 0 success - 1 not a file - 2 not enough buffer - 3 not found - -1 unknown + */ +int8_t read(struct FAT32DriverRequest request); + +/** + * FAT32 write, write a file or folder to file system. + * + * @param request All attribute will be used for write, buffer_size == 0 then create a folder / directory + * @return Error code: 0 success - 1 file/folder already exist - 2 invalid parent cluster - -1 unknown + */ +int8_t write(struct FAT32DriverRequest request); + + +/** + * FAT32 delete, delete a file or empty directory (only 1 DirectoryEntry) in file system. + * + * @param request buf and buffer_size is unused + * @return Error code: 0 success - 1 not found - 2 folder is not empty - -1 unknown + */ +int8_t delete(struct FAT32DriverRequest request); + +#endif \ No newline at end of file diff --git a/src/framebuffer/framebuffer.c b/src/framebuffer/framebuffer.c new file mode 100644 index 0000000..c7e4d1d --- /dev/null +++ b/src/framebuffer/framebuffer.c @@ -0,0 +1,34 @@ +#include "framebuffer/framebuffer.h" +#include "std/stdtype.h" +#include "std/stdmem.h" +#include "portio/portio.h" + +void framebuffer_set_cursor(uint8_t r, uint8_t c) { + // Initialized the cursor position byte + uint16_t position = r*80 + c; + + // Give the cursor position to hardware port + out(CURSOR_PORT_CMD, 0x0F); + out(CURSOR_PORT_DATA, (uint8_t) (position & 0xFF)); + out(CURSOR_PORT_CMD, 0x0E); + out(CURSOR_PORT_DATA, (uint8_t) ((position >> 8) & 0xFF)); +} + +void framebuffer_write(uint8_t row, uint8_t col, char c, uint8_t fg, uint8_t bg) { + // Merge the colour byte and char into a complete 2 byte + uint16_t colourBit = (bg << 4) | fg ; + uint16_t fullBit = (colourBit << 8) | c ; + + // Assign the character 2 byte into the memory + volatile uint16_t* where = (volatile uint16_t*) MEMORY_FRAMEBUFFER + (row * 80 + col) ; + *where = fullBit; +} + +void framebuffer_clear(void) { + // Set all character 2 byte in the 80x25 framebuffer into black blank + for (int i = 0; i < 25; i++) { + for (int j = 0; j < 80; j++) { + framebuffer_write(i, j, 0x00, 0x7, 0); + } + } +} diff --git a/src/lib-header/framebuffer.h b/src/framebuffer/framebuffer.h similarity index 93% rename from src/lib-header/framebuffer.h rename to src/framebuffer/framebuffer.h index 1e6786e..4253d4d 100644 --- a/src/lib-header/framebuffer.h +++ b/src/framebuffer/framebuffer.h @@ -1,9 +1,9 @@ #ifndef _FRAMEBUFFER_H #define _FRAMEBUFFER_H -#include "lib-header/stdtype.h" +#include "std/stdtype.h" -#define MEMORY_FRAMEBUFFER (uint8_t *) 0xB8000 +#define MEMORY_FRAMEBUFFER (uint8_t *) 0xC00B8000 #define CURSOR_PORT_CMD 0x03D4 #define CURSOR_PORT_DATA 0x03D5 diff --git a/src/gdt/gdt.c b/src/gdt/gdt.c new file mode 100644 index 0000000..f04431c --- /dev/null +++ b/src/gdt/gdt.c @@ -0,0 +1,131 @@ +#include "std/stdtype.h" +#include "gdt/gdt.h" + +/** + * global_descriptor_table, predefined GDT. + * Initial SegmentDescriptor already set properly according to GDT definition in Intel Manual & OSDev. + * Table entry : [{Null Descriptor}, {Kernel Code}, {Kernel Data (variable, etc)}, ...]. + */ +struct GlobalDescriptorTable global_descriptor_table = { + .table = { + { + // Null Descriptor + .segment_low = 0x0000, + .base_low = 0x0000, + + .base_mid = 0x00, + .type_bit = 0x00, + .non_system = 0x00, + .dpl_bit = 0x00, + .present = 0x00, + .segment_high = 0x00, + .available_bit = 0x00, + .code_in_64bit = 0x00, + .db_flag = 0x00, + .granularity = 0x00, + .base_high = 0x00, + }, + { + // Kernel code segment + .segment_low = 0xFFFF, + .base_low = 0x0000, + + .base_mid = 0x00, + .type_bit = 0xA, + .non_system = 0x01, + .dpl_bit = 0x00, + .present = 0x01, + .segment_high = 0xF, + .available_bit = 0x00, + .code_in_64bit = 0x00, + .db_flag = 0x01, + .granularity = 0x01, + .base_high = 0x00, + }, + { + // Kernel data segment + .segment_low = 0xFFFF, + .base_low = 0x0000, + + .base_mid = 0x00, + .type_bit = 0x2, + .non_system = 0x01, + .dpl_bit = 0x00, + .present = 0x01, + .segment_high = 0xF, + .available_bit = 0x00, + .code_in_64bit = 0x00, + .db_flag = 0x01, + .granularity = 0x01, + .base_high = 0x00, + }, + { + // User code segment + .segment_low = 0xFFFF, + .base_low = 0x0000, + + .base_mid = 0x00, + .type_bit = 0xA, + .non_system = 0x01, + .dpl_bit = 0x03, + .present = 0x01, + .segment_high = 0xF, + .available_bit = 0x00, + .code_in_64bit = 0x00, + .db_flag = 0x01, + .granularity = 0x01, + .base_high = 0x00, + }, + { + // User data segment + .segment_low = 0xFFFF, + .base_low = 0x0000, + + .base_mid = 0x00, + .type_bit = 0x2, + .non_system = 0x01, + .dpl_bit = 0x03, + .present = 0x01, + .segment_high = 0xF, + .available_bit = 0x00, + .code_in_64bit = 0x00, + .db_flag = 0x01, + .granularity = 0x01, + .base_high = 0x00, + }, + { + .segment_low = sizeof(struct TSSEntry), + .base_low = 0, + + .base_mid = 0, + .type_bit = 0x9, + .non_system = 0, // S bit + .dpl_bit = 0, // DPL + .present = 1, // P bit + .segment_high = (sizeof(struct TSSEntry) & (0xF << 16)) >> 16, + .available_bit = 0, + .code_in_64bit = 0, // L bit + .db_flag = 1, // D/B bit + .granularity = 0, // G bit + .base_high = 0 + }, + {0} + } +}; + +/** + * _gdt_gdtr, predefined system GDTR. + * GDT pointed by this variable is already set to point global_descriptor_table above. + * From: https://wiki.osdev.org/Global_Descriptor_Table, GDTR.size is GDT size minus 1. + */ +struct GDTR _gdt_gdtr = { + .size = sizeof(global_descriptor_table) - 1, + .address = &global_descriptor_table +}; + +void gdt_install_tss(void) { + uint32_t base = (uint32_t) &_interrupt_tss_entry; + global_descriptor_table.table[5].base_high = (base & (0xFF << 24)) >> 24; + global_descriptor_table.table[5].base_mid = (base & (0xFF << 16)) >> 16; + global_descriptor_table.table[5].base_low = base & 0xFFFF; +} \ No newline at end of file diff --git a/src/gdt/gdt.h b/src/gdt/gdt.h new file mode 100644 index 0000000..8f2fc8d --- /dev/null +++ b/src/gdt/gdt.h @@ -0,0 +1,76 @@ +#ifndef _GDT_H +#define _GDT_H + +#include "std/stdtype.h" +#include "interrupt/interrupt.h" + +#define GDT_MAX_ENTRY_COUNT 32 +#define GDT_USER_CODE_SEGMENT_SELECTOR 0x18 +#define GDT_USER_DATA_SEGMENT_SELECTOR 0x20 +#define GDT_TSS_SELECTOR 0x28 + +extern struct GDTR _gdt_gdtr; + +/** + * Segment Descriptor storing system segment information. + * Struct defined exactly as Intel Manual Segment Descriptor definition (Figure 3-8 Segment Descriptor). + * Manual can be downloaded at www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html/ + * + * @param segment_low 16-bit lower-bit segment limit + * @param base_low 16-bit lower-bit base address + * @param base_mid 8-bit middle-bit base address + * @param type_bit 4-bit contain type flags + * @param non_system 1-bit contain system (S) + * @param dpl_bit 2-bit contain descriptor privilege level (DPL) + * @param present 1-bit contain segment existence in memory (P) + * @param segment_high 4-bit higher-bit segment limit + * @param available_bit 1-bit contain free-use bit (AVL) + * @param code_in_64bit 1-bit contain 64-bit code existence (L) + * @param db_flag 1-bit contain operation size (D/B) + * @param granularity 1-bit contain scaling of segment limit field (G) + * @param base_high 8-bit higher base address + */ +struct SegmentDescriptor { + // First 32-bit + uint16_t segment_low; + uint16_t base_low; + + // Last 32-bit (Bit 32 to 63) + uint8_t base_mid; + uint8_t type_bit : 4; + uint8_t non_system : 1; + uint8_t dpl_bit : 2; + uint8_t present : 1; + uint8_t segment_high : 4; + uint8_t available_bit : 1; + uint8_t code_in_64bit : 1; + uint8_t db_flag : 1; + uint8_t granularity : 1; + uint8_t base_high; +} __attribute__((packed)); + +/** + * Global Descriptor Table containing list of segment descriptor. One GDT already defined in memory.c. + * More details at https://wiki.osdev.org/GDT_Tutorial + * @param table Fixed-width array of SegmentDescriptor with size GDT_MAX_ENTRY_COUNT + */ +struct GlobalDescriptorTable { + struct SegmentDescriptor table[GDT_MAX_ENTRY_COUNT]; +} __attribute__((packed)); + +/** + * GDTR, carrying information where's the GDT located and GDT size. + * Global kernel variable defined at memory.c. + * + * @param size Global Descriptor Table size, use sizeof operator + * @param address GDT address, GDT should already defined properly + */ +struct GDTR { + uint16_t size; + struct GlobalDescriptorTable *address; +} __attribute__((packed)); + +// Set GDT_TSS_SELECTOR with proper TSS values, accessing _interrupt_tss_entry +void gdt_install_tss(void); + +#endif \ No newline at end of file diff --git a/src/inserter/external-inserter.c b/src/inserter/external-inserter.c new file mode 100644 index 0000000..b34884f --- /dev/null +++ b/src/inserter/external-inserter.c @@ -0,0 +1,129 @@ +#include +#include +// #include "std/stdtype.h" + +// Usual gcc fixed width integer type +typedef u_int32_t uint32_t; +typedef u_int8_t uint8_t; + +// Manual import from fat32.h, disk.h, & stdmem.h due some issue with size_t +#define BLOCK_SIZE 512 + +struct FAT32DriverRequest { + void *buf; + char name[8]; + char ext[3]; + uint32_t parent_cluster_number; + uint32_t buffer_size; +} __attribute__((packed)); + +void* memcpy(void* restrict dest, const void* restrict src, size_t n); + +void create_fat32(void); + +void initialize_filesystem_fat32(void); +/** + * FAT32 Folder / Directory read + * + * @param request buf point to struct FAT32DirectoryTable, + * name is directory name, + * ext is unused, + * parent_cluster_number is target directory table to read, + * buffer_size must be exactly sizeof(struct FAT32DirectoryTable) + * @return Error code: 0 success - 1 not a folder - 2 not found - -1 unknown + */ +int8_t read_directory(struct FAT32DriverRequest request); + +/** + * FAT32 read, read a file from file system. + * + * @param request All attribute will be used for read, buffer_size will limit reading count + * @return Error code: 0 success - 1 not a file - 2 not enough buffer - 3 not found - -1 unknown + */ +int8_t read(struct FAT32DriverRequest request); + +/** + * FAT32 write, write a file or folder to file system. + * + * @param request All attribute will be used for write, buffer_size == 0 then create a folder / directory + * @return Error code: 0 success - 1 file/folder already exist - 2 invalid parent cluster - -1 unknown + */ +int8_t write(struct FAT32DriverRequest request); + +/** + * FAT32 delete, delete a file or empty directory (only 1 DirectoryEntry) in file system. + * + * @param request buf and buffer_size is unused + * @return Error code: 0 success - 1 not found - 2 folder is not empty - -1 unknown + */ +int8_t delete(struct FAT32DriverRequest request); + +// Global variable +uint8_t *image_storage; +uint8_t *file_buffer; + +void read_blocks(void *ptr, uint32_t logical_block_address, uint8_t block_count) { + for (int i = 0; i < block_count; i++) + memcpy((uint8_t*) ptr + BLOCK_SIZE*i, image_storage + BLOCK_SIZE*(logical_block_address+i), BLOCK_SIZE); +} + +void write_blocks(const void *ptr, uint32_t logical_block_address, uint8_t block_count) { + for (int i = 0; i < block_count; i++) + memcpy(image_storage + BLOCK_SIZE*(logical_block_address+i), (uint8_t*) ptr + BLOCK_SIZE*i, BLOCK_SIZE); +} + + +int main(int argc, char *argv[]) { + if (argc < 4) { + fprintf(stderr, "inserter: ./inserter \n"); + exit(1); + } + + // Read storage into memory, requiring 4 MB memory + image_storage = malloc(4*1024*1024); + file_buffer = malloc(4*1024*1024); + FILE *fptr = fopen(argv[3], "r"); + fread(image_storage, 4*1024*1024, 1, fptr); + fclose(fptr); + + // Read target file, assuming file is less than 4 MiB + FILE *fptr_target = fopen(argv[1], "r"); + size_t filesize = 0; + if (fptr_target == NULL) + filesize = 0; + else { + fread(file_buffer, 4*1024*1024, 1, fptr_target); + fseek(fptr_target, 0, SEEK_END); + filesize = ftell(fptr_target); + fclose(fptr_target); + } + + printf("Filename : %s\n", argv[1]); + printf("Filesize : %ld bytes\n", filesize); + + // FAT32 operations + initialize_filesystem_fat32(); + struct FAT32DriverRequest request = { + .buf = file_buffer, + .ext = "\0\0\0", + .buffer_size = filesize, + }; + sscanf(argv[2], "%u", &request.parent_cluster_number); + sscanf(argv[1], "%8s", request.name); + int retcode = write(request); + if (retcode == 0) + puts("Write success"); + else if (retcode == 1) + puts("Error: File/folder name already exist"); + else if (retcode == 2) + puts("Error: Invalid parent cluster"); + else + puts("Error: Unknown error"); + + // Write image in memory into original, overwrite them + fptr = fopen(argv[3], "w"); + fwrite(image_storage, 4*1024*1024, 1, fptr); + fclose(fptr); + + return 0; +} \ No newline at end of file diff --git a/src/interrupt/idt.c b/src/interrupt/idt.c new file mode 100644 index 0000000..491c276 --- /dev/null +++ b/src/interrupt/idt.c @@ -0,0 +1,47 @@ +#include "std/stdtype.h" +#include "idt.h" + +/** + * interrupt_descriptor_table, predefined IDT. + * Initial IDTGate already set properly according to IDT definition + */ +struct InterruptDescriptorTable interrupt_descriptor_table; + +/** + * _idt_idtr, predefined system IDTR. + * IDT pointed by this variable is already set to point global_descriptor_table above. +*/ +struct IDTR _idt_idtr = { + .size = sizeof(interrupt_descriptor_table) - 1, + .address = &interrupt_descriptor_table +}; + +void set_interrupt_gate(uint8_t int_vector, void *handler_address, uint16_t gdt_seg_selector, uint8_t privilege) { + struct IDTGate *idt_int_gate = &interrupt_descriptor_table.table[int_vector]; + idt_int_gate->offset_low = (uint32_t)handler_address & 0xFFFF; + idt_int_gate->segment = gdt_seg_selector; + + // Target system 32-bit and flag this as valid interrupt gate + idt_int_gate->_r_bit_1 = INTERRUPT_GATE_R_BIT_1; + idt_int_gate->_r_bit_2 = INTERRUPT_GATE_R_BIT_2; + idt_int_gate->gate_32 = 1; + idt_int_gate->_r_bit_3 = INTERRUPT_GATE_R_BIT_3; + idt_int_gate->dpl_bit = privilege; + idt_int_gate->valid_bit = 1; + idt_int_gate->offset_high = (uint32_t) handler_address >> 16; +} + +extern void* isr_stub_table[]; + +void initialize_idt(void) { + for (uint8_t i = 0; i < ISR_STUB_TABLE_LIMIT; i++) { + if (i >= 0x30 && i <= 0x3F) { + set_interrupt_gate(i, isr_stub_table[i], GDT_KERNEL_CODE_SEGMENT_SELECTOR, 0x3); + } else { + set_interrupt_gate(i, isr_stub_table[i], GDT_KERNEL_CODE_SEGMENT_SELECTOR, 0); + } + } + + __asm__ volatile("lidt %0" : : "m"(_idt_idtr)); // load the new IDT + __asm__ volatile("sti"); // set the interrupt flag +} \ No newline at end of file diff --git a/src/interrupt/idt.h b/src/interrupt/idt.h new file mode 100644 index 0000000..2d39d52 --- /dev/null +++ b/src/interrupt/idt.h @@ -0,0 +1,92 @@ +#ifndef _IDT_H +#define _IDT_H + +#include "std/stdtype.h" + +// IDT hard limit, see Intel x86 manual 3a - 6.10 Interrupt Descriptor Table +#define IDT_MAX_ENTRY_COUNT 256 +#define ISR_STUB_TABLE_LIMIT 64 +#define INTERRUPT_GATE_R_BIT_1 0b000 +#define INTERRUPT_GATE_R_BIT_2 0b110 +#define INTERRUPT_GATE_R_BIT_3 0b0 + +// Some GDT Constant +#define GDT_KERNEL_CODE_SEGMENT_SELECTOR 0x8 +#define GDT_KERNEL_DATA_SEGMENT_SELECTOR 0x10 + +// Interrupt Handler / ISR stub for reducing code duplication, this array can be iterated in initialize_idt() +extern void *isr_stub_table[ISR_STUB_TABLE_LIMIT]; + +extern struct IDTR _idt_idtr; + +/** + * IDTGate, IDT entry that point into interrupt handler + * Struct defined exactly in Intel x86 Vol 3a - Figure 6-2. IDT Gate Descriptors + * + * @param offset_low 16-bit lower-bit offset + * @param segment 16-bit contain segment selector + * @param _reserved 5-bit contain reseved empty-bit + * @param _r_bit_1 3-bit reserved for idtgate type (0 0 0) + * @param _r_bit_2 3-bit reserved for idtgate type (1 1 0) + * @param gate_32 1-bit contain gate size (1 - 32bits, 0 - 16bits) + * @param _r_bit_3 1-bit reserved for idtgate type (0) + * @param dpl_bit 2-bit contain descriptor privilege level (DPL) + * @param valid_bit 1-bit contain segment existence in memory (P) + * @param offset_high 16-bit higher-bit offset + */ +struct IDTGate { + // First 32-bit (Bit 0 to 31) + uint16_t offset_low; + uint16_t segment; + + // Last 32-bit + uint8_t _reserved : 5; + uint8_t _r_bit_1 : 3; + uint8_t _r_bit_2 : 3; + uint8_t gate_32 : 1; + uint8_t _r_bit_3 : 1; + uint8_t dpl_bit : 2; + uint8_t valid_bit : 1; + uint16_t offset_high; + +} __attribute__((packed)); + +/** + * Interrupt Descriptor Table, containing lists of IDTGate. + * One IDT already defined in idt.c + * + * @param table Fixed-width array of IDTGate with size IDT_MAX_ENTRY_COUNT + */ +struct InterruptDescriptorTable { + struct IDTGate table[IDT_MAX_ENTRY_COUNT]; +} __attribute__((packed)); + +/** + * IDTR, carrying information where's the IDT located and size. + * Global kernel variable defined at idt.c. + * + * @param size Interrupt Descriptor Table size, use sizeof operator + * @param address IDT address, IDT should already defined properly + */ +struct IDTR { + uint16_t size; + struct InterruptDescriptorTable *address; +} __attribute__((packed)); + +/** + * Set IDTGate with proper interrupt handler values. + * Will directly edit global IDT variable and set values properly + * + * @param int_vector Interrupt vector to handle + * @param handler_address Interrupt handler address + * @param gdt_seg_selector GDT segment selector, for kernel use GDT_KERNEL_CODE_SEGMENT_SELECTOR + * @param privilege Descriptor privilege level + */ +void set_interrupt_gate(uint8_t int_vector, void *handler_address, uint16_t gdt_seg_selector, uint8_t privilege); + +/** + * Set IDT with proper values and load with lidt + */ +void initialize_idt(void); + +#endif \ No newline at end of file diff --git a/src/interrupt/interrupt.c b/src/interrupt/interrupt.c new file mode 100644 index 0000000..9bb9c5a --- /dev/null +++ b/src/interrupt/interrupt.c @@ -0,0 +1,123 @@ +#include "interrupt.h" +#include "portio/portio.h" +#include "std/stdmem.h" +#include "std/string.h" +#include "keyboard/keyboard.h" +#include "framebuffer/framebuffer.h" +#include "idt.h" +#include "filesystem/fat32.h" + +// Inisialisasi _interrupt_tss_entry +struct TSSEntry _interrupt_tss_entry = { + .ss0 = GDT_KERNEL_DATA_SEGMENT_SELECTOR, +}; + +void activate_keyboard_interrupt(void) { + out(PIC1_DATA, PIC_DISABLE_ALL_MASK ^ (1 << IRQ_KEYBOARD)); + out(PIC2_DATA, PIC_DISABLE_ALL_MASK); +} + +void deactivate_keyboard_interrupt(void) { + out(PIC1_DATA, PIC_DISABLE_ALL_MASK); + out(PIC2_DATA, PIC_DISABLE_ALL_MASK); +} + +void io_wait(void) { + out(0x80, 0); +} + +void pic_ack(uint8_t irq) { + if (irq >= 8) + out(PIC2_COMMAND, PIC_ACK); + out(PIC1_COMMAND, PIC_ACK); +} + +void pic_remap(void) { + uint8_t a1, a2; + + // Save masks + a1 = in(PIC1_DATA); + a2 = in(PIC2_DATA); + + // Starts the initialization sequence in cascade mode + out(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); + io_wait(); + out(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); + io_wait(); + out(PIC1_DATA, PIC1_OFFSET); // ICW2: Master PIC vector offset + io_wait(); + out(PIC2_DATA, PIC2_OFFSET); // ICW2: Slave PIC vector offset + io_wait(); + out(PIC1_DATA, 0b0100); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100) + io_wait(); + out(PIC2_DATA, 0b0010); // ICW3: tell Slave PIC its cascade identity (0000 0010) + io_wait(); + + out(PIC1_DATA, ICW4_8086); + io_wait(); + out(PIC2_DATA, ICW4_8086); + io_wait(); + + // Restore masks + out(PIC1_DATA, a1); + out(PIC2_DATA, a2); +} + +void set_tss_kernel_current_stack(void) { + uint32_t stack_ptr; + // Reading base stack frame instead esp + __asm__ volatile ("mov %%ebp, %0": "=r"(stack_ptr) : /* */); + // Add 8 because 4 for ret address and other 4 is for stack_ptr variable + _interrupt_tss_entry.esp0 = stack_ptr + 8; +} + +void syscall(struct CPURegister cpu, __attribute__((unused)) struct InterruptStack info) { + if (cpu.eax == 0) { + struct FAT32DriverRequest request = *(struct FAT32DriverRequest*) cpu.ebx; + *((int8_t*) cpu.ecx) = read(request); + } else if (cpu.eax == 1) { + struct FAT32DriverRequest request = *(struct FAT32DriverRequest*) cpu.ebx; + *((int8_t*) cpu.ecx) = read_directory(request); + } else if (cpu.eax == 2) { + struct FAT32DriverRequest request = *(struct FAT32DriverRequest*) cpu.ebx; + *((int8_t*) cpu.ecx) = write(request); + } else if (cpu.eax == 3) { + struct FAT32DriverRequest request = *(struct FAT32DriverRequest*) cpu.ebx; + *((int8_t*) cpu.ecx) = delete(request); + } else if (cpu.eax == 4) { + keyboard_state_activate(); + __asm__("sti"); // Due IRQ is disabled when main_interrupt_handler() called + while (is_keyboard_blocking()); + char buf[KEYBOARD_BUFFER_SIZE]; + get_keyboard_buffer(buf); + memcpy((char *) cpu.ebx, buf, cpu.ecx); + } else if (cpu.eax == 5) { + puts((char *) cpu.ebx, cpu.ecx, cpu.edx); + } else if (cpu.eax == 6) { + // Interrupt No.6 : to get directory table of a directory + // eax = interrupt number + // ebx = pointer to FAT32DirectoryTable struct + // ecx = directory cluster number + // edx = unused + read_clusters((struct FAT32DirectoryTable*) cpu.ebx, cpu.ecx, 1); + } else if (cpu.eax == 7) { + // Interrupt No.7 : to clear page + // No register used + framebuffer_clear(); + reset_keyboard_position(); + } +} + +void main_interrupt_handler(__attribute__((unused)) struct CPURegister cpu, uint32_t int_number, __attribute__((unused)) struct InterruptStack info) { + switch (int_number) { + case PAGE_FAULT : + __asm__("hlt"); + break; + case PIC1 + IRQ_KEYBOARD: + keyboard_isr(); + break; + case 0x30: + syscall(cpu, info); + break; + } +} \ No newline at end of file diff --git a/src/interrupt/interrupt.h b/src/interrupt/interrupt.h new file mode 100644 index 0000000..96be960 --- /dev/null +++ b/src/interrupt/interrupt.h @@ -0,0 +1,141 @@ +#ifndef _INTERRUPT_H +#define _INTERRUPT_H + +#include "std/stdtype.h" +#include "portio/portio.h" + +/* -- PIC constants -- */ + +// PIC interrupt offset +#define PIC1_OFFSET 0x20 +#define PIC2_OFFSET 0x28 + +// PIC ports +#define PIC1 0x20 +#define PIC2 0xA0 +#define PIC1_COMMAND PIC1 +#define PIC1_DATA (PIC1 + 1) +#define PIC2_COMMAND PIC2 +#define PIC2_DATA (PIC2 + 1) + +// PIC ACK & mask constant +#define PIC_ACK 0x20 +#define PIC_DISABLE_ALL_MASK 0xFF + +// PIC remap constants +#define ICW1_ICW4 0x01 /* ICW4 (not) needed */ +#define ICW1_SINGLE 0x02 /* Single (cascade) mode */ +#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */ +#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */ +#define ICW1_INIT 0x10 /* Initialization - required! */ + +#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */ +#define ICW4_AUTO 0x02 /* Auto (normal) EOI */ +#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */ +#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */ +#define ICW4_SFNM 0x10 /* Special fully nested (not) */ + +/* -- PICs IRQ list -- */ + +// PIC Master +#define IRQ_TIMER 0 +#define IRQ_KEYBOARD 1 +#define IRQ_CASCADE 2 +#define IRQ_COM2 3 +#define IRQ_COM1 4 +#define IRQ_LPT2 5 +#define IRQ_FLOPPY_DISK 6 +#define IRQ_LPT1_SPUR 7 + +// PIC Slave +#define IRQ_CMOS 8 +#define IRQ_PERIPHERAL_1 9 +#define IRQ_PERIPHERAL_2 10 +#define IRQ_PERIPHERAL_3 11 +#define IRQ_MOUSE 12 +#define IRQ_FPU 13 +#define IRQ_PRIMARY_ATA 14 +#define IRQ_SECOND_ATA 15 + +#define PAGE_FAULT 0xe + +extern struct TSSEntry _interrupt_tss_entry; + +/** + * TSSEntry, Task State Segment. Used when jumping back to ring 0 / kernel + */ +struct TSSEntry { + uint32_t prev_tss; // Previous TSS + uint32_t esp0; // Stack pointer to load when changing to kernel mode + uint32_t ss0; // Stack segment to load when changing to kernel mode + // Unused variables + uint32_t unused_register[23]; +} __attribute__((packed)); + +// Set kernel stack in TSS +void set_tss_kernel_current_stack(void); + +/** + * CPURegister, store CPU registers that can be used for interrupt handler / ISRs + * + * @param gp_register CPU general purpose register (a, b, c, d) + * @param stack_register CPU stack register (bp, sp) + */ +struct CPURegister { + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t ebp; + uint32_t esp; +} __attribute__((packed)); + +/** + * InterruptInfo, data pushed by CPU when interrupt / exception is raised. + * Refer to Intel x86 Vol 3a: Figure 6-4 Stack usage on transfer to Interrupt. + * + * Note, when returning from interrupt handler with iret, esp must be pointing to eip pushed before + * or in other words, CPURegister, int_number and error_code should be pop-ed from stack. + * + * @param error_code Error code that pushed with the exception + * @param eip Instruction pointer where interrupt is raised + * @param cs Code segment selector where interrupt is raised + * @param eflags CPU eflags register when interrupt is raised + */ +struct InterruptStack { + uint32_t error_code; + uint32_t eip; + uint32_t cs; + uint32_t eflags; +} __attribute__((packed)); + +// Activate PIC mask for keyboard only +void activate_keyboard_interrupt(void); + +// Deactivate PIC mask for keyboard only +void deactivate_keyboard_interrupt(void); + +// I/O port wait, around 1-4 microsecond, for I/O synchronization purpose +void io_wait(void); + +// Send ACK to PIC - @param irq Interrupt request number destination, note: this function already include PIC1_OFFSET +void pic_ack(uint8_t irq); + +// Shift PIC interrupt number to PIC1_OFFSET and PIC2_OFFSET (master and slave) +void pic_remap(void); + +/** + * Main interrupt handler when any interrupt / exception is raised. + * Do not call this function normally. + * + * This function will be called first if any INT 0x00 - 0x40 is raised, + * and will call proper ISR for respective interrupt / exception. + * + * Again, this function is not for normal function call, all parameter will be automatically set when interrupt is called. + * @param cpu CPU register when interrupt is raised + * @param int_number Interrupt number that trigger interrupt exception + * @param info Information about interrupt that pushed automatically by CPU + */ +void main_interrupt_handler(struct CPURegister cpu, uint32_t int_number, struct InterruptStack info); + +#endif \ No newline at end of file diff --git a/src/interrupt/intsetup.s b/src/interrupt/intsetup.s new file mode 100644 index 0000000..6a96d6a --- /dev/null +++ b/src/interrupt/intsetup.s @@ -0,0 +1,122 @@ +extern main_interrupt_handler +global isr_stub_table + +; Generic handler section for interrupt +call_generic_handler: + ; Before interrupt_handler_n is called (caller of this generic handler section), + ; stack will have these value that pushed automatically by CPU + ; [esp + 12] eflags + ; [esp + 8 ] cs + ; [esp + 4 ] eip + ; [esp + 0 ] error code + + ; CPURegister + push esp + push ebp + push edx + push ecx + push ebx + push eax + + ; call the C function + call main_interrupt_handler + + ; restore the registers + pop eax + pop ebx + pop ecx + pop edx + pop ebp + pop esp + + ; restore the esp (interrupt number & error code) + add esp, 8 + + ; return to the code that got interrupted + ; at this point, stack should be structured like this + ; [esp], [esp+4], [esp+8] + ; eip, cs, eflags + ; improper value will cause invalid return address & register + sti + iret + +; Macro for creating interrupt handler that only push interrupt number +%macro no_error_code_interrupt_handler 1 +interrupt_handler_%1: + push dword 0 ; push 0 as error code + push dword %1 ; push the interrupt number + jmp call_generic_handler ; jump to the common handler +%endmacro + +%macro error_code_interrupt_handler 1 +interrupt_handler_%1: + push dword %1 + jmp call_generic_handler +%endmacro + +; CPU exception handlers +no_error_code_interrupt_handler 0 ; 0x0 - Division by zero +no_error_code_interrupt_handler 1 ; 0x1 - Debug Exception +no_error_code_interrupt_handler 2 ; 0x2 - NMI, Non-Maskable Interrupt +no_error_code_interrupt_handler 3 ; 0x3 - Breakpoint Exception +no_error_code_interrupt_handler 4 ; 0x4 - INTO Overflow +no_error_code_interrupt_handler 5 ; 0x5 - Out of Bounds +no_error_code_interrupt_handler 6 ; 0x6 - Invalid Opcode +no_error_code_interrupt_handler 7 ; 0x7 - Device Not Available +error_code_interrupt_handler 8 ; 0x8 - Double Fault +no_error_code_interrupt_handler 9 ; 0x9 - Deprecated +error_code_interrupt_handler 10 ; 0xA - Invalid TSS +error_code_interrupt_handler 11 ; 0xB - Segment Not Present +error_code_interrupt_handler 12 ; 0xC - Stack-Segment Fault +error_code_interrupt_handler 13 ; 0xD - General Protection Fault +error_code_interrupt_handler 14 ; 0xE - Page Fault +no_error_code_interrupt_handler 15 ; 0xF - Reserved +no_error_code_interrupt_handler 16 ; 0x10 - x87 Floating-Point Exception +error_code_interrupt_handler 17 ; 0x11 - Alignment Check Exception +no_error_code_interrupt_handler 18 ; 0x12 - Machine Check Exception +no_error_code_interrupt_handler 19 ; 0x13 - SIMD Floating-Point Exception +no_error_code_interrupt_handler 20 ; 0x14 - Virtualization Exception +no_error_code_interrupt_handler 21 ; 0x15 - Control Protection Exception +no_error_code_interrupt_handler 22 ; 0x16 - Reserved +no_error_code_interrupt_handler 23 ; 0x17 - Reserved +no_error_code_interrupt_handler 24 ; 0x18 - Reserved +no_error_code_interrupt_handler 25 ; 0x19 - Reserved +no_error_code_interrupt_handler 26 ; 0x1A - Reserved +no_error_code_interrupt_handler 27 ; 0x1B - Reserved +no_error_code_interrupt_handler 28 ; 0x1C - Hypervisor Injection Exception +no_error_code_interrupt_handler 29 ; 0x1D - VMM Communication Exception +error_code_interrupt_handler 30 ; 0x1E - Security Exception +no_error_code_interrupt_handler 31 ; 0x1F - Reserved + +; User defined interrupt handler +; Assuming PIC1 & PIC2 offset is 0x20 and 0x28 +; 32 - 0x20 - IRQ0: Programmable Interval Timer +; 33 - 0x21 - IRQ1: Keyboard +; 34 - 0x22 - IRQ2: PIC Cascade, used internally +; 35 - 0x23 - IRQ3: COM2, if enabled +; 36 - 0x24 - IRQ4: COM1, if enabled +; 37 - 0x25 - IRQ5: LPT2, if enabled +; 38 - 0x26 - IRQ6: Floppy Disk +; 39 - 0x27 - IRQ7: LPT1 + +; 40 - 0x28 - IRQ8: CMOS real-time clock +; 41 - 0x29 - IRQ9: Free +; 42 - 0x2A - IRQ10: Free +; 43 - 0x2B - IRQ11: Free +; 44 - 0x2C - IRQ12: PS2 Mouse +; 45 - 0x2D - IRQ13: Coprocessor +; 46 - 0x2E - IRQ14: Primary ATA Hard Disk +; 47 - 0x2F - IRQ15: Secondary ATA Hard Disk +%assign i 32 +%rep 32 +no_error_code_interrupt_handler i +%assign i i+1 +%endrep + +; ISR stub table, useful for reducing code repetition +isr_stub_table: + %assign i 0 + %rep 64 + dd interrupt_handler_%+i + %assign i i+1 + %endrep \ No newline at end of file diff --git a/src/kernel.c b/src/kernel.c deleted file mode 100644 index 01b5a07..0000000 --- a/src/kernel.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "lib-header/portio.h" -#include "lib-header/stdtype.h" -#include "lib-header/stdmem.h" -#include "lib-header/gdt.h" -#include "lib-header/framebuffer.h" -#include "lib-header/kernel_loader.h" - -void kernel_setup(void) { - uint32_t a; - uint32_t volatile b = 0x0000BABE; - __asm__("mov $0xCAFE0000, %0" : "=r"(a)); - while (TRUE) b += 1; -} \ No newline at end of file diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c new file mode 100644 index 0000000..05d9a03 --- /dev/null +++ b/src/kernel/kernel.c @@ -0,0 +1,58 @@ +#include "portio/portio.h" +#include "std/stdtype.h" +#include "std/stdmem.h" +#include "std/string.h" +#include "gdt/gdt.h" +#include "framebuffer/framebuffer.h" +#include "kernel_loader.h" +#include "interrupt/interrupt.h" +#include "interrupt/idt.h" +#include "filesystem/fat32.h" +#include "keyboard/keyboard.h" +#include "paging/paging.h" +#include "kernel_loader.h" + +void kernel_setup(void) { + enter_protected_mode(&_gdt_gdtr); + pic_remap(); + initialize_idt(); + activate_keyboard_interrupt(); + framebuffer_clear(); + framebuffer_set_cursor(0, 0); + initialize_filesystem_fat32(); + gdt_install_tss(); + set_tss_register(); + + // Allocate first 4 MiB virtual memory + allocate_single_user_page_frame((uint8_t*) 0); + + // Write shell into memory + struct FAT32DriverRequest request = { + .buf = (uint8_t*) 0, + .name = "shell", + .ext = "\0\0\0", + .parent_cluster_number = ROOT_CLUSTER_NUMBER, + .buffer_size = 0x100000, + }; + + read(request); + struct ClusterBuffer cbuf[2]; + memcpy(cbuf, "Yami wo haratte yami wo haratte\nYoru no tobari ga oritara aizu da\nAitai shite mawaru kanjousen\nZaregoto nado wa hakisute ike to\n", 132); + request.buf = cbuf; + clear(&request.name, 8); + clear(&request.ext, 3); + memcpy(&request.name, "kaikai", 6); + memcpy(&request.ext, "txt", 3); + request.buffer_size = CLUSTER_SIZE; + write(request); + clear(&request.name, 8); + memcpy(cbuf, "Ini dari file lain\n", 20); + memcpy(&request.name, "lain", 4); + write(request); + + // Set TSS $esp pointer and jump into shell + set_tss_kernel_current_stack(); + kernel_execute_user_program((uint8_t*) 0); + + while (TRUE); +} \ No newline at end of file diff --git a/src/kernel/kernel_loader.h b/src/kernel/kernel_loader.h new file mode 100644 index 0000000..1e94674 --- /dev/null +++ b/src/kernel/kernel_loader.h @@ -0,0 +1,35 @@ +#include "std/stdtype.h" + +#ifndef _KERNEL_LOADER +#define _KERNEL_LOADER + +// Optional linker variable : Pointing to kernel start & end address +// Note : Use & operator, example : a = (uint32_t) &_linker_kernel_stack_top; +extern uint32_t _linker_kernel_virtual_addr_start; +extern uint32_t _linker_kernel_virtual_addr_end; +extern uint32_t _linker_kernel_physical_addr_start; +extern uint32_t _linker_kernel_physical_addr_end; +extern uint32_t _linker_kernel_stack_top; + +/** + * Load GDT from gdtr and launch protected mode. This function defined in asm source code. + * + * @param gdtr Pointer to already defined & initialized GDTR + * @warning Invalid address / definition of GDT will cause bootloop after calling this function. + */ +extern void enter_protected_mode(struct GDTR *gdtr); + +/** + * Execute user program from kernel, one way jump. This function is defined in asm source code. + * + * @param virtual_addr Pointer into user program that already in memory + * @warning Assuming pointed memory is properly loaded + */ +extern void kernel_execute_user_program(void *virtual_addr); + +/** + * Set the tss register pointing to GDT_TSS_SELECTOR with ring 0 + */ +extern void set_tss_register(void); // Note : Already implemented in kernel_loader.asm + +#endif \ No newline at end of file diff --git a/src/kernel/kernel_loader.s b/src/kernel/kernel_loader.s new file mode 100644 index 0000000..699edee --- /dev/null +++ b/src/kernel/kernel_loader.s @@ -0,0 +1,110 @@ +global loader ; the entry symbol for ELF +global enter_protected_mode ; go to protected mode +global set_tss_register ; set tss register to GDT entry +global kernel_execute_user_program ; execute user program from kernel +extern kernel_setup ; kernel C entrypoint +extern _paging_kernel_page_directory ; kernel page directory + +KERNEL_VIRTUAL_BASE equ 0xC0000000 ; kernel virtual memory +KERNEL_STACK_SIZE equ 2097152 ; size of stack in bytes +MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constant +FLAGS equ 0x0 ; multiboot flags +CHECKSUM equ -MAGIC_NUMBER ; calculate the checksum + ; (magic number + checksum + flags should equal 0) + + +section .bss +align 4 ; align at 4 bytes +kernel_stack: ; label points to beginning of memory + resb KERNEL_STACK_SIZE ; reserve stack for the kernel + + +section .multiboot ; GRUB multiboot header +align 4 ; the code must be 4 byte aligned + dd MAGIC_NUMBER ; write the magic number to the machine code, + dd FLAGS ; the flags, + dd CHECKSUM ; and the checksum + + + +section .setup.text ; start of the text (code) section +loader equ (loader_entrypoint - KERNEL_VIRTUAL_BASE) +loader_entrypoint: ; the loader label (defined as entry point in linker script) + ; Set CR3 (CPU page register) + mov eax, _paging_kernel_page_directory - KERNEL_VIRTUAL_BASE + mov cr3, eax + + ; Use 4 MB paging + mov eax, cr4 + or eax, 0x00000010 ; PSE (4 MB paging) + mov cr4, eax + + ; Enable paging + mov eax, cr0 + or eax, 0x80000000 ; PG flag + mov cr0, eax + + ; Jump into higher half first, cannot use C because call stack is still not working + lea eax, [loader_virtual] + jmp eax + +loader_virtual: + mov dword [_paging_kernel_page_directory], 0 + invlpg [0] ; Delete identity mapping and invalidate TLB cache for first page + mov esp, kernel_stack + KERNEL_STACK_SIZE ; Setup stack register to proper location + call kernel_setup +.loop: + jmp .loop ; loop forever + + + +section .text +; More details: https://en.wikibooks.org/wiki/X86_Assembly/Protected_Mode +enter_protected_mode: + ; Load GDT from GDTDescriptor + cli + mov eax, [esp+4] + lgdt [eax] + + ; Set Protection Enable bit-flag in Control Register 0 (CR0) + ; Or in other words: Switch to protected mode + mov eax, cr0 + or eax, 1 + mov cr0, eax + + ; Far jump to update cs register + ; Warning: Invalid GDT will raise exception in any instruction below + jmp 0x8:flush_cs +flush_cs: + ; Update all segment register + mov ax, 10h + mov ss, ax + mov ds, ax + mov es, ax + + ret + +set_tss_register: + mov ax, 0x28 | 0 ; GDT TSS Selector, ring 0 + ltr ax + ret + +kernel_execute_user_program: + mov eax, 0x20 | 0x3 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + mov ecx, [esp+4] ; Save this first (before pushing anything to stack) for last push + push eax ; Stack segment selector (GDT_USER_DATA_SELECTOR), user privilege + mov eax, ecx + add eax, 0x400000 - 4 + push eax ; User space stack pointer (esp), move it into last 4 MiB + pushf ; eflags register state, when jump inside user program + mov eax, 0x18 | 0x3 + push eax ; Code segment selector (GDT_USER_CODE_SELECTOR), user privilege + mov eax, ecx + push eax ; eip register to jump back + + iret \ No newline at end of file diff --git a/src/kernel_loader.s b/src/kernel_loader.s deleted file mode 100644 index 20445c0..0000000 --- a/src/kernel_loader.s +++ /dev/null @@ -1,54 +0,0 @@ -global loader ; the entry symbol for ELF -global enter_protected_mode ; go to protected mode -extern kernel_setup ; kernel - - - -KERNEL_STACK_SIZE equ 4096 ; size of stack in bytes -MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constant -FLAGS equ 0x0 ; multiboot flags -CHECKSUM equ -MAGIC_NUMBER ; calculate the checksum - ; (magic number + checksum + flags should equal 0) - -section .bss -align 4 ; align at 4 bytes -kernel_stack: ; label points to beginning of memory - resb KERNEL_STACK_SIZE ; reserve stack for the kernel - -section .text ; start of the text (code) section -align 4 ; the code must be 4 byte aligned - dd MAGIC_NUMBER ; write the magic number to the machine code, - dd FLAGS ; the flags, - dd CHECKSUM ; and the checksum - - - -loader: ; the loader label (defined as entry point in linker script) - mov esp, kernel_stack + KERNEL_STACK_SIZE ; setup stack register to proper location - call kernel_setup -.loop: - jmp .loop ; loop forever - - -; More details: https://en.wikibooks.org/wiki/X86_Assembly/Protected_Mode -enter_protected_mode: - cli - mov eax, [esp+4] - ; TODO: Load GDT from GDTDescriptor - ; eax at this line will carry GDTR location, dont forget to use square bracket [eax] - - mov eax, cr0 - ; TODO: Set bit-0 (Protection Enable bit-flag) in Control Register 0 (CR0) - ; Set eax with above condition, eax will be copied to CR0 with next instruction - mov cr0, eax - - ; Far jump to update cs register - ; Warning: Invalid GDT will raise exception in any instruction below - jmp 0x8:flush_cs -flush_cs: - mov ax, 10h - ; TODO: Set all data segment register with 0x10 - ; Segments register need to set with 0x10: ss, ds, es - mov ss, ax - - ret diff --git a/src/keyboard/keyboard.c b/src/keyboard/keyboard.c new file mode 100644 index 0000000..0dd343c --- /dev/null +++ b/src/keyboard/keyboard.c @@ -0,0 +1,123 @@ +#include "keyboard.h" +#include "portio/portio.h" +#include "framebuffer/framebuffer.h" +#include "std/stdmem.h" +#include "interrupt/interrupt.h" +#include "std/string.h" + +// Global variable declarations +struct KeyboardDriverState keyboard_state; +char buff[500]; + +// Declare 2 static variables to determine rows and cols +static int keyboard_buffer_write_pos = 0; +static int keyboard_buffer_read_pos = 0; + +// Character list of elements +const char keyboard_scancode_1_to_ascii_map[256] = { + 0, 0x1B, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', 0, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// Activate keyboard ISR / start listen keyboard & save to buffer +void keyboard_state_activate(void) { + if (!keyboard_state.keyboard_input_on) { + activate_keyboard_interrupt(); + keyboard_state.keyboard_input_on = TRUE; + } +} + +// Deactivate keyboard ISR / stop listening keyboard interrupt +void keyboard_state_deactivate(void) { + if (keyboard_state.keyboard_input_on) { + deactivate_keyboard_interrupt(); + keyboard_state.keyboard_input_on = FALSE; + } +} + +// Get keyboard buffer values - @param buf Pointer to char buffer, recommended size at least KEYBOARD_BUFFER_SIZE +void get_keyboard_buffer(char *buf) { + memcpy(buf, buff, strlen(buff)); + *(buf + strlen(buff))= '\n'; +} + +// Check whether keyboard ISR is active or not - @return Equal with keyboard_input_on value +bool is_keyboard_blocking(void) { + return keyboard_state.keyboard_input_on; +} + +void keyboard_isr(void) { + // read_pos = baris, write_pos = kolom + if (!keyboard_state.keyboard_input_on) { + keyboard_state.buffer_index = 0; + } else { + uint8_t scancode = in (KEYBOARD_DATA_PORT); + char mapped_char = keyboard_scancode_1_to_ascii_map[scancode]; + if (mapped_char == '\b' && keyboard_state.buffer_index > 0) { + keyboard_state.buffer_index--; + keyboard_buffer_write_pos--; + framebuffer_write(keyboard_buffer_read_pos, keyboard_buffer_write_pos, ' ', 0x0, 0x0); + framebuffer_set_cursor(keyboard_buffer_read_pos, keyboard_buffer_write_pos); + } else if (mapped_char == '\b' && keyboard_state.buffer_index == 0) { + framebuffer_write(keyboard_buffer_read_pos, keyboard_buffer_write_pos, ' ', 0x0, 0x0); + framebuffer_set_cursor(keyboard_buffer_read_pos, keyboard_buffer_write_pos); + } else if (mapped_char != 0 && keyboard_state.buffer_index < KEYBOARD_BUFFER_SIZE-1) { + keyboard_state.keyboard_buffer[keyboard_state.buffer_index] = mapped_char; + keyboard_state.buffer_index++; + framebuffer_write(keyboard_buffer_read_pos, keyboard_buffer_write_pos, mapped_char, 0xF, 0x0); + keyboard_buffer_write_pos++; + framebuffer_set_cursor(keyboard_buffer_read_pos, keyboard_buffer_write_pos); + } + + if (mapped_char == '\n') { + // Copy to temp buff + // Processing empty the buffer + keyboard_state.keyboard_buffer[keyboard_state.buffer_index] = '\0'; + memcpy(buff, keyboard_state.keyboard_buffer, keyboard_state.buffer_index); + keyboard_state.buffer_index = 0; + keyboard_buffer_write_pos--; + framebuffer_write(keyboard_buffer_read_pos, keyboard_buffer_write_pos, ' ', 0x0, 0x0); + keyboard_buffer_read_pos++; + keyboard_buffer_write_pos = 0; + framebuffer_set_cursor(keyboard_buffer_read_pos, keyboard_buffer_write_pos); + + keyboard_state_deactivate(); + } + } + pic_ack(IRQ_KEYBOARD); +} + +// Reset the keyboard position, back to start +void reset_keyboard_position() { + keyboard_buffer_read_pos = 0; + keyboard_buffer_write_pos = 0; +} + +void puts(char *buf, int count, uint8_t color) { + for (int i = 0; i < count; i++) { + if (buf[i] == '\n') { + keyboard_buffer_read_pos++; + keyboard_buffer_write_pos = 0; + framebuffer_set_cursor(keyboard_buffer_read_pos, keyboard_buffer_write_pos); + } else { + framebuffer_write(keyboard_buffer_read_pos, keyboard_buffer_write_pos, buf[i], color, 0x0); + keyboard_buffer_write_pos++; + } + } + framebuffer_set_cursor(keyboard_buffer_read_pos, keyboard_buffer_write_pos); +} \ No newline at end of file diff --git a/src/keyboard/keyboard.h b/src/keyboard/keyboard.h new file mode 100644 index 0000000..8f50c87 --- /dev/null +++ b/src/keyboard/keyboard.h @@ -0,0 +1,75 @@ +#ifndef _USER_ISR_H +#define _USER_ISR_H + +#include "interrupt/interrupt.h" +#include "std/stdtype.h" + +#define EXT_SCANCODE_UP 0x48 +#define EXT_SCANCODE_DOWN 0x50 +#define EXT_SCANCODE_LEFT 0x4B +#define EXT_SCANCODE_RIGHT 0x4D + +#define KEYBOARD_DATA_PORT 0x60 +#define EXTENDED_SCANCODE_BYTE 0xE0 + +#define KEYBOARD_BUFFER_SIZE 256 + +/** + * keyboard_scancode_1_to_ascii_map[256], Convert scancode values that correspond to ASCII printables + * How to use this array: ascii_char = k[scancode] + * + * By default, QEMU using scancode set 1 (from empirical testing) + */ +extern const char keyboard_scancode_1_to_ascii_map[256]; + +/** + * KeyboardDriverState - Contain all driver states + * + * @param read_extended_mode Optional, can be used for signaling next read is extended scancode (ex. arrow keys) + * @param keyboard_input_on Indicate whether keyboard ISR is activated or not + * @param buffer_index Used for keyboard_buffer index + * @param keyboard_buffer Storing keyboard input values in ASCII + */ +struct KeyboardDriverState { + bool read_extended_mode; + bool keyboard_input_on; + uint8_t buffer_index; + char keyboard_buffer[KEYBOARD_BUFFER_SIZE]; +} __attribute((packed)); + +/* -- Driver Interfaces -- */ + +// Activate keyboard ISR / start listen keyboard & save to buffer +void keyboard_state_activate(void); + +// Deactivate keyboard ISR / stop listening keyboard interrupt +void keyboard_state_deactivate(void); + +// Get keyboard buffer values - @param buf Pointer to char buffer, recommended size at least KEYBOARD_BUFFER_SIZE +void get_keyboard_buffer(char *buf); + +// Check whether keyboard ISR is active or not - @return Equal with keyboard_input_on value +bool is_keyboard_blocking(void); + +/* -- Keyboard Interrupt Service Routine -- */ + +/** + * Handling keyboard interrupt & process scancodes into ASCII character. + * Will start listen and process keyboard scancode if keyboard_input_on. + * + * Will only print printable character into framebuffer. + * Stop processing when enter key (line feed) is pressed. + * + * Note that, with keyboard interrupt & ISR, keyboard reading is non-blocking. + * This can be made into blocking input with `while (is_keyboard_blocking());` + * after calling `keyboard_state_activate();` + */ +void keyboard_isr(void); + +// Reset the keyboard position, back to start +void reset_keyboard_position(); + +// Modified puts() on kernel side +void puts(char *buf, int count, uint8_t color); + +#endif \ No newline at end of file diff --git a/src/lib-header/gdt.h b/src/lib-header/gdt.h deleted file mode 100644 index 8071183..0000000 --- a/src/lib-header/gdt.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef _GDT_H -#define _GDT_H - -#include "lib-header/stdtype.h" - -#define GDT_MAX_ENTRY_COUNT 32 - -extern struct GDTR _gdt_gdtr; - -/** - * Segment Descriptor storing system segment information. - * Struct defined exactly as Intel Manual Segment Descriptor definition (Figure 3-8 Segment Descriptor). - * Manual can be downloaded at www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html/ - * - * @param segment_low 16-bit lower-bit segment limit - * @param base_low 16-bit lower-bit base address - * @param base_mid 8-bit middle-bit base address - * @param type_bit 4-bit contain type flags - * @param non_system 1-bit contain system - */ -struct SegmentDescriptor { - // First 32-bit - uint16_t segment_low; - uint16_t base_low; - - // Next 16-bit (Bit 32 to 47) - uint8_t base_mid; - uint8_t type_bit : 4; - uint8_t non_system : 1; - // TODO : Continue GDT definition - -} __attribute__((packed)); - -/** - * Global Descriptor Table containing list of segment descriptor. One GDT already defined in memory.c. - * More details at https://wiki.osdev.org/GDT_Tutorial - * @param table Fixed-width array of SegmentDescriptor with size GDT_MAX_ENTRY_COUNT - */ -struct GlobalDescriptorTable { - struct SegmentDescriptor table[GDT_MAX_ENTRY_COUNT]; -} __attribute__((packed)); - -/** - * GDTR, carrying information where's the GDT located and GDT size. - * Global kernel variable defined at memory.c. - * - * @param size Global Descriptor Table size, use sizeof operator - * @param address GDT address, GDT should already defined properly - */ -struct GDTR { - uint16_t size; - struct GlobalDescriptorTable *address; -} __attribute__((packed)); - -#endif \ No newline at end of file diff --git a/src/lib-header/kernel_loader.h b/src/lib-header/kernel_loader.h deleted file mode 100644 index 65ae26e..0000000 --- a/src/lib-header/kernel_loader.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _KERNEL_LOADER -#define _KERNEL_LOADER - -/** - * Load GDT from gdtr and launch protected mode. This function defined in asm source code. - * - * @param gdtr Pointer to already defined & initialized GDTR - * @warning Invalid address / definition of GDT will cause bootloop after calling this function. - */ -extern void enter_protected_mode(struct GDTR *gdtr); - -#endif \ No newline at end of file diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..cd121cd --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,48 @@ +ENTRY(loader) /* the name of the entry label */ + +/* relocation on address at 0xC001 0000, but load address (physical location) still at 0x100000 */ +SECTIONS { + . = 0xC0100000; /* use relocation address (memory references) at 0xC010 0000 */ + + /* Optional variable that can be used in kernel, starting address of kernel */ + _linker_kernel_virtual_addr_start = .; + _linker_kernel_physical_addr_start = . - 0xC0000000; + + .multiboot ALIGN (0x1000) : AT (ADDR (.multiboot) - 0xC0000000) + { + *(.multiboot) /* put GRUB multiboot header at front */ + } + + .setup.text ALIGN (0x1000) : AT (ADDR (.setup.text) - 0xC0000000) + { + *(.setup.text) /* initial setup code */ + } + + .text ALIGN (0x1000) : AT (ADDR (.text) - 0xC0000000) + { + *(.text) /* all text sections from all files */ + } + + .rodata ALIGN (0x1000) : AT (ADDR (.rodata) - 0xC0000000) + { + *(.rodata*) /* all read-only data sections from all files */ + } + + .data ALIGN (0x1000) : AT (ADDR (.data) - 0xC0000000) + { + *(.data) /* all data sections from all files */ + } + + .bss ALIGN (0x1000) : AT (ADDR (.bss) - 0xC0000000) + { + *(COMMON) /* all COMMON sections from all files */ + *(.bss) /* all bss sections from all files */ + bin/kernel_loader.o(.bss) + _linker_kernel_stack_top = .; + } + + /* Optional variable that can be used in kernel, show end address of kernel */ + _linker_kernel_virtual_addr_end = .; + _linker_kernel_physical_addr_end = . - 0xC0000000; +} + diff --git a/src/menu.lst b/src/menu.lst new file mode 100644 index 0000000..a3828c9 --- /dev/null +++ b/src/menu.lst @@ -0,0 +1,5 @@ +default 0 +timeout 0 + +title os +kernel /boot/kernel \ No newline at end of file diff --git a/src/paging/paging.c b/src/paging/paging.c new file mode 100644 index 0000000..2d74df3 --- /dev/null +++ b/src/paging/paging.c @@ -0,0 +1,56 @@ +#include "paging.h" + +__attribute__((aligned(0x1000))) struct PageDirectory _paging_kernel_page_directory = { + .table = { + [0] = { + .flag.present_bit = 1, + .flag.write_bit = 1, + .lower_address = 0, + .flag.use_pagesize_4_mb = 1, + }, + [0x300] = { + .flag.present_bit = 1, + .flag.write_bit = 1, + .lower_address = 0, + .flag.use_pagesize_4_mb = 1, + }, + } +}; + +static struct PageDriverState page_driver_state = { + .last_available_physical_addr = (uint8_t*) 0 + PAGE_FRAME_SIZE, +}; + +void update_page_directory(void *physical_addr, void *virtual_addr, struct PageDirectoryEntryFlag flag) { + uint32_t page_index = ((uint32_t) virtual_addr >> 22) & 0x3FF; + + _paging_kernel_page_directory.table[page_index].flag = flag; + _paging_kernel_page_directory.table[page_index].lower_address = ((uint32_t)physical_addr >> 22) & 0x3FF; + flush_single_tlb(virtual_addr); +} + +int8_t allocate_single_user_page_frame(void *virtual_addr) { + // Using default QEMU config (128 MiB max memory) + uint32_t last_physical_addr = (uint32_t) page_driver_state.last_available_physical_addr; + + // Flag page directory + struct PageDirectoryEntryFlag flag = { + .present_bit = 1, + .write_bit = 1, + .user_supervisor_bit = 1, + .use_pagesize_4_mb = 1, + .pwt_bit = 0, + .pcd_bit = 0, + .accessed_bit = 0, + .dirty_bit = 0 + }; + + // Alokasi page ke physical memory + update_page_directory((void*)last_physical_addr, virtual_addr, flag); + page_driver_state.last_available_physical_addr = (uint8_t*)(last_physical_addr + PAGE_FRAME_SIZE); + return 0; +} + +void flush_single_tlb(void *virtual_addr) { + asm volatile("invlpg (%0)" : /* */ : "b"(virtual_addr): "memory"); +} diff --git a/src/paging/paging.h b/src/paging/paging.h new file mode 100644 index 0000000..045f97c --- /dev/null +++ b/src/paging/paging.h @@ -0,0 +1,108 @@ +#ifndef _PAGING_H +#define _PAGING_H + +#include "std/stdtype.h" + +#define PAGE_ENTRY_COUNT 1024 +#define PAGE_FRAME_SIZE (4*1024*1024) + +// Operating system page directory, using page size PAGE_FRAME_SIZE (4 MiB) +extern struct PageDirectory _paging_kernel_page_directory; + +/** + * Page Directory Entry Flag, only first 8 bit + * + * @param present_bit Indicate whether this entry is exist or not + * @param write_bit Indicate whether writes may be allowed or not + * @param user_supervisor_bit Indicate whether user-mode accesses may be allowed or not + * @param pwt_bit Indicate the page-level write-through + * @param pcd_bit Indicate the page-level cache disable + * @param accessed_bit Indicate whether the entry has been accessed or not + * @param dirty_bit Indicate whether the entry has been written or not + * @param use_pagesize_4_mb Must be 1, otherwise the entry references to a page table + * ... + */ +struct PageDirectoryEntryFlag { + uint8_t present_bit : 1; + uint8_t write_bit : 1; + uint8_t user_supervisor_bit : 1; + uint8_t pwt_bit : 1; + uint8_t pcd_bit : 1; + uint8_t accessed_bit : 1; + uint8_t dirty_bit : 1; + uint8_t use_pagesize_4_mb : 1; +} __attribute__((packed)); + +/** + * Page Directory Entry, for page size 4 MB. + * Check Intel Manual 3a - Ch 4 Paging - Figure 4-4 PDE: 4MB page + * + * @param flag Contain 8-bit page directory entry flag + * @param global_page Is this page translation global (also cannot be flushed) + * @param ignored Ignored part + * @param pat_support Is PAT supported or not + * @param higher_address Bits 39:32 of address + * @param reserved Reserved part + * @param lower_address Bits 31:22 of address + * ... + */ +struct PageDirectoryEntry { + struct PageDirectoryEntryFlag flag; + uint16_t global_page : 1; + uint16_t ignored : 3; + uint16_t pat_support : 1; + uint16_t higher_address : 4; + uint16_t reserved : 5; + uint16_t lower_address : 10; +} __attribute__((packed)); + +/** + * Page Directory, contain array of PageDirectoryEntry. + * Note: This data structure not only can be manipulated by kernel, + * MMU operation, TLB hit & miss also affecting this data structure (dirty, accessed bit, etc). + * Warning: Address must be aligned in 4 KB (listed on Intel Manual), use __attribute__((aligned(0x1000))), + * unaligned definition of PageDirectory will cause triple fault + * + * @param table Fixed-width array of PageDirectoryEntry with size PAGE_ENTRY_COUNT + */ +struct PageDirectory { + struct PageDirectoryEntry table[PAGE_ENTRY_COUNT]; +} __attribute__((packed, aligned(0x1000))); + +/** + * Containing page driver states + * + * @param last_available_physical_addr Pointer to last empty physical addr (multiple of 4 MiB) + */ +struct PageDriverState { + uint8_t *last_available_physical_addr; +} __attribute__((packed)); + +/** + * update_page_directory, + * Edit _paging_kernel_page_directory with respective parameter + * + * @param physical_addr Physical address to map + * @param virtual_addr Virtual address to map + * @param flag Page entry flags + */ +void update_page_directory(void *physical_addr, void *virtual_addr, struct PageDirectoryEntryFlag flag); + +/** + * flush_single_tlb, + * invalidate page that contain virtual address in parameter + * + * @param virtual_addr Virtual address to flush + */ +void flush_single_tlb(void *virtual_addr); + +/** + * Allocate user memory into specified virtual memory address. + * Multiple call on same virtual address will unmap previous physical address and change it into new one. + * + * @param virtual_addr Virtual address to be mapped + * @return int8_t 0 success, -1 for failed allocation + */ +int8_t allocate_single_user_page_frame(void *virtual_addr); + +#endif \ No newline at end of file diff --git a/src/portio.c b/src/portio.c deleted file mode 100644 index 5f51edd..0000000 --- a/src/portio.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "lib-header/stdtype.h" -#include "lib-header/portio.h" - -/** x86 inb/outb: - * @param dx target port - * @param al input/output byte - */ - -void out(uint16_t port, uint8_t data) { - __asm__( - "outb %0, %1" - : // - : "a"(data), "Nd"(port) - ); -} - -uint8_t in(uint16_t port) { - uint8_t result; - __asm__ volatile( - "inb %1, %0" - : "=a"(result) - : "Nd"(port) - ); - return result; -} \ No newline at end of file diff --git a/src/portio/portio.c b/src/portio/portio.c new file mode 100644 index 0000000..a1a17b1 --- /dev/null +++ b/src/portio/portio.c @@ -0,0 +1,38 @@ +#include "std/stdtype.h" +#include "portio.h" + +void out16(uint16_t port, uint16_t data) { + __asm__( + "outw %0, %1" + : // + : "a"(data), "Nd"(port) + ); +} + +uint16_t in16(uint16_t port) { + uint16_t result; + __asm__ volatile( + "inw %1, %0" + : "=a"(result) + : "Nd"(port) + ); + return result; +} + +void out(uint16_t port, uint8_t data) { + __asm__( + "outb %0, %1" + : // + : "a"(data), "Nd"(port) + ); +} + +uint8_t in(uint16_t port) { + uint8_t result; + __asm__ volatile( + "inb %1, %0" + : "=a"(result) + : "Nd"(port) + ); + return result; +} \ No newline at end of file diff --git a/src/lib-header/portio.h b/src/portio/portio.h similarity index 81% rename from src/lib-header/portio.h rename to src/portio/portio.h index c4080b9..3314e07 100644 --- a/src/lib-header/portio.h +++ b/src/portio/portio.h @@ -1,7 +1,11 @@ #ifndef _PORTIO_H #define _PORTIO_H -#include "lib-header/stdtype.h" +#include "std/stdtype.h" + +void out16(uint16_t port, uint16_t data); + +uint16_t in16(uint16_t port); /** out: * Sends the given data to the given I/O port diff --git a/src/stdmem.c b/src/std/stdmem.c similarity index 89% rename from src/stdmem.c rename to src/std/stdmem.c index c96b48f..dd63547 100644 --- a/src/stdmem.c +++ b/src/std/stdmem.c @@ -1,5 +1,6 @@ -#include "lib-header/stdtype.h" -#include "lib-header/stdmem.h" +#include "stdtype.h" +#include "stdmem.h" +#include "string.h" void* memset(void *s, int c, size_t n) { uint8_t *buf = (uint8_t*) s; @@ -16,7 +17,7 @@ void* memcpy(void* restrict dest, const void* restrict src, size_t n) { return dstbuf; } -int memcmp(const void *s1, const void *s2, size_t n) { +int memcmp (const void *s1, const void *s2, size_t n) { const uint8_t *buf1 = (const uint8_t*) s1; const uint8_t *buf2 = (const uint8_t*) s2; for (size_t i = 0; i < n; i++) { diff --git a/src/lib-header/stdmem.h b/src/std/stdmem.h similarity index 97% rename from src/lib-header/stdmem.h rename to src/std/stdmem.h index a95fce1..10ecd1b 100644 --- a/src/lib-header/stdmem.h +++ b/src/std/stdmem.h @@ -1,7 +1,7 @@ #ifndef _STDMEM_H #define _STDMEM_H -#include "lib-header/stdtype.h" +#include "stdtype.h" /** * C standard memset, check man memset or diff --git a/src/lib-header/stdtype.h b/src/std/stdtype.h similarity index 100% rename from src/lib-header/stdtype.h rename to src/std/stdtype.h diff --git a/src/std/string.c b/src/std/string.c new file mode 100644 index 0000000..0099cb3 --- /dev/null +++ b/src/std/string.c @@ -0,0 +1,45 @@ +#include "string.h" + +void clear(void *pointer, size_t n) { + uint8_t *ptr = (uint8_t*) pointer; + for (size_t i = 0; i < n; i++) { + ptr[i] = 0x00; + } +} + +size_t strlen(char *string) { + size_t i = 0; + while (string[i] != '\0') + i++; + return i; +} + +// 0 sama, 1 beda +uint8_t strcmp(char *s1, char *s2) { + size_t i = 0; + if (strlen(s1) == strlen(s2)) { + while (s1[i] != '\0') { + if (s1[i] != s2[i]) + return 1; + i++; + } + return 0; + } + return 1; +} + +void strcpy(char *dst, char *src, int type) { + size_t i = 0; + clear(dst, strlen(dst)); + if (type == 1) { + while (src[i] != '\0') { + dst[i] = src[i]; + i++; + } + } else { + while (src[i] != 0xA) { + dst[i] = src[i]; + i++; + } + } +} \ No newline at end of file diff --git a/src/std/string.h b/src/std/string.h new file mode 100644 index 0000000..a527498 --- /dev/null +++ b/src/std/string.h @@ -0,0 +1,15 @@ +// Implementasi library pemrosesan string +#include "stdtype.h" + +#ifndef _STRINGH_ +#define _STRINGH_ + +void clear(void *pointer, size_t n); + +size_t strlen(char *string); + +uint8_t strcmp(char *s1, char *s2); + +void strcpy(char *dst, char *src, int type); + +#endif \ No newline at end of file diff --git a/src/user/cat.c b/src/user/cat.c new file mode 100644 index 0000000..9dfb1e5 --- /dev/null +++ b/src/user/cat.c @@ -0,0 +1,206 @@ +// File : cat.c +// Contains the implementation of functions needed to process cat command + +#include "cat.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" + +void showFiles (char* args_val, int (*args_info)[2], int args_pos) { + // Variables to keep track the currently visited directory + uint32_t search_directory_number = ROOT_CLUSTER_NUMBER; + char srcName[8] = {'\0','\0','\0','\0','\0','\0','\0','\0'}; + char srcExt[3] = {'\0','\0','\0'}; + + // Variables for parsing the arguments + int posName = (*(args_info + args_pos))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + args_pos))[0] + (*(args_info + args_pos))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool fileFound = FALSE; + bool directoryNotFound = FALSE; + + int errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_pos)) { + search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } else { + if (fileFound && index != posEndArgs) { + errorCode = 1; + directoryNotFound = TRUE; + fileFound = FALSE; + endOfArgs = TRUE; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + int i = 0; + + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + + if (i >= lenName) { + errorCode = 3; + endOfArgs = TRUE; + } else { + clear(srcName, 8); + clear(srcExt, 3); + + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + + if (i < lenName) { // Jika ada extension + memcpy(srcName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(srcExt, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(srcName, args_val + posName, lenName); + } + + entry_index = findEntryName(srcName); + + if (entry_index == -1) { + fileFound = TRUE; + } else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low);; + updateDirectoryTable(search_directory_number); + } else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(search_directory_number); + } else { + clear(srcName, 8); + clear(srcExt, 3); + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + memcpy(srcName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(srcExt, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(srcName, args_val + posName, lenName); + } + entry_index = findEntryName(srcName); + if (entry_index == -1) { + directoryNotFound = TRUE; + endOfArgs = TRUE; + } else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low);; + updateDirectoryTable(search_directory_number); + } else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (directoryNotFound) { + put("cat: '", BIOS_RED); + putn(args_val + (*(args_info + args_pos))[0], BIOS_RED, (*(args_info + args_pos))[1]); + switch (errorCode) { + case 1: + put("': Not a directory\n", BIOS_RED); + break; + case 3: + put("': Argument name is too long\n", BIOS_RED); + break; + default: + put("': No such file or directory\n", BIOS_RED); + break; + } + } else { + struct ClusterBuffer cl = {0}; + struct FAT32DriverRequest request = { + .buf = &cl, + .name = "\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = search_directory_number, + .buffer_size = 4 * CLUSTER_SIZE, + }; + + memcpy(&(request.name), srcName, 8); + memcpy(&(request.ext), srcExt, 3); + int32_t retcode; + + interrupt(0, (uint32_t) &request, (uint32_t) &retcode, 0x0); + if (retcode != 0) { + put("cat: '", BIOS_RED); + putn(args_val + (*(args_info + args_pos))[0], BIOS_RED, (*(args_info + args_pos))[1]); + switch (retcode) { + case 1: + put("': Is a directory\n", BIOS_RED); + break; + case 2: + put("': Buffer size is not enough\n", BIOS_RED); + break; + case 3: + put("': No such file or directory\n", BIOS_RED); + break; + } + } else { + put((char *) &cl, BIOS_BROWN); + } + } +} + +void cat (char* args_val, int (*args_info)[2], int args_count) { + if (args_count < 2) { + put("cat: missing operand\n", BIOS_RED); + } else { + for (int i = 1; i < args_count; i++) { + showFiles (args_val, args_info, i); + } + } +} \ No newline at end of file diff --git a/src/user/cat.h b/src/user/cat.h new file mode 100644 index 0000000..4a630ae --- /dev/null +++ b/src/user/cat.h @@ -0,0 +1,11 @@ +// File : cat.h +// Header for cat.c, contains the declaration of functions needed to process cat command + +#ifndef _CAT_H_ +#define _CAT_H_ + +void showFiles (char* args_val, int (*args_info)[2], int args_pos); + +void cat (char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file diff --git a/src/user/cd.c b/src/user/cd.c new file mode 100644 index 0000000..81c9ef1 --- /dev/null +++ b/src/user/cd.c @@ -0,0 +1,114 @@ +// File : cd.c +// Contains the implementation of functions needed to process cd command + +#include "cd.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" + +void cd(char* args_val, int (*args_info)[2], int args_count) { + if (args_count > 2) { + put("cd: too many arguments\n", BIOS_RED); + } + else if (args_count == 2) { + uint32_t search_directory_number = ROOT_CLUSTER_NUMBER; + char* name = "\0\0\0\0\0\0\0\0"; + + // Variables for parsing the arguments + int posName = (*(args_info + 1))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + 1))[0] + (*(args_info + 1))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + + int errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, 1)) { + search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + errorCode = 3; + endOfArgs = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(search_directory_number); + } + else { + clear(name, 8); + memcpy(name, args_val + posName, lenName); + entry_index = findEntryName(name); + if (entry_index == -1) { + errorCode = 1; + endOfArgs = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(search_directory_number); + } + else { + errorCode = 2; + endOfArgs = TRUE; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (errorCode != 0) { + put("cd: ", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + switch (errorCode) { + case 1: + put(": No such file or directory\n", BIOS_RED); + break; + case 2: + put(": Not a directory\n", BIOS_RED); + break; + case 3: + put(": Argument name is too long\n", BIOS_RED); + } + } + else { + current_directory = search_directory_number; + } + } +} \ No newline at end of file diff --git a/src/user/cd.h b/src/user/cd.h new file mode 100644 index 0000000..40b66ea --- /dev/null +++ b/src/user/cd.h @@ -0,0 +1,9 @@ +// File : cd.h +// Header for cd.c, contains the declaration of functions needed to process cd command + +#ifndef _CD_H_ +#define _CD_H_ + +void cd(char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file diff --git a/src/user/cp.c b/src/user/cp.c new file mode 100644 index 0000000..04d3971 --- /dev/null +++ b/src/user/cp.c @@ -0,0 +1,425 @@ +// File : cp.c +// Contains the implementation of functions needed to process cp command + +#include "cp.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" +#include "std/string.h" + +void copy(char* args_val, int (*args_info)[2], int args_count) { + /* Searches if the destination exists and if it is a file or directory. + Returns 1 if it is a file, 0 if it is a directory, -1 if it is not found */ + + // Variables to keep track the currently visited directory + uint32_t dest_search_directory_number = ROOT_CLUSTER_NUMBER; + char destName[8] = {'\0','\0','\0','\0','\0','\0','\0','\0'}; + char destExt[3] = {'\0','\0','\0'}; + + // Variables for parsing the arguments + int posName = (*(args_info + args_count-1))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + args_count-1))[0] + (*(args_info + args_count-1))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool newFileFound = FALSE; + + int errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_count-1)) { + dest_search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(dest_search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } + else { + // If there is a new word after non-existent directory, set an error code and stop parsing + if (newFileFound) { + newFileFound = FALSE; + if (errorCode == 5) { + errorCode = 1; + } + else { + errorCode = 4; + } + endOfArgs = TRUE; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + // Cek extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i >= lenName) { + errorCode = 3; + endOfArgs = TRUE; + } else if (lenName-i-1 > 3) { + errorCode = 3; + endOfArgs = TRUE; + } else { + clear(destName, 8); + clear(destExt,3); + memcpy(destName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(destExt, args_val + posName + i + 1, lenName-i-1); + } + entry_index = findEntryName(destName); + if (entry_index == -1) { + newFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + newFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + dest_search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + clear(destName, 8); + clear(destExt,3); + // Cek extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + if (lenName-i-1 > 3) { // Jika extension lebih dari 3 karakter + errorCode = 3; + break; + } + memcpy(destName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(destExt, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(destName, args_val + posName, lenName); + } + entry_index = findEntryName(destName); + if (entry_index == -1) { + newFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + newFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (errorCode == 3 || errorCode == 4) { + put("cp: Destination not valid\n", BIOS_RED); + return; + } + else if (args_count > 3 && newFileFound) { // Jika lebih dari satu file yang dicopy, maka harus dimasukkan ke dalam folder + put("cp: target '", BIOS_RED); + putn(args_val + (*(args_info + args_count-1))[0], BIOS_RED, (*(args_info + args_count-1))[1]); + put("' is not a directory\n", BIOS_RED); + return; + } + + // Read each files that need to be copied + for (int j=1; j 8) { + // Periksa extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + clear(srcName, 8); + clear(srcExt,3); + if (i >= lenName) { + errorCode = 3; + endOfArgs = TRUE; + } + else if (lenName-i-1 > 3) { + errorCode = 3; + endOfArgs = TRUE; + } + else { + memcpy(srcName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(srcExt, args_val + posName + i + 1, lenName-i-1); + } + entry_index = findEntryName(srcName); + if (entry_index == -1) { + srcNewFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + src_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(src_search_directory_number); + } + else { + srcNewFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + src_search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(src_search_directory_number); + } + else { + clear(srcName, 8); + clear(srcExt,3); + // Periksa extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + if (lenName-i-1 > 3) { // Jika extension lebih dari 3 karakter + errorCode = 3; + break; + } + memcpy(srcName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(srcExt, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(srcName, args_val + posName, lenName); + } + entry_index = findEntryName(srcName); + if (entry_index == -1) { + srcNewFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + src_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(src_search_directory_number); + } + else { + srcNewFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (errorCode == 3 || errorCode == 4) { + put("cp: cannot stat '", BIOS_RED); + putn(args_val + (*(args_info + j))[0], BIOS_RED, (*(args_info + j))[1]); + put("': No such file or directory\n", BIOS_RED); + return; + } + else if (!srcNewFileFound) { + put("cp: '", BIOS_RED); + putn(args_val + (*(args_info + j))[0], BIOS_RED, (*(args_info + j))[1]); + put("' is a directory\n", BIOS_RED); + return; + } + + struct ClusterBuffer cbuf = {0}; + struct FAT32DriverRequest srcReq = { + .buf = &cbuf, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = src_search_directory_number, + .buffer_size = CLUSTER_SIZE + }; + memcpy(&(srcReq.name), srcName, 8); + memcpy(&(srcReq.ext), srcExt, 3); + int retCode; + + interrupt(0, (uint32_t) &srcReq, (uint32_t) &retCode, 0x0); + + if (retCode != 0) { + put("cp: cannot stat '", BIOS_RED); + putn(args_val + (*(args_info + j))[0], BIOS_RED, (*(args_info + j))[1]); + switch (retCode) { + case 1: + put("': Is a directory\n", BIOS_RED); + return; + case 2: + put("': Buffer size is not enough\n", BIOS_RED); + return; + case 3: + put("': No such file or directory\n", BIOS_RED); + return; + case -1: + put("': Unknown error\n", BIOS_RED); + return; + } + } else { + if (!newFileFound) { + // Tujuan berupa direktori + srcReq.parent_cluster_number = dest_search_directory_number; + interrupt(3, (uint32_t) &srcReq, (uint32_t) &retCode, 0x0); + interrupt(2, (uint32_t) &srcReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("cp: cannot copy '", BIOS_RED); + putn(args_val + (*(args_info + j))[0], BIOS_RED, (*(args_info + j))[1]); + switch (retCode) { + case 1: + put("': File exist\n", BIOS_RED); + return; + case -1: + put("': Unknown error occured\n", BIOS_RED); + return; + } + } + } else { + // Tujuan berupa file + struct FAT32DriverRequest destReq = { + .buf = &cbuf, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = dest_search_directory_number, + .buffer_size = CLUSTER_SIZE + }; + memcpy(&(destReq.name), destName, 8); + memcpy(&(destReq.ext), destExt, 3); + interrupt(3, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + interrupt(2, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("cp: cannot copy '", BIOS_RED); + putn(args_val + (*(args_info + j))[0], BIOS_RED, (*(args_info + j))[1]); + switch (retCode) { + case 1: + put("': File exist\n", BIOS_RED); + return; + case -1: + put("': Unknown error occured\n", BIOS_RED); + return; + } + } + } + } + } +} + +void cp(char* args_val, int (*args_info)[2], int args_count) { + if (args_count > 2) { + copy(args_val, args_info, args_count); + } + else if (args_count == 2) { + put("cp: missing destination file operand after '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + put("'\n", BIOS_RED); + } + else { + put("cp: missing file operand\n", BIOS_RED); + } +} \ No newline at end of file diff --git a/src/user/cp.h b/src/user/cp.h new file mode 100644 index 0000000..a606f28 --- /dev/null +++ b/src/user/cp.h @@ -0,0 +1,11 @@ +// File : cp.h +// Header for cp.c, contains the declaration of functions needed to process cp command + +#ifndef _CP_H_ +#define _CP_H_ + +void copy(char* args_val, int (*args_info)[2], int args_count); + +void cp(char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file diff --git a/src/user/ls.c b/src/user/ls.c new file mode 100644 index 0000000..0dd8b54 --- /dev/null +++ b/src/user/ls.c @@ -0,0 +1,191 @@ +// File : ls.c +// Contains the implementation of functions needed to process ls command + +#include "ls.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" + +void printDirectoryTable() { + for (int i = 1; i < 63; i++) { + if (dir_table.table[i].user_attribute == UATTR_NOT_EMPTY) { + if (dir_table.table[i].name[7] != '\0') { + putn(dir_table.table[i].name, BIOS_LIGHT_BLUE, 8); + } + else { + put(dir_table.table[i].name, BIOS_LIGHT_BLUE); + } + if (dir_table.table[i].attribute != ATTR_SUBDIRECTORY && strlen(dir_table.table[i].ext) != 0) { + put(".", BIOS_LIGHT_BLUE); + putn(dir_table.table[i].ext, BIOS_LIGHT_BLUE, 3); + } + put("\n", BIOS_LIGHT_BLUE); + } + } + if (dir_table.table[63].user_attribute == UATTR_NOT_EMPTY) { + if (dir_table.table[63].name[7] != '\0') { + putn(dir_table.table[63].name, BIOS_LIGHT_BLUE, 8); + } + else { + put(dir_table.table[63].name, BIOS_LIGHT_BLUE); + } + if (dir_table.table[63].attribute != ATTR_SUBDIRECTORY && strlen(dir_table.table[63].ext) != 0) { + put(".", BIOS_LIGHT_BLUE); + putn(dir_table.table[63].ext, BIOS_LIGHT_BLUE, 3); + } + put("\n", BIOS_LIGHT_BLUE); + } +} + +void access(char* args_val, int (*args_info)[2], int args_pos) { + // Variables to keep track the currently visited directory + uint32_t search_directory_number = ROOT_CLUSTER_NUMBER; + int oneArgFlag = args_pos; + + if (args_pos == -1) { + search_directory_number = current_directory; + updateDirectoryTable(search_directory_number); + printDirectoryTable(); + } + else { + if (args_pos == 0) { + args_pos++; + } + + // Variables for parsing the arguments + int posName = (*(args_info + args_pos))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + char* name = "\0\0\0\0\0\0\0\0"; + + int posEndArgs = (*(args_info + args_pos))[0] + (*(args_info + args_pos))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool fileFound = FALSE; + bool directoryNotFound = FALSE; + + int errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_pos)) { + search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } + else { + if (fileFound && index != posEndArgs) { + errorCode = 1; + directoryNotFound = TRUE; + fileFound = FALSE; + endOfArgs = TRUE; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + errorCode = 3; + directoryNotFound = TRUE; + endOfArgs = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(search_directory_number); + } + else { + clear(name, 8); + memcpy(name, args_val + posName, lenName); + entry_index = findEntryName(name); + if (entry_index == -1) { + directoryNotFound = TRUE; + endOfArgs = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(search_directory_number); + } + else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (directoryNotFound) { + put("ls: cannot access '", BIOS_RED); + putn(args_val + (*(args_info + args_pos))[0], BIOS_RED, (*(args_info + args_pos))[1]); + switch (errorCode) { + case 1: + put("': Not a directory\n", BIOS_RED); + break; + case 3: + put("': Directory name is too long\n", BIOS_RED); + break; + default: + put("': No such file or directory\n", BIOS_RED); + break; + } + } + else { + if (fileFound) { + putn(args_val + (*(args_info + args_pos))[0], BIOS_WHITE, (*(args_info + args_pos))[1]); + put("\n", BIOS_WHITE); + } + else { + if (oneArgFlag > 0) { + putn(args_val + (*(args_info + args_pos))[0], BIOS_WHITE, (*(args_info + args_pos))[1]); + put(":\n", BIOS_WHITE); + } + printDirectoryTable(); + } + } + } +} + +void ls(char* args_val, int (*args_info)[2], int args_count) { + if (args_count > 2) { + for (int i = 1; i < args_count-1; i++) { + access(args_val, args_info, i); + put("\n", BIOS_GREY); + } + access(args_val, args_info, args_count - 1); + put("\n", BIOS_GREY); + } + else if (args_count == 2) { + access(args_val, args_info, 0); + } + else { + access(args_val, args_info, -1); + } +} \ No newline at end of file diff --git a/src/user/ls.h b/src/user/ls.h new file mode 100644 index 0000000..1e10a59 --- /dev/null +++ b/src/user/ls.h @@ -0,0 +1,13 @@ +// File : ls.h +// Header for ls.c, contains the declaration of functions needed to process ls command + +#ifndef _LS_H_ +#define _LS_H_ + +void printDirectoryTable(); + +void access(char* args_val, int (*args_info)[2], int args_pos); + +void ls(char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file diff --git a/src/user/mkdir.c b/src/user/mkdir.c new file mode 100644 index 0000000..4e5185e --- /dev/null +++ b/src/user/mkdir.c @@ -0,0 +1,160 @@ +// File : mkdir.c +// Contains the implementation of functions needed to process mkdir command + +#include "mkdir.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" + +void createDirectory(char* args_val, int (*args_info)[2], int args_pos) { +// Variables to keep track the currently visited directory + uint32_t search_directory_number = ROOT_CLUSTER_NUMBER; + char* name = "\0\0\0\0\0\0\0\0"; + + // Variables for parsing the arguments + int posName = (*(args_info + args_pos))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + args_pos))[0] + (*(args_info + args_pos))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool newFileFound = FALSE; + + int errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_pos)) { + search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } + else { + // If there is a new word after non-existent directory, set an error code and stop parsing + if (newFileFound) { + newFileFound = FALSE; + if (errorCode == 5) { + errorCode = 1; + } + else { + errorCode = 4; + } + endOfArgs = TRUE; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + errorCode = 3; + endOfArgs = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(search_directory_number); + } + else { + clear(name, 8); + memcpy(name, args_val + posName, lenName); + entry_index = findEntryName(name); + if (entry_index == -1) { + newFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(search_directory_number); + } + else { + newFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (!newFileFound) { + put("mkdir: cannot create directory '", BIOS_RED); + putn(args_val + (*(args_info + args_pos))[0], BIOS_RED, (*(args_info + args_pos))[1]); + switch (errorCode) { + case 0: + case 5: + put("': File exist\n", BIOS_RED); + break; + case 1: + put("': Not a directory\n", BIOS_RED); + break; + case 3: + put("': Argument name is too long\n", BIOS_RED); + break; + case 4: + put("': No such file or directory\n", BIOS_RED); + break; + } + } + else { + struct FAT32DriverRequest request = { + .buf = 0, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = search_directory_number, + .buffer_size = 0 + }; + memcpy(&(request.name), args_val + posName, lenName); + + interrupt(2, (uint32_t) &request, (uint32_t) &errorCode, 0x0); + if (errorCode != 0) { + put("mkdir: cannot create directory '", BIOS_RED); + putn(args_val + (*(args_info + args_pos))[0], BIOS_RED, (*(args_info + args_pos))[1]); + switch (errorCode) { + case 1: + put("': File exist\n", BIOS_RED); + break; + case -1: + put("': Unknown error occured\n", BIOS_RED); + break; + } + } + } +} + +void mkdir(char* args_val, int (*args_info)[2], int args_count) { + if (args_count < 2) { + put("mkdir: missing operand\n", BIOS_RED); + } + else { + for (int i = 1; i < args_count; i++) { + createDirectory(args_val, args_info, i); + } + } +} \ No newline at end of file diff --git a/src/user/mkdir.h b/src/user/mkdir.h new file mode 100644 index 0000000..9370fdd --- /dev/null +++ b/src/user/mkdir.h @@ -0,0 +1,11 @@ +// File : mkdir.h +// Header for mkdir.h, contains the declaration of functions needed to process mkdir command + +#ifndef _MKDIR_H_ +#define _MKDIR_H_ + +void createDirectory(char* args_val, int (*args_info)[2], int args_pos); + +void mkdir(char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file diff --git a/src/user/mv.c b/src/user/mv.c new file mode 100644 index 0000000..8ba421d --- /dev/null +++ b/src/user/mv.c @@ -0,0 +1,625 @@ +// File : mv.c +// Contains the implementation of functions needed to process mv command + +#include "mv.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" + +bool remove_mv(char* args_val, int (*args_info)[2], int args_count) { + // Variables to keep track the currently visited directory + uint32_t dest_search_directory_number = ROOT_CLUSTER_NUMBER; + char* name = "\0\0\0\0\0\0\0\0"; + char* extension = "\0\0\0"; + + // Variables for parsing the arguments + int posName = (*(args_info + args_count))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + args_count))[0] + (*(args_info + args_count))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool fileFound = FALSE; + bool directoryNotFound = FALSE; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_count)) { + dest_search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(dest_search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } else { + if (fileFound && index != posEndArgs) { + directoryNotFound = TRUE; + fileFound = FALSE; + endOfArgs = TRUE; + } else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + directoryNotFound = TRUE; + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i >= lenName) { + endOfArgs = TRUE; + } else { + clear(name, 8); + clear(extension,3); + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + memcpy(name, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(extension, args_val + posName + i + 1, lenName-i-1); + } + } else if (lenName-i-1 > 3) { // Jika extension lebih dari 3 karakter + break; + } + else { + memcpy(name, args_val + posName, lenName); + } + entry_index = findEntryName(name); + if (entry_index == -1) { + fileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + dest_search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } else { + clear(name, 8); + clear(extension,3); + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + if (lenName-i-1 > 3) { // Jika extension lebih dari 3 karakter + break; + } + memcpy(name, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(extension, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(name, args_val + posName, lenName); + } + entry_index = findEntryName(name); + if (entry_index == -1) { + directoryNotFound = TRUE; + endOfArgs = TRUE; + } else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } else { + index++; + } + } + } + + struct FAT32DriverRequest destReq = { + .buf = 0, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = dest_search_directory_number, + .buffer_size = 0 + }; + memcpy(&(destReq.name), name, 8); + memcpy(&(destReq.ext), extension, 3); + uint32_t retCode = 0; + + if (directoryNotFound || fileFound) { + // Check if it is a file or it doesnt exist + interrupt(3, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("mv: cannot delete '", BIOS_RED); + putn(args_val + (*(args_info + args_count))[0], BIOS_RED, (*(args_info + args_count))[1]); + switch (retCode) { + case 1: + put("': File not found\n", BIOS_RED); + break; + default: + put("': No such file or directory\n", BIOS_RED); + break; + } + return FALSE; + } + } + else { + // Directory + destReq.parent_cluster_number = ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + interrupt(3, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("mv: cannot delete '", BIOS_RED); + putn(args_val + (*(args_info + args_count))[0], BIOS_RED, (*(args_info + args_count))[1]); + switch (retCode) { + case 1: + put("': Directory not found\n", BIOS_RED); + break; + case 2: + put("': Directory not empty\n", BIOS_RED); + break; + default: + put("': No such file or directory\n", BIOS_RED); + break; + } + return FALSE; + } + } + return TRUE; +} + + +bool copy_mv(char* args_val, int (*args_info)[2], int args_count) { + /* Searches if the destination exists and if it is a file or directory. + Returns 1 if it is a file, 0 if it is a directory, -1 if it is not found */ + + // Variables to keep track the currently visited directory + uint32_t dest_search_directory_number = ROOT_CLUSTER_NUMBER; + char destName[8] = {'\0','\0','\0','\0','\0','\0','\0','\0'}; + char destExt[3] = {'\0','\0','\0'}; + + // Variables for parsing the arguments + int posName = (*(args_info + args_count-1))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + args_count-1))[0] + (*(args_info + args_count-1))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool newFileFound = FALSE; + + int errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_count-1)) { + dest_search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(dest_search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } + else { + // If there is a new word after non-existent directory, set an error code and stop parsing + if (newFileFound) { + newFileFound = FALSE; + if (errorCode == 5) { + errorCode = 1; + } + else { + errorCode = 4; + } + endOfArgs = TRUE; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + // Cek extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i >= lenName) { + errorCode = 3; + endOfArgs = TRUE; + } else if (lenName-i-1 > 3) { + errorCode = 3; + endOfArgs = TRUE; + } else { + clear(destName, 8); + clear(destExt,3); + memcpy(destName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(destExt, args_val + posName + i + 1, lenName-i-1); + } + entry_index = findEntryName(destName); + if (entry_index == -1) { + newFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + newFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + dest_search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + clear(destName, 8); + clear(destExt,3); + // Cek extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + if (lenName-i-1 > 3) { // Jika extension lebih dari 3 karakter + errorCode = 3; + break; + } + memcpy(destName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(destExt, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(destName, args_val + posName, lenName); + } + entry_index = findEntryName(destName); + if (entry_index == -1) { + newFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + newFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (errorCode == 3 || errorCode == 4) { + put("mv : Destination not valid\n", BIOS_RED); + return FALSE; + } + else if (args_count > 3 && newFileFound) { // Jika lebih dari satu file yang dicopy, maka harus dimasukkan ke dalam folder + put("mv: target '", BIOS_RED); + putn(args_val + (*(args_info + args_count-1))[0], BIOS_RED, (*(args_info + args_count-1))[1]); + put("' is not a directory\n", BIOS_RED); + return FALSE; + } + + // Read each files that need to be copied + // Variables to keep track the currently visited directory + uint32_t src_search_directory_number = ROOT_CLUSTER_NUMBER; + char srcName[8] = {'\0','\0','\0','\0','\0','\0','\0','\0'}; + char srcExt[3] = {'\0','\0','\0'}; + + // Variables for parsing the arguments + posName = (*(args_info + 1))[0]; + lenName = 0; + index = posName; + entry_index = -1; + + posEndArgs = (*(args_info + 1))[0] + (*(args_info + 1))[1]; + endOfArgs = (posName+lenName-1 == posEndArgs); + endWord = TRUE; + bool srcNewFileFound = FALSE; + + errorCode = 0; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, 1)) { + src_search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(src_search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } + else { + // If there is a new word after non-existent directory, set an error code and stop parsing + if (srcNewFileFound) { + srcNewFileFound = FALSE; + if (errorCode == 5) { + errorCode = 1; + } + else { + errorCode = 4; + } + endOfArgs = TRUE; + } + else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } + else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + // Periksa extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + clear(srcName, 8); + clear(srcExt,3); + if (i >= lenName) { + errorCode = 3; + endOfArgs = TRUE; + } + else if (lenName-i-1 > 3) { + errorCode = 3; + endOfArgs = TRUE; + } + else { + memcpy(srcName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(srcExt, args_val + posName + i + 1, lenName-i-1); + } + entry_index = findEntryName(srcName); + if (entry_index == -1) { + srcNewFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + src_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(src_search_directory_number); + } + else { + srcNewFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + src_search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(src_search_directory_number); + } + else { + clear(srcName, 8); + clear(srcExt,3); + // Periksa extension + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + if (lenName-i-1 > 3) { // Jika extension lebih dari 3 karakter + errorCode = 3; + break; + } + memcpy(srcName, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(srcExt, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(srcName, args_val + posName, lenName); + } + entry_index = findEntryName(srcName); + if (entry_index == -1) { + srcNewFileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + src_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(src_search_directory_number); + } + else { + srcNewFileFound = TRUE; + errorCode = 5; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + if (errorCode == 3 || errorCode == 4) { + put("mv: cannot stat '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + put("': No such file or directory\n", BIOS_RED); + return FALSE; + } + else if (!srcNewFileFound) { + put("mv: '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + put("' is a directory\n", BIOS_RED); + return FALSE; + } + + struct ClusterBuffer cbuf = {0}; + struct FAT32DriverRequest srcReq = { + .buf = &cbuf, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = src_search_directory_number, + .buffer_size = CLUSTER_SIZE + }; + memcpy(&(srcReq.name), srcName, 8); + memcpy(&(srcReq.ext), srcExt, 3); + int retCode = 0; + + interrupt(0, (uint32_t) &srcReq, (uint32_t) &retCode, 0x0); + + if (retCode != 0) { + put("mv: cannot stat '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + switch (retCode) { + case 1: + put("': Is a directory\n", BIOS_RED); + break; + case 2: + put("': Buffer size is not enough\n", BIOS_RED); + break; + case 3: + put("': No such file or directory\n", BIOS_RED); + break; + case -1: + put("': Unknown error\n", BIOS_RED); + break; + } + return FALSE; + } else { + if (!newFileFound) { + // Tujuan berupa direktori + srcReq.parent_cluster_number = dest_search_directory_number; + interrupt(3, (uint32_t) &srcReq, (uint32_t) &retCode, 0x0); + interrupt(2, (uint32_t) &srcReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("mv : cannot copy '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + switch (retCode) { + case 1: + put("': File exist\n", BIOS_RED); + break; + case -1: + put("': Unknown error occured\n", BIOS_RED); + break; + } + return FALSE; + } + } else { + // Tujuan berupa file + struct FAT32DriverRequest destReq = { + .buf = &cbuf, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = dest_search_directory_number, + .buffer_size = CLUSTER_SIZE + }; + memcpy(&(destReq.name), destName, 8); + memcpy(&(destReq.ext), destExt, 3); + interrupt(3, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + interrupt(2, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("mv : cannot copy '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + switch (retCode) { + case 1: + put("': File exist\n", BIOS_RED); + break; + case -1: + put("': Unknown error occured\n", BIOS_RED); + break; + } + return FALSE; + } + } + } + return TRUE; +} + +void rename(char* args_val, int (*args_info)[2], int args_count) { + copy_mv(args_val, args_info, args_count); + remove_mv(args_val, args_info, 2); +} + +void move(char* args_val, int (*args_info)[2], int args_count) { + copy_mv(args_val, args_info, args_count); +} + +void mv(char* args_val, int (*args_info)[2], int args_count) { + if (args_count == 3) { + if (copy_mv(args_val, args_info, args_count)) { + remove_mv(args_val, args_info, 1); + } + } else if (args_count == 2) { + put("mv: missing destination file operand after '", BIOS_RED); + putn(args_val + (*(args_info + 1))[0], BIOS_RED, (*(args_info + 1))[1]); + put("'\n", BIOS_RED); + } + else { + put("mv: missing file operand\n", BIOS_RED); + } +} \ No newline at end of file diff --git a/src/user/mv.h b/src/user/mv.h new file mode 100644 index 0000000..d97884b --- /dev/null +++ b/src/user/mv.h @@ -0,0 +1,15 @@ +// File : mv.h +// Header for mv.c, contains the declaration of functions needed to process mv command + +#ifndef _MV_H_ +#define _MV_H_ + +#include "std/stdtype.h" + +bool remove_mv(char* args_val, int (*args_info)[2], int args_count); + +bool copy_mv(char* args_val, int (*args_info)[2], int args_count); + +void mv(char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file diff --git a/src/user/rm.c b/src/user/rm.c new file mode 100644 index 0000000..a0313f3 --- /dev/null +++ b/src/user/rm.c @@ -0,0 +1,197 @@ +// File : rm.c +// Contains the implementation of functions needed to process rm command + +#include "rm.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" +#include "std/string.h" + +void remove(char* args_val, int (*args_info)[2], int args_count) { + // Variables to keep track the currently visited directory + uint32_t dest_search_directory_number = ROOT_CLUSTER_NUMBER; + char* name = "\0\0\0\0\0\0\0\0"; + char* extension = "\0\0\0"; + + // Variables for parsing the arguments + int posName = (*(args_info + args_count))[0]; + int lenName = 0; + int index = posName; + int entry_index = -1; + + int posEndArgs = (*(args_info + args_count))[0] + (*(args_info + args_count))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + bool fileFound = FALSE; + bool directoryNotFound = FALSE; + + // If path is not absolute, set the currently visited directory to current working directory + if (!isPathAbsolute(args_val, args_info, args_count-1)) { + dest_search_directory_number = current_directory; + } + + // Get the directory table of the visited directory + updateDirectoryTable(dest_search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } else { + if (fileFound && index != posEndArgs) { + directoryNotFound = TRUE; + fileFound = FALSE; + endOfArgs = TRUE; + } else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } + } else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName > 8) { + directoryNotFound = TRUE; + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i >= lenName) { + endOfArgs = TRUE; + } else { + clear(name, 8); + clear(extension,3); + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + memcpy(name, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(extension, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(name, args_val + posName, lenName); + } + entry_index = findEntryName(name); + if (entry_index == -1) { + fileFound = TRUE; + } + else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } + else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } else if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + dest_search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } else { + clear(name, 8); + clear(extension,3); + int i = 0; + while (i < lenName && memcmp(".", args_val + posName + i, 1) != 0) { + i++; + } + if (i < lenName) { // Jika ada extension + memcpy(name, args_val + posName, i); + if (*(args_val + posName + i + 1) != 0x0A) { + memcpy(extension, args_val + posName + i + 1, lenName-i-1); + } + } else { + memcpy(name, args_val + posName, lenName); + } + entry_index = findEntryName(name); + if (entry_index == -1) { + directoryNotFound = TRUE; + endOfArgs = TRUE; + } else { + if (dir_table.table[entry_index].attribute == ATTR_SUBDIRECTORY) { + dest_search_directory_number = (int) ((dir_table.table[entry_index].cluster_high << 16) | dir_table.table[entry_index].cluster_low); + updateDirectoryTable(dest_search_directory_number); + } else { + fileFound = TRUE; + } + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } else { + index++; + } + } + } + + struct FAT32DriverRequest destReq = { + .buf = 0, + .name = "\0\0\0\0\0\0\0\0", + .ext = "\0\0\0", + .parent_cluster_number = dest_search_directory_number, + .buffer_size = 0 + }; + memcpy(&(destReq.name), name, 8); + memcpy(&(destReq.ext), extension, 3); + int retCode = 0; + + if (directoryNotFound || fileFound) { + // Check if it is a file or it doesnt exist + interrupt(3, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("rm: cannot delete '", BIOS_RED); + putn(args_val + (*(args_info + args_count))[0], BIOS_RED, (*(args_info + args_count))[1]); + switch (retCode) { + case 1: + put("': File not found\n", BIOS_RED); + break; + default: + put("': No such file or directory\n", BIOS_RED); + break; + } + } + } + else { + // Directory + destReq.parent_cluster_number = ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + interrupt(3, (uint32_t) &destReq, (uint32_t) &retCode, 0x0); + if (retCode != 0) { + put("rm: cannot delete '", BIOS_RED); + putn(args_val + (*(args_info + args_count))[0], BIOS_RED, (*(args_info + args_count))[1]); + switch (retCode) { + case 1: + put("': Directory not found\n", BIOS_RED); + break; + case 2: + put("': Directory not empty\n", BIOS_RED); + break; + default: + put("': No such file or directory\n", BIOS_RED); + break; + } + } + } +} + +void rm(char* args_val, int (*args_info)[2], int args_count) { + if (args_count >= 2) { + for(int i=1; i */ : "r"(ebx)); + __asm__ volatile("mov %0, %%ecx" : /* */ : "r"(ecx)); + __asm__ volatile("mov %0, %%edx" : /* */ : "r"(edx)); + __asm__ volatile("mov %0, %%eax" : /* */ : "r"(eax)); + // Note : gcc usually use %eax as intermediate register, + // so it need to be the last one to mov + __asm__ volatile("int $0x30"); +} + +/* ========================================================= PARSER ========================================================= */ +int inputparse (char *args_val, int args_info[128][2]) { + // Declare the vars + int nums = 0; + + // Process to count the args, initialize 0 + int i = 0; + int j = 0; + int k = 0; + + bool endWord = TRUE; + bool startWord = TRUE; + int countchar = 0; + + // Iterate all over the chars + // Ignore blanks at first + while (args_val[i] == ' ' && args_val[i] != 0x0A) { + i++; + } + + // While belum eof + while (args_val[i] != 0x0A) { + // Ignore blanks + while (args_val[i] == ' ' && args_val[i] != 0x0A) { + if (!endWord) { + k = 0; + j++; + endWord = TRUE; + } + startWord = TRUE; + i++; + } + + // Return the number of args + if (args_val[i] == 0x0A) { + return nums; + } + + // Out then it is not the end of the word + endWord = FALSE; + + // Process other chars + if (startWord) { + nums++; + countchar = 0; + args_info[j][k] = i; + startWord = FALSE; + k++; + } + + countchar++; + args_info[j][k] = countchar; + i++; // Next char + } + + return nums; +} + +/* ========================================================= PRINTER ========================================================= */ + +// Wrapper for the base interrupt +void put(char* buf, uint8_t color) { + interrupt(5, (uint32_t) buf, strlen(buf), color); +} + +// Wrapper for the base interrupt, have additional argument n +void putn(char* buf, uint8_t color, int n) { + interrupt(5, (uint32_t) buf, n, color); +} + +// Print Current Working Directory +void printCWD(char* path_str, uint32_t current_dir) { + // Intantiate vars and length vars + int pathlen = 0; + int nodecount = 0; + char nodeIndex [10][64]; + + // Biasakan untuk clear dulu + clear(path_str, 128); + for (int i = 0; i < 10; i++) { + clear(nodeIndex[i], 64); + } + + if (current_dir == ROOT_CLUSTER_NUMBER) { + path_str[pathlen++] = '/'; + put (path_str, BIOS_LIGHT_BLUE); + return; + } + + // Loop sampe parentnya ROOT + uint32_t parent = current_dir; + path_str[pathlen++] = '/'; + while (parent != ROOT_CLUSTER_NUMBER) { + // Isi dir_table dengan isi dari cluster sekarang + updateDirectoryTable(parent); + + // Ambil parentnya + parent = (uint32_t) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + + // Masukin namanya ke list + memcpy(nodeIndex[nodecount], dir_table.table[0].name, strlen(dir_table.table[0].name)); + nodecount++; + } + + // Iterate back to get the full pathstr + for (int i = nodecount - 1; i >= 0; i--) { + for (size_t j = 0; j < strlen(nodeIndex[i]); j++) { + path_str[pathlen++] = nodeIndex[i][j]; + } + + if (i > 0) { + path_str[pathlen++] = '/'; + } + } + + put (path_str, BIOS_LIGHT_BLUE); +} + +/* ======================================================== PATHING ======================================================== */ + +// Check if the path argument is an absolute path or not +bool isPathAbsolute(char* args_val, int (*args_info)[2], int args_pos) { + return (memcmp(args_val + (*(args_info + args_pos))[0], "/", 1) == 0); +} + +// Update the dir_table according to the cluster number +void updateDirectoryTable(uint32_t cluster_number) { + interrupt(6, (uint32_t) &dir_table, cluster_number, 0x0); +} + +// Find the name of directory in the dir_table and return its cluster number +int findEntryName(char* name) { + int result = -1; + + int i = 1; + bool found = FALSE; + while (i < 64 && !found) { + if (memcmp(dir_table.table[i].name, name, 8) == 0 && + dir_table.table[i].user_attribute == UATTR_NOT_EMPTY) { + result = i; + found = TRUE; + } + else { + i++; + } + } + + return result; +} + +void initScreen() { + put("\n\n\n\n\n", BIOS_LIGHT_GREEN); + put(" _____ _ __ ____ _____ ____ _____ \n", BIOS_LIGHT_GREEN); + put(" /\\ / ____| | |/ / / __ \\ / ____| / __ \\ / ____|\n", BIOS_LIGHT_GREEN); + put(" / \\ _ __ __ _| | __ __ _| ' / ___| | | | (___ | | | | (___ \n", BIOS_LIGHT_GREEN); + put(" / /\\ \\ | '_ \\ / _` | | |_ |/ _` | < / _ \\ | | |\\___ \\ | | | |\\___ \\ \n", BIOS_LIGHT_GREEN); + put(" / ____ \\| |_) | (_| | |__| | (_| | . \\ __/ |__| |____) | | |__| |____) |\n", BIOS_LIGHT_GREEN); + put(" /_/ \\_\\ .__/ \\__,_|\\_____|\\__,_|_|\\_\\___|\\____/|_____/ \\____/|_____/ \n", BIOS_LIGHT_GREEN); + put(" | | \n", BIOS_LIGHT_GREEN); + put(" |_| \n", BIOS_LIGHT_GREEN); + put(" ApaGaKeOS - version 1.0.0\n", BIOS_LIGHT_GREEN); + put(" GitHub repository: https://github.com/Sister20/if2230-2023-apagakeos \n\n", BIOS_LIGHT_GREEN); + put(" WELCOME!\n", BIOS_LIGHT_GREEN); + put(" Press enter to get started\n\n", BIOS_LIGHT_GREEN); +} +/* ======================================================= MAIN FUNCTION ======================================================= */ + +// the main function where shell run +int main(void) { + // The buffers + char args_val[2048]; + int args_info[128][2]; + char path_str[2048]; + + initScreen(); + interrupt (4, (uint32_t) args_val, 2048, 0x0); + interrupt(7, 0, 0, 0); + + while (TRUE) { + // Always start by clearing the buffer + clear(args_val, 2048); + for (int i = 0; i < 128; i++) { + clear(args_info[i], 2); + } + clear(path_str, 2048); + + // Initialize + put("ApaGaKeOS@OS-IF2230", BIOS_LIGHT_GREEN); + put(":", BIOS_GREY); + printCWD(path_str, current_directory); + put("$ ", BIOS_GREY); + + // Asking for inputs + interrupt (4, (uint32_t) args_val, 2048, 0x0); + + // Get the numbers of input args + int args_count = inputparse (args_val, args_info); + + // processing the command + if (args_count != 0) { + if ((memcmp(args_val + *(args_info)[0], "cd", 2) == 0) && ((*(args_info))[1] == 2)) { + cd(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "ls", 2) == 0) && ((*(args_info))[1] == 2)) { + ls(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "mkdir", 5) == 0)&& ((*(args_info))[1] == 5)) { + mkdir(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "cat", 3) == 0)&& ((*(args_info))[1] == 3)) { + cat(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "cp", 2) == 0)&& ((*(args_info))[1] == 2)) { + cp(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "rm", 2) == 0)&& ((*(args_info))[1] == 2)) { + rm(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "mv", 2) == 0)&& ((*(args_info))[1] == 2)) { + mv(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "whereis", 7) == 0)&& ((*(args_info))[1] == 7)) { + whereis(args_val, args_info, args_count); + } + else if ((memcmp(args_val + *(args_info)[0], "clear", 5) == 0)&& ((*(args_info))[1] == 5)) { + if (args_count > 1) { + put("clear: too many arguments\n", BIOS_RED); + } else { + interrupt(7, 0, 0, 0); + } + } + else { + for (char i = 0; i < (*(args_info))[1]; i++) { + putn(args_val + (*(args_info))[0] + i, BIOS_RED, 1); + } + put(": command not found\n", BIOS_RED); + } + } + } + + return 0; +} \ No newline at end of file diff --git a/src/user/user-shell.h b/src/user/user-shell.h new file mode 100644 index 0000000..776f158 --- /dev/null +++ b/src/user/user-shell.h @@ -0,0 +1,51 @@ +// File : user-shell.h +// Header for user-shell.c, contains the declaration of functions needed to run shell program + +#ifndef _USERSHELL_ +#define _USERSHELL_ + +#include "std/stdtype.h" +#include "std/stdmem.h" +#include "filesystem/fat32.h" +#include "std/string.h" + +// Color declarations +#define BIOS_LIGHT_GREEN 0b1010 +#define BIOS_GREY 0b0111 +#define BIOS_DARK_GREY 0b1000 +#define BIOS_LIGHT_BLUE 0b1001 +#define BIOS_RED 0b1100 +#define BIOS_BROWN 0b0110 +#define BIOS_WHITE 0b1111 +#define BIOS_BLACK 0b0000 + +// Position of current directory +extern uint32_t current_directory; +extern struct FAT32DirectoryTable dir_table; + +// Get the parsed args nums and components +int inputparse (char *args_val, int args_info[128][2]); + +// Interrupt to main +void interrupt (uint32_t eax, uint32_t ebx, uint32_t ecx, uint32_t edx); + +// Some function using the base interrupt +// Put chars to screen +void put (char* buf, uint8_t color); +void putn(char* buf, uint8_t color, int n); + +// Print Current Working Directory +void printCWD (char* path_str, uint32_t current_dir); + +/* Functions for pathing */ + +bool isPathAbsolute(char* args_val, int (*args_info)[2], int args_pos); + +void updateDirectoryTable(uint32_t cluster_number); + +int findEntryName(char* name); + +/* Splash screen */ +void screenInit(); + +#endif \ No newline at end of file diff --git a/src/user/whereis.c b/src/user/whereis.c new file mode 100644 index 0000000..5c24ad0 --- /dev/null +++ b/src/user/whereis.c @@ -0,0 +1,199 @@ +// File : whereis.c +// Contains the implementation of functions needed to process whereis command + +#include "whereis.h" +#include "user-shell.h" +#include "std/stdtype.h" +#include "std/stdmem.h" + +void processDFS (char srcName[8], uint32_t search_directory_number, int v, bool visited[63]) { + char path_list[2048]; + clear(path_list, 2048); + // Kunjungi dulu simpulnya + visited[v - 1] = TRUE; + + // define bool visied yang baru + bool visitedNew [63]; + clear(visitedNew, 63); + + // Melakukan traversal terhadap dir table sekarang ke tetangganya + for (int i = 1; i < 64; i++) { + // Memastikan ada isinya, tidak kosong + if (dir_table.table[i].user_attribute == UATTR_NOT_EMPTY) { + // Kalau folder, salin trus traverse dalamnya + if (dir_table.table[i].attribute == ATTR_SUBDIRECTORY) { + // Cek apakah namanya sama, kalo sama cetak + if (memcmp(dir_table.table[i].name, srcName, 8) == 0) { + printCWD(path_list, current_directory); + updateDirectoryTable(search_directory_number); + put("/", BIOS_LIGHT_BLUE); + put(srcName, BIOS_LIGHT_BLUE); + put(" ", BIOS_BLACK); + } + // Sama maupun tidak, proses pencarian tetap dilakukan + if (!visitedNew[i - 1]) { + search_directory_number = (int) ((dir_table.table[i].cluster_high << 16) | dir_table.table[i].cluster_low); + current_directory = search_directory_number; + updateDirectoryTable(search_directory_number); + processDFS (srcName, search_directory_number, i, visitedNew); + + // NAIK + visitedNew[i - 1] = TRUE; + search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + current_directory = search_directory_number; + updateDirectoryTable(search_directory_number); + } + } + // Kalo bukan folder, cek, namanya sama apa ga + else { + // Cek apakah namanya sama, kalo sama cetak + if (memcmp(dir_table.table[i].name, srcName, 8) == 0) { + printCWD(path_list, current_directory); + updateDirectoryTable(search_directory_number); + put("/", BIOS_LIGHT_BLUE); + put(srcName, BIOS_LIGHT_BLUE); + put(".", BIOS_LIGHT_BLUE); + put(dir_table.table[i].ext, BIOS_LIGHT_BLUE); + put(" ", BIOS_BLACK); + } + visited[i - 1] = TRUE; + // kalo ga do nothing, set aja visitednya jad true + } + } + } +} + +void doWhereis (char* args_val, int (*args_info)[2], int args_pos) { + // Variables to keep track the currently visited directory + uint32_t search_directory_number = ROOT_CLUSTER_NUMBER; + char srcName[8] = {'\0','\0','\0','\0','\0','\0','\0','\0'}; + char srcExt[3] = {'\0','\0','\0'}; + bool visited[63]; + + // Variables for parsing the arguments + int posName = (*(args_info + args_pos))[0]; + int lenName = 0; + int i = 0; + bool titikFound = TRUE; + int index = posName; + + int posEndArgs = (*(args_info + args_pos))[0] + (*(args_info + args_pos))[1]; + bool endOfArgs = (posName+lenName-1 == posEndArgs); + bool endWord = TRUE; + + // Get the directory table of the visited directory + updateDirectoryTable(search_directory_number); + + // Start searching for the directory to make + while (!endOfArgs) { + // If current char is not '/', process the information of word. Else, process the word itself + if (memcmp(args_val + index, "/", 1) != 0 && index != posEndArgs) { + // If word already started, increment the length. Else, start new word + if (!endWord) { + lenName++; + } else { + endWord = FALSE; + posName = index; + lenName = 1; + } + } else { + // Process the word + if (!endWord) { + // If word length more than 8, set an error code and stop parsing. Else, check if the word exist as directory + if (lenName == 2 && memcmp(args_val + posName, "..", 2) == 0) { + lenName += 2; + } else { + clear(srcName, 8); + clear(srcExt, 3); + + while (i < lenName && titikFound) { + if (memcmp(".", args_val + posName + i, 1) == 0) { + titikFound = FALSE; + break; + } + i++; + } + } + endWord = TRUE; + } + } + + if (!endOfArgs) { + if (index == posEndArgs) { + endOfArgs = TRUE; + } + else { + index++; + } + } + } + + // Copy file name + if (i < lenName) { + memcpy(srcName, args_val + posName, i); + } else { + memcpy(srcName, args_val + posName, lenName); + } + + put(srcName, BIOS_WHITE); + put(": ", BIOS_WHITE); + + char path_list[2048]; + clear(path_list, 2048); + clear(visited, 64); + + for (int i = 1; i < 64; i++) { + // Pastiin tidak kosong + if (dir_table.table[i].user_attribute == UATTR_NOT_EMPTY) { + // Kalau folder, salin trus traverse dalamnya + if (dir_table.table[i].attribute == ATTR_SUBDIRECTORY) { + // Cek apakah namanya sama, kalo sama cetak + if (memcmp(dir_table.table[i].name, srcName, 8) == 0) { + printCWD(path_list, current_directory); + updateDirectoryTable(search_directory_number); + put(srcName, BIOS_LIGHT_BLUE); + put(" ", BIOS_BLACK); + } + // Sama maupun tidak, proses pencarian tetap dilakukan + if (!visited[i - 1]) { + search_directory_number = (int) ((dir_table.table[i].cluster_high << 16) | dir_table.table[i].cluster_low); + current_directory = search_directory_number; + updateDirectoryTable(search_directory_number); + processDFS (srcName, search_directory_number, i, visited); + + // NAIK + visited[i - 1] = TRUE; + search_directory_number = (int) ((dir_table.table[0].cluster_high << 16) | dir_table.table[0].cluster_low); + current_directory = search_directory_number; + updateDirectoryTable(search_directory_number); + } + } + // Kalo bukan folder, cek, namanya sama apa ga + else { + // Cek apakah namanya sama, kalo sama cetak + if (memcmp(dir_table.table[i].name, srcName, 8) == 0) { + printCWD(path_list, current_directory); + updateDirectoryTable(search_directory_number); + put(srcName, BIOS_LIGHT_BLUE); + put(".", BIOS_LIGHT_BLUE); + put(dir_table.table[i].ext, BIOS_LIGHT_BLUE); + put(" ", BIOS_BLACK); + } + visited[i - 1] = TRUE; + // kalo ga do nothing, set aja visitednya jad true + } + } + } + + put("\n\n", BIOS_WHITE); +} + +void whereis (char* args_val, int (*args_info)[2], int args_count) { + if (args_count < 2) { + put ("whereis: missing operand\n", BIOS_RED); + } else { + for (int i = 1; i < args_count; i++) { + doWhereis (args_val, args_info, i); + } + } +} \ No newline at end of file diff --git a/src/user/whereis.h b/src/user/whereis.h new file mode 100644 index 0000000..89f23ad --- /dev/null +++ b/src/user/whereis.h @@ -0,0 +1,15 @@ +// File : whereis.h +// Header for whereis.c, contains the declaration of functions needed to process whereis command + +#ifndef _WHEREIS_H_ +#define _WHEREIS_H_ + +#include "std/stdtype.h" + +void processDFS (char srcName[8], uint32_t search_directory_number, int v, bool visited[63]); + +void doWhereis (char* args_val, int (*args_info)[2], int args_pos); + +void whereis (char* args_val, int (*args_info)[2], int args_count); + +#endif \ No newline at end of file