diff --git a/Makefile b/Makefile index 2584e4a..5366849 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,7 @@ $U/usys.o : $U/usys.S $U/_forktest: $U/forktest.o $(ULIB) # forktest has less library code linked in - needs to be small # in order to be able to max out the proc table. - $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_forktest $U/forktest.o $U/ulib.o $U/usys.o $U/umalloc.o $U/printf.o + $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_forktest $U/forktest.o $U/ulib.o $U/usys.o $U/umalloc.o $(OBJDUMP) -S $U/_forktest > $U/forktest.asm mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h @@ -130,11 +130,13 @@ UPROGS=\ $U/_stressfs\ $U/_usertests\ $U/_grind\ + $U/_vcp\ $U/_wc\ $U/_zombie\ -fs.img: mkfs/mkfs README.md $(UPROGS) - mkfs/mkfs fs.img README.md $(UPROGS) +fs.img: mkfs/mkfs README.md getlinetest.txt $(UPROGS) + mkfs/mkfs fs.img README.md getlinetest.txt vcpTest.txt $(UPROGS) + -include kernel/*.d user/*.d diff --git a/docs/VCP_doc b/docs/VCP_doc new file mode 100644 index 0000000..0f5b003 --- /dev/null +++ b/docs/VCP_doc @@ -0,0 +1,48 @@ +vcp + +The "vcp" command revolves around the idea of a version control/backup program. A hidden root directory (vctl) is created when the command is ran; within it a sub-directory with the name of the file we are working with is also created. Within this sub-directory a maximum of 3 files can be saved (in essence 3 different versions of the sub-directory's file name). + + Example: vctl / filename / Version1_filename + The point is to be able to save the most important versions of a program so that if necessary, the user can come back and review past versions of that exact program and extract the needed info that can be applied in the user's current program. When 3 versions already exist for a specific file and the user wants to save another version, the oldest of the 3 versions will be overwritten upon confirmation that the user agrees upon this process. If agreed, all existing versions will be shifted forward and renamed and the newest version will be added to the version control directory. + + Example: Version2_filename -> Version1_filename, Version3_filename -> Version2_filename and filename -> Version3_filename + +Added Files: +user/vcp.c +// Testing +FogOS-VCP/getlinetest.txt +FogOS-VCP/vcpTest.txt +our lab with broken.c to test long files + +Modifications: +FogOS-VCP/Makefile + +Flags: + + (-saveVersion): saves the current state of the file as a new version and label it with its respective version number + + (-listVersions) : displays a list of all saved versions of the specified file for example: Version3_filename.txt + + (-viewVersion) : displays the selected file version existing in the list of file versions for the specified file like the format of 'cat [filename]' allowing user to copy and access functions or implementations they wish they didn't get rid of etc. For example: /vcp -viewVersion Version2_filename.txt + +How to Test | How it Works | Limitations: +You can refer to the file vcpTest.txt where I included examples of how to test the program within QEMU. +As of now you can only test with files in your OS directory (where your Makefile exists) or with files you create in QEMU with echo. Future implementation will handle accessing files from other directories. +Due to limitations in xv6: only inside QEMU using echo can you really see the multiple versions of saving, viewing, and listing [refer to vcpTest.txt for why]. Saving, viewing, and listing can also be done for files in OS but not in the way the program was designed [vcpTest.txt] + +If testing in OS, ensure the file is added to your Makefile fs.img: 2nd line only. Do not make clean after you save versions as this will erase all versions and directories you've saved with vcp (due to xv6 limitations). You can exit QEMU and then rerun QEMU and the data will still be there. +Inside QEMU: + + /vcp -saveVersion filename.txt + /vcp -listVersions filename.txt + /vcp -viewVersion Version1_filename.txt + The program will work like normal, saving max 3 versions and overwriting but all versions will have same content (but they are separate files [refer to vcpTest.txt]) + +If testing inside QEMU: create a program with content like so + + echo "content you want" > filename.txt (any extension) + /vcp -saveVersion filename.txt + echo "different content" > filename.txt (to change the file content) + /vcp -saveVersion filename.txt + Now you can /vcp -listVersions filename.txt + and then you can view each version. If you want to try overwriting, save more than 3 versions and then check if everything has been shifted forward diff --git a/getlinetest.txt b/getlinetest.txt new file mode 100644 index 0000000..7437364 --- /dev/null +++ b/getlinetest.txt @@ -0,0 +1,13 @@ +# FogOS + +Hello world!! + +What's going on, class?? + +How about that fog out there?! + +Let's create a REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY REALLY long line. IS THAT LONG ENOUGH?!?!?! + +Okay all done. + +Have a good one! diff --git a/kernel/fs.h b/kernel/fs.h index 139dcc9..3f23edb 100644 --- a/kernel/fs.h +++ b/kernel/fs.h @@ -51,7 +51,7 @@ struct dinode { #define BBLOCK(b, sb) ((b)/BPB + sb.bmapstart) // Directory is a file containing a sequence of dirent structures. -#define DIRSIZ 14 +#define DIRSIZ 30 struct dirent { ushort inum; diff --git a/user/user.h b/user/user.h index 2e6fc55..a67da16 100644 --- a/user/user.h +++ b/user/user.h @@ -1,3 +1,16 @@ +#define NULL ((void *) 0) + +#define bool _Bool +#define true 1 +#define false 0 + +#define SEEK_SET 0 +#define STDIN_FILENO 0 +#define SEEK_CUR 1 +#define STDOUT_FILENO 1 +#define SEEK_END 2 +#define STDERR_FILENO 2 + struct stat; // system calls @@ -22,6 +35,9 @@ int getpid(void); char* sbrk(int); int sleep(int); int uptime(void); +int reboot(void); +int shutdown(void); +uint64 timestamp(void); // ulib.c int stat(const char*, struct stat*); @@ -32,8 +48,8 @@ int strcmp(const char*, const char*); void fprintf(int, const char*, ...); void printf(const char*, ...); char* gets(char*, int max); -int fgets(int fd, char*, int max); -int getline(char **lineptr, uint *n, int fd); +int fgets(int fd, char *buf, int max); +int getline(char **ptr, uint *size, int fd); uint strlen(const char*); void* memset(void*, int, uint); void* malloc(uint); diff --git a/user/vcp.c b/user/vcp.c new file mode 100644 index 0000000..184c063 --- /dev/null +++ b/user/vcp.c @@ -0,0 +1,514 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" +#include "kernel/fcntl.h" +#include "kernel/fs.h" + +// 3 Versions can be saved and accessed at a time +#define MAX_VERS 3 + +/** + * Ensure the root directory "vctl" exists. This will be the root directory for all future + * subdirectories (filenames) which in turn contain versions of the specified filename. + * If it doesn't exist, create it now. + * @return 0 or -1: root exists/created successfully or failed + */ +int ensure_Vctl_Dir(){ + // Try to open the /vctl directory + if (open("/vctl", O_RDONLY) < 0) { + // If /vctl doesn't exist, create it now + if (mkdir("/vctl") < 0) { + printf("Error: /vctl directory failed to be created\n"); + return -1; + } + } + return 0; +} + +/** + * Construct the file path that can be used to access and work within directories + * root + filename + * @param filename: the name of the file we are working with + * @return filePath: the constructed file path for general file directory ex: /vctl/filename + */ +char* constructFile_Path(char *filename) { + char *root = "/vctl/"; + char *filePath; + + int pathSize = strlen(root) + strlen(filename) + 1; + filePath = (char *) malloc(pathSize); + + if (filePath == NULL) { + printf("Error: Memory allocation has failed\n"); + return NULL; + } + + strcpy(filePath, root); // copy "/vctl/" + strcpy(filePath + strlen(root), filename); // append the filename + + return filePath; +} + +/** + * Ensure the directory for the filename exists already and if not create it. + * @param filename: the name of the file we are working with + * @return 0 or -1: directory of a certain file exists/created successfully or failed + */ +int ensure_Ver_Dir(char *filename) { + // Time to construct the path inside the root directory "vctl" to the new directory + // that will hold the versions of a certain file + // ex: /vctl/textfile.txt -> inside would be textfileV1, textfileV2 etc. + char *dir = constructFile_Path(filename); + + // Try to open directory and if it doesn't exist, create it + if (open(dir, O_RDONLY) < 0) { + if (mkdir(dir) < 0) { + printf("Error: /vctl/%s failed to be created\n", filename); + free(dir); + return -1; + } + } + free(dir); + return 0; +} + +/** + * Helper function to identify valid count of versions saved in struct + * @param str: name of entire file version saved + * @param prefix: "Version" is what we want to make sure str starts with to be able to count + * how may file versions exist in the current directory + * @return 0 or 1: prefix matches or not + */ +int startsWith(char *str, char *prefix) { + while (*prefix) { + if (*str != *prefix) { + return 0; // strings are not equal + } + str++; + prefix++; + } + return 1; // prefix does match +} + +/** + * Function to retrieve the current total of versions within the directory: 1, 2, or 3. + * @param filename: current file name + * @return verCount: total number of versions currently in directory + */ +int retrieveVer_Count(char *filename) { + char buf[512]; + int fd; + struct dirent de; + struct stat st; + int verCount = 0; + + // Construct the path to the file's ver directory + // ex: /vctl/filename + char *dir = constructFile_Path(filename); + + // Try to open directory that holds the specific file versions and make sure + // the directory exists and entries are valid (is a directory) + if ((fd = open(dir, O_RDONLY)) < 0) { + printf("Error: failed to open directory %s\n", dir); + free(dir); + return -1; + } + if (fstat(fd, &st) < 0 || st.type != T_DIR) { + printf("Error: %s is not a directory\n", dir); + close(fd); + free(dir); + return -1; + } + // Even though the version limit is 3, there are still possibly empty entires in + // the directory if a file was deleted or replaced so we don't want to invalidate the count + while (read(fd, &de, sizeof(de)) == sizeof(de)) { + if (de.inum == 0) { + continue; + } + int dirLength = strlen(dir); + int deName_Length = strlen(de.name); + + if (dirLength + 1 + deName_Length + 1 > sizeof(buf)) { + printf("Error: Path Buffer Overflow\n"); + continue; + } + + strcpy(buf, dir); // /vctl/filename + buf[dirLength] = '/'; // /vctl/filename/ + strcpy(buf + dirLength + 1, de.name); // /vctl/filename/Version2_testfile.txt + buf[dirLength + 1 + deName_Length] = '\0'; + + if (stat(buf, &st) < 0) { + printf("Error: unable to stat %s\n", buf); + continue; + } + if (st.type == T_FILE && startsWith(de.name, "Version")) { + verCount++; + } + } + close(fd); + free(dir); + + return verCount; +} + +/** + * Helper function to construct the full file version path to be used + * @param filename: the current filename + * @param verNum: the version number 1-3 + * @return verPath: return the full constructed file version path ex: /vctl/filename/Version#_filename + */ +char* constructVer_Path(char *filename, int verNum) { + char *root = "/vctl/"; + char verPrefix[16]; // buffer for version number portion /Version#_ + char *verPath; // pointer for final version path + + if (verNum == 1) { + strcpy(verPrefix, "/Version1_"); + } else if (verNum == 2) { + strcpy(verPrefix, "/Version2_"); + } else if (verNum == 3) { + strcpy(verPrefix, "/Version3_"); + } + + int pathSize = strlen(root) + strlen(filename) + strlen(verPrefix) + strlen(filename) + 1; //null term + verPath = (char *) malloc(pathSize); + + if (verPath == NULL) { + printf("Error: Memory allocation has failed\n"); + return NULL; + } + + // Construct the full path for filename and versions + strcpy(verPath, root); + strcpy(verPath + strlen(root), filename); + strcpy(verPath + strlen(root) + strlen(filename), verPrefix); + strcpy(verPath + strlen(root) + strlen(filename) + strlen(verPrefix), filename); + + return verPath; +} + +/** + * Function that will implement that -saveVersion command/flag + * - Check that all root directories and subdirectories have been made + * - Check how many versions of the file there exists in the directory already + * - If 3 already exist, overwrite of oldest version must occur and all remaining versions must shift up if user approves + * - All remaining versions must be renamed and the newest version must be saved in last slot now (Ver3) + * - If less than 3 exist, then save the version into the next open slot in the directory + * @param filename: file we are working with + */ +void saveVersion(char *filename){ + int verCount; + char decision[32]; + char *verPath; + + // Ensure the root directory to hold all file version directories exist + if (ensure_Vctl_Dir() < 0) { + return; + } + // Ensure the specific file has an existing version directory + if (ensure_Ver_Dir(filename) < 0) { + return; + } + verCount = retrieveVer_Count(filename); + + if(verCount >= MAX_VERS) { + printf("Maximum number of versions reached for this file. Do you want to overwrite the oldest version? (y/n): "); + read(0, decision, sizeof(decision)); + if (decision[0] != 'y') { + return; + } + // Overwrite oldest version and prepare all other files to update version numbers (shift forwards) + + char temp[512]; // buff to hold the content ver temporarily + int fd, bytesRead; + + char *ver1 = constructVer_Path(filename, 1); + char *ver2 = constructVer_Path(filename, 2); + char *ver3 = constructVer_Path(filename, 3); + + if (!ver1 || !ver2 || !ver3) { + printf("Error: Constructing version paths has failed\n"); + return; + } + + if (open(ver1, O_RDONLY) >= 0) { + // Write Version2 -> Version1 + if ((fd = open(ver2, O_RDONLY)) >= 0) { + // O_TRUNC is to make size 0 to ensure everything is overwritten and no + // leftover bytes remain in new version + int fd_new = open(ver1, O_CREATE | O_WRONLY | O_TRUNC); + if (fd_new < 0) { + printf("Error: Failed to shift Ver2 -> Ver1\n"); + free(ver1); free(ver2); free(ver3); + return; + } + while ((bytesRead = read(fd, temp, sizeof(temp))) > 0) { + write(fd_new, temp, bytesRead); + } + close(fd); + close(fd_new); + } + + // Write Version3 -> Version2 + if ((fd = open(ver3, O_RDONLY)) >= 0) { + int fd_new = open(ver2, O_CREATE | O_WRONLY | O_TRUNC); + if (fd_new < 0) { + printf("Error: Failed to shift Ver3 -> Ver2\n"); + free(ver1); free(ver2); free(ver3); + return; + } + while ((bytesRead = read(fd, temp, sizeof(temp))) > 0) { + write(fd_new, temp, bytesRead); + } + close(fd); + close(fd_new); + } + + //Write newest Version to become Version3 + int fd_new = open(ver3, O_CREATE | O_WRONLY | O_TRUNC); + if (fd_new < 0) { + printf("Error: Failed to create newest Version, check for invalid file name\n"); + free(ver1); free(ver2); free(ver3); + return; + } + int fd_source = open(filename, O_RDONLY); + if (fd_source < 0) { + printf("Error: Failed to open source file, check for invalid file name\n"); + close(fd_new); + free(ver1); free(ver2); free(ver3); + return; + } + while ((bytesRead = read(fd_source, temp, sizeof(temp))) > 0) { + write(fd_new, temp, bytesRead); + } + close(fd_source); + close(fd_new); + printf("Newest version of %s saved\n", filename); + } + + free(ver1); free(ver2); free(ver3); + } else { + // If MAX_VERS has not been reached yet, save version in the next open slot + char temp[512]; // buff to hold the content ver temporarily + int bytesRead; + verPath = constructVer_Path(filename, verCount + 1); + + if (!verPath) { + printf("Error: Constructing version path has failed\n"); + return; + } + printf("Version path is %s\n", verPath); + int newVer = open(verPath, O_CREATE | O_WRONLY); + if (newVer < 0) { + printf("Error: Failed to save newest Version, check for invalid file name\n"); + free(verPath); + return; + } + int fd_source = open(filename, O_RDONLY); + if (fd_source < 0) { + printf("Error: Failed to open source file, file invalid\n"); + close(newVer); + free(verPath); + return; + } + while ((bytesRead = read(fd_source, temp, sizeof(temp))) > 0) { + write(newVer, temp, bytesRead); + } + close(fd_source); + close(newVer); + free(verPath); + printf("Current Version of %s has been saved\n", filename); + + } +} + +/** + * Function that will implement the -listVersions command/flag: + * - Opens the specified filename's directory and checks for existence + * - Proceeds to print all existing version names saved in the directory + * @param filename: file we are working with + */ +void listVersions(char *filename){ + // open the directory and list the versions + char *dir = constructFile_Path(filename); + + int fd = open(dir, O_RDONLY); + if (fd < 0) { + printf("Error: Failed to open directory/file, please check if directory exists in mkdir as there might be no past versions of this file %s, or file doesn't exist\n", dir); + free(dir); + return; + } + struct dirent de; + struct stat st; + printf("Versions of %s:\n", filename); + while(read(fd, &de, sizeof(de)) == sizeof(de)) { + if (de.inum == 0) { + continue; + } + char buf[512]; + int dirLength = strlen(dir); + int deName_Length = strlen(de.name); + + if (dirLength + 1 + deName_Length + 1 > sizeof(buf)) { + printf("Error: Path Buffer Overflow\n"); + continue; + } + + strcpy(buf, dir); // /vctl/filename + buf[dirLength] = '/'; // /vctl/filename/ + strcpy(buf + dirLength + 1, de.name); // /vctl/filename/Version2_testfile.txt + buf[dirLength + 1 + deName_Length] = '\0'; + + if (stat(buf, &st) < 0) { + printf("Error: unable to stat %s\n", buf); + continue; + } + if (st.type == T_FILE && startsWith(de.name, "Version")) { + printf("%s\n", de.name); + } + } + close(fd); + free(dir); +} + +/** + * Helper function that will check if the entered file name version is valid + * @param: str: ex: Version2_ + * @return 0 or 1: if both version and number are valid + */ +int isValidVersion (char *str) { + int i = 0; + while (str[i] != '\0') { + i++; + } + if (i < 8) { + return 0; // invalid since Version is 7 and + 1 digit + } + if ((str[0] != 'V' && str[0] != 'v') || + (str[1] != 'E' && str[1] != 'e') || + (str[2] != 'R' && str[2] != 'r') || + (str[3] != 'S' && str[3] != 's') || + (str[4] != 'I' && str[4] != 'i') || + (str[5] != 'O' && str[5] != 'o') || + (str[6] != 'N' && str[6] != 'n')) { + return 0; + } + if (str[7] < '0' || str[7] > '4') { + return 0; // invalid version number + } + // else if all checks pass this is valid + return 1; +} + +/** + * Helper function to extract just the filename from the full version file name + * @param str: Version2_textfile.txt + * @return &str[i + 1] : everything that comes after the '_' character is the filename + */ +char *extract_fileName (char *str) { + int i = 0; + while (str[i] != '\0') { + if (str[i] == '_') { + return &str[i+1]; + } + i++; + } + printf("Invalid version name provided, missing '_'\n"); + return 0; +} + +/** + * Helper function to extract just the version number from the full version file name + * @param str: Version2_textfile.txt + * @return verNum: return any valid version number (1-3); returns -1 for invalid number + */ +int extract_verNum(char *str) { + int i = 0; + while (str[i] != '_' && str[i] != '\0') { + i++; + } + if (i > 7 && str[i - 1] >= '1' && str[i - 1] <= '3') { + int verNum = str[i - 1] - '0'; + return verNum; + } + printf("Invalid version number providing missing number 1-3\n"); + return -1; +} + +/** + * Function that will implement the -viewVersion command/flag: + * - Check that all root and subdirectories for the file exist + * - Check that the provided version is correct + * - Check that the provided version number is correct + * - Get the entire version path for the specified file and open its content + * - Read and write the file content to the output like cat filename + * @param: ver_filename: Version2_textfile.txt + */ +void viewVersion(char *ver_filename){ + char *filename = extract_fileName(ver_filename); + + if (ensure_Vctl_Dir() < 0) { + return; + } + if (ensure_Ver_Dir(filename) < 0) { + printf("Error: Directory does not exist\n"); + return; + } + if (isValidVersion(ver_filename) == 0) { + printf("Error: Invalid version name provided\n"); + return; + } + if (extract_verNum(ver_filename) < 0) { + printf("Error: Invalid version number provided\n"); + return; + } + + int verNum = extract_verNum(ver_filename); + printf("Version number is: %d\n", verNum); + + char *verPath = constructVer_Path(filename, verNum); + int verCount = retrieveVer_Count(filename); + + int fd = open(verPath, O_RDONLY); + if (fd < 0) { + printf("Error: Failed to open version %d of %s\n", verNum, filename); + printf("There exists: %d total versions under this file name. Ensure Version and filename are valid\n", verCount); + free(verPath); + return; + } + + char temp[512]; + int bytesRead; + while((bytesRead = read(fd, temp, sizeof(temp))) > 0) { + write(1, temp, bytesRead); + } + close(fd); + free(verPath); +} + +/** + * Check to ensure the correct number of arguments are provided in the command line and provide options for user + * Compare arguments to then respond and call associated functions that handle the flags entered + * If invalid flags are inputted, give user information on the valid entries + */ +int main(int argc, char *argv[]) { + if (argc <= 2) { + printf("Invalid number of arguments provided: Examples below: \n" + "/t vcp -listVersions textfile.txt \n" + "/t vcp -viewVersion Version1_textfile.txt\n" + "/t vcp -saveVersion textfile.txt\n"); + exit(1); + } + if (strcmp(argv[1], "-listVersions") == 0) { + listVersions(argv[2]); + } else if (strcmp(argv[1],"-viewVersion") == 0) { + viewVersion(argv[2]); + } else if (strcmp(argv[1], "-saveVersion") == 0) { + saveVersion(argv[2]); + } else { + printf("Sorry, please insert valid flag/command: -listVersions, -viewVersion, -saveVersion; followed by file name as next arg\n"); + return 0; + } + return 0; +} diff --git a/vcpTest.txt b/vcpTest.txt new file mode 100644 index 0000000..d8a94de --- /dev/null +++ b/vcpTest.txt @@ -0,0 +1,38 @@ +-viewVersion tests: +/vcp -viewVersion [insert] + +Version3_textfile.txt // if only 2 versions exists +VErsion2_textfile.txt // test mix of capital and noncapital letters +Vwrsion4_textfile.txt // test if error handling catches mispelling and invalid version # +Version_textfile.txt // test if error handling catches when no version # is added +Version1_teeeee.c // Handle if teeee.c doesn't exist (invalid file name) +.c, .txt, .md // save different files (must have .[file extension]) + +-listVersions tests: +/vcp -listVersions [insert] + +Version3_textfile.txt // only takes filename not version numbers (access directory not direct file ver) +textfile // handling no extension and invalid entry + + +-saveVersion tests: +/vcp -saveVersion [insert] + +Version3_textfile.txt // saves current file state, not specific version that already is saved in directory +textfile.c // normal entry +idontexist.c // enter a file that doesn't exist + +General Testing: +Check if too many args are provided +Check if too few args are provided +Check if invalid flags are provided +Saved long files like lab with broken.c +Saved short files and medium files like getlinetest.txt +Saved txt, c, md files etc + +Only inside QEMU using echo can you test multiple versions of saving, viewing, listing due to limitations of xv6 +If files are in os-[insert your id] and specified in makefile correctly, inside qemu you can save a version of that +outside file however, any changes made back outside QEMU will not be reflected when trying to save it again in QEMU +due to xv6 limitations. It will save the past file version again and populate it as version 2 which can be used with +all flags as normal but again due to the limitations of xv6, the new changes will not be seen in QEMU thus the reason +for echo to test the actual changes being saved and overwritten accordingly.