diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2bcc03 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# 2024 Spring Linux System Programming + +## Getting started +- Linux +Ubuntu 20.04 LTS +- openssl + You need to install openssl library + + sudo apt-get install libssl-dev + +- git clone https://github.com/JjungminLee/LSP-kathy.git + + +## P1 : SSU-Backup + +A program that manages user-desired file backups in a backup folder, handled through a global linked list and metadata, allowing for flexible deletion, restoration, and listing of files. + +### Usage + +When the program is run, a backup folder and a metadata file named "Kathy.meta.txt" are created. This metadata file stores the state of the original files alongside their corresponding backup files, formatted as “OriginalFilePath@BackupFilePath”. When deleting the backup folder, the metadata file must also be deleted to accurately reflect the state in the global linked list. The global backup linked list holds nodes that contain both the original and backup paths. + + + +## p2 : SSU-Repo + +A program where users can add file paths to a staging list, which then tracks whether the files are new, removed, or modified. This enables free generation of commit, status, and log outputs. The program also backs up files during a revert operation. If a file path is removed from the staging list, it will no longer be tracked. The program manages committed files through a linked list. + +## p3 : SSU-Sync +When a user enters a specific path, the program uses a daemon process to monitor it for modifications and deletions. If a modification occurs, a backup file is created, but no backup file is made in the case of deletion. Backup logs are viewable in a tree format. Removing the daemon process's PID not only stops the daemon but also deletes all associated backup files. \ No newline at end of file diff --git a/p1/Makefile b/p1/Makefile new file mode 100644 index 0000000..d1bf007 --- /dev/null +++ b/p1/Makefile @@ -0,0 +1,18 @@ +CC = gcc + +HEADER = ssu_header +BACKUP = ssu_backup +HELP = ssu_help + +$(BACKUP) : $(BACKUP).o $(HELP).o + $(CC) -o $(BACKUP) $(BACKUP).o $(HELP).o -lcrypto + +$(BACKUP).o : $(HEADER).h $(BACKUP).c + $(CC) -c -o $@ $(BACKUP).c -lcrypto + +$(HELP).o : $(HELP).c + $(CC) -c -o $@ $(HELP).c -lcrypto + +clean : + rm -rf $(BACKUP) + rm -rf *.o \ No newline at end of file diff --git a/p1/a.txt b/p1/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/p1/b.txt b/p1/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/p1/ssu_backup b/p1/ssu_backup new file mode 100755 index 0000000..3f8cab8 Binary files /dev/null and b/p1/ssu_backup differ diff --git a/p1/ssu_backup.c b/p1/ssu_backup.c new file mode 100644 index 0000000..9441d4f --- /dev/null +++ b/p1/ssu_backup.c @@ -0,0 +1,8326 @@ +#include "ssu_header.h" + +int BackupFile(char *path, char *date, command_parameter *parameter) +{ + + int len; + int fd, fd1, fd2, fd3, fd4; + int idx, cnt; + int i; + int fileSize, tmpFileSize; + char buf[1000000]; + struct stat statbuf, tmpbuf; + struct dirent **namelist; + char filepath[PATHMAX] = ""; + char filename[NAMEMAX] = ""; + char tmpdir[PATHMAX] = ""; + char newPath[PATHMAX] = ""; + char pathtmp[PATHMAX] = ""; + // 현재 path에 대한 해시값 + char filehash[NAMEMAX] = ""; + char logStr[PATHMAX * 2] = ""; + char metaPath[PATHMAX * 2] = ""; + + strcpy(filepath, path); + sprintf(pathtmp, "%s", path); + for (idx = strlen(filepath) - 1; filepath[idx] != '/'; idx--) + ; + strcpy(filename, filepath + idx + 1); + + // 파일이름만 가져오기 위해서 문자열자르기 + char *tmpptr = strstr(filepath, exePATH); + if (tmpptr != NULL) + { + strcpy(tmpptr, tmpptr + 1 + strlen(exePATH)); + } + + strcpy(filename, tmpptr); + filepath[idx] = '\0'; + + // path의 속성 + if (lstat(path, &statbuf) < 0) + { + fprintf(stderr, "ERROR다!: lstat error for %s\n", path); + return 1; + } + // path의 파일 사이즈 + fileSize = statbuf.st_size; + + // 백업할 디렉토리를 버퍼에 저장 + strcat(tmpdir, backupPATH); + strcat(tmpdir, "/"); + strcat(tmpdir, date); + + // 이미 y옵션인 경우 + if (parameter->commandopt & OPT_Y) + { + + if (access(tmpdir, F_OK)) + mkdir(tmpdir, 0777); + + // 파일경로명에 디렉토리명이 포함될경우 삭제 + for (idx = strlen(filename) - 1; idx >= 0; idx--) + { + if (filename[idx] == '/') + { + strcpy(filename, filename + idx + 1); + break; + } + } + // 새로운경로 strcat으로 이어주기 + strcat(newPath, tmpdir); + strcat(newPath, "/"); + strcat(newPath, filename); + + if ((fd1 = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", path); + return 1; + } + + if ((fd2 = open(newPath, O_CREAT | O_TRUNC | O_WRONLY, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", newPath); + return 1; + } + + while ((len = read(fd1, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd3 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + // ssubagLog에 작성할 로크 스트링 버퍼에 저장 + strcat(logStr, date); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, " backuped to "); + strcat(logStr, "\""); + strcat(logStr, newPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + if (write(fd3, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터에 원본경로@백업경로형태로 저장 + if ((fd4 = open(metaPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", newPath); + return 1; + } + strcat(metaPath, path); + strcat(metaPath, "@"); + strcat(metaPath, newPath); + strcat(metaPath, "\n"); + + if (write(fd4, metaPath, strlen(metaPath)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + printf("\"%s\" backuped to \"%s\"\n", path, newPath); + } + else + { + + // -y 옵션이 아닌 경우 + // 전역으로 관리하는 백업 링크드리스트를 뒤지면서 백업하려는 파일이 존재하는지 확인 + ConvertHash(path, filehash); + if ((cnt = scandir(backupPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", path); + return 1; + } + for (int i = 0; i < cnt; i++) + { + free(namelist[i]); + } + free(namelist); + + // 백업폴더에 로그가 하나존재하는 경우 + backupList *currBackupLog = mainBackupList; + + if (cnt > 3) + { + while (true) + { + // 이거 지우면 already backuped가 안됨 + if (currBackupLog->next == NULL) + { + break; + } + currBackupLog = currBackupLog->next; + } + + // 역방향 순회로 (최신순 탐색) + while (true) + { + if (currBackupLog == NULL) + { + break; + } + /* + [FIX] : 240320 수업에서 교수님이 동일파일인지 비교할떄 해시값비교 & 파일 사이즈 비교 해야한다해서 + 로직수정 + */ + struct stat tmpStatBuf; + if (lstat(currBackupLog->cur->log, &tmpStatBuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", path); + return 1; + } + tmpFileSize = tmpStatBuf.st_size; + + char tmpHash[PATHMAX] = ""; + ConvertHash(currBackupLog->cur->backuplog, tmpHash); + if (!strcmp(filehash, tmpHash) && !strcmp(currBackupLog->cur->log, path) && (tmpFileSize == fileSize)) + { + fprintf(stderr, "\"%s\" is already backuped to \"%s\" \n", path, currBackupLog->cur->backuplog); + return 1; + } + currBackupLog = currBackupLog->prev; + } + } + + if (access(tmpdir, F_OK)) + mkdir(tmpdir, 0777); + + // 파일경로명에 디렉토리명이 포함될경우 삭제 + for (idx = strlen(filename) - 1; idx >= 0; idx--) + { + if (filename[idx] == '/') + { + strcpy(filename, filename + idx + 1); + break; + } + } + // 새로운경로 strcat으로 이어주기 + strcat(newPath, tmpdir); + strcat(newPath, "/"); + strcat(newPath, filename); + + if ((fd1 = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", path); + return 1; + } + + if ((fd2 = open(newPath, O_CREAT | O_TRUNC | O_WRONLY, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", newPath); + return 1; + } + + while ((len = read(fd1, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd3 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", newPath); + return 1; + } + // ssubagLog에 작성할 로크 스트링 버퍼에 저장 + strcat(logStr, date); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, " backuped to "); + strcat(logStr, "\""); + strcat(logStr, newPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + if (write(fd3, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터에 원본경로@백업경로형태로 저장 + if ((fd4 = open(metaPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", newPath); + return 1; + } + strcat(metaPath, path); + strcat(metaPath, "@"); + strcat(metaPath, newPath); + strcat(metaPath, "\n"); + + if (write(fd4, metaPath, strlen(metaPath)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + printf("\"%s\" backuped to \"%s\"\n", path, newPath); + } + return 0; +} + +int BackupFileByDir(char *exePath, char *path, char *date, command_parameter *parameter) +{ + + int len; + int fd, fd1, fd2, fd3, fd4; + int idx, cnt; + int i; + int fileSize, tmpFileSize; + char buf[1000000]; + struct stat statbuf, tmpbuf; + struct dirent **namelist; + char filepath[PATHMAX] = ""; + char filename[NAMEMAX] = ""; + char tmpdir[PATHMAX] = ""; + char newPath[PATHMAX] = ""; + char pathtmp[PATHMAX] = ""; + // 현재 path에 대한 해시값 + char filehash[NAMEMAX] = ""; + char logStr[PATHMAX * 2] = ""; + char metaPath[PATHMAX * 2] = ""; + char new_filename[PATHMAX] = ""; + + strcpy(filepath, path); + sprintf(pathtmp, "%s", path); + for (idx = strlen(filepath) - 1; filepath[idx] != '/'; idx--) + ; + strcpy(filename, filepath + idx + 1); + + // 파일명은 실행경로 밑에서부터 입력 + char *tmpptr = strstr(filepath, exePATH); + if (tmpptr != NULL) + { + strcpy(tmpptr, tmpptr + 1 + strlen(exePATH)); + } + + strcpy(filename, tmpptr); + filepath[idx] = '\0'; + + // 주어진 파일의 속성을 확인 + if (lstat(path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", path); + return 1; + } + // path의 파일 사이즈 + fileSize = statbuf.st_size; + + // 백업할 디렉토리를 버퍼에 저장 + strcat(tmpdir, backupPATH); + strcat(tmpdir, "/"); + strcat(tmpdir, date); + + // 이미 y옵션인 경우 + if (parameter->commandopt & OPT_Y) + { + + // 현재 path 임시저장소 + char pathTemp[PATHMAX] = ""; + strcpy(pathTemp, path); + + /* + 루트 디렉토리와 서브 디렉토리에서 실행했을 때 처리 + */ + + // exePath에 대한 처리 exePath가 루트 디렉토리 일때와 아닐떄 구분을 해야함 + if (access(tmpdir, F_OK)) + mkdir(tmpdir, 0777); + + // 루트디렉이 아난경우 + char exeTemp[PATHMAX] = ""; + char exeTempDirPath[PATHMAX] = ""; + char tempExeDir[PATHMAX] = ""; + char exeDirPath[PATHMAX] = ""; + + strcpy(exeTemp, exePath); + char *ptrExe = strstr(exeTemp, exePATH); + if (ptrExe != NULL) + { + strcpy(exeTempDirPath, ptrExe + 1 + strlen(exePATH)); + } + strcat(exeDirPath, tmpdir); + strcat(exeDirPath, "/"); + + // 실행 디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPos = strchr(exeTempDirPath, '/'); + + // 밑에 더 디렉토리가 있을 때 + if (slashPos != NULL) + { + + size_t totalLength = strlen(exeTempDirPath); + + // '/' 이후까지의 문자열 길이 계산 + size_t dirLength = totalLength - (slashPos - exeTempDirPath + 1); + + char *dir = malloc(dirLength + 1); + if (dir != NULL) + { + + strncpy(dir, slashPos + 1, dirLength); + dir[dirLength] = '\0'; // 널 종료 문자 추가 + + if (!strcmp(slashPos, dir)) + { + strcat(exeDirPath, dir); + strcat(exeDirPath, "/"); + } + + if (access(exeDirPath, F_OK)) + mkdir(exeDirPath, 0777); + } + + // / 기준으로 밑에 완전히 잘라내기 + char backupTemp[PATHMAX] = ""; + memmove(exeTempDirPath, slashPos + 1, strlen(slashPos + 1) + 1); + strcpy(backupTemp, exeTempDirPath); + // strtok를 사용하여 '/'를 구분자로 사용하여 문자열을 토큰으로 분리 + char *token = strtok(backupTemp, "/"); + strcat(exeDirPath, token); + + // token이 NULL이거나 token,backupTemp가 같다(즉 / 가 없어서 그대로 나온경우) + if (token == NULL || !strcmp(token, exeTempDirPath)) + { + } + else + { + + if (access(exeDirPath, F_OK)) + mkdir(exeDirPath, 0777); + while (1) + { + token = strtok(NULL, "/"); // 다음 토큰으로 이동 + if (token == NULL) + { + break; + } + + strcat(exeDirPath, "/"); + strcat(exeDirPath, token); + if (access(exeDirPath, F_OK)) + mkdir(exeDirPath, 0777); + } + } + } + else + { + // 밑에 더 디렉토리가 없는경우 (루트디렉토리였을떄) + if (slashPos == NULL) + { + strcat(exeDirPath, ""); + } + else + { + + strcat(exeDirPath, exeTempDirPath); + } + } + + /* + 실행 디렉토리 이하에서도 (실제로 백업 되는 부분) 디렉토리가 발견된 경우 + */ + + char tmpBackupDirPath[PATHMAX] = ""; + char backupDirPath[PATHMAX] = ""; + strcpy(backupDirPath, exeDirPath); + + if (backupDirPath[strlen(backupDirPath) - 1] != '/') + { + backupDirPath[strlen(backupDirPath)] = '/'; + } + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePath); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePath)); + } + + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPos2 = strchr(tmpBackupDirPath, '/'); + + // 밑에 더 디렉토리가 있을 때 + if (slashPos2 != NULL) + { + // 실행 디렉토리 잘라내도 뎁스가 있다면 그에 해당하게 mkdir해주기 + size_t dirLength = slashPos2 - tmpBackupDirPath; // '/' 이전까지의 문자열 길이 계산 + char *dir = malloc(dirLength + 1); + if (dir != NULL) + { + strncpy(dir, tmpBackupDirPath, dirLength); // '/' 이전까지의 문자열 복사 + dir[dirLength] = '\0'; // 널 종료 문자 추가 + + strcat(backupDirPath, dir); + strcat(backupDirPath, "/"); + + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + } + // / 기준으로 밑에 완전히 잘라내기 + char backupTemp[PATHMAX] = ""; + memmove(tmpBackupDirPath, slashPos2 + 1, strlen(slashPos2 + 1) + 1); + strcpy(backupTemp, tmpBackupDirPath); + // 맨 뒤 파일 명만 가져오기 + // 파일경로명에 디렉토리명이 포함될경우 삭제 + for (idx = strlen(filename) - 1; idx >= 0; idx--) + { + if (filename[idx] == '/') + { + break; + } + } + // '/' 문자가 발견된 위치부터 문자열의 끝까지의 길이를 구하기 + len = strlen(filename) - idx - 1; + + strncpy(new_filename, filename + idx + 1, len); + new_filename[len] = '\0'; // 문자열의 끝을 나타내는 NULL 문자를 추가. + + // 맨 뒤 파일 명 지워내기 /a.txt같은거 (디렉토리만 남게) + // backupTemp에서 '/' 문자를 뒤에서부터 찾기 + char *lastSlashPos = strrchr(backupTemp, '/'); + if (lastSlashPos != NULL) + { + // '/' 문자 이전까지의 문자열만 남기고 삭제 + *lastSlashPos = '\0'; + } + + // strtok를 사용하여 '/'를 구분자로 사용하여 문자열을 토큰으로 분리 + char *token = strtok(backupTemp, "/"); + strcat(backupDirPath, token); + + // new_filename랑 token이 같은경우 (파일명으로 같을것) -> 아래에 디렉토리가 더 없는경우 + if (!strcmp(token, new_filename)) + { + } + else + { + // 다른 경우 -> 아래에 디렉토리가 더 있음 + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + while (1) + { + token = strtok(NULL, "/"); // 다음 토큰으로 이동 + if (token == NULL) + { + break; + } + + strcat(backupDirPath, "/"); + strcat(backupDirPath, token); + + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + } + + // 마지막에 파일명 붙여주기! + strcat(backupDirPath, "/"); + strcat(backupDirPath, new_filename); + } + } + else + { + // 밑에 더 디렉토리가 없는경우 + strcat(backupDirPath, tmpBackupDirPath); + } + + if ((fd1 = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", path); + return 1; + } + + if ((fd2 = open(backupDirPath, O_CREAT | O_TRUNC | O_WRONLY, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", backupDirPath); + return 1; + } + + while ((len = read(fd1, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd3 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + // ssubagLog에 작성할 로크 스트링 버퍼에 저장 + strcat(logStr, date); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, " backuped to "); + strcat(logStr, "\""); + strcat(logStr, backupDirPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + if (write(fd3, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터에 원본경로@백업경로형태로 저장 + if ((fd4 = open(metaPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", metaPATH); + return 1; + } + strcat(metaPath, path); + strcat(metaPath, "@"); + strcat(metaPath, backupDirPath); + strcat(metaPath, "\n"); + + if (write(fd4, metaPath, strlen(metaPath)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + printf("\"%s\" backuped to \"%s\"\n", path, backupDirPath); + } + else + { + // 전역으로 관리하는 백업 링크드리스트를 뒤지면서 백업하려는 파일이 존재하는지 확인 + ConvertHash(path, filehash); + if ((cnt = scandir(backupPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", path); + return 1; + } + for (int i = 0; i < cnt; i++) + { + free(namelist[i]); + } + free(namelist); + // 백업폴더에 로그가 하나존재하는 경우 + backupList *currBackupLog = mainBackupList; + // @ todo : cnt가 4일때 로그가 존재하며, already backup이 안뜨는 문제 + + backupList *currCountBackupLog = mainBackupList; + int countCnt = 0; + + while (true) + { + if (currCountBackupLog == NULL) + { + break; + } + + currCountBackupLog = currCountBackupLog->next; + countCnt++; + } + + if (cnt == 4 && countCnt > 0) + { + + while (true) + { + if (currBackupLog->next == NULL) + { + + break; + } + + currBackupLog = currBackupLog->next; + } + + // 역방향 순회로 (최신순 탐색) + while (true) + { + + if (currBackupLog == NULL) + { + + break; + } + + /* + [FIX] : 240320 수업에서 교수님이 동일파일인지 비교할떄 해시값비교 & 파일 사이즈 비교 해야한다해서 + 로직수정 + */ + + struct stat tmpStatBuf; + if (lstat(currBackupLog->cur->log, &tmpStatBuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", path); + return 1; + } + tmpFileSize = tmpStatBuf.st_size; + + char tmpHash[PATHMAX] = ""; + ConvertHash(currBackupLog->cur->backuplog, tmpHash); + + if (!strcmp(filehash, tmpHash) && !strcmp(currBackupLog->cur->log, path) && (tmpFileSize == fileSize)) + { + fprintf(stderr, "\"%s\" is already backuped to \"%s\" \n", path, currBackupLog->cur->backuplog); + return 1; + } + currBackupLog = currBackupLog->prev; + } + } + else if (cnt > 4) + { + while (true) + { + if (currBackupLog->next == NULL) + { + break; + } + + currBackupLog = currBackupLog->next; + } + + // 역방향 순회로 (최신순 탐색) + while (true) + { + + if (currBackupLog == NULL) + { + break; + } + + /* + [FIX] : 240320 수업에서 교수님이 동일파일인지 비교할떄 해시값비교 & 파일 사이즈 비교 해야한다해서 + 로직수정 + */ + + struct stat tmpStatBuf; + if (lstat(currBackupLog->cur->log, &tmpStatBuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", path); + return 1; + } + tmpFileSize = tmpStatBuf.st_size; + + char tmpHash[PATHMAX] = ""; + ConvertHash(currBackupLog->cur->backuplog, tmpHash); + + if (!strcmp(filehash, tmpHash) && !strcmp(currBackupLog->cur->log, path) && (tmpFileSize == fileSize)) + { + fprintf(stderr, "\"%s\" is already backuped to \"%s\" \n", path, currBackupLog->cur->backuplog); + return 1; + } + currBackupLog = currBackupLog->prev; + } + } + + // 현재 path 임시저장소 + char pathTemp[PATHMAX] = ""; + strcpy(pathTemp, path); + + /* + 루트 디렉토리와 서브 디렉토리에서 실행했을 때 처리 + */ + + // exePath에 대한 처리 exePath가 루트 디렉토리 일때와 아닐떄 구분을 해야함 + if (access(tmpdir, F_OK)) + mkdir(tmpdir, 0777); + + // 루트디렉이 아난경우 + char exeTemp[PATHMAX] = ""; + char exeTempDirPath[PATHMAX] = ""; + char tempExeDir[PATHMAX] = ""; + char exeDirPath[PATHMAX] = ""; + + strcpy(exeTemp, exePath); + char *ptrExe = strstr(exeTemp, exePATH); + if (ptrExe != NULL) + { + strcpy(exeTempDirPath, ptrExe + 1 + strlen(exePATH)); + } + strcat(exeDirPath, tmpdir); + strcat(exeDirPath, "/"); + + // 실행 디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPos = strchr(exeTempDirPath, '/'); + + // 밑에 더 디렉토리가 있을 때 + if (slashPos != NULL) + { + size_t totalLength = strlen(exeTempDirPath); + + // '/' 이후까지의 문자열 길이 계산 + size_t dirLength = totalLength - (slashPos - exeTempDirPath + 1); + + char *dir = malloc(dirLength + 1); + if (dir != NULL) + { + + strncpy(dir, slashPos + 1, dirLength); + dir[dirLength] = '\0'; // 널 종료 문자 추가 + + if (!strcmp(slashPos, dir)) + { + strcat(exeDirPath, dir); + strcat(exeDirPath, "/"); + } + + if (access(exeDirPath, F_OK)) + mkdir(exeDirPath, 0777); + } + + // / 기준으로 밑에 완전히 잘라내기 + char backupTemp[PATHMAX] = ""; + memmove(exeTempDirPath, slashPos + 1, strlen(slashPos + 1) + 1); + strcpy(backupTemp, exeTempDirPath); + // strtok를 사용하여 '/'를 구분자로 사용하여 문자열을 토큰으로 분리 + char *token = strtok(backupTemp, "/"); + strcat(exeDirPath, token); + + // token이 NULL이거나 token,backupTemp가 같다(즉 / 가 없어서 그대로 나온경우) + if (token == NULL || !strcmp(token, exeTempDirPath)) + { + } + else + { + + if (access(exeDirPath, F_OK)) + mkdir(exeDirPath, 0777); + while (1) + { + token = strtok(NULL, "/"); // 다음 토큰으로 이동 + if (token == NULL) + { + break; + } + + strcat(exeDirPath, "/"); + strcat(exeDirPath, token); + if (access(exeDirPath, F_OK)) + mkdir(exeDirPath, 0777); + } + } + } + else + { + // 밑에 더 디렉토리가 없는경우 (루트디렉토리였을떄) + if (slashPos == NULL) + { + strcat(exeDirPath, ""); + } + else + { + + strcat(exeDirPath, exeTempDirPath); + } + } + + /* + 실행 디렉토리 이하에서도 (실제로 백업 되는 부분) 디렉토리가 발견된 경우 + */ + + char tmpBackupDirPath[PATHMAX] = ""; + char backupDirPath[PATHMAX] = ""; + strcpy(backupDirPath, exeDirPath); + + if (backupDirPath[strlen(backupDirPath) - 1] != '/') + { + backupDirPath[strlen(backupDirPath)] = '/'; + } + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePath); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePath)); + } + + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPos2 = strchr(tmpBackupDirPath, '/'); + + // 밑에 더 디렉토리가 있을 때 + if (slashPos2 != NULL) + { + // 실행 디렉토리 잘라내도 뎁스가 있다면 그에 해당하게 mkdir해주기 + size_t dirLength = slashPos2 - tmpBackupDirPath; // '/' 이전까지의 문자열 길이 계산 + char *dir = malloc(dirLength + 1); + if (dir != NULL) + { + strncpy(dir, tmpBackupDirPath, dirLength); // '/' 이전까지의 문자열 복사 + dir[dirLength] = '\0'; // 널 종료 문자 추가 + + strcat(backupDirPath, dir); + strcat(backupDirPath, "/"); + + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + } + // / 기준으로 밑에 완전히 잘라내기 + char backupTemp[PATHMAX] = ""; + memmove(tmpBackupDirPath, slashPos2 + 1, strlen(slashPos2 + 1) + 1); + strcpy(backupTemp, tmpBackupDirPath); + // 맨 뒤 파일 명만 가져오기 + // 파일경로명에 디렉토리명이 포함될경우 삭제 + for (idx = strlen(filename) - 1; idx >= 0; idx--) + { + if (filename[idx] == '/') + { + break; + } + } + // '/' 문자가 발견된 위치부터 문자열의 끝까지의 길이를 구하기 + len = strlen(filename) - idx - 1; + + strncpy(new_filename, filename + idx + 1, len); + new_filename[len] = '\0'; // 문자열의 끝을 나타내는 NULL 문자를 추가. + + // 맨 뒤 파일 명 지워내기 /a.txt같은거 (디렉토리만 남게) + // backupTemp에서 '/' 문자를 뒤에서부터 찾기 + char *lastSlashPos = strrchr(backupTemp, '/'); + if (lastSlashPos != NULL) + { + // '/' 문자 이전까지의 문자열만 남기고 삭제 + *lastSlashPos = '\0'; + } + + // strtok를 사용하여 '/'를 구분자로 사용하여 문자열을 토큰으로 분리 + char *token = strtok(backupTemp, "/"); + strcat(backupDirPath, token); + + // new_filename랑 token이 같은경우 (파일명으로 같을것) -> 아래에 디렉토리가 더 없는경우 + if (!strcmp(token, new_filename)) + { + } + else + { + // 다른 경우 -> 아래에 디렉토리가 더 있음 + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + while (1) + { + token = strtok(NULL, "/"); // 다음 토큰으로 이동 + if (token == NULL) + { + break; + } + + strcat(backupDirPath, "/"); + strcat(backupDirPath, token); + + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + } + + // 마지막에 파일명 붙여주기! + strcat(backupDirPath, "/"); + strcat(backupDirPath, new_filename); + } + } + else + { + // 밑에 더 디렉토리가 없는경우 + strcat(backupDirPath, tmpBackupDirPath); + } + + if ((fd1 = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", path); + return 1; + } + + if ((fd2 = open(backupDirPath, O_CREAT | O_TRUNC | O_WRONLY, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", backupDirPath); + return 1; + } + + while ((len = read(fd1, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd3 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + // ssubagLog에 작성할 로크 스트링 버퍼에 저장 + strcat(logStr, date); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, " backuped to "); + strcat(logStr, "\""); + strcat(logStr, backupDirPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + if (write(fd3, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터에 원본경로@백업경로형태로 저장 + if ((fd4 = open(metaPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", metaPATH); + return 1; + } + strcat(metaPath, path); + strcat(metaPath, "@"); + strcat(metaPath, backupDirPath); + strcat(metaPath, "\n"); + + if (write(fd4, metaPath, strlen(metaPath)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + printf("\"%s\" backuped to \"%s\"\n", path, backupDirPath); + } + + return 0; +} + +// 주어진 디렉토리와 그 하위 항목들을 백업 +int BackupDir(char *path, char *date, command_parameter *parameter) +{ + + struct dirent **namelist, **subNameList; + struct stat statbuf; + int cnt, subCnt, changeDirmod, idx, len; + + if (lstat(path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", path); + return 1; + } + if (!S_ISDIR(statbuf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not a directory \n", path); + return 1; + } + // path로 받아온 디렉토리의 권한 부여 + if ((changeDirmod = chmod(path, S_IRWXU | S_IRWXG | S_IRWXO)) != 0) + { + fprintf(stderr, "ERROR: chmod error for %s\n", path); + return 1; + } + + // d옵션만 입력시 + if (parameter->commandopt == OPT_D) + { + if ((cnt = scandir(path, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", path); + return 1; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + char tmppath[PATHMAX] = ""; + strcpy(tmppath, path); + strcat(tmppath, "/"); + strcat(tmppath, namelist[i]->d_name); + if (lstat(tmppath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmppath); + return 1; + } + + if (S_ISREG(statbuf.st_mode)) + { + BackupFileByDir(path, tmppath, date, parameter); + } + } + + // namelist 메모리 해제 + for (int idx = 0; idx < cnt; idx++) + { + free(namelist[idx]); + } + free(namelist); + } + // r옵션 or r,d옵션 동시 입력시 or y옵션 입력시 + else if (parameter->commandopt == OPT_R || parameter->commandopt == OPT_Y) + { + + if ((cnt = scandir(path, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", path); + return 1; + } + + // bfs를 통해 경로를 재귀적 탐색 + queue Queue; + initQueue(&Queue); + for (int i = 0; i < cnt; i++) + { + + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + struct stat statbuf; + char enqueuePath[PATHMAX] = ""; + strcat(enqueuePath, path); + strcat(enqueuePath, "/"); + strcat(enqueuePath, namelist[i]->d_name); + enqueue(&Queue, enqueuePath); + } + for (int i = 0; i < cnt; i++) + { + free(namelist[i]); + } + free(namelist); + while (1) + { + if (isEmpty(&Queue)) + { + break; + } + + char *nodePath = dequeue(&Queue); + struct stat statbuf; + + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", nodePath); + return 1; + } + // 파일이라면 그대로 백업을 시켜본다 + if (!S_ISDIR(statbuf.st_mode)) + { + BackupFileByDir(path, nodePath, date, parameter); + } + else + { + // 디렉토리라면 현재 디렉토리 하위의 파일이나 폴더를 탐색 후 path부분만 지우고 다시 큐에 넣는다. + subCnt = 0; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + struct stat statbuf; + + // 새로운 경로를 만들기 + char newPath[PATHMAX] = ""; + char tmpDirPath[PATHMAX] = ""; + strcpy(tmpDirPath, nodePath); + + // 서브디렉토리가 있는경우 만들어주기 + if (lstat(tmpDirPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmpDirPath); + return 1; + } + + // 새로 경로 만들기 + char checkTmpPath[PATHMAX] = ""; + strcat(checkTmpPath, nodePath); + strcat(checkTmpPath, "/"); + strcat(checkTmpPath, subNameList[i]->d_name); + if (lstat(checkTmpPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", checkTmpPath); + return 1; + } + + // bfs유망성 판단해주기 + // 파일이면 -> 바로 파일 백업 함수로 가게 + if (S_ISREG(statbuf.st_mode)) + { + BackupFileByDir(path, checkTmpPath, date, parameter); + } + // 디렉토리일때만 큐에 넣어준다. + if (S_ISDIR(statbuf.st_mode)) + { + enqueue(&Queue, checkTmpPath); + } + } + } + } + } +} + +int BackupCommand(command_parameter *parameter) +{ + + struct stat statbuf; + char originPath[PATHMAX]; + char newBackupPath[PATHMAX]; + char **backupPathList = NULL; + int backupPathDepth = 0; + int i; + char buf[PATHMAX] = ""; + + strcpy(originPath, parameter->filename); + + // lstat을 사용해 디렉토리 속성을 가져옴 + if (lstat(originPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR!!: lstat error for %s %s\n", originPath, parameter->filename); + return 1; + } + // 디렉토리인지 파일인지 확인 + if (!S_ISREG(statbuf.st_mode) && !S_ISDIR(statbuf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not directory or regular file\n", originPath); + return -1; + } + + // 디렉토리인데 옵션으로 -d 또는 -r로 지정되지 않았다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISDIR(statbuf.st_mode) && !((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R) || ((parameter->commandopt & OPT_Y)))) + { + fprintf(stderr, "ERROR: %s is a directory file\n", originPath); + return -1; + } + + if (S_ISREG(statbuf.st_mode) && ((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R))) + { + fprintf(stderr, "ERROR: %s is a directory not a regular file \n", originPath); + return -1; + } + + // 백업될 경로를 생성 이는 백업 디렉토리인 backupPATH와 사용자의 홈 디렉토리인 homePATH의 차이를 이용하여 상대 경로를 계산 + char *backupDate = getDate(); + // 새로운 backupPath stcat으로 이어주기 + strcat(newBackupPath, backupPATH); + strcat(newBackupPath, "/"); + strcat(newBackupPath, backupDate); + + if ((backupPathList = GetSubstring(newBackupPath, &backupPathDepth, "/")) == NULL) + { + fprintf(stderr, "ERROR: %s can't be backuped\n", originPath); + return -1; + } + + // 파일일 경우 BackupFile() 함수를 호출하여 백업을 수행. 디렉토리일 경우 디렉토리 내의 모든 파일과 하위 디렉토리를 백업 + if (S_ISREG(statbuf.st_mode)) + { + BackupFile(originPath, backupDate, parameter); + } + else if (S_ISDIR(statbuf.st_mode)) + { + mainDirList = (dirList *)malloc(sizeof(dirList)); + dirNode *head = (dirNode *)malloc(sizeof(dirNode)); + mainDirList->head = head; + dirNode *curr = head->next; + dirNode *new = (dirNode *)malloc(sizeof(dirNode)); + + strcpy(new->path, originPath); + curr = new; + mainDirList->tail = curr; + + while (curr != NULL) + { + BackupDir(curr->path, backupDate, parameter); + curr = curr->next; + } + } + + return 0; +} + +int RemoveFile(char *path, command_parameter *parameter) +{ + + struct stat statbuf; + char originPath[PATHMAX]; + int fd, cnt, candidateIdx, candidateCnt, fd2; + struct dirent **namelist, **subNameList; + logList *candidateLoglist = NULL; + char originHash[PATHMAX]; + char *tmpPath; + backupList *currBackupLog = mainBackupList; + char *originFilePath; + + // exepath제외하고 파일경로만 판단 + originFilePath = strstr(originPath, exePATH); + if (originFilePath != NULL) + { + strcpy(originFilePath, originFilePath + 1 + strlen(exePATH)); + } + + if (parameter->commandopt & OPT_A) + { + // 모든 백업파일을 삭제한다. + // 1) 전역 백업 링크드리스트 돌면서 매개변수로 받아온 path와 원본경로를 가지는 path를 찾는다. + // 2) 원본경로에 대응하는 백업경로를 candidateList에 담는다. + while (true) + { + + if (currBackupLog == NULL) + { + break; + } + if (!strcmp(path, currBackupLog->cur->log)) + { + // 동일한 경로는 candidateList애 담아주기 + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + candidateCnt++; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + candidateCnt++; + } + curr->next = candidateNode; + candidateNode->prev = curr; + candidateCnt++; + } + } + + currBackupLog = currBackupLog->next; + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + // 후보로 뽑은 리스트을 순회하면서 전부 삭제한다. + logList *currCandiLog = candidateLoglist; + while (true) + { + if (currCandiLog == NULL) + { + break; + } + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + else + { + char logStr[PATHMAX * 2] = ""; + + printf("\"%s\" removed by \"%s\"\n", currCandiLog->cur->backuplog, path); + char *logTime = getDate(); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + currCandiLog = currCandiLog->next; + } + } + else + { + // 1) 전역 백업 링크드리스트 돌면서 매개변수로 받아온 path와 원본경로를 가지는 path를 찾는다. + // 2) 원본경로에 대응하는 백업경로를 candidateList에 담는다. + while (true) + { + + if (currBackupLog == NULL) + { + break; + } + if (!strcmp(path, currBackupLog->cur->log)) + { + // 동일한 경로는 candidateList애 담아주기 + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + candidateCnt++; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + candidateCnt++; + } + curr->next = candidateNode; + candidateNode->prev = curr; + candidateCnt++; + } + } + + currBackupLog = currBackupLog->next; + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + // 후보로 뽑은 리스트 돌려주기 + logList *currCandiLog = candidateLoglist; + if (candidateCnt == 0) + { + // 사용자가 입력한 경로가 백업 링크드 리스트에 없는경우 + fprintf(stderr, "ERROR : invalid remove path : %s\n", path); + return 1; + } + else if (candidateCnt == 1) + { + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + else + { + char logStr[PATHMAX * 2] = ""; + + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", currCandiLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + } + else if (candidateCnt > 1) + { + + printf("backup files of %s\n", path); + printf("0. exit\n"); + int iidx = 1; + while (true) + { + if (currCandiLog == NULL) + { + break; + } + char backupLog[PATHMAX] = ""; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX] = ""; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + // 파일 크기 알아내기 + struct stat file_stat; + if (stat(currCandiLog->cur->backuplog, &file_stat) == -1) + { + fprintf(stderr, "ERROR: stat error\n"); + return 1; + } + printf("%d. %s %lldbytes\n", iidx, realBackupTime, (long long)file_stat.st_size); + + currCandiLog = currCandiLog->next; + iidx++; + } + + // 유저의 입력받기 + int userInputIdx; + printf(">>"); + scanf("%d", &userInputIdx); + if (userInputIdx == 0) + { + exit(1); + } + + // 유저입력과 같은 번째의 노드인경우 삭제 갈기기 + iidx = 1; + currCandiLog = candidateLoglist; + while (true) + { + if (currCandiLog == NULL) + { + break; + } + if (iidx == userInputIdx) + { + // 삭제하기 + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + else + { + char logStr[PATHMAX * 2] = ""; + char *backupTime = splitBackupDate(currCandiLog->cur->backuplog); + printf("\"%s\" removed by \"%s\"\n", currCandiLog->cur->backuplog, path); + strcat(logStr, backupTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 로그파일에 작성 + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + } + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + } + currCandiLog = currCandiLog->next; + iidx++; + } + } + } + + return 0; +} + +// 전역 removeCandidateList + +logList *removeCandidateLoglist = NULL; +// 2) RemoveFileByDir에서 전역 removeCandidateLoglist 만들어서 해당 로그를 저장한다. +int RemoveFileByDir(char *path, char *backupPath, command_parameter *parameter) +{ + + char backupTemp[PATHMAX] = ""; + char backupDirTmp[PATHMAX] = ""; + char backupDateTmp[PATHMAX] = ""; + char pathTemp[PATHMAX] = ""; + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // backupPath에서 backupPATH제외하고 백업폴더 제외하고 남는 부분을 path뒤에 strcat해준다. + strcpy(backupTemp, backupPath); + strcpy(backupDateTmp, backupPath); + strcpy(pathTemp, path); + + char *ptrBack = strstr(backupTemp, backupPATH); + if (ptrBack != NULL) + { + // backupPATH 문자열의 길이만큼을 더해, 그 뒤의 문자열을 가리키게 + char *startCopyPosition = ptrBack + strlen(backupPATH); + strcpy(backupDirTmp, startCopyPosition); + } + + char *backupDate = splitBackupDate(backupDateTmp); + + char *ptrBack2 = strstr(backupTemp, backupDate); + if (ptrBack2 != NULL) + { + // backupPATH 문자열의 길이만큼을 더해, 그 뒤의 문자열을 가리키게 + char *startCopyPosition2 = ptrBack2 + strlen(backupDate); + strcpy(backupDirTmp, startCopyPosition2); + } + + strcat(pathTemp, backupDirTmp); + + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, backupPath); + strcpy(candiNodeElement->log, pathTemp); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (removeCandidateLoglist == NULL) + { + // removeCandidateLoglist가 비어 있는 경우 + removeCandidateLoglist = candidateNode; + } + else + { + curr = removeCandidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + + return 0; +} + +int RemoveDir(char *path, command_parameter *parameter) +{ + + struct stat statbuf; + char originPath[PATHMAX]; + int fd, cnt, candidateIdx, fd2, subCnt; + int removeCnt = 0; + int candidateCnt = 0; + struct dirent **namelist, **subNameList; + char originHash[PATHMAX]; + char *tmpPath; + char *originFilePath; + + // d옵션인경우 + // 0) 전역 백업 링크드 리스트를 돈다 + // 1) 유저가 입력한 디렉토리 절대경로와 문자열의 맨앞에서부터 유저가 작성한 절대경로의 문자열 길이 만큼 같은지 판단하고 + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + // 2) Regfile인애들만 remove + + if (parameter->commandopt == OPT_D) + { + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + strcpy(pathTemp, path); + + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + char *backupDate; + + // 0) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + + if (!strncmp(extractLogPath, path, strlen(path))) + { + + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + if ((cnt = scandir(backupDirPath, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupDirPath); + return 1; + } + + for (int i = 0; i < cnt; i++) + { + + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + char subTemp[PATHMAX] = ""; + strcat(subTemp, backupDirPath); + strcat(subTemp, "/"); + strcat(subTemp, namelist[i]->d_name); + + if (lstat(subTemp, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", subTemp); + return 1; + } + // 2) Regfile인애들만 remove + if (!S_ISDIR(statbuf.st_mode) && !strcmp(subTemp, currBackupLog->cur->backuplog)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + + break; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 3)삭제할 애들이 모여있는 리스트 + + logList *currCandiLog = candidateLoglist; + bool exitFlag = false; + while (true) + { + if (currCandiLog == NULL || currCandiLog->cur->log == NULL) + { + break; + } + // removeCandidatLoglist의 최신상태를 반영할 수 있게 (지우면 세그폴트 뜸!!!!) + currCandiLog = candidateLoglist; + + // 반복문 안에서 똑같은 원본 파일이 있는지 확인하는 리스트를 만든다. + logList *subCandiList = NULL; + logList *subPos = candidateLoglist; + while (true) + { + if (subPos == NULL) + { + break; + } + if (!strcmp(subPos->cur->log, currCandiLog->cur->log)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, subPos->cur->backuplog); + strcpy(candiNodeElement->log, subPos->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (subCandiList == NULL) + { + // mainlogList가 비어 있는 경우 + subCandiList = candidateNode; + } + else + { + curr = subCandiList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + subPos = subPos->next; + } + + // 서브 리스트 속에 원소가 몇개인지 센다 + logList *subCandiLog = subCandiList; + int subPosCnt = 0; + while (true) + { + if (subCandiLog == NULL) + { + break; + } + + subPosCnt++; + subCandiLog = subCandiLog->next; + } + + subCandiLog = subCandiList; + + if (subPosCnt == 1) + { + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", subCandiLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + // candiLogList에서도 삭제 반영 + removeBackuplog(&candidateLoglist, subCandiLog->cur->backuplog); + removeLogAndUpdateList(&candidateLoglist, subCandiLog->cur->log); + } + else if (subPosCnt > 1) + { + // 주의! 서브 리스트 안에서 삭제를 해야함! + printf("remove files of %s\n", subCandiLog->cur->log); + printf("0. exit\n"); + int iidx = 1; + while (true) + { + + if (subCandiLog == NULL) + { + break; + } + + char backupLog[PATHMAX] = ""; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX] = ""; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + // 파일 크기 알아내기 + struct stat file_stat; + if (stat(subCandiLog->cur->backuplog, &file_stat) == -1) + { + fprintf(stderr, "ERROR: stat error\n"); + return 1; + } + printf("%d. %s %lldbytes\n", iidx, realBackupTime, (long long)file_stat.st_size); + + subCandiLog = subCandiLog->next; + iidx++; + } + + // 유저의 입력받기 + int userInputIdx; + printf(">>"); + scanf("%d", &userInputIdx); + if (userInputIdx == 0) + { + exit(1); + } + + // 유저입력과 같은 번째의 노드인경우 삭제 갈기기 + iidx = 1; + // 서브 리스트 안에서 삭제 해야하기 때문에 subCandiList를 가리키는 포인터 + subCandiLog = subCandiList; + while (1) + { + if (subCandiLog == NULL) + { + break; + } + + if (iidx == userInputIdx) + { + + // 삭제하기 + if (remove(subCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", subCandiLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 로그파일에 작성 + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + // 백업로그 먼저 삭제 + + removeBackuplog(&candidateLoglist, subCandiLog->cur->backuplog); + // candidateLoglist에도 반영 + removeLogAndUpdateList(&candidateLoglist, subCandiLog->cur->log); + break; + } + else + { + subCandiLog = subCandiLog->next; + iidx++; + } + } + } + + if (currCandiLog == NULL) + { + break; + } + else + { + currCandiLog = currCandiLog->next; + } + } + } + + // r옵션인경우 + // 0) 전역 백업 링크드 리스트를 돈다 + // 1) 유저가 입력한 디렉토리 절대경로와 백업경로가 문자열의 맨앞에서부터 유저가 작성한 절대경로의 문자열 길이 만큼 같은지 판단하고 + // 2) 맞다면 거기서 backupDate가져와서 유저가 입력한 경로에서 실행디렉 잘라내고 backupPATH/백업날짜/실행디렉 잘라낸 유저가 입력한 경로 -> 이렇게 새로운 백업경로 생성 + // 3) 새롭게 만든 백업경로에서 namelist strcat시키면서 디렉이면 경로자체를 큐에넣고 파일이면 안넣는다. + + else if (parameter->commandopt == OPT_R) + { + + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + // 큐에 넣을 애들 중 중복검사 + logList *enqueueLogList = NULL; + char *backupDate; + queue Queue; + initQueue(&Queue); + strcpy(pathTemp, path); + + // 1) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + if (!strncmp(extractLogPath, path, strlen(path))) + { + + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + logList *enqueueLog = enqueueLogList; + bool enqueueFlag = true; + while (true) + { + if (enqueueLog == NULL) + { + + break; + } + if (!strcmp(enqueueLog->cur->backuplog, backupDirPath)) + { + enqueueFlag = false; + } + enqueueLog = enqueueLog->next; + } + + if (enqueueFlag) + { + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, backupDirPath); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (enqueueLogList == NULL) + { + // removeCandidateLoglist가 비어 있는 경우 + enqueueLogList = candidateNode; + } + else + { + curr = enqueueLogList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // 3-1 )중복 제거해서 큐에 넣는다. + logList *enqueueLog = enqueueLogList; + while (true) + { + if (enqueueLog == NULL) + { + break; + } + enqueue(&Queue, enqueueLog->cur->backuplog); + enqueueLog = enqueueLog->next; + } + + // 3-2) 큐가 다 비어있을때까지 반복 + + while (1) + { + if (isEmpty(&Queue)) + { + break; + } + + char *nodePath = dequeue(&Queue); + struct stat statbuf; + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", nodePath); + return 1; + } + // 파일이라면 그대로 백업을 시켜본다 + if (!S_ISDIR(statbuf.st_mode)) + { + RemoveFileByDir(path, nodePath, parameter); + } + else + { + // 디렉토리라면 현재 디렉토리 하위의 파일이나 폴더를 탐색 후 path부분만 지우고 다시 큐에 넣는다. + subCnt = 0; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + struct stat statbuf; + + // 새로운 경로를 만들기 + char newPath[PATHMAX] = ""; + char tmpDirPath[PATHMAX] = ""; + strcpy(tmpDirPath, nodePath); + + // 서브디렉토리가 있는경우 만들어주기 + if (lstat(tmpDirPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmpDirPath); + return 1; + } + + // 새로 경로 만들기 + char checkTmpPath[PATHMAX] = ""; + strcat(checkTmpPath, nodePath); + strcat(checkTmpPath, "/"); + strcat(checkTmpPath, subNameList[i]->d_name); + + if (lstat(checkTmpPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", checkTmpPath); + return 1; + } + + // bfs유망성 판단해주기 + // 파일이면 -> 바로 파일 백업 함수로 가게 + if (S_ISREG(statbuf.st_mode)) + { + RemoveFileByDir(path, checkTmpPath, parameter); + } + // 디렉토리일때만 큐에 넣어준다. + if (S_ISDIR(statbuf.st_mode)) + { + enqueue(&Queue, checkTmpPath); + } + } + } + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 4) RemoveFileDir에서 bfs하면서 전역 링크드 리스트에 저장시켜놓은 애들을 다시 가져와서 진짜 삭제하기! + logList *currRemoveLog = removeCandidateLoglist; + while (true) + { + if (currRemoveLog == NULL || currRemoveLog->cur->log == NULL) + { + break; + } + // removeCandidatLoglist의 최신상태를 반영할 수 있게 (지우면 세그폴트 뜸!!!!) + currRemoveLog = removeCandidateLoglist; + // 반복문 안에서 똑같은 원본 파일이 있는지 확인하는 리스트를 만든다. + logList *subCandiList = NULL; + logList *subPos = removeCandidateLoglist; + while (true) + { + if (subPos == NULL) + { + break; + } + + if (!strcmp(subPos->cur->log, currRemoveLog->cur->log)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, subPos->cur->backuplog); + strcpy(candiNodeElement->log, subPos->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (subCandiList == NULL) + { + // mainlogList가 비어 있는 경우 + subCandiList = candidateNode; + } + else + { + curr = subCandiList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + subPos = subPos->next; + } + + // 서브 리스트 속에 원소가 몇개인지 센다 + logList *subCandiLog = subCandiList; + int subPosCnt = 0; + while (true) + { + if (subCandiLog == NULL) + { + break; + } + + subPosCnt++; + subCandiLog = subCandiLog->next; + } + + subCandiLog = subCandiList; + + if (subPosCnt == 1) + { + if (remove(currRemoveLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", subCandiLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + // 백업로그를 먼저 삭제한다 (삭제한거 다시 접근하지 않게) + removeBackuplog(&removeCandidateLoglist, subCandiLog->cur->backuplog); + // removecandiLogList에서도 삭제 반영 + removeLogAndUpdateList(&removeCandidateLoglist, subCandiLog->cur->log); + } + else if (subPosCnt > 1) + { + // 주의! 서브 리스트 안에서 삭제를 해야함! + printf("remove files of %s\n", subCandiLog->cur->log); + printf("0. exit\n"); + int iidx = 1; + while (true) + { + + if (subCandiLog == NULL) + { + break; + } + + char backupLog[PATHMAX] = ""; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX] = ""; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + // 파일 크기 알아내기 + struct stat file_stat; + if (stat(subCandiLog->cur->backuplog, &file_stat) == -1) + { + fprintf(stderr, "ERROR: stat error\n"); + return 1; + } + printf("%d. %s %lldbytes\n", iidx, realBackupTime, (long long)file_stat.st_size); + + subCandiLog = subCandiLog->next; + iidx++; + } + + // 유저의 입력받기 + int userInputIdx; + printf(">>"); + scanf("%d", &userInputIdx); + if (userInputIdx == 0) + { + exit(1); + } + + // 유저입력과 같은 번째의 노드인경우 삭제 갈기기 + iidx = 1; + // 서브 리스트 안에서 삭제 해야하기 때문에 subCandiList를 가리키는 포인터 + subCandiLog = subCandiList; + while (1) + { + if (subCandiLog == NULL) + { + break; + } + + if (iidx == userInputIdx) + { + + // 삭제하기 + if (remove(subCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", subCandiLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 로그파일에 작성 + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + // 백업로그를 먼저 삭제한다 (삭제한거 다시 접근하지 않게) + removeBackuplog(&removeCandidateLoglist, subCandiLog->cur->backuplog); + // removeCandidateLoglist 에서도 삭제 반영 + removeLogAndUpdateList(&removeCandidateLoglist, subCandiLog->cur->log); + break; + } + else + { + subCandiLog = subCandiLog->next; + iidx++; + } + } + } + + if (currRemoveLog != NULL) + { + currRemoveLog = currRemoveLog->next; + } + else + { + break; + } + } + } + + // -d 옵션과 -a옵션 + else if (parameter->commandopt == (OPT_D | OPT_A)) + { + + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + strcpy(pathTemp, path); + + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + char *backupDate; + + // 0) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + + if (!strncmp(extractLogPath, path, strlen(path))) + { + + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + if ((cnt = scandir(backupDirPath, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupDirPath); + return 1; + } + + for (int i = 0; i < cnt; i++) + { + + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + char subTemp[PATHMAX] = ""; + strcat(subTemp, backupDirPath); + strcat(subTemp, "/"); + strcat(subTemp, namelist[i]->d_name); + + if (lstat(subTemp, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", subTemp); + return 1; + } + // 2) Regfile인애들만 remove + if (!S_ISDIR(statbuf.st_mode) && !strcmp(subTemp, currBackupLog->cur->backuplog)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + + break; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 3)삭제할 애들이 모여있는 리스트 + + logList *currCandiLog = candidateLoglist; + while (true) + { + if (currCandiLog == NULL) + { + break; + } + + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", currCandiLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + // candiLogList에서도 삭제 반영 + removeBackuplog(&candidateLoglist, currCandiLog->cur->backuplog); + + if (currCandiLog == NULL) + { + break; + } + else + { + currCandiLog = currCandiLog->next; + } + } + } + else if (parameter->commandopt == (OPT_R | OPT_A)) + { + + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + // 큐에 넣을 애들 중 중복검사 + logList *enqueueLogList = NULL; + char *backupDate; + queue Queue; + initQueue(&Queue); + strcpy(pathTemp, path); + + // 1) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + if (!strncmp(extractLogPath, path, strlen(path))) + { + + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + logList *enqueueLog = enqueueLogList; + bool enqueueFlag = true; + while (true) + { + if (enqueueLog == NULL) + { + + break; + } + if (!strcmp(enqueueLog->cur->backuplog, backupDirPath)) + { + enqueueFlag = false; + } + enqueueLog = enqueueLog->next; + } + + if (enqueueFlag) + { + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, backupDirPath); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (enqueueLogList == NULL) + { + // removeCandidateLoglist가 비어 있는 경우 + enqueueLogList = candidateNode; + } + else + { + curr = enqueueLogList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // 3-1 )중복 제거해서 큐에 넣는다. + logList *enqueueLog = enqueueLogList; + while (true) + { + if (enqueueLog == NULL) + { + break; + } + enqueue(&Queue, enqueueLog->cur->backuplog); + enqueueLog = enqueueLog->next; + } + + // 3-2) 큐가 다 비어있을때까지 반복 + + while (1) + { + if (isEmpty(&Queue)) + { + break; + } + + char *nodePath = dequeue(&Queue); + + struct stat statbuf; + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", nodePath); + return 1; + } + // 파일이라면 그대로 백업을 시켜본다 + if (!S_ISDIR(statbuf.st_mode)) + { + RemoveFileByDir(path, nodePath, parameter); + } + else + { + // 디렉토리라면 현재 디렉토리 하위의 파일이나 폴더를 탐색 후 path부분만 지우고 다시 큐에 넣는다. + subCnt = 0; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + struct stat statbuf; + + // 새로운 경로를 만들기 + char newPath[PATHMAX] = ""; + char tmpDirPath[PATHMAX] = ""; + strcpy(tmpDirPath, nodePath); + + // 서브디렉토리가 있는경우 만들어주기 + if (lstat(tmpDirPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmpDirPath); + return 1; + } + + // 새로 경로 만들기 + char checkTmpPath[PATHMAX] = ""; + strcat(checkTmpPath, nodePath); + strcat(checkTmpPath, "/"); + strcat(checkTmpPath, subNameList[i]->d_name); + + if (lstat(checkTmpPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", checkTmpPath); + return 1; + } + + // bfs유망성 판단해주기 + // 파일이면 -> 바로 파일 백업 함수로 가게 + if (S_ISREG(statbuf.st_mode)) + { + RemoveFileByDir(path, checkTmpPath, parameter); + } + // 디렉토리일때만 큐에 넣어준다. + if (S_ISDIR(statbuf.st_mode)) + { + enqueue(&Queue, checkTmpPath); + } + } + } + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 4) RemoveFileDir에서 bfs하면서 전역 링크드 리스트에 저장시켜놓은 애들을 다시 가져와서 진짜 삭제하기! + logList *currRemoveLog = removeCandidateLoglist; + while (true) + { + if (currRemoveLog == NULL || currRemoveLog->cur->log == NULL) + { + break; + } + // removeCandidatLoglist의 최신상태를 반영할 수 있게 (지우면 세그폴트 뜸!!!!) + currRemoveLog = removeCandidateLoglist; + + if (remove(currRemoveLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currRemoveLog->cur->backuplog); + return 1; + } + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" removed by \"%s\"\n", currRemoveLog->cur->backuplog, path); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currRemoveLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " removed by "); + strcat(logStr, "\""); + strcat(logStr, path); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(currRemoveLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currRemoveLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currRemoveLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currRemoveLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currRemoveLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + removeBackuplog(&removeCandidateLoglist, currRemoveLog->cur->backuplog); + if (currRemoveLog == NULL) + { + break; + } + else + { + currRemoveLog = currRemoveLog->next; + } + } + } + + return 0; +} + +int RemoveCommand(command_parameter *parameter) +{ + struct stat statbuf; + char originPath[PATHMAX]; + int fd, cnt, candidateIdx, candidateCnt, fd2; + struct dirent **namelist, **subNameList; + logList *candidateLoglist = NULL; + char originHash[PATHMAX]; + char *tmpPath; + + strcpy(originPath, parameter->filename); + // lstat을 사용해 디렉토리 속성을 가져옴 + if (lstat(originPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s %s\n", originPath, parameter->filename); + return 1; + } + + // 디렉토리인데 옵션으로 -d 또는 -r로 지정되지 않았다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISDIR(statbuf.st_mode) && !((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R) || (parameter->commandopt & OPT_Y))) + { + fprintf(stderr, "ERROR: %s is a directory file\n", originPath); + return -1; + } + + // 파일인데 옵션으로 -d 또는 -r로 지정되지 않았다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISREG(statbuf.st_mode) && ((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R) || (parameter->commandopt & OPT_Y))) + { + fprintf(stderr, "ERROR: %s is a directory not a regular file \n", originPath); + return -1; + } + if ((cnt = scandir(backupPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupPATH); + return 1; + } + if (S_ISDIR(statbuf.st_mode)) + { + RemoveDir(originPath, parameter); + } + else + { + RemoveFile(originPath, parameter); + } + + return 0; +} + +int RecoverDir(char *originPath, char *tmpPath, command_parameter *parameter) +{ + + char *buf = (char *)malloc(sizeof(char) * PATHMAX); + struct stat statbuf; + int fd, cnt, candidateIdx, fd2, subCnt, fdBack, fdOrigin, length; + int removeCnt = 0; + int candidateCnt = 0; + struct dirent **namelist, **subNameList; + char originHash[PATHMAX]; + char *originFilePath; + + if (parameter->commandopt == OPT_D) + { + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + strcpy(pathTemp, originPath); + + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + char *backupDate; + + // 0) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + if (!strcmp(extractLogPath, originPath)) + { + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + if ((cnt = scandir(backupDirPath, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupDirPath); + return 1; + } + + for (int i = 0; i < cnt; i++) + { + + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + char subTemp[PATHMAX] = ""; + strcat(subTemp, backupDirPath); + strcat(subTemp, "/"); + strcat(subTemp, namelist[i]->d_name); + + if (lstat(subTemp, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", subTemp); + return 1; + } + // 2) Regfile인애들만 recover + if (!S_ISDIR(statbuf.st_mode) && !strcmp(subTemp, currBackupLog->cur->backuplog)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + + break; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 3)복구할 애들이 모여있는 리스트 + // 복구 했을 때 currCandiLog에 삭제된 상황 반영이 되어있어야함! + // mainbackuplist에도 삭제된 상황이 반영되어야 -> refresh메서드 쓰거나 삭제 메서드 쓸것 + logList *currCandiLog = candidateLoglist; + bool exitFlag = false; + while (true) + { + if (currCandiLog == NULL || currCandiLog->cur->log == NULL) + { + break; + } + + // 반복문 안에서 똑같은 원본 파일이 있는지 확인하는 리스트를 만든다. + logList *subCandiList = NULL; + logList *subPos = candidateLoglist; + while (true) + { + if (subPos == NULL) + { + break; + } + if (!strcmp(subPos->cur->log, currCandiLog->cur->log)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, subPos->cur->backuplog); + strcpy(candiNodeElement->log, currCandiLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (subCandiList == NULL) + { + // mainlogList가 비어 있는 경우 + subCandiList = candidateNode; + } + else + { + curr = subCandiList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + subPos = subPos->next; + } + + // 서브 리스트 속에 원소가 몇개인지 센다 + logList *subCandiLog = subCandiList; + int subPosCnt = 0; + while (true) + { + if (subCandiLog == NULL) + { + break; + } + + subPosCnt++; + subCandiLog = subCandiLog->next; + } + + subCandiLog = subCandiList; + + if (subPosCnt == 1) + { + // 원본폴더로 파일 디스크립터 열기 이미 데이터가 있다면 덮어쓸 수 있게 다 밀어버리기 + if ((fdOrigin = open(currCandiLog->cur->log, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", currCandiLog->cur->log); + return 1; + } + if ((fdBack = open(currCandiLog->cur->backuplog, O_RDONLY, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", currCandiLog->cur->backuplog); + return 1; + } + // backup파일에서 읽은 데이터를 원본경로에 쓴다. + while ((length = read(fdBack, buf, BUFFER_SIZE)) > 0) + { + write(fdOrigin, buf, length); + } + // 백업 파일 삭제하기 + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" recovered to \"%s\"\n", subCandiLog->cur->backuplog, subCandiLog->cur->log); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->log); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + //@todo + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + // 백업로그 먼저 삭제 (이미 삭제한거 접근하지 않게) + removeBackuplog(&candidateLoglist, subCandiLog->cur->backuplog); + // candiLogList에서도 삭제 반영 + removeLogAndUpdateList(&candidateLoglist, subCandiLog->cur->log); + } + else if (subPosCnt > 1) + { + // 주의! 서브 리스트 안에서 삭제를 해야함! + printf("backup files %s\n", subCandiLog->cur->log); + printf("0. exit\n"); + int iidx = 1; + while (true) + { + + if (subCandiLog == NULL) + { + break; + } + + char backupLog[PATHMAX] = ""; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX] = ""; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + // 파일 크기 알아내기 + struct stat file_stat; + if (stat(subCandiLog->cur->backuplog, &file_stat) == -1) + { + fprintf(stderr, "ERROR: stat error\n"); + return 1; + } + printf("%d. %s %lldbytes\n", iidx, realBackupTime, (long long)file_stat.st_size); + + subCandiLog = subCandiLog->next; + iidx++; + } + + // 유저의 입력받기 + int userInputIdx; + printf(">>"); + scanf("%d", &userInputIdx); + if (userInputIdx == 0) + { + exit(1); + } + + // 유저입력과 같은 번째의 노드인경우 삭제 갈기기 + iidx = 1; + // 서브 리스트 안에서 삭제 해야하기 때문에 subCandiList를 가리키는 포인터 + subCandiLog = subCandiList; + while (1) + { + if (subCandiLog == NULL) + { + break; + } + if (iidx == userInputIdx) + { + // 원본폴더로 파일 디스크립터 열기 이미 데이터가 있다면 덮어쓸 수 있게 다 밀어버리기 + if ((fdOrigin = open(currCandiLog->cur->log, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", currCandiLog->cur->log); + return 1; + } + if ((fdBack = open(currCandiLog->cur->backuplog, O_RDONLY, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", currCandiLog->cur->backuplog); + return 1; + } + // backup파일에서 읽은 데이터를 원본경로에 쓴다. + while ((length = read(fdBack, buf, BUFFER_SIZE)) > 0) + { + write(fdOrigin, buf, length); + } + // 삭제하기 + if (remove(subCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR!!!! : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" recovered to \"%s\"\n", subCandiLog->cur->backuplog, subCandiLog->cur->log); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->log); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 로그파일에 작성 + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + // 백업로그 먼저 삭제 (이미 삭제한거 접근하지 않게) + removeBackuplog(&candidateLoglist, subCandiLog->cur->backuplog); + // candiLogList에서도 삭제 반영 + removeLogAndUpdateList(&candidateLoglist, subCandiLog->cur->log); + break; + } + else + { + subCandiLog = subCandiLog->next; + iidx++; + } + } + } + + if (currCandiLog == NULL || currCandiLog->next == NULL) + { + break; + } + currCandiLog = currCandiLog->next; + } + } + else if (parameter->commandopt == OPT_R) + { + + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + // 큐에 넣을 애들 중 중복검사 + logList *enqueueLogList = NULL; + char *backupDate; + queue Queue; + initQueue(&Queue); + strcpy(pathTemp, originPath); + + // 1) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + if (!strncmp(extractLogPath, originPath, strlen(originPath))) + { + + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + logList *enqueueLog = enqueueLogList; + bool enqueueFlag = true; + while (true) + { + if (enqueueLog == NULL) + { + + break; + } + if (!strcmp(enqueueLog->cur->backuplog, backupDirPath)) + { + enqueueFlag = false; + } + enqueueLog = enqueueLog->next; + } + + if (enqueueFlag) + { + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, backupDirPath); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (enqueueLogList == NULL) + { + // removeCandidateLoglist가 비어 있는 경우 + enqueueLogList = candidateNode; + } + else + { + curr = enqueueLogList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // 3-1 )중복 제거해서 큐에 넣는다. + logList *enqueueLog = enqueueLogList; + while (true) + { + if (enqueueLog == NULL) + { + break; + } + enqueue(&Queue, enqueueLog->cur->backuplog); + enqueueLog = enqueueLog->next; + } + + // 3-2) 큐가 다 비어있을때까지 반복 + + while (1) + { + if (isEmpty(&Queue)) + { + break; + } + + char *nodePath = dequeue(&Queue); + struct stat statbuf; + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", nodePath); + return 1; + } + // 파일이라면 그대로 백업을 시켜본다 + if (!S_ISDIR(statbuf.st_mode)) + { + RemoveFileByDir(originPath, nodePath, parameter); + } + else + { + // 디렉토리라면 현재 디렉토리 하위의 파일이나 폴더를 탐색 후 path부분만 지우고 다시 큐에 넣는다. + subCnt = 0; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + struct stat statbuf; + + // 새로운 경로를 만들기 + char newPath[PATHMAX] = ""; + char tmpDirPath[PATHMAX] = ""; + strcpy(tmpDirPath, nodePath); + + // 서브디렉토리가 있는경우 만들어주기 + if (lstat(tmpDirPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmpDirPath); + return 1; + } + + // 새로 경로 만들기 + char checkTmpPath[PATHMAX] = ""; + strcat(checkTmpPath, nodePath); + strcat(checkTmpPath, "/"); + strcat(checkTmpPath, subNameList[i]->d_name); + + if (lstat(checkTmpPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", checkTmpPath); + return 1; + } + + // bfs유망성 판단해주기 + // 파일이면 -> 바로 파일 백업 함수로 가게 + if (S_ISREG(statbuf.st_mode)) + { + RemoveFileByDir(originPath, checkTmpPath, parameter); + } + // 디렉토리일때만 큐에 넣어준다. + if (S_ISDIR(statbuf.st_mode)) + { + enqueue(&Queue, checkTmpPath); + } + } + } + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 4) RemoveFileDir에서 bfs하면서 전역 링크드 리스트에 저장시켜놓은 애들을 다시 가져와서 진짜 삭제하기! + logList *currRemoveLog = removeCandidateLoglist; + while (true) + { + // removeCandidatLoglist의 최신상태를 반영할 수 있게 + currRemoveLog = removeCandidateLoglist; + + if (currRemoveLog == NULL || currRemoveLog->cur->log == NULL) + { + break; + } + + // 반복문 안에서 똑같은 원본 파일이 있는지 확인하는 리스트를 만든다. + logList *subCandiList = NULL; + logList *subPos = removeCandidateLoglist; + while (true) + { + if (subPos == NULL) + { + break; + } + + if (!strcmp(subPos->cur->log, currRemoveLog->cur->log)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, subPos->cur->backuplog); + strcpy(candiNodeElement->log, subPos->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (subCandiList == NULL) + { + // mainlogList가 비어 있는 경우 + subCandiList = candidateNode; + } + else + { + curr = subCandiList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + subPos = subPos->next; + } + + // 서브 리스트 속에 원소가 몇개인지 센다 + logList *subCandiLog = subCandiList; + int subPosCnt = 0; + while (true) + { + if (subCandiLog == NULL) + { + break; + } + + subPosCnt++; + subCandiLog = subCandiLog->next; + } + + subCandiLog = subCandiList; + + if (subPosCnt == 1) + { + // 복원시키기 + // 원본폴더로 파일 디스크립터 열기 이미 데이터가 있다면 덮어쓸 수 있게 다 밀어버리기 + if ((fdOrigin = open(currRemoveLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", currRemoveLog->cur->backuplog); + return 1; + } + if ((fdBack = open(currRemoveLog->cur->backuplog, O_RDONLY, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", currRemoveLog->cur->backuplog); + return 1; + } + // backup파일에서 읽은 데이터를 원본경로에 쓴다. + while ((length = read(fdBack, buf, BUFFER_SIZE)) > 0) + { + write(fdOrigin, buf, length); + } + // 백업 파일 삭제하기 + + if (remove(currRemoveLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" recovered to \"%s\"\n", subCandiLog->cur->backuplog, subCandiLog->cur->log); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->log); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + // 백업로그를 먼저 삭제한다 (삭제한거 다시 접근하지 않게) + removeBackuplog(&removeCandidateLoglist, subCandiLog->cur->backuplog); + // removecandiLogList에서도 삭제 반영 + removeLogAndUpdateList(&removeCandidateLoglist, subCandiLog->cur->log); + } + else if (subPosCnt > 1) + { + // 주의! 서브 리스트 안에서 삭제를 해야함! + printf("backup files of %s\n", subCandiLog->cur->log); + printf("0. exit\n"); + int iidx = 1; + while (true) + { + + if (subCandiLog == NULL) + { + break; + } + + char backupLog[PATHMAX] = ""; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX] = ""; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + // 파일 크기 알아내기 + struct stat file_stat; + if (stat(subCandiLog->cur->backuplog, &file_stat) == -1) + { + fprintf(stderr, "ERROR: stat error\n"); + return 1; + } + printf("%d. %s %lldbytes\n", iidx, realBackupTime, (long long)file_stat.st_size); + + subCandiLog = subCandiLog->next; + iidx++; + } + + // 유저의 입력받기 + int userInputIdx; + printf(">>"); + scanf("%d", &userInputIdx); + if (userInputIdx == 0) + { + exit(1); + } + + // 유저입력과 같은 번째의 노드인경우 삭제 갈기기 + iidx = 1; + // 서브 리스트 안에서 삭제 해야하기 때문에 subCandiList를 가리키는 포인터 + subCandiLog = subCandiList; + while (1) + { + if (subCandiLog == NULL) + { + break; + } + + if (iidx == userInputIdx) + { + // 복원시키기 + // 원본폴더로 파일 디스크립터 열기 이미 데이터가 있다면 덮어쓸 수 있게 다 밀어버리기 + if ((fdOrigin = open(subCandiLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", subCandiLog->cur->backuplog); + return 1; + } + if ((fdBack = open(subCandiLog->cur->backuplog, O_RDONLY, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", subCandiLog->cur->backuplog); + return 1; + } + // backup파일에서 읽은 데이터를 원본경로에 쓴다. + while ((length = read(fdBack, buf, BUFFER_SIZE)) > 0) + { + write(fdOrigin, buf, length); + } + // 백업 파일 삭제하기 + + // 삭제하기 + if (remove(subCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" recovered to \"%s\"\n", subCandiLog->cur->backuplog, subCandiLog->cur->log); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->log); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 로그파일에 작성 + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + // 백업로그를 먼저 삭제한다 (삭제한거 다시 접근하지 않게) + removeBackuplog(&removeCandidateLoglist, subCandiLog->cur->backuplog); + // removeCandidateLoglist 에서도 삭제 반영 + removeLogAndUpdateList(&removeCandidateLoglist, subCandiLog->cur->log); + break; + } + else + { + subCandiLog = subCandiLog->next; + iidx++; + } + } + } + if (currRemoveLog == NULL) + { + break; + } + else + { + currRemoveLog = currRemoveLog->next; + } + } + } + else if (parameter->commandopt == (OPT_D | OPT_L)) + { + + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + char *backupDate; + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + strcpy(pathTemp, originPath); + + // 0) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + if (!strcmp(extractLogPath, originPath)) + { + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + if ((cnt = scandir(backupDirPath, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupDirPath); + return 1; + } + + for (int i = 0; i < cnt; i++) + { + + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + char subTemp[PATHMAX] = ""; + strcat(subTemp, backupDirPath); + strcat(subTemp, "/"); + strcat(subTemp, namelist[i]->d_name); + + if (lstat(subTemp, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", subTemp); + return 1; + } + + // 2) Regfile인애들만 recover + if (!S_ISDIR(statbuf.st_mode) && !strcmp(subTemp, currBackupLog->cur->backuplog)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + + break; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 3)복구할 애들이 모여있는 리스트 + // 복구 했을 때 currCandiLog에 삭제된 상황 반영이 되어있어야함! + // mainbackuplist에도 삭제된 상황이 반영되어야 -> refresh메서드 쓰거나 삭제 메서드 쓸것 + logList *currCandiLog = candidateLoglist; + + while (true) + { + if (currCandiLog == NULL) + { + break; + } + + // 반복문 안에서 똑같은 원본 파일이 있는지 확인하는 리스트를 만든다. + logList *subCandiList = NULL; + logList *subPos = candidateLoglist; + while (true) + { + if (subPos == NULL) + { + break; + } + if (!strcmp(subPos->cur->log, currCandiLog->cur->log)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, subPos->cur->backuplog); + strcpy(candiNodeElement->log, currCandiLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (subCandiList == NULL) + { + // mainlogList가 비어 있는 경우 + subCandiList = candidateNode; + } + else + { + curr = subCandiList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + subPos = subPos->next; + } + + // 서브 리스트 끝까지 간다. + logList *subCandiLog = subCandiList; + + while (true) + { + if (subCandiLog->next == NULL) + { + break; + } + + subCandiLog = subCandiLog->next; + } + + // 원본폴더로 파일 디스크립터 열기 이미 데이터가 있다면 덮어쓸 수 있게 다 밀어버리기 + if ((fdOrigin = open(subCandiLog->cur->log, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", subCandiLog->cur->log); + return 1; + } + if ((fdBack = open(subCandiLog->cur->backuplog, O_RDONLY, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", subCandiLog->cur->backuplog); + return 1; + } + // backup파일에서 읽은 데이터를 원본경로에 쓴다. + while ((length = read(fdBack, buf, BUFFER_SIZE)) > 0) + { + write(fdOrigin, buf, length); + } + // 백업 파일 삭제하기 + if (remove(subCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + else + { + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" recovered to \"%s\"\n", subCandiLog->cur->backuplog, subCandiLog->cur->log); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->log); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + + // 백업로그 먼저 삭제 (이미 삭제한거 접근하지 않게) + removeBackuplog(&candidateLoglist, subCandiLog->cur->backuplog); + // candiLogList에서도 삭제 반영 + removeLogAndUpdateList(&candidateLoglist, subCandiLog->cur->log); + + if (currCandiLog == NULL) + { + break; + } + else + { + currCandiLog = currCandiLog->next; + } + } + } + else if (parameter->commandopt == (OPT_R | OPT_L)) + { + char pathTemp[PATHMAX] = ""; + char tmpBackupDirPath[PATHMAX] = ""; + backupList *currBackupLog = mainBackupList; + logList *candidateLoglist = NULL; + // 큐에 넣을 애들 중 중복검사 + logList *enqueueLogList = NULL; + char *backupDate; + queue Queue; + initQueue(&Queue); + strcpy(pathTemp, originPath); + + // 1) 전역 백업 링크드 리스트를 돈다 + while (true) + { + if (currBackupLog == NULL) + { + break; + } + + // 1) 유저가 입력한 디렉토리 절대경로와 로그에서 마지막 파일명만 제거한 문자열이 같으면 + char *extractLogPath = extractPath(currBackupLog->cur->log); + // 2) 해당 날짜에 해당하는 백업 폴더로 가서 + if (!strncmp(extractLogPath, originPath, strlen(originPath))) + { + + backupDate = splitBackupDate(currBackupLog->cur->backuplog); + + char backupDirPath[PATHMAX] = ""; + strcat(backupDirPath, backupPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, backupDate); + + // 매개변수로 받아온 path에서 최상위 루트디렉토리만 삭제해서 strcat하고 큐에 넣어준다. + // 매개변수로 받아온 실행경로 만큼 잘라내기 + char *ptrDir = strstr(pathTemp, exePATH); + if (ptrDir != NULL) + { + strcpy(tmpBackupDirPath, ptrDir + 1 + strlen(exePATH)); + } + char slashRootDir[PATHMAX] = ""; + strcpy(slashRootDir, tmpBackupDirPath); + // 백업디렉토리중 가장 상위 디렉토리 잘라내기 + char *slashPosition = strchr(slashRootDir, '/'); + + if (slashPosition != NULL) + { + memmove(slashRootDir, slashPosition + 1, strlen(slashPosition)); + } + + if (strcmp(tmpBackupDirPath, slashRootDir)) + { + strcat(backupDirPath, "/"); + strcat(backupDirPath, slashRootDir); + } + + logList *enqueueLog = enqueueLogList; + bool enqueueFlag = true; + while (true) + { + if (enqueueLog == NULL) + { + + break; + } + if (!strcmp(enqueueLog->cur->backuplog, backupDirPath)) + { + enqueueFlag = false; + } + enqueueLog = enqueueLog->next; + } + + if (enqueueFlag) + { + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, backupDirPath); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (enqueueLogList == NULL) + { + // removeCandidateLoglist가 비어 있는 경우 + enqueueLogList = candidateNode; + } + else + { + curr = enqueueLogList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + } + + currBackupLog = currBackupLog->next; + } + + // 3-1 )중복 제거해서 큐에 넣는다. + logList *enqueueLog = enqueueLogList; + while (true) + { + if (enqueueLog == NULL) + { + break; + } + enqueue(&Queue, enqueueLog->cur->backuplog); + enqueueLog = enqueueLog->next; + } + + // 3-2) 큐가 다 비어있을때까지 반복 + + while (1) + { + if (isEmpty(&Queue)) + { + break; + } + + char *nodePath = dequeue(&Queue); + struct stat statbuf; + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", nodePath); + return 1; + } + // 파일이라면 그대로 백업을 시켜본다 + if (!S_ISDIR(statbuf.st_mode)) + { + RemoveFileByDir(originPath, nodePath, parameter); + } + else + { + // 디렉토리라면 현재 디렉토리 하위의 파일이나 폴더를 탐색 후 path부분만 지우고 다시 큐에 넣는다. + subCnt = 0; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + struct stat statbuf; + + // 새로운 경로를 만들기 + char newPath[PATHMAX] = ""; + char tmpDirPath[PATHMAX] = ""; + strcpy(tmpDirPath, nodePath); + + // 서브디렉토리가 있는경우 만들어주기 + if (lstat(tmpDirPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmpDirPath); + return 1; + } + + // 새로 경로 만들기 + char checkTmpPath[PATHMAX] = ""; + strcat(checkTmpPath, nodePath); + strcat(checkTmpPath, "/"); + strcat(checkTmpPath, subNameList[i]->d_name); + + if (lstat(checkTmpPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", checkTmpPath); + return 1; + } + + // bfs유망성 판단해주기 + // 파일이면 -> 바로 파일 백업 함수로 가게 + if (S_ISREG(statbuf.st_mode)) + { + RemoveFileByDir(originPath, checkTmpPath, parameter); + } + // 디렉토리일때만 큐에 넣어준다. + if (S_ISDIR(statbuf.st_mode)) + { + enqueue(&Queue, checkTmpPath); + } + } + } + } + + // ssubackLog에 작성할 파일디스크립터 생성 + if ((fd2 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + // 4) RemoveFileDir에서 bfs하면서 전역 링크드 리스트에 저장시켜놓은 애들을 다시 가져와서 진짜 삭제하기! + logList *currRemoveLog = removeCandidateLoglist; + while (true) + { + // removeCandidatLoglist의 최신상태를 반영할 수 있게 + currRemoveLog = removeCandidateLoglist; + + if (currRemoveLog == NULL || currRemoveLog->cur->log == NULL) + { + break; + } + + // 반복문 안에서 똑같은 원본 파일이 있는지 확인하는 리스트를 만든다. + logList *subCandiList = NULL; + logList *subPos = removeCandidateLoglist; + while (true) + { + if (subPos == NULL) + { + break; + } + + if (!strcmp(subPos->cur->log, currRemoveLog->cur->log)) + { + + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, subPos->cur->backuplog); + strcpy(candiNodeElement->log, subPos->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (subCandiList == NULL) + { + // mainlogList가 비어 있는 경우 + subCandiList = candidateNode; + } + else + { + curr = subCandiList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = candidateNode; + candidateNode->prev = curr; + } + } + subPos = subPos->next; + } + + // 서브 리스트 끝까지 간다. + logList *subCandiLog = subCandiList; + + while (true) + { + if (subCandiLog->next == NULL) + { + break; + } + subCandiLog = subCandiLog->next; + } + + // 복원시키기 + // 원본폴더로 파일 디스크립터 열기 이미 데이터가 있다면 덮어쓸 수 있게 다 밀어버리기 + if ((fdOrigin = open(subCandiLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", subCandiLog->cur->backuplog); + return 1; + } + if ((fdBack = open(subCandiLog->cur->backuplog, O_RDONLY, 0644)) < 0) + { + fprintf(stderr, "ERROR : open error %s\n", subCandiLog->cur->backuplog); + return 1; + } + // backup파일에서 읽은 데이터를 원본경로에 쓴다. + while ((length = read(fdBack, buf, BUFFER_SIZE)) > 0) + { + write(fdOrigin, buf, length); + } + // 백업 파일 삭제하기 + + if (remove(subCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", subCandiLog->cur->backuplog); + return 1; + } + + char logStr[PATHMAX * 2] = ""; + char *logTime = getDate(); + printf("\"%s\" recovered to \"%s\"\n", subCandiLog->cur->backuplog, subCandiLog->cur->log); + strcat(logStr, logTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, subCandiLog->cur->log); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + if (write(fd2, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 메타데이터도 지워주기 + if ((clearMetaData(subCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, subCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, subCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // subCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, subCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + // 백업로그를 먼저 삭제한다 (삭제한거 다시 접근하지 않게) + removeBackuplog(&removeCandidateLoglist, subCandiLog->cur->backuplog); + // removecandiLogList에서도 삭제 반영 + removeLogAndUpdateList(&removeCandidateLoglist, subCandiLog->cur->log); + if (currRemoveLog == NULL) + { + break; + } + else + { + currRemoveLog = currRemoveLog->next; + } + } + } + return 0; +} + +int RecoverFile(char *originPath, char *tmpPath, command_parameter *parameter) +{ + + int fd1, fd2, fd3, len, candidateCnt; + char *buf = (char *)malloc(sizeof(char) * PATHMAX); + logList *candidateLoglist = NULL; + backupList *currBackupLog = mainBackupList; + + if ((fd1 = open(ssubakLogPATH, O_WRONLY | O_APPEND, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", ssubakLogPATH); + return 1; + } + + if ((fd2 = open(originPath, O_WRONLY | O_CREAT | O_TRUNC, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", originPath); + return 1; + } + + if (parameter->commandopt == OPT_L) + { + // 1) 전역 백업 링크드리스트 돌면서 originPath에 해당하는 백업파일들을 candidateList에 담는다. + while (true) + { + + if (currBackupLog == NULL) + { + break; + } + // originPath와 동일한 경로는 candidateList애 담아주기 + if (!strcmp(originPath, currBackupLog->cur->log)) + { + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + candidateCnt++; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + candidateCnt++; + } + curr->next = candidateNode; + candidateNode->prev = curr; + candidateCnt++; + } + } + + currBackupLog = currBackupLog->next; + } + + logList *currCandiLog = candidateLoglist; + + // 2) candidateList 크기가 한개일경우 + if (candidateCnt == 1) + { + struct stat statbuf; + // 주어진 파일의 속성을 확인 + if (lstat(currCandiLog->cur->backuplog, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-1) originPath에 백업파일을 그대로 복사한다. (originPath에 파일이 있다면 덮어쓰기) + if ((fd3 = open(currCandiLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", originPath); + return 1; + } + while ((len = read(fd3, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + // 2-2) 백업파일을 삭제한다. + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-3) 복구 성공시 메세지 출력 + char logStr[PATHMAX * 2] = ""; + char *backupTime = splitBackupDate(currCandiLog->cur->backuplog); + + printf("\"%s\"recovered to \"%s\"\n", currCandiLog->cur->backuplog, originPath); + strcat(logStr, backupTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, originPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 2-4) 복구 성공시 로그 append + if (write(fd1, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + // 2-5) 현재 백업디렉토리가 빈폴더라면 삭제하기 + + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLogg->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + } + else if (candidateCnt > 1) + { + // 3) candidateList 크기가 두개 이상일 경우 + + // 3-1) 링크드리스트 끝으로 가기 + while (true) + { + if (currCandiLog->next == NULL) + { + break; + } + + currCandiLog = currCandiLog->next; + } + struct stat statbuf; + // 주어진 파일의 속성을 확인 + if (lstat(currCandiLog->cur->backuplog, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-1) originPath에 백업파일을 그대로 복사한다. (originPath에 파일이 있다면 덮어쓰기) + if ((fd3 = open(currCandiLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", originPath); + return 1; + } + while ((len = read(fd3, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + // 2-2) 백업파일을 삭제한다. + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-3) 복구 성공시 메세지 출력 + char logStr[PATHMAX * 2] = ""; + char *backupTime = splitBackupDate(currCandiLog->cur->backuplog); + + printf("\"%s\"recovered to \"%s\"\n", currCandiLog->cur->backuplog, originPath); + strcat(logStr, backupTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, originPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 2-4) 복구 성공시 로그 append + if (write(fd1, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 2-5) 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + } + } + else + { + // 1) 전역 백업 링크드리스트 돌면서 originPath에 해당하는 백업파일들을 candidateList에 담는다. + while (true) + { + + if (currBackupLog == NULL) + { + break; + } + // originPath와 동일한 경로는 candidateList애 담아주기 + if (!strcmp(originPath, currBackupLog->cur->log)) + { + logList *candidateNode = (logList *)malloc(sizeof(logList)); + // 노드 안의 로그 생성 + logElement *candiNodeElement = (logElement *)malloc(sizeof(logElement)); + memset(candiNodeElement, 0, sizeof(candiNodeElement)); + strcpy(candiNodeElement->backuplog, currBackupLog->cur->backuplog); + strcpy(candiNodeElement->log, currBackupLog->cur->log); + candidateNode->cur = candiNodeElement; + candidateNode->prev = NULL; + candidateNode->next = NULL; + + // 로그 리스트에 로그 노드 추가 + logList *curr; + if (candidateLoglist == NULL) + { + // mainlogList가 비어 있는 경우 + candidateLoglist = candidateNode; + candidateCnt++; + } + else + { + curr = candidateLoglist; + while (curr->next != NULL) + { + curr = curr->next; + candidateCnt++; + } + curr->next = candidateNode; + candidateNode->prev = curr; + candidateCnt++; + } + } + + currBackupLog = currBackupLog->next; + } + + logList *currCandiLog = candidateLoglist; + + // 2) candidateList 크기가 한개일경우 + if (candidateCnt == 1) + { + struct stat statbuf; + // 주어진 파일의 속성을 확인 + if (lstat(currCandiLog->cur->backuplog, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-1) originPath에 백업파일을 그대로 복사한다. (originPath에 파일이 있다면 덮어쓰기) + if ((fd3 = open(currCandiLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", originPath); + return 1; + } + while ((len = read(fd3, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + // 2-2) 백업파일을 삭제한다. + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-3) 복구 성공시 메세지 출력 + char logStr[PATHMAX * 2] = ""; + char *backupTime = splitBackupDate(currCandiLog->cur->backuplog); + + printf("\"%s\"recovered to \"%s\"\n", currCandiLog->cur->backuplog, originPath); + strcat(logStr, backupTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, originPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 2-4) 복구 성공시 로그 append + if (write(fd1, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + + // 2-5) 현재 백업디렉토리가 빈폴더라면 삭제하기 + + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLogg->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + } + else if (candidateCnt > 1) + { + // 3) candidateList 크기가 두개 이상일 경우 + printf("backup files of %s\n", originPath); + printf("0. exit\n"); + int iidx = 1; + // 3-1) 후보인 로그 리스트로 뽑아내기 + while (true) + { + if (currCandiLog == NULL) + { + break; + } + char backupLog[PATHMAX] = ""; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX] = ""; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + // 파일 크기 알아내기 + struct stat file_stat; + if (stat(currCandiLog->cur->backuplog, &file_stat) == -1) + { + fprintf(stderr, "ERROR: stat error\n"); + return 1; + } + printf("%d. %s %lldbytes\n", iidx, realBackupTime, (long long)file_stat.st_size); + + currCandiLog = currCandiLog->next; + iidx++; + } + + // 3-2) 유저의 입력받기 + int userInputIdx; + printf(">>"); + scanf("%d", &userInputIdx); + if (userInputIdx == 0) + { + exit(1); + } + + // 3-3) 유저입력과 같은 번째의 노드인경우 복구 갈기기 + iidx = 1; + currCandiLog = candidateLoglist; + while (true) + { + if (currCandiLog == NULL) + { + break; + } + if (iidx == userInputIdx) + { + + struct stat statbuf; + // 주어진 파일의 속성을 확인 + if (lstat(currCandiLog->cur->backuplog, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-1) originPath에 백업파일을 그대로 복사한다. (originPath에 파일이 있다면 덮어쓰기) + if ((fd3 = open(currCandiLog->cur->backuplog, O_WRONLY | O_CREAT | O_TRUNC, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", originPath); + return 1; + } + while ((len = read(fd3, buf, statbuf.st_size)) > 0) + { + write(fd2, buf, len); + } + // 2-2) 백업파일을 삭제한다. + if (remove(currCandiLog->cur->backuplog) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", currCandiLog->cur->backuplog); + return 1; + } + // 2-3) 복구 성공시 메세지 출력 + char logStr[PATHMAX * 2] = ""; + char *backupTime = splitBackupDate(currCandiLog->cur->backuplog); + + printf("\"%s\"recovered to \"%s\"\n", currCandiLog->cur->backuplog, originPath); + strcat(logStr, backupTime); + strcat(logStr, " : "); + strcat(logStr, "\""); + strcat(logStr, currCandiLog->cur->backuplog); + strcat(logStr, "\""); + strcat(logStr, " recovered to "); + strcat(logStr, "\""); + strcat(logStr, originPath); + strcat(logStr, "\""); + strcat(logStr, "\n"); + + // 메타데이터도 지워주기 + if ((clearMetaData(currCandiLog->cur->backuplog)) < 0) + { + fprintf(stderr, "Error : clean meta data error\n"); + } + + // 2-4) 복구 성공시 로그 append + if (write(fd1, logStr, strlen(logStr)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", ssubakLogPATH); + return 1; + } + // 2-5) 현재 백업디렉토리가 빈폴더라면 삭제하기 + char backupLog[PATHMAX]; + strcpy(backupLog, currCandiLog->cur->backuplog); + char backupTimeTemp[PATHMAX]; + strcpy(backupTimeTemp, currCandiLog->cur->backuplog); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + + // currCandiLog->cur->backuplog에서 backupPATH/realBackupTime 제거하고 + // 맨뒤에 파일명 제거한 부분이 서브디렉토리가 될것 + char backupTmpPath[PATHMAX] = ""; + strcat(backupTmpPath, currCandiLog->cur->backuplog); + char *subDirPath; + char tmpBackDir[PATHMAX] = ""; + strcat(tmpBackDir, backupPATH); + strcat(tmpBackDir, "/"); + strcat(tmpBackDir, realBackupTime); + // 현재 백업 경로를 bfs로 돌면서 subcnt==2이면 그 폴더 삭제 + + queue Queue2; + initQueue(&Queue2); + enqueue(&Queue2, tmpBackDir); + + while (1) + { + if (isEmpty(&Queue2)) + { + break; + } + char *nodePath = dequeue(&Queue2); + + struct stat statbuf; + if (access(nodePath, F_OK) == 0) + { + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", nodePath); + return 1; + } + // 디렉토리인 애들 중 + // subCnt가 2가 아니면 자기자신도 큐에 넣고 (나중에 돌아오면서 삭제위함) 서브디렉토리도 찾아서 걔를 큐에 넣는다. + if (S_ISDIR(statbuf.st_mode)) + { + + int subCnt; + struct dirent **subNameList; + char prevPath[PATHMAX] = ""; + + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + + if (subCnt == 2) + { + if (remove(nodePath) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", nodePath); + return 1; + } + } + else if (subCnt >= 3) + { + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..")) + continue; + + struct stat substatbuf; + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + strcpy(prevPath, nodePath); + + strcat(nodePath, "/"); + strcat(nodePath, subNameList[i]->d_name); + + // 만약 새로운경로가 디렉이면 큐에 넣고 + if (lstat(nodePath, &substatbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", nodePath); + return 1; + } + if (S_ISDIR(substatbuf.st_mode)) + { + enqueue(&Queue2, nodePath); + // 빈 디렉이라면 이전 새로운 경로를 한번 더 넣어준다. + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return 1; + } + if (subCnt == 2) + { + enqueue(&Queue2, prevPath); + } + } + } + } + } + } + } + } + + // 백업폴더도 지워주기 + int cnt; + struct dirent **nameList; + + if (access(tmpBackDir, F_OK) == 0) + { + if ((cnt = scandir(tmpBackDir, &nameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", tmpBackDir); + return 1; + } + if (cnt == 2) + { + if (remove(tmpBackDir) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", tmpBackDir); + return 1; + } + } + } + } + currCandiLog = currCandiLog->next; + iidx++; + } + } + } + return 0; +} + +int RecoverCommand(command_parameter *parameter) +{ + + struct stat statbuf, tmpbuf; + char originPath[PATHMAX]; + int fd, cnt, candidateIdx, candidateCnt; + int originFileSize, tmpFileSize; + struct dirent **namelist, **subNameList; + logList *candidateLoglist = NULL; + char filehash[PATHMAX] = ""; + char tmphash[PATHMAX] = ""; + char tmppath[PATHMAX] = ""; + + strcpy(originPath, parameter->filename); + strcpy(tmppath, parameter->tmpname); + // 인자로 받아온 경로 + strcpy(tmppath, parameter->tmpname); + + // lstat을 사용해 디렉토리 속성을 가져옴 + if (lstat(originPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s \n", originPath); + return 1; + } + + originFileSize = statbuf.st_size; + + // 디렉토리인데 옵션으로 -d 또는 -r로 지정되지 않았다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISDIR(statbuf.st_mode) && !((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R))) + { + fprintf(stderr, "ERROR: %s is a directory \n", originPath); + return -1; + } + + // 파일인데 옵션으로 -d 또는 -r로 지정되었다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISREG(statbuf.st_mode) && ((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R))) + { + fprintf(stderr, "ERROR!!: %s is a file \n", originPath); + return -1; + } + if ((cnt = scandir(backupPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupPATH); + return 1; + } + if (parameter->commandopt & OPT_N) + { + + if (lstat(tmppath, &tmpbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s \n", tmppath); + return 1; + } + // 인자로 받아온 경로 + if (lstat(tmppath, &tmpbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmppath); + return 1; + } + // n 뒤에 경로 입력하지 않았다면 비정상 종료 + if ((originPath == NULL) && ((parameter->commandopt & OPT_N))) + { + fprintf(stderr, "ERROR: did not write a path %s \n", originPath); + return -1; + } + // n 뒤에 경로가 올바르지 않는 경우 + if ((lstat(tmppath, &tmpbuf) < 0) && ((parameter->commandopt & OPT_N))) + { + fprintf(stderr, "ERROR: invalid file path for %s\n", parameter->filename); + return -1; + } + // 경로가 홈 디렉토리를 벗어나는 경우, 백업 디렉토리에 백업되려고 할때 + if (strncmp(tmppath, homePATH, strlen(homePATH)) || !strncmp(tmppath, backupPATH, strlen(backupPATH)) || !strcmp(tmppath, homePATH)) + { + fprintf(stderr, "ERROR: filename %s is out of home path \n", parameter->filename); + + return -1; + } + // d, r을 사용안했는데 n뒤에 경로가 존재하고 디렉인경우s + if (S_ISDIR(tmpbuf.st_mode) && !((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R)) && (parameter->commandopt & OPT_N) && parameter->tmpname) + { + fprintf(stderr, "ERROR: %s is a directory \n", originPath); + return -1; + } + + // d,r을 사용했는데 n뒤에 경로 존재하고 파일인경우 + if (S_ISREG(tmpbuf.st_mode) && ((parameter->commandopt & OPT_D) || (parameter->commandopt & OPT_R)) && (parameter->commandopt & OPT_N) && parameter->tmpname) + { + fprintf(stderr, "ERROR: %s is a file\n", originPath); + return -1; + } + } + + if (S_ISDIR(statbuf.st_mode)) + { + RecoverDir(originPath, tmppath, parameter); + } + else + { + //[예외처리] 인자로 입력받은 경로에 대한 백업 파일이 원본 파일과 내용이 같을 경우 복원을 진행하지 않고, (“원본 경로” not changed with “백업 경로”)를 표준 출력(5점) + // 1) 링크드 리스트를 돌면서 해당 경로에 대한 백업파일을 리턴받는다 + // 2) 원본 파일과 백업파일의 해시값을 비교한다. + char *searchBackupPath = findBackupFileByOriginPath(originPath); + if (searchBackupPath == NULL) + { + fprintf(stderr, "ERROR : file does not exist in the backup list %s\n", originPath); + } + + struct stat searchBackupBuf; + if (lstat(searchBackupPath, &tmpbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmppath); + return 1; + } + tmpFileSize = searchBackupBuf.st_size; + + ConvertHash(originPath, filehash); + ConvertHash(searchBackupPath, tmphash); + if (!strcmp(filehash, tmphash) && (originFileSize == tmpFileSize)) + { + printf("%s not changed with %s\n", originPath, searchBackupPath); + exit(1); + } + RecoverFile(originPath, tmppath, parameter); + } + + return 0; +} + +void SystemExec(char **arglist) +{ + pid_t pid; + char whichPath[PATHMAX]; + + sprintf(whichPath, "/usr/bin/%s", arglist[0]); + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + execv(whichPath, arglist); + exit(0); + } + else + { + pid = wait(NULL); + } +} + +void ListParameterInit(list_parameter *parameter) +{ + parameter->command = (char *)malloc(sizeof(char) * PATHMAX); + parameter->index = (char *)malloc(sizeof(char) * PATHMAX); + parameter->tmpname = (char *)malloc(sizeof(char) * PATHMAX); + // 명령어의 옵션 나타내는 플래그 + parameter->commandopt = 0; +} + +void ListPrompt(int listSize) +{ + + char input[STRMAX]; + int argcnt = 0; + char **arglist = NULL; + int command; + int argc = 0; + char *argvStr = NULL; + list_parameter ListParameter = {(char *)0, (char *)0, (char *)0, 0}; + + while (1) + { + int lastind; + printf(">>"); + fgets(input, sizeof(input), stdin); + + if ((arglist = GetSubstring(input, &argcnt, " ")) == NULL) + { + continue; + } + if (argcnt == 0) + { + continue; + } + // 커멘드에 불필요한 공백이 생길 경우 제거 + for (int i = 0; i < argcnt; i++) + { + char *arg = arglist[i]; + arg = trim_left(arg); + arg = trim_right(arg); + strcpy(arglist[i], arg); + } + + if (!strcmp(arglist[0], commandData[5])) + { + command = CMD_REM; + } + else if (!strcmp(arglist[0], commandData[6])) + { + command = CMD_REC; + } + else if (!strcmp(arglist[0], commandData[7])) + { + command = CMD_SYS; + } + else if (!strcmp(arglist[0], commandData[8])) + { + command = CMD_SYS; + } + else if (!strcmp(arglist[0], commandData[9])) + { + command = CMD_EXIT; + printf("Program exit(0)\n"); + exit(0); + } + else + { + command = NOT_CMD; + } + + // [예외처리] 잘못된 명령어 입력시 + if (command == NOT_CMD) + { + fprintf(stderr, "ERROR : invalid command\n"); + exit(1); + } + + // [예외처리] 잘못된 인덱스 입력시 + if (command & (CMD_REM | CMD_REC)) + { + ListParameter.command = arglist[0]; + ListParameter.index = arglist[1]; + int atoiIdx = atoi(ListParameter.index); + if (atoiIdx < 0 || atoiIdx > listSize) + { + fprintf(stderr, "ERROR: invalid index number\n"); + exit(1); + } + } + else if (command & (CMD_SYS)) + { + SystemExec(arglist); + } + // 옵션처리 + lastind = 2; + } +} + +int ListCommand(command_parameter *parameter) +{ + + struct stat statbuf, tmpbuf; + char originPath[PATHMAX]; + int listSize = 0; + int fd; + + strcpy(originPath, parameter->filename); + if (lstat(originPath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", originPath); + return 1; + } + + // 인자로 받아온 filename에 대한 리스트 출력 + // 1) 파일일 경우 + if (S_ISREG(statbuf.st_mode)) + { + // originPath에 해당하는 백업 경로 가져오기 + char *backupPath = findBackupFileByOriginPath(originPath); + char *backupDate = splitBackupDate(backupPath); + // 백업파일의 사이즈 알아내기 + off_t filesize; + fd = open(backupPath, O_RDONLY); + filesize = lseek(fd, 0, SEEK_END); + char *convertBackupFileSize = addCommas(filesize); + printf("%d. %s %s %sbytes\n", listSize, originPath, backupDate, convertBackupFileSize); + + ListPrompt(listSize); + } + // 2) 디렉토리일 경우 + if (S_ISDIR(statbuf.st_mode)) + { + } + + return 0; +} + +int HelpCommand(char *parameter) +{ + help(parameter); + return 0; +} + +// 파라미터 초기화 해주는 함수 +void ParameterInit(command_parameter *parameter) +{ + parameter->command = (char *)malloc(sizeof(char) * PATHMAX); + parameter->filename = (char *)malloc(sizeof(char) * PATHMAX); + parameter->tmpname = (char *)malloc(sizeof(char) * PATHMAX); + // 명령어의 옵션 나타내는 플래그 + parameter->commandopt = 0; +} + +int ParameterProcessing(int argcnt, char **arglist, int command, command_parameter *parameter) +{ + struct stat buf; // 파일의 메타 데이터 저장함 + optind = 0; + opterr = 0; + int lastind; + int option; + int optcnt = 0; + + switch (command) + { + // 파일백업 명령어 + case CMD_BACKUP: + { + + // 경로를 입력하지 않은 경우 + if (argcnt < 3) + { + fprintf(stderr, "Usage : backup [OPTION] \n"); + return -1; + } + char *resolved = realpath(arglist[2], parameter->filename); + // 경로가 올바르지 않은경우 + if (resolved == NULL) + { + fprintf(stderr, "ERROR: %s is invalid filepath\n", parameter->filename); + return -1; + } + // 경로가 길이 제한을 넘는 경우 + if (strlen(resolved) > PATHMAX) + { + fprintf(stderr, "ERROR : %s exceeds 4096 bytes \n", parameter->filename); + return -1; + } + + // 파일이나 디렉토리가 존재하지 않는 경우 + if (lstat(parameter->filename, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", parameter->filename); + return -1; + } + // 일반 파일이거나 디렉토리가 아닌경우 + if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not regular file\n", parameter->filename); + return -1; + } + // 해당경로에 대한 접근 권한이 없는 경우 + if (access(parameter->filename, R_OK | W_OK) < 0) + { + fprintf(stderr, "ERROR: %s permission denied \n", parameter->filename); + return -1; + } + // 경로가 홈 디렉토리를 벗어나는 경우, 백업 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, homePATH, strlen(homePATH)) || !strncmp(parameter->filename, backupPATH, strlen(backupPATH)) || !strcmp(parameter->filename, homePATH)) + { + fprintf(stderr, "ERROR: filename %s can't be backuped %s \n", parameter->filename, backupPATH); + + return -1; + } + + // 옵션 파싱과정에서 마지막으로 처리된 옵션 인덱스 + lastind = 2; + + while ((option = getopt(argcnt, arglist, "dry")) != -1) + { + + // 옵션이 올바르지 않은 경우 + if (option != 'd' && option != 'r' && option != 'y') + { + fprintf(stderr, "ERROR: unknown option %c\n", optopt); + printf("Usage:\n"); + printf(" > backup [OPTION]... : backup file if is file\n"); + printf(" -d : backup files in directory if is directory \n"); + printf(" -r : backup files in directory recursive if is directory \n"); + printf(" -y : backup file although already backuped\n"); + return -1; + } + // 옵션이 올바르지 않은 경우 + if (optind == lastind) + { + fprintf(stderr, "ERROR: wrong option input\n"); + printf("Usage:\n"); + printf(" > backup [OPTION]... : backup file if is file\n"); + printf(" -d : backup files in directory if is directory \n"); + printf(" -r : backup files in directory recursive if is directory \n"); + printf(" -y : backup file although already backuped\n"); + return -1; + } + if (option == 'd') + { + // 중복으로 사용했는지 검사 + if (parameter->commandopt & OPT_D) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return 1; + } + // 만약 -r이나 -y가 있다면 추가하지 않음 + if (!(parameter->commandopt & OPT_R) && !(parameter->commandopt & OPT_Y)) + { + // 비트연산을 통해 옵션 추가 + parameter->commandopt |= OPT_D; + } + } + + if (option == 'r') + { + if (parameter->commandopt & OPT_R) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return 1; + } + // 만약 -y가 있다면 추가하지 않음 + if (!(parameter->commandopt & OPT_Y)) + { + // 만약 -d가 있다면 AND연산을 통해 d제거 + if (parameter->commandopt & OPT_D) + { + parameter->commandopt &= ~OPT_D; + } + parameter->commandopt |= OPT_R; + } + } + + if (option == 'y') + { + if (parameter->commandopt & OPT_Y) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + // 만약 -d나 -r이 있다면 이를 삭제 + if (parameter->commandopt & OPT_D || parameter->commandopt & OPT_R) + { + parameter->commandopt &= ~OPT_D; + parameter->commandopt &= ~OPT_R; + } + parameter->commandopt |= OPT_Y; + } + // 옵션 개수 세주기 + optcnt++; + lastind = optind; + } + + if (argcnt - optcnt != 3) + { + fprintf(stderr, "argument error\n"); + return -1; + } + + BackupCommand(parameter); + break; + } + case CMD_HELP: + { + HelpCommand(arglist[2]); + break; + } + case CMD_REM: + { + if (argcnt < 3) + { + fprintf(stderr, "Usage : backup [OPTION] \n"); + return -1; + } + + char *resolved = realpath(arglist[2], parameter->filename); + // 경로를 입력하지 않은 경우 + if (resolved == NULL) + { + fprintf(stderr, "ERROR: %s is invalid filepath\n", parameter->filename); + printf("Usage:\n"); + printf(" -d : remove backuped files in directory if is directory \n"); + printf(" -r : remove backuped files in directory recursive if is directory \n"); + printf(" -a : remove all backuped files \n"); + return -1; + } + // 경로가 길이 제한을 넘는 경우 + if (strlen(resolved) > PATHMAX) + { + fprintf(stderr, "ERROR : %s exceeds 4096 bytes \n", parameter->filename); + return -1; + } + + // 파일이나 디렉토리가 존재하지 않는 경우 + if (lstat(parameter->filename, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", parameter->filename); + return -1; + } + + // 일반 파일이거나 디렉토리가 아닌경우 + if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not regular file\n", parameter->filename); + return -1; + } + + // 경로가 홈 디렉토리를 벗어나는 경우, 백업 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, homePATH, strlen(homePATH)) || !strncmp(parameter->filename, backupPATH, strlen(backupPATH)) || !strcmp(parameter->filename, homePATH)) + { + fprintf(stderr, "ERROR: filename %s is out of home path \n", parameter->filename); + + return -1; + } + + lastind = 2; + while ((option = getopt(argcnt, arglist, "dra")) != -1) + { + + // 옵션이 올바르지 않은 경우 + if (option != 'd' && option != 'r' && option != 'a') + { + fprintf(stderr, "ERROR: unknown option %c\n", optopt); + printf("Usage:\n"); + printf(" -d : remove backuped files in directory if is directory \n"); + printf(" -r : remove backuped files in directory recursive if is directory \n"); + printf(" -a : remove all backuped files \n"); + return -1; + } + // 옵션이 올바르지 않은 경우 + if (optind == lastind) + { + fprintf(stderr, "ERROR: wrong option input\n"); + printf("Usage:\n"); + printf(" -d : remove backuped files in directory if is directory \n"); + printf(" -r : remove backuped files in directory recursive if is directory \n"); + printf(" -a : remove all backuped files \n"); + return -1; + } + + if (option == 'd') + { + // 중복으로 사용했는지 검사 + if (parameter->commandopt & OPT_D) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + // 만약 -r이 있다면 추가 x + if (!(parameter->commandopt & OPT_R)) + { + + // 비트연산을 통해 옵션 추가 + parameter->commandopt |= OPT_D; + } + } + + if (option == 'r') + { + if (parameter->commandopt & OPT_R) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + // 만약 -d가 있다면 AND연산을 통해 d제거 + if (parameter->commandopt & OPT_D) + { + parameter->commandopt &= ~OPT_D; + } + parameter->commandopt |= OPT_R; + } + + if (option == 'a') + { + if (parameter->commandopt & OPT_A) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + parameter->commandopt |= OPT_A; + } + // 옵션 개수 세주기 + optcnt++; + lastind = optind; + } + int res = argcnt - optcnt; + + if (res != 3) + { + fprintf(stderr, "argument error\n"); + return -1; + } + + RemoveCommand(parameter); + break; + } + case CMD_REC: + { + // + if (argcnt < 3) + { + fprintf(stderr, "Usage : backup [OPTION] \n"); + return -1; + } + + char *resolved = realpath(arglist[2], parameter->filename); + // 경로를 입력하지 않은 경우 + if (resolved == NULL) + { + fprintf(stderr, "ERROR: %s is invalid filepath\n", parameter->filename); + printf("Usage:\n"); + printf(" -d : recover backuped files in directory if is directory\n"); + printf(" -r : recover backuped files in directory recursive if is directory \n"); + printf(" -l : recover latest backuped files \n"); + printf(" -n : recover backuped file with new path \n"); + return -1; + } + // 경로가 길이 제한을 넘는 경우 + if (strlen(resolved) > PATHMAX) + { + fprintf(stderr, "ERROR : %s exceeds 4096 bytes \n", parameter->filename); + return -1; + } + + // 파일이나 디렉토리가 존재하지 않는 경우 + if (lstat(parameter->filename, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", parameter->filename); + return -1; + } + + // 일반 파일이거나 디렉토리가 아닌경우 + if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not regular file\n", parameter->filename); + return -1; + } + + // 경로가 홈 디렉토리를 벗어나는 경우, 백업 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, homePATH, strlen(homePATH)) || !strncmp(parameter->filename, backupPATH, strlen(backupPATH))) + { + fprintf(stderr, "ERROR: filename %s is out of home path \n", parameter->filename); + + return -1; + } + + // 경로가 홈 디렉토리를 벗어나는 경우, 백업 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, homePATH, strlen(homePATH)) || !strncmp(parameter->filename, backupPATH, strlen(backupPATH)) || !strcmp(parameter->filename, homePATH)) + { + fprintf(stderr, "ERROR: filename %s is out of home path \n", parameter->filename); + + return -1; + } + + lastind = 2; + while ((option = getopt(argcnt, arglist, "drln:")) != -1) + { + + // 옵션이 올바르지 않은 경우 + if (option != 'd' && option != 'r' && option != 'l' && option != 'n') + { + fprintf(stderr, "ERROR: unknown option %c\n", optopt); + printf("Usage:\n"); + printf(" -d : recover backuped files in directory if is directory\n"); + printf(" -r : recover backuped files in directory recursive if is directory \n"); + printf(" -l : recover latest backuped files \n"); + printf(" -n : recover backuped file with new path \n"); + return -1; + } + // 옵션이 올바르지 않은 경우 + if (optind == lastind) + { + fprintf(stderr, "ERROR: wrong option input\n"); + printf("Usage:\n"); + printf(" -d : recover backuped files in directory if is directory\n"); + printf(" -r : recover backuped files in directory recursive if is directory \n"); + printf(" -l : recover latest backuped files \n"); + printf(" -n : recover backuped file with new path \n"); + return -1; + } + + if (option == 'd') + { + // 중복으로 사용했는지 검사 + if (parameter->commandopt & OPT_D) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + // 만약 -r이 있다면 추가 x + if (!(parameter->commandopt & OPT_R)) + { + + // 비트연산을 통해 옵션 추가 + parameter->commandopt |= OPT_D; + } + } + + if (option == 'r') + { + if (parameter->commandopt & OPT_R) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + // 만약 -d가 있다면 AND연산을 통해 d제거 + if (parameter->commandopt & OPT_D) + { + parameter->commandopt &= ~OPT_D; + } + parameter->commandopt |= OPT_R; + } + + if (option == 'l') + { + if (parameter->commandopt & OPT_L) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + parameter->commandopt |= OPT_L; + } + + if (option == 'n') + { + char *newPath = optarg; + + realpath(newPath, parameter->tmpname); + if (parameter->commandopt & OPT_N) + { + fprintf(stderr, "ERROR: duplicate option -%c\n", option); + return -1; + } + parameter->commandopt |= OPT_N; + } + // 옵션 개수 세주기 + optcnt++; + lastind = optind; + } + + int res = argcnt - optcnt; + if (res != 3 && res != 4) + { + fprintf(stderr, "argument error\n"); + return -1; + } + + RecoverCommand(parameter); + break; + } + case CMD_LIST: + { + // 경로를 입력하지 않은 경우 + if (argcnt < 3) + { + fprintf(stderr, "Usage : list [OPTION] \n"); + return -1; + } + char *resolved = realpath(arglist[2], parameter->filename); + // 경로가 올바르지 않은경우 + if (resolved == NULL) + { + fprintf(stderr, "ERROR: %s is invalid filepath\n", parameter->filename); + return -1; + } + // 경로가 길이 제한을 넘는 경우 + if (strlen(resolved) > PATHMAX) + { + fprintf(stderr, "ERROR : %s exceeds 4096 bytes \n", parameter->filename); + return -1; + } + + // 파일이나 디렉토리가 존재하지 않는 경우 + if (lstat(parameter->filename, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", parameter->filename); + return -1; + } + + // 경로가 홈 디렉토리를 벗어나는 경우, 백업 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, homePATH, strlen(homePATH)) || !strncmp(parameter->filename, backupPATH, strlen(backupPATH)) || !strcmp(parameter->filename, homePATH)) + { + fprintf(stderr, "ERROR: filename %s is out of home path \n", parameter->filename); + + return -1; + } + + ListCommand(parameter); + break; + } + } +} + +int Init() +{ + + struct stat statbuf; + int cnt, fd, subCnt, fd2; + struct dirent **namelist, **subNameList; + // 사용자 정보 가지는 구조체 + struct passwd *pw; + char *loginName; + uid_t uid; + // 현재 사용자의 로그인 이름을 얻기 + loginName = getlogin(); + if (loginName == NULL) + { + // getlogin이 실패한 경우, 사용자 ID를 사용 + uid = getuid(); + pw = getpwuid(uid); + } + else + { + // 특정 사용자 이름(loginName)에 대한 사용자 계정 정보를 검색하고, 그 결과를 pw 변수에 저장 + pw = getpwnam(loginName); + } + + if (pw == NULL) + { + printf("ERROR: Failed to get user information\n"); + return 1; + } + // 실행경로 받아오기 + getcwd(exePATH, PATHMAX); + + // Home경로 받아오기 (출력하지 않고 지정된 버퍼에 저장) + sprintf(homePATH, "%s", pw->pw_dir); + // backup경로 받아오기 + sprintf(backupPATH, "%s/backup", pw->pw_dir); + sprintf(ssubakLogPATH, "%s/backup/ssubak.log", pw->pw_dir); + // 메타데이터 경로받아오기 + sprintf(metaPATH, "%s/kathy.meta.txt", pw->pw_dir); + + // backup 경로에 폴더가 없다면 만들어주기 + if (access(backupPATH, F_OK)) + { + mkdir(backupPATH, 0777); + } + // ssubak.log파일이 없다면 생성 + if (access(ssubakLogPATH, F_OK)) + { + fd = 0; + if ((fd = creat(ssubakLogPATH, 0666)) < 0) + { + fprintf(stderr, "error for creating ssubak.log file\n"); + return 1; + } + } + // 메타 데이터 경로에 메타데이터가 없으면 생성 + if (access(metaPATH, F_OK)) + { + fd2 = 0; + if ((fd2 = creat(metaPATH, 0666)) < 0) + { + fprintf(stderr, "error for creating meta data file\n"); + return 1; + } + } + + // 전역 백업 링크드리스트로 상태관리 + + if (refreshBackupLinkedList() < 0) + { + fprintf(stderr, "error for refresh backup status\n"); + return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + bool isCommandValid = false; + int cmd; + char argvStr[PATHMAX] = ""; + int argcnt = 0; + char **arglist = NULL; + // 초기화 + command_parameter parameter = {(char *)0, (char *)0, (char *)0, 0}; + + Init(); + // GetSubstring에 전달할 명령어를 문자열로 이어붙임 + for (int i = 0; i < argc; i++) + { + strcat(argvStr, argv[i]); + strcat(argvStr, " "); + } + + arglist = GetSubstring(argvStr, &argcnt, " "); + + // 첫번째 인자를 입력하지 않을 경우 프로그램 비정상 종료 + if (argc == 1) + { + fprintf(stderr, "ERROR : invalid command error %s \n", argv[1]); + exit(1); + } + + // 첫번째 인자로 잘못된 명령어를 입력할 경우 프로그램 비정상 종료 + for (int i = 0; i < CMD_COUNT; i++) + { + if (!strcmp(commandData[i], argv[1])) + { + isCommandValid = true; + } + } + if (!isCommandValid) + { + fprintf(stderr, "ERROR : unknown command error %s \n", argv[1]); + exit(1); + } + + // argv의 1번째 인자에 따라 command 설정해주기 (정수로) + if (!strcmp(arglist[1], commandData[0])) + { + cmd = CMD_BACKUP; + } + else if (!strcmp(arglist[1], commandData[1])) + { + cmd = CMD_REM; + } + else if (!strcmp(arglist[1], commandData[2])) + { + cmd = CMD_REC; + } + else if (!strcmp(arglist[1], commandData[3])) + { + cmd = CMD_LIST; + } + else if (!strcmp(arglist[1], commandData[4])) + { + cmd = CMD_HELP; + } + else + { + cmd = NOT_CMD; + } + + ParameterInit(¶meter); + parameter.command = arglist[0]; + ParameterProcessing(argcnt, arglist, cmd, ¶meter); + + exit(0); +} diff --git a/p1/ssu_backup.o b/p1/ssu_backup.o new file mode 100644 index 0000000..4073b09 Binary files /dev/null and b/p1/ssu_backup.o differ diff --git a/p1/ssu_header.h b/p1/ssu_header.h new file mode 100644 index 0000000..82e1148 --- /dev/null +++ b/p1/ssu_header.h @@ -0,0 +1,1448 @@ + +#define OPENSSL_API_COMPAT 0x10100000L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define true 1 +#define false 0 + +#define HASH_MD5 33 +#define HASH_SHA1 41 + +#define NAMEMAX 255 +#define PATHMAX 4096 +#define STRMAX 4096 +#define QUEUE_SIZE 200 +#define BUFFER_SIZE 4096 + +#define CMD_BACKUP 0b0000001 +#define CMD_REM 0b0000010 +#define CMD_REC 0b0000100 +#define CMD_LIST 0b0001000 +#define CMD_HELP 0b0010000 +#define CMD_SYS 0b0100000 +#define CMD_EXIT 0b1000000 +#define NOT_CMD 0b0000000 +#define CMD_COUNT 5 + +#define OPT_D 0b000001 +#define OPT_R 0b000010 +#define OPT_Y 0b000100 +#define OPT_N 0b001000 +#define OPT_A 0b010000 +#define OPT_L 0b100000 +#define NOT_OPT 0b000000 + +char exeNAME[PATHMAX]; +char exePATH[PATHMAX]; +char homePATH[PATHMAX]; +char backupPATH[PATHMAX]; +char ssubakLogPATH[PATHMAX]; +char metaPATH[PATHMAX]; +int hash; + +void help(); + +char *commandData[10] = { + "backup", + "remove", + "recover", + "list", + "help", + "rm", + "rc", + "vi", + "vim", + "exit"}; + +typedef struct command_parameter +{ + char *command; + char *filename; + char *tmpname; + int commandopt; + char *argv[10]; +} command_parameter; + +typedef struct list_parameter +{ + char *command; + char *index; + char *tmpname; + int commandopt; + char *argv[10]; +} list_parameter; + +typedef struct backupNode +{ + char backupPath[PATHMAX]; + char newPath[PATHMAX]; + struct stat statbuf; + + struct backupNode *next; +} backupNode; + +typedef struct fileNode +{ + char path[PATHMAX]; + struct stat statbuf; + + backupNode *head; + + struct fileNode *next; +} fileNode; + +typedef struct dirNode +{ + char path[PATHMAX]; + char backupPath[PATHMAX]; + char newPath[PATHMAX]; + + fileNode *head; + + struct dirNode *next; +} dirNode; + +typedef struct dirList +{ + struct dirNode *head; + struct dirNode *tail; +} dirList; + +dirList *mainDirList; + +typedef struct pathList_ +{ + struct pathList_ *next; + struct pathList_ *prev; + char path[NAMEMAX]; + +} pathList; + +typedef struct logElement +{ + char log[PATHMAX]; + char backuplog[PATHMAX]; +} logElement; + +// 전역으로 백업상태관리 리스트 +typedef struct backupList +{ + struct logElement *cur; + struct backupList *prev; + struct backupList *next; +} backupList; + +backupList *mainBackupList; +logElement *backupLogElement; + +typedef struct logList +{ + struct logElement *cur; + struct logList *prev; + struct logList *next; +} logList; + +typedef struct metaList +{ + struct logElement *cur; + struct metaList *prev; + struct metaList *next; +} metaList; + +logList *mainlogList; +logElement *logNode; +logElement *metaNode; +logList *mainlogList; +metaList *mainMetaList; + +typedef struct node +{ + char *data; + struct node *next; +} node; + +typedef struct queue +{ + node *front; + node *rear; + int count; +} queue; +void initQueue(queue *q) +{ + q->front = q->rear = NULL; + q->count = 0; +} +int isEmpty(queue *q) +{ + return q->count == 0; +} +void enqueue(queue *queue, char *data) +{ + + node *newNode = (node *)malloc(sizeof(node) * QUEUE_SIZE); + if (newNode == NULL) + { + fprintf(stderr, "ERROR: Memory allocation failed\n"); + return; + } + newNode->data = strdup(data); + if (newNode->data == NULL) + { + fprintf(stderr, "ERROR: Memory allocation failed\n"); + free(newNode); + return; + } + newNode->next = NULL; + if (isEmpty(queue)) + { + queue->front = newNode; + } + else + { + queue->rear->next = newNode; + } + queue->rear = newNode; + queue->count++; +} + +char *dequeue(queue *queue) +{ + + char *data; + node *ptr; + + if (isEmpty(queue)) + { + printf("ERROR: Queue is Empty\n"); + exit(1); + } + ptr = queue->front; + + if (ptr == NULL) + { + printf("ERROR: Node or Data is NULL\n"); + exit(1); + } + if (ptr == NULL || ptr->data == NULL) + { + printf("ERROR: Node or Data is NULL\n"); + exit(1); + } + + int dataLength = strlen(ptr->data); + data = (char *)malloc(dataLength + 1); + if (data == NULL) + { + printf("ERROR: Memory allocation failed\n"); + exit(1); + } + + strcpy(data, ptr->data); + queue->front = ptr->next; + queue->count--; + if (ptr->data != NULL) + { + free(ptr->data); // 데이터 메모리 해제 추가 + } + free(ptr); + return data; +} + +void help(); + +// evp방식으로 변경 +int md5(char *target_path, char *hash_result) +{ + + FILE *fp; + unsigned char hash[MD5_DIGEST_LENGTH]; + unsigned char buffer[BUFSIZ]; + int bytes = 0; + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + + if ((fp = fopen(target_path, "rb")) == NULL) + { + printf("ERROR: fopen error for %s\n", target_path); + EVP_MD_CTX_free(mdctx); + return 1; + } + // 이전 버전의 MD5_Init(&md5); + EVP_DigestInit_ex(mdctx, EVP_md5(), NULL); + + while ((bytes = fread(buffer, 1, BUFSIZ, fp)) > 0) + EVP_DigestUpdate(mdctx, buffer, bytes); + + EVP_DigestFinal_ex(mdctx, hash, NULL); + + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(hash_result + (i * 2), "%02x", hash[i]); + hash_result[HASH_MD5 - 1] = 0; + + fclose(fp); + EVP_MD_CTX_free(mdctx); + + return 0; +} + +// int md5(char *target_path, char *hash_result) +// { +// FILE *fp; +// unsigned char hash[MD5_DIGEST_LENGTH]; +// unsigned char buffer[SHRT_MAX]; +// int bytes = 0; +// MD5_CTX md5; + +// if ((fp = fopen(target_path, "rb")) == NULL){ +// printf("ERROR: fopen error for %s\n", target_path); +// return 1; +// } + +// MD5_Init(&md5); + +// while ((bytes = fread(buffer, 1, SHRT_MAX, fp)) != 0) +// MD5_Update(&md5, buffer, bytes); + +// MD5_Final(hash, &md5); + +// for (int i = 0; i < MD5_DIGEST_LENGTH; i++) +// sprintf(hash_result + (i * 2), "%02x", hash[i]); +// hash_result[HASH_MD5-1] = 0; + +// fclose(fp); + +// return 0; +// } + +int ConvertHash(char *target_path, char *hash_result) +{ + md5(target_path, hash_result); +} + +int cmpHash(char *path1, char *path2) +{ + char *hash1 = (char *)malloc(sizeof(char) * hash); + char *hash2 = (char *)malloc(sizeof(char) * hash); + + ConvertHash(path1, hash1); + ConvertHash(path2, hash2); + + return strcmp(hash1, hash2); +} + +char *cvtNumComma(int a) +{ + char *str = (char *)malloc(sizeof(char) * STRMAX); + char *ret = (char *)malloc(sizeof(char) * STRMAX); + int i; + for (i = 0; a > 0; i++) + { + str[i] = a % 10 + '0'; + a /= 10; + if (i % 4 == 2) + { + i++; + str[i] = ','; + } + } + str[i] = '\0'; + + for (i = 0; i < strlen(str); i++) + { + ret[i] = str[strlen(str) - i - 1]; + } + ret[i] = '\0'; + + return ret; +} + +char *GetFileName(char file_path[]) +{ + char *file_name; + + while (*file_path) + { + if (*file_path == '/' && (file_path + 1) != NULL) + { + file_name = file_path + 1; + } + file_path++; + } + return file_name; +} + +char *strToHex(char *str) +{ + char *result = (char *)malloc(sizeof(char) * PATHMAX); + for (int i = 0; i < strlen(str); i++) + { + sprintf(result + (i * 2), "%02X", str[i]); + } + result[strlen(str) * 2] = '\0'; + + return result; +} + +char *getDate() +{ + char *date = (char *)malloc(sizeof(char) * 50); + time_t timer; + struct tm *t; + + timer = time(NULL); + t = localtime(&timer); + + sprintf(date, "%02d%02d%02d%02d%02d%02d", t->tm_year % 100, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); + + return date; +} + +char *strTodate(char *old_str, char **date) +{ + char *result = (char *)calloc(sizeof(char), PATHMAX); + *date = getDate(); + + sprintf(result, "%s_%s", old_str, *date); + + return result; +} + +char *strTodirPATH(char *dirpath, char *filepath) +{ + char *fullpath = (char *)malloc(sizeof(char) * PATHMAX); + strcpy(fullpath, dirpath); + strcat(fullpath, "/"); + if (filepath != NULL) + strcat(fullpath, filepath); + return fullpath; +} + +// 문자와 구분자를 받아들여 해당 구분자 이후의 문자열 반환 +char *QuoteCheck(char **str, char del) +{ + char *tmp = *str + 1; + int i = 0; + + while (*tmp != '\0' && *tmp != del) + { + tmp++; + i++; + } + // 구분자를 발견 못하고 문자열 끝에 도달 + if (*tmp == '\0') + { + *str = tmp; + return NULL; + } + // 구분자를 발견한 경우 + if (*tmp == del) + { + for (char *c = *str; *c != '\0'; c++) + { + *c = *(c + 1); + } + *str += i; + for (char *c = *str; *c != '\0'; c++) + { + *c = *(c + 1); + } + } +} + +// 구분자 기준으로 토큰 분리 +char *Tokenize(char *str, char *del) +{ + int i = 0; + int del_len = strlen(del); + static char *tmp = NULL; + char *tmp2 = NULL; + + if (str != NULL && tmp == NULL) + { + tmp = str; + } + + if (str == NULL && tmp == NULL) + { + return NULL; + } + + char *idx = tmp; + + while (i < del_len) + { + if (*idx == del[i]) + { + idx++; + i = 0; + } + else + { + i++; + } + } + if (*idx == '\0') + { + tmp = NULL; + return tmp; + } + tmp = idx; + + while (*tmp != '\0') + { + if (*tmp == '\'' || *tmp == '\"') + { + QuoteCheck(&tmp, *tmp); + continue; + } + for (i = 0; i < del_len; i++) + { + if (*tmp == del[i]) + { + *tmp = '\0'; + break; + } + } + tmp++; + if (i < del_len) + { + break; + } + } + + return idx; +} + +char **GetSubstring(char *str, int *cnt, char *del) +{ + *cnt = 0; + int i = 0; + char *token = NULL; + char *templist[100] = { + NULL, + }; + token = Tokenize(str, del); + if (token == NULL) + { + return NULL; + } + + while (token != NULL) + { + templist[*cnt] = token; + *cnt += 1; + token = Tokenize(NULL, del); + } + + char **temp = (char **)malloc(sizeof(char *) * (*cnt + 1)); + for (i = 0; i < *cnt; i++) + { + temp[i] = templist[i]; + } + + return temp; +} + +int ConvertPath(char *origin, char *resolved) +{ + int idx = 0; + int i; + char *path = (char *)malloc(sizeof(char) * PATHMAX * 2); + char *tmppath = (char *)malloc(sizeof(char) * PATHMAX); + char **pathlist; + int pathcnt; + + if (origin == NULL) + { + return -1; + } + + if (origin[0] == '~') + { + sprintf(path, "%s%s", homePATH, origin + 1); + } + else if (origin[0] != '/') + { + sprintf(path, "%s/%s", exePATH, origin); + } + else + { + sprintf(path, "%s", origin); + } + + if (!strcmp(path, "/")) + { + resolved = "/"; + return 0; + } + + if ((pathlist = GetSubstring(path, &pathcnt, "/")) == NULL) + { + return -1; + } + + pathList *headpath = (pathList *)malloc(sizeof(pathList)); + pathList *currpath = headpath; + + for (i = 0; i < pathcnt; i++) + { + if (!strcmp(pathlist[i], ".")) + { + continue; + } + else if (!strcmp(pathlist[i], "..")) + { + currpath = currpath->prev; + currpath->next = NULL; + continue; + } + + pathList *newpath = (pathList *)malloc(sizeof(pathList)); + strcpy(newpath->path, pathlist[i]); + currpath->next = newpath; + newpath->prev = currpath; + + currpath = currpath->next; + } + + currpath = headpath->next; + + strcpy(tmppath, "/"); + while (currpath != NULL) + { + strcat(tmppath, currpath->path); + if (currpath->next != NULL) + { + strcat(tmppath, "/"); + } + currpath = currpath->next; + } + + strcpy(resolved, tmppath); + + return 0; +} + +int cmpPath(char *path1, char *path2) +{ + int i; + int cnt1, cnt2; + char tmp1[PATHMAX], tmp2[PATHMAX]; + strcpy(tmp1, path1); + strcpy(tmp2, path2); + char **pathlist1 = GetSubstring(tmp1, &cnt1, "/"); + char **pathlist2 = GetSubstring(tmp2, &cnt2, "/"); + + if (cnt1 == cnt2) + { + for (i = 0; i < cnt1; i++) + { + if (!strcmp(pathlist1[i], pathlist2[i])) + continue; + return -strcmp(pathlist1[i], pathlist2[i]); + } + } + else + { + return cnt1 < cnt2; + } + return 1; +} +// 문자열 왼쪽 공백제거 +char *trim_left(char *str) +{ + while (*str) + { + if (isspace(*str)) + { + str++; + } + else + { + break; + } + } + return str; +} + +char *trim_right(char *str) +{ + int len = (int)strlen(str) - 1; + + while (len >= 0) + { + if (isspace(*(str + len))) + { + len--; + } + else + { + break; + } + } + *(str + ++len) = '\0'; + return str; +} + +char *findBackupFileByOriginPath(char *originPath) +{ + char *returnBackupPath = (char *)malloc(sizeof(char) * PATHMAX); + int cnt; + struct dirent **namelist; + + if ((cnt = scandir(backupPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupPATH); + exit(1); + } + // 백업폴더에 로그가 하나이상 존재할경우 탐색 + if (cnt > 3) + { + + backupList *currBackupLog = mainBackupList; + + while (true) + { + if (currBackupLog == NULL) + { + break; + } + if (!strcmp(currBackupLog->cur->log, originPath)) + { + strcpy(returnBackupPath, currBackupLog->cur->backuplog); + return currBackupLog->cur->backuplog; + } + currBackupLog = currBackupLog->next; + } + } + return NULL; +} + +// 백업경로를 인자로 주면 메타데이터를 뒤져서 원본경로 반환하는 함수 +// 메타데이터는 원본경로@백업경로 형태 +char *findMetaData(char *backupPath) +{ + struct stat metaStat; + long long metasize; + metaList *mainMetaList = NULL; + ssize_t bytes_read; + int prevPos = 0; + int curPos = 0; + int fd; + char *returnPath = (char *)malloc(sizeof(char *) * PATHMAX); + if ((fd = open(metaPATH, O_RDONLY, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", metaPATH); + exit(1); + } + + if (stat(metaPATH, &metaStat) == 0) + { + metasize = metaStat.st_size; + } + else + { + fprintf(stderr, " ERROR : file stat error %s\n", metaPATH); + exit(1); + } + if (metasize > 0) + { + char metaBuffer[metasize]; + bytes_read = read(fd, metaBuffer, metasize - 1); // 버퍼 사이즈보다 작게 읽기 + if (bytes_read == -1) + { + fprintf(stderr, "ERROR : reading file %s", metaPATH); + exit(1); + } + metaBuffer[bytes_read] = '\0'; + + while (true) + { + + if (metaBuffer[curPos] == '\0') + { + char str[PATHMAX * 2] = ""; + // 메타 데이터 한줄을 읽기 + int iidx = 0; + for (int j = prevPos; j < curPos; j++) + { + str[iidx] = metaBuffer[j]; + iidx++; + } + trim_right(str); + char *res = strtok(str, "@"); + char splitStrings[2][PATHMAX]; + char origin[PATHMAX] = ""; + char backup[PATHMAX] = ""; + int idx = 0; + while (res != NULL) + { + strcpy(splitStrings[idx], res); + idx++; + res = strtok(NULL, " "); + } + strcpy(origin, splitStrings[0]); + strcpy(backup, splitStrings[1]); + + metaNode = (logElement *)malloc(sizeof(logElement)); + strcpy(metaNode->log, origin); + strcpy(metaNode->backuplog, backup); + + metaList *newMeta = (metaList *)malloc(sizeof(metaList)); + newMeta->cur = metaNode; + newMeta->prev = NULL; + newMeta->next = NULL; + + // 메타로그 리스트에 로그 노드 추가 + if (mainMetaList == NULL) + { + // mainlogList가 비어 있는 경우 + mainMetaList = newMeta; + } + else + { + metaList *curr = mainMetaList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = newMeta; + newMeta->prev = curr; + } + break; + } + else if (metaBuffer[curPos] == '\n') + { + + char str[PATHMAX * 2] = ""; + // 메타 데이터 한줄을 읽기 + int iidx = 0; + for (int j = prevPos; j < curPos; j++) + { + str[iidx] = metaBuffer[j]; + iidx++; + } + trim_right(str); + char *res = strtok(str, "@"); + char splitStrings[2][PATHMAX]; + char origin[PATHMAX] = ""; + char backup[PATHMAX] = ""; + int idx = 0; + while (res != NULL) + { + strcpy(splitStrings[idx], res); + idx++; + res = strtok(NULL, " "); + } + strcpy(origin, splitStrings[0]); + strcpy(backup, splitStrings[1]); + + metaNode = (logElement *)malloc(sizeof(logElement)); + strcpy(metaNode->log, origin); + strcpy(metaNode->backuplog, backup); + + metaList *newMeta = (metaList *)malloc(sizeof(metaList)); + newMeta->cur = metaNode; + newMeta->prev = NULL; + newMeta->next = NULL; + + // 메타로그 리스트에 로그 노드 추가 + if (mainMetaList == NULL) + { + // mainlogList가 비어 있는 경우 + mainMetaList = newMeta; + } + else + { + metaList *curr = mainMetaList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = newMeta; + newMeta->prev = curr; + } + prevPos = (curPos + 1); + curPos++; + } + else + { + curPos++; + } + } + } + + metaList *currMeta = mainMetaList; + char originHash[PATHMAX] = ""; + ConvertHash(backupPath, originHash); + while (true) + { + if (currMeta == NULL) + { + break; + } + char tmpHash[PATHMAX] = ""; + ConvertHash(currMeta->cur->backuplog, tmpHash); + if (!strcmp(currMeta->cur->backuplog, backupPath) && !strcmp(tmpHash, originHash)) + { + strcpy(returnPath, currMeta->cur->log); + return returnPath; + } + currMeta = currMeta->next; + } + + return NULL; +} + +// 삭제되는 백업경로를 인자로 넘겨주면 메타데이터 내에도 해당 백업경로를 삭제하고 다시 쓰는 로직 +// errno : -1 정상적 삭제 :1 +int clearMetaData(char *backupPath) +{ + + struct stat metaStat; + long long metasize; + metaList *mainMetaList = NULL; + ssize_t bytes_read; + int prevPos = 0; + int curPos = 0; + int fd; + char deletePath[PATHMAX] = ""; + if ((fd = open(metaPATH, O_RDONLY, 777)) < 0) + { + fprintf(stderr, "ERROR: open error for %s\n", metaPATH); + return -1; + } + + if (stat(metaPATH, &metaStat) == 0) + { + metasize = metaStat.st_size; + } + else + { + fprintf(stderr, " ERROR : file stat error %s\n", metaPATH); + return -1; + } + + if (metasize > 0) + { + char metaBuffer[metasize]; + bytes_read = read(fd, metaBuffer, metasize - 1); // 버퍼 사이즈보다 작게 읽기 + if (bytes_read == -1) + { + fprintf(stderr, "ERROR : reading file %s", metaPATH); + return -1; + } + metaBuffer[bytes_read] = '\0'; + + while (true) + { + + if (metaBuffer[curPos] == '\0') + { + char str[PATHMAX * 2] = ""; + // 메타 데이터 한줄을 읽기 + int iidx = 0; + for (int j = prevPos; j < curPos; j++) + { + str[iidx] = metaBuffer[j]; + iidx++; + } + trim_right(str); + char *res = strtok(str, "@"); + char splitStrings[2][PATHMAX]; + char origin[PATHMAX] = ""; + char backup[PATHMAX] = ""; + int idx = 0; + while (res != NULL) + { + strcpy(splitStrings[idx], res); + idx++; + res = strtok(NULL, " "); + } + strcpy(origin, splitStrings[0]); + strcpy(backup, splitStrings[1]); + + metaNode = (logElement *)malloc(sizeof(logElement)); + strcpy(metaNode->log, origin); + strcpy(metaNode->backuplog, backup); + + metaList *newMeta = (metaList *)malloc(sizeof(metaList)); + newMeta->cur = metaNode; + newMeta->prev = NULL; + newMeta->next = NULL; + + // 메타로그 리스트에 로그 노드 추가 + if (mainMetaList == NULL) + { + // mainlogList가 비어 있는 경우 + mainMetaList = newMeta; + } + else + { + metaList *curr = mainMetaList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = newMeta; + newMeta->prev = curr; + } + break; + } + else if (metaBuffer[curPos] == '\n') + { + + char str[PATHMAX * 2] = ""; + // 메타 데이터 한줄을 읽기 + int iidx = 0; + for (int j = prevPos; j < curPos; j++) + { + str[iidx] = metaBuffer[j]; + iidx++; + } + trim_right(str); + char *res = strtok(str, "@"); + char splitStrings[2][PATHMAX]; + char origin[PATHMAX] = ""; + char backup[PATHMAX] = ""; + int idx = 0; + while (res != NULL) + { + strcpy(splitStrings[idx], res); + idx++; + res = strtok(NULL, " "); + } + strcpy(origin, splitStrings[0]); + strcpy(backup, splitStrings[1]); + + metaNode = (logElement *)malloc(sizeof(logElement)); + strcpy(metaNode->log, origin); + strcpy(metaNode->backuplog, backup); + + metaList *newMeta = (metaList *)malloc(sizeof(metaList)); + newMeta->cur = metaNode; + newMeta->prev = NULL; + newMeta->next = NULL; + + // 메타로그 리스트에 로그 노드 추가 + if (mainMetaList == NULL) + { + // mainlogList가 비어 있는 경우 + mainMetaList = newMeta; + } + else + { + metaList *curr = mainMetaList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = newMeta; + newMeta->prev = curr; + } + prevPos = (curPos + 1); + curPos++; + } + else + { + curPos++; + } + } + } + // 메타데이터 파일 초기화 + // 1) 메타데이터 파일을 삭제 + // 2) 다시 만든다. + // 매개변수로 받아온 경로와 같은 경로이면 메타데이터에 쓰지 않는다. + + if (remove(metaPATH) < 0) + { + fprintf(stderr, "ERROR : remove error %s\n", metaPATH); + return -1; + } + if (access(metaPATH, F_OK)) + { + fd = 0; + if ((fd = creat(metaPATH, 0666)) < 0) + { + fprintf(stderr, "error for creating meta data file\n"); + return -1; + } + } + metaList *currMeta = mainMetaList; + while (true) + { + if (currMeta == NULL) + { + break; + } + char tmpHash[PATHMAX] = ""; + char metaData[PATHMAX * 3] = ""; + // 매개변수로 받아온 backupPath와 다른경우만 써준다. + if (strcmp(currMeta->cur->backuplog, backupPath)) + { + strcat(metaData, currMeta->cur->log); + strcat(metaData, "@"); + strcat(metaData, currMeta->cur->backuplog); + strcat(metaData, "\n"); + if (write(fd, metaData, strlen(metaData)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", metaPATH); + return 1; + } + } + currMeta = currMeta->next; + } + + return 1; +} + +// 링크드 리스트로 백업 상태관리 (초기 빌드시, 로그 삭제시, recover시) +int refreshBackupLinkedList() +{ + struct stat statbuf; + int cnt, fd, subCnt, fd2; + struct dirent **namelist, **subNameList; + + if (lstat(backupPATH, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", backupPATH); + return -1; + } + if ((cnt = scandir(backupPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", backupPATH); + return -1; + } + // 백업폴더에 로그가 하나 이상일 경우 링크드 리스트로 상태관리 + if (cnt > 3) + { + + queue Queue; + initQueue(&Queue); + for (int i = 0; i < cnt - 1; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..") || !strcmp(namelist[i]->d_name, "ssubak.log")) + continue; + struct stat buf; + char tmppath[PATHMAX] = ""; + strcat(tmppath, backupPATH); + strcat(tmppath, "/"); + strcat(tmppath, namelist[i]->d_name); + + // /home/ubuntu/backup/24032012/ 까지 추적 -> 하위 파일 추적하러 가기 + if (lstat(tmppath, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmppath); + return -1; + } + + enqueue(&Queue, tmppath); + } + + while (true) + { + if (Queue.count == 0) + { + break; + } + char *nodePath = dequeue(&Queue); + if ((subCnt = scandir(nodePath, &subNameList, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", nodePath); + return -1; + } + + struct stat statbuf; + if (lstat(nodePath, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", nodePath); + return -1; + } + + for (int i = 0; i < subCnt; i++) + { + if (!strcmp(subNameList[i]->d_name, ".") || !strcmp(subNameList[i]->d_name, "..") || !strcmp(subNameList[i]->d_name, "ssubak.log")) + continue; + struct stat subbuf; + char tmppath[PATHMAX] = ""; + strcat(tmppath, nodePath); + strcat(tmppath, "/"); + strcat(tmppath, subNameList[i]->d_name); + // /home/ubuntu/backup/24032012/ 까지 추적 -> 하위 파일 추적하러 가기 + if (lstat(tmppath, &subbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmppath); + return -1; + } + if (S_ISDIR(subbuf.st_mode)) + { + enqueue(&Queue, tmppath); + } + else + { + // 노드 생성 + backupList *newBackupNode = (backupList *)malloc(sizeof(backupList)); + // 노드 안의 로그 생성 + logElement *backupLogElement = (logElement *)malloc(sizeof(logElement)); + memset(backupLogElement, 0, sizeof(backupLogElement)); + strcpy(backupLogElement->backuplog, tmppath); + + char *originPath = findMetaData(tmppath); + if (originPath != NULL) + { + strcpy(backupLogElement->log, originPath); + } + newBackupNode->cur = backupLogElement; + newBackupNode->prev = NULL; + newBackupNode->next = NULL; + // 로그 리스트에 로그 노드 추가 + backupList *curr; + if (mainBackupList == NULL) + { + // mainlogList가 비어 있는 경우 + mainBackupList = newBackupNode; + } + else + { + curr = mainBackupList; + while (curr->next != NULL) + { + curr = curr->next; + } + curr->next = newBackupNode; + newBackupNode->prev = curr; + } + } + } + } + backupList *currBackupLog = mainBackupList; + while (true) + { + if (currBackupLog->next == NULL) + { + break; + } + currBackupLog = currBackupLog->next; + } + } + + return 0; +} + +// 백업경로에서 백업 시간만 잘라내기 +char *splitBackupDate(char *path) +{ + + char backupLog[PATHMAX]; + char backupTimeTemp[PATHMAX]; + char *returnDate = (char *)malloc(sizeof(char *) * PATHMAX); + + strcpy(backupLog, path); + strcpy(backupTimeTemp, path); + char *backuptime = strstr(backupTimeTemp, backupPATH); + if (backuptime != NULL) + { + strcpy(backuptime, backuptime + 1 + strlen(backupPATH)); + } + // 백업시간만 잘라내기 + char *realBackupTime = strtok(backuptime, "/"); + strcpy(returnDate, realBackupTime); + return returnDate; +} + +char *addCommas(int num) +{ + char str[STRMAX]; + sprintf(str, "%d", num); + int len = strlen(str); + char *result = (char *)malloc((len / 3) * 2 + len + 1); + if (result == NULL) + { + fprintf(stderr, "ERROR: Memory allocation error \n"); + exit(EXIT_FAILURE); + } + result[0] = '\0'; + + int commaCount = 0; + for (int i = len - 1; i >= 0; i--) + { + if (commaCount == 3) + { + strcat(result, ","); + commaCount = 0; + } + char temp[2] = {str[i], '\0'}; + strcat(result, temp); + commaCount++; + } + + // 문자열 뒤집기 + int resultLen = strlen(result); + for (int i = 0; i < resultLen / 2; i++) + { + char temp = result[i]; + result[i] = result[resultLen - i - 1]; + result[resultLen - i - 1] = temp; + } + return result; +} + +char *concatPath(char *path1, char *path2) +{ + size_t totalLength = strlen(path1) + strlen(path2) + 2; + char *result = malloc(totalLength); + if (result == NULL) + { + fprintf(stderr, "Memory allocation failed\n"); + return NULL; // 메모리 할당 실패 처리 + } + + // 첫 번째 경로 복사 + strcpy(result, path1); + + // 경로 사이에 슬래시 추가 (이미 슬래시로 끝나는 경우를 처리하지 않음) + strcat(result, "/"); + + // 두 번째 경로 추가 + strcat(result, path2); + + return result; +} + +// 마지막에 파일명만 자르는 함수 + +char *extractPath(const char *fullPath) +{ + // fullPath에서 마지막 '/'를 찾는다 + const char *lastSlash = strrchr(fullPath, '/'); + if (lastSlash == NULL) + { + // '/'가 없는 경우, 전체 경로를 그대로 반환 + return strdup(fullPath); + } + else + { + // 마지막 '/'까지의 길이를 계산 + int newPathLength = lastSlash - fullPath; + + char *newPath = (char *)malloc(newPathLength + 1); + if (newPath == NULL) + { + fprintf(stderr, "ERROR: Memory allocation failed\n"); + exit(EXIT_FAILURE); + } + + strncpy(newPath, fullPath, newPathLength); + + newPath[newPathLength] = '\0'; + return newPath; + } +} + +void removeLogAndUpdateList(logList **head, char *logToRemove) +{ + logList *temp = *head, *prev = NULL; + + while (1) + { + if (temp == NULL) + { + break; + } + if (temp->cur->log == NULL || temp->cur == NULL) + { + break; + } + + if (!strcmp(temp->cur->log, logToRemove)) + { + // 로그가 일치하는 경우 + if (prev == NULL) + { + *head = temp->next; // 헤드를 다음 노드로 업데이트 + } + else + { + prev->next = temp->next; // 이전 노드의 다음을 현재 노드의 다음으로 설정 + } + + if (temp->next != NULL) + { + temp->next->prev = prev; + } + + // 메모리 해제 전에 다음 노드로 이동 + logList *nextTemp = temp->next; + free(temp->cur); + free(temp); + + // 다음 노드로 이동 후 현재 노드 메모리 해제 + temp = nextTemp; + } + else + { + prev = temp; // 현재 노드를 이전으로 설정 + temp = temp->next; // 다음 노드로 이동 + } + } +} + +void removeBackuplog(logList **head, char *logToRemove) +{ + logList *temp = *head, *prev = NULL; + + while (temp != NULL) + { + + if (temp->cur != NULL && !strcmp(temp->cur->backuplog, logToRemove)) + { + // 로그가 일치하는 경우 + if (prev == NULL) + { + *head = temp->next; // 헤드를 다음 노드로 업데이트 + } + else + { + prev->next = temp->next; // 이전 노드의 다음을 현재 노드의 다음으로 설정 + } + + if (temp->next != NULL) + { + temp->next->prev = prev; + } + + // 메모리 해제 전에 다음 노드로 이동 + logList *nextTemp = temp->next; + free(temp->cur); + free(temp); + + // 다음 노드로 이동 후 현재 노드 메모리 해제 + temp = nextTemp; + return; // 하나만 삭제 후 함수 종료 + } + else + { + prev = temp; // 현재 노드를 이전으로 설정 + temp = temp->next; // 다음 노드로 이동 + } + } +} diff --git a/p1/ssu_help.c b/p1/ssu_help.c new file mode 100644 index 0000000..c873b34 --- /dev/null +++ b/p1/ssu_help.c @@ -0,0 +1,76 @@ + +#include +#include +char *commands[10] = { + "backup", + "remove", + "recover", + "list", + "help", + "rm", + "rc", + "vi", + "vim", + "exit"}; + +void help(char *command) +{ + if (command == NULL) + { + printf("Usage:\n"); + printf(" > backup [OPTION]... : backup file if is file\n"); + printf(" -d : backup files in directory if is directory \n"); + printf(" -r : backup files in directory recursive if is directory \n"); + printf(" -y : backup file although already backuped\n"); + printf(" > remove [OPTION]... : remove backuped file if is file\n"); + printf(" -d : remove backuped files in directory if is directory \n"); + printf(" -r : remove backuped files in directory recursive if is directory \n"); + printf(" -a : remove all backuped files \n"); + printf(" > recover [OPTION]... : recover backuped file if is file\n"); + printf(" -d : recover backuped files in directory if is directory\n"); + printf(" -r : recover backuped files in directory recursive if is directory \n"); + printf(" -l : recover latest backuped files \n"); + printf(" -n : recover backuped file with new path \n"); + printf(" > list [PATH] : show backup list by directory structure \n"); + printf(" >> rm [OPTION]... : remove backuped files of [INDEX] with [OPTION] \n"); + printf(" >> rc [OPTION]... : recover backuped files of [INDEX] with [OPTION] \n"); + printf(" >> vi(m) : edit original file of [INDEX] \n"); + printf(" >> exit : exit program \n"); + printf(" > help [COMMAND] : show commands of program \n"); + } + else if (!strcmp(command, commands[0])) + { + printf("Usage:\n"); + printf(" > backup [OPTION]... : backup file if is file\n"); + printf(" -d : backup files in directory if is directory \n"); + printf(" -r : backup files in directory recursive if is directory \n"); + printf(" -y : backup file although already backuped\n"); + } + else if (!strcmp(command, commands[1])) + { + printf("Usage:\n"); + printf(" > remove [OPTION]... : remove backuped file if is file\n"); + printf(" -d : remove backuped files in directory if is directory \n"); + printf(" -r : remove backuped files in directory recursive if is directory \n"); + printf(" -a : remove all backuped files \n"); + } + + else if (!strcmp(command, commands[2])) + { + printf("Usage:\n"); + printf(" > recover [OPTION]... : recover backuped file if is file\n"); + printf(" -d : recover backuped files in directory if is directory\n"); + printf(" -r : recover backuped files in directory recursive if is directory \n"); + printf(" -l : recover latest backuped files \n"); + printf(" -n : recover backuped file with new path \n"); + } + else if (!strcmp(command, commands[3])) + { + printf("Usage:\n"); + printf(" > list [PATH] : show backup list by directory structure \n"); + printf(" >> rm [OPTION]... : remove backuped files of [INDEX] with [OPTION] \n"); + printf(" >> rc [OPTION]... : recover backuped files of [INDEX] with [OPTION] \n"); + printf(" >> vi(m) : edit original file of [INDEX] \n"); + printf(" >> exit : exit program \n"); + } +} diff --git a/p1/ssu_help.o b/p1/ssu_help.o new file mode 100644 index 0000000..43b95e1 Binary files /dev/null and b/p1/ssu_help.o differ diff --git a/p2/Makefile b/p2/Makefile new file mode 100644 index 0000000..a510e66 --- /dev/null +++ b/p2/Makefile @@ -0,0 +1,77 @@ +CC = gcc + +HEADER = ssu_header +REPO = ssu_repo +HELP = ssu_help +INIT = ssu_init +STRUCT = ssu_struct +ADD = ssu_add +REMOVE = ssu_remove +COMMIT = ssu_commit +STATUSS = ssu_status +REVERT = ssu_revert +LOG = ssu_log + +all : $(REPO) $(HELP) $(ADD) $(REMOVE) $(COMMIT) $(STATUSS) $(REVERT) $(LOG) + +$(REPO) : $(REPO).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(HELP) : $(HELP).o + $(CC) -g -o $@ $^ -lcrypto + +$(ADD) : $(ADD).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(REMOVE) : $(REMOVE).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(COMMIT) : $(COMMIT).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(STATUSS) : $(STATUSS).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(REVERT) : $(REVERT).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(LOG) : $(LOG).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(REPO).o : $(HEADER).h $(REPO).c + $(CC) -c -o $@ $(REPO).c -lcrypto + +$(HELP).o : $(HELP).h $(HELP).c + $(CC) -c -o $@ $(HELP).c -lcrypto + +$(HEADER).o : $(HEADER).h $(HEADER).c + $(CC) -c -o $@ $(HEADER).c -lcrypto + +$(INIT).o : $(HEADER).h $(INIT).c + $(CC) -c -o $@ $(INIT).c -lcrypto + +$(STRUCT).o : $(HEADER).h $(STRUCT).c + $(CC) -c -o $@ $(STRUCT).c -lcrypto + +$(ADD).o : $(HEADER).h $(ADD).c + $(CC) -c -o $@ $(ADD).c -lcrypto + +$(REMOVE).o : $(HEADER).h $(REMOVE).c + $(CC) -c -o $@ $(REMOVE).c -lcrypto + +$(COMMIT).o : $(HEADER).h $(COMMIT).c + $(CC) -c -o $@ $(COMMIT).c -lcrypto + +$(STATUSS).o : $(HEADER).h $(STATUSS).c + $(CC) -c -o $@ $(STATUSS).c -lcrypto + +$(REVERT).o : $(HEADER).h $(REVERT).c + $(CC) -c -o $@ $(REVERT).c -lcrypto + +$(LOG).o : $(HEADER).h $(LOG).c + $(CC) -c -o $@ $(LOG).c -lcrypto + + +clean : + rm -rf $(REPO) $(HELP) $(ADD) $(REMOVE) $(COMMIT) $(STATUSS) $(REVERT) $(LOG) + rm -rf *.o \ No newline at end of file diff --git a/p2/a.txt b/p2/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/p2/a/a.txt b/p2/a/a.txt new file mode 100644 index 0000000..902d5de --- /dev/null +++ b/p2/a/a.txt @@ -0,0 +1 @@ +dddd \ No newline at end of file diff --git a/p2/a/b.txt b/p2/a/b.txt new file mode 100644 index 0000000..69ec23d --- /dev/null +++ b/p2/a/b.txt @@ -0,0 +1 @@ +ddd \ No newline at end of file diff --git a/p2/b.txt b/p2/b.txt new file mode 100644 index 0000000..902d5de --- /dev/null +++ b/p2/b.txt @@ -0,0 +1 @@ +dddd \ No newline at end of file diff --git a/p2/b/c.txt b/p2/b/c.txt new file mode 100644 index 0000000..e69de29 diff --git a/p2/b/d.txt b/p2/b/d.txt new file mode 100644 index 0000000..69ec23d --- /dev/null +++ b/p2/b/d.txt @@ -0,0 +1 @@ +ddd \ No newline at end of file diff --git a/p2/c.txt b/p2/c.txt new file mode 100644 index 0000000..88f0595 --- /dev/null +++ b/p2/c.txt @@ -0,0 +1 @@ +dddddd \ No newline at end of file diff --git a/p2/d.txt b/p2/d.txt new file mode 100644 index 0000000..e69de29 diff --git a/p2/ssu_add.c b/p2/ssu_add.c new file mode 100644 index 0000000..2e1d913 --- /dev/null +++ b/p2/ssu_add.c @@ -0,0 +1,110 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; + +int check_already_add(dirNode *dirNode, char *absolutePath, + char *relativePath) +{ + if (dirNode == NULL) + return 0; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(absolutePath, bNode->origin_path)) + { + printf("\"%s\" already exist in staging area \n", relativePath); + return 1; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + // 재귀호출하는데 유망한지 한번 더 확인해야함!! + if (check_already_add(dirNode, absolutePath, relativePath) == 1) + { + return 1; + } + dirNode = dirNode->next_dir; + } +} + +int ssu_add(char *path) +{ + int fd; + int len; + char buf[BUF_MAX + 200]; + char pathBuf[BUF_MAX]; + char absolutePathBuf[BUF_MAX]; + char relativePathBuf[BUF_MAX]; + char absolutePath[BUF_MAX]; + // stagingLogPath열기 + if ((fd = open(stagingLogPATH, O_WRONLY | O_APPEND)) == -1) + { + fprintf(stderr, "ERROR: open error for '%s'\n", stagingLogPATH); + return 1; + } + strcpy(pathBuf, path); + // 절대경로 + realpath(pathBuf, absolutePathBuf); + strcpy(absolutePath, absolutePathBuf); + // 상대경로 + char *relativePath = getRelativePath(absolutePathBuf); + + if (check_already_add(staging_dir_list, absolutePath, relativePath) == 1) + { + return 1; + } + // log와 표준출력쓰기 + sprintf(buf, "add \"%s\"\n", absolutePath); + if (write(fd, buf, strlen(buf)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", stagingLogPATH); + return 1; + } + printf("add \"%s\" \n", relativePath); + + return 0; +} + +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + repoPATH = (char *)malloc(PATH_MAX); + stagingLogPATH = (char *)malloc(PATH_MAX); + commitLogPATH = (char *)malloc(PATH_MAX); + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + return -1; + } + ssu_add(argv[1]); + exit(0); +} \ No newline at end of file diff --git a/p2/ssu_commit.c b/p2/ssu_commit.c new file mode 100644 index 0000000..268c652 --- /dev/null +++ b/p2/ssu_commit.c @@ -0,0 +1,979 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; + +int change = 0; +int insert = 0; +int delete = 0; + +int dircnt = 0; + +int backup_file(char *origin_path, char *backup_path, char *repo_name) +{ + struct stat statbuf; + int fd1, fd2; + int len; + char buf[BUF_MAX]; + char backup_file_path[PATH_MAX]; + char backupTmp[PATH_MAX] = ""; + char backupdirtmp[PATH_MAX] = ""; + strcpy(backupTmp, origin_path); + char *backPtr = strstr(backupTmp, exePATH); + int backStart = backPtr - backupTmp + strlen(exePATH); + int backEnd = strlen(backupTmp); + char *backupPrefix = substr(backupTmp, backStart, backEnd); + // backupPrefix에서 맨 뒤에서 부터 순회하면서 처음 나온 슬래시를 기준으로 그 뒤를 전부 자르기 + + char *last_slash = strrchr(backupPrefix, '/'); // 마지막 '/'의 위치를 찾기 + + if (last_slash != NULL) + { + *last_slash = '\0'; // 마지막 '/' 위치에 널 문자를 삽입하여 문자열을 종료 + } + char backupDirPath[PATH_MAX] = ""; + strcat(backupDirPath, repoPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, repo_name); + + strcat(backupDirPath, backupPrefix); + strcpy(backupdirtmp, backupPrefix); + char newString[PATH_MAX] = ""; + char tempPath[PATH_MAX] = ""; + char currentPath[PATH_MAX] = ""; + // 마지막 '/' 위치 찾기 + char *lastSlash = strrchr(backupdirtmp, '/'); + if (lastSlash != NULL) + { + strcpy(newString, lastSlash + 1); + *lastSlash = '\0'; + } + strcpy(tempPath, backupdirtmp); + char *token = strtok(tempPath, "/"); + + strcat(currentPath, repoPATH); + strcat(currentPath, "/"); + strcat(currentPath, repo_name); + + while (token != NULL) + { + + strcat(currentPath, "/"); + strcat(currentPath, token); + + // 디렉터리 존재 여부 확인 후 없으면 생성 + if (access(currentPath, F_OK) != 0) + { + if (mkdir(currentPath, 0777) == -1) + { + fprintf(stderr, "Failed to create directory"); + return -1; + } + } + token = strtok(NULL, "/"); + } + + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + + if ((fd1 = open(origin_path, O_RDONLY)) == -1) + { + fprintf(stderr, "ERROR: open error for '%s'\n", origin_path); + return -1; + } + + if ((fd2 = open(backup_path, O_WRONLY | O_CREAT | O_TRUNC, 0777)) == -1) + { + fprintf(stderr, "ERROR: open error for '%s'\n", backup_path); + return -1; + } + + while (len = read(fd1, buf, BUF_MAX)) + { + write(fd2, buf, len); + } + // 원본 파일의 소유자 및 그룹 설정 + if (chown(backup_path, statbuf.st_uid, statbuf.st_gid) == -1) + { + fprintf(stderr, "ERROR: chown error\n"); + return -1; + } + + // 타임스탬프 설정 + struct timespec times[2]; + times[0].tv_sec = statbuf.st_atime; + // 나노초 부분은 0으로 설정 + times[0].tv_nsec = 0; + times[1].tv_sec = statbuf.st_mtime; + times[1].tv_nsec = 0; + + if (utimensat(AT_FDCWD, backup_path, times, 0) == -1) + { + fprintf(stderr, "ERROR : Failed to update timestamps"); + return -1; + } + + close(fd1); + close(fd2); + + return 1; +} + +void current_commit_list(dirNode *dirNode, char *commit_repo_name) +{ + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0 && (!strcmp(bNode->command, "new file") || !strcmp(bNode->command, "modified"))) + { + struct stat statbuf; + if (lstat(bNode->origin_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR5: lstat error\n"); + exit(1); + } + char backupTmp[PATH_MAX] = ""; + strcpy(backupTmp, bNode->origin_path); + char *backPtr = strstr(backupTmp, exePATH); + int backStart = backPtr - backupTmp + strlen(exePATH); + int backEnd = strlen(backupTmp); + char *backupPrefix = substr(backupTmp, backStart, backEnd); + char backup_path[PATH_MAX] = ""; + strcat(backup_path, repoPATH); + strcat(backup_path, "/"); + strcat(backup_path, commit_repo_name); + strcat(backup_path, backupPrefix); + backup_file(bNode->origin_path, backup_path, commit_repo_name); + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + current_commit_list(dirNode, commit_repo_name); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} +// 5. current_commit_list에서 Modify랑 new file에 대해서만 .repo에 백업 +int backup_proccess(char *repo_name) +{ + char backupDirPath[PATH_MAX] = ""; + strcat(backupDirPath, repoPATH); + strcat(backupDirPath, "/"); + strcat(backupDirPath, repo_name); + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + current_commit_list(current_commit_dir_list, repo_name); + return 0; +} + +void print_current_commit(dirNode *dirNode, char *commit_name, int commit_fd) +{ + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + char buf[BUF_MAX + 20]; + memset(buf, 0, sizeof(buf)); + char *relativePath = getRelativePath(bNode->origin_path); + sprintf(buf, "commit: \"%s\" - %s: \"%s\" \n", commit_name, bNode->command, bNode->origin_path); + if (write(commit_fd, buf, strlen(buf)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", stagingLogPATH); + return; + } + printf(" %s : %s\n", bNode->command, relativePath); + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + print_current_commit(dirNode, commit_name, commit_fd); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} + +void lookup_current_commit(dirNode *dirNode) +{ + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(bNode->command, "new file")) + { + + insert++; + } + else if (!strcmp(bNode->command, "modified")) + { + change++; + } + else if (!strcmp(bNode->command, "removed")) + { + delete ++; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + lookup_current_commit(dirNode); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} + +int check_new_file(dirNode *dirNode, char *target_path) +{ + // 더 이상 탐색할 요소가 없으므로, 새 파일이라고 가정 + if (dirNode == NULL) + return 1; + + // 파일 노드 순회 + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(target_path, bNode->origin_path)) + { + // 일치하는 경우, 새 파일이 아니므로 0 반환 + return 0; + } + } + + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 노드 순회 + int found = 1; // 초기값을 1로 설정하여, 새 파일이라고 가정 + dirNode = dirNode->subdir_head; + while (dirNode != NULL && found) + { + // 재귀 호출을 통해 하위 디렉토리 탐색 + found = check_new_file(dirNode, target_path); + if (found == 0) + { + // 일치하는 파일을 찾았으므로 0 반환 + return 0; + } + dirNode = dirNode->next_dir; + } + + // 전체 리스트를 순회했으나 target_path와 일치하는 경로를 찾지 못한 경우, 새 파일로 간주하여 1 반환 + return found; +} + +// staging에는 없고 commit에는 있으면 -1리턴 +// remove할거 없으면 1리턴(하나라도 둘다같은게 발견되면 1리턴) +// 커밋 리스트 path ,staging리스트 path +int remove_bfs(char *commit_path, char *staging_path) +{ + + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + dirNode_append(dirList, staging_path, ""); + int isAvailable = 0; // true + + curr_dir = dirList->next_dir; + int found = -1; + + while (curr_dir != NULL) + { + // @todo 여기 scandir에러 뜨는거 + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", curr_dir->dir_name); + return -1; + } + if (S_ISREG(statbuf.st_mode)) + { + + if (S_ISREG(statbuf.st_mode) && !strcmp(commit_path, curr_dir->dir_name)) + { + + return 1; + } + } + else if (S_ISDIR(statbuf.st_mode)) + { + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", curr_dir->dir_name); + return -1; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX] = ""; + + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmp_path); + return -1; + } + + // 커밋경로랑 tmp_path가 같다는건 스테이징 경로에 해당 커밋 경로가 있다는거! 즉 안없어진걸 의미 + if (S_ISREG(statbuf.st_mode) && !strcmp(commit_path, tmp_path)) + { + + return 1; + } + else if (S_ISDIR(statbuf.st_mode)) + { + + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } + return -1; +} +int traverseRemoveBackupNodesReverse(backupNode *backup_head, char *exe_path) +{ + if (backup_head == NULL) + return -1; + + backupNode *lastBackup = backup_head; + while (lastBackup && lastBackup->next_backup != NULL) + { + lastBackup = lastBackup->next_backup; + } + + backupNode *backup = lastBackup; + while (backup != NULL) + { + if (backup && strlen(backup->origin_path) > 0) + { + // 커밋 리스트 path ,staging리스트 path + // 백업에만 남아있으면 + + if (remove_bfs(backup->origin_path, exe_path) < 0) + { + // 전역 currentlist에 추가 + dircnt++; + delete ++; + backup_list_insert(current_commit_dir_list, "removed", backup->origin_path, "", ""); + + return 1; + } + else + { + + return -1; + } + backup = backup->prev_backup; + } + } +} +int traverseRemoveFileNodesReverse(fileNode *file_head, char *exe_path) +{ + if (file_head == NULL) + return -1; + + fileNode *lastFile = file_head; + while (lastFile && lastFile->next_file != NULL) + { + lastFile = lastFile->next_file; + } + + while (lastFile != NULL) + { + if (lastFile->backup_head) + { + int result = traverseRemoveBackupNodesReverse(lastFile->backup_head, exe_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + lastFile = lastFile->prev_file; + } + return -1; +} + +int traverseRemoveDirNodesReverse(dirNode *dir_head, char *exe_path) +{ + if (dir_head == NULL) + return -1; + + dirNode *lastDir = dir_head; + while (lastDir && lastDir->next_dir != NULL) + { + lastDir = lastDir->next_dir; + } + + while (lastDir != NULL) + { + if (lastDir->file_head) + { + int result = traverseRemoveFileNodesReverse(lastDir->file_head, exe_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + if (lastDir->subdir_head) + { + int result = traverseRemoveDirNodesReverse(lastDir->subdir_head, exe_path); + if (result == 1) + return 1; // 하위 디렉토리에서 파일 수정 발견 + } + lastDir = lastDir->prev_dir; + } + return -1; +} +int check_remove_file(dirNode *dirNode, char *exe_path) +{ + + // 역순으로 commit list순회 + if (traverseRemoveDirNodesReverse(commit_dir_list, exe_path) == 1) + { + return 1; + } + else + { + return -1; + } +} + +// 수정된게 있으면1 없으면 -1 +int traverseBackupNodesReverse(backupNode *backup_head, char *target_path) +{ + if (backup_head == NULL) + return -1; + + backupNode *lastBackup = backup_head; + // 마지막 백업 노드로 이동 + while (lastBackup && lastBackup->next_backup != NULL) + { + lastBackup = lastBackup->next_backup; + } + // 역순으로 노드를 검사 + backupNode *backup = lastBackup; + while (backup != NULL) + { + if (backup->origin_path && strlen(backup->origin_path) > 0) + { + + // 해시값이 다르다 -> 수정 됨 + if (!strcmp(backup->origin_path, target_path) && cmpHash(backup->backup_path, target_path)) + { + + return 1; + } + // 해시값이 같다 -> 수정 안됨 + if (!strcmp(backup->origin_path, target_path) && !cmpHash(backup->backup_path, target_path)) + { + + return -1; + } + } + backup = backup->prev_backup; // 이전 백업 노드로 이동 + } + return -1; // 수정된 파일 없음 +} +int traverseFileNodesReverse(fileNode *file_head, char *target_path) +{ + if (file_head == NULL) + return -1; + + fileNode *lastFile = file_head; + while (lastFile && lastFile->next_file != NULL) + { + lastFile = lastFile->next_file; + } + + while (lastFile != NULL) + { + if (lastFile->backup_head) + { + int result = traverseBackupNodesReverse(lastFile->backup_head, target_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + lastFile = lastFile->prev_file; + } + return -1; +} + +int traverseDirNodesReverse(dirNode *dir_head, char *target_path) +{ + if (dir_head == NULL) + return -1; + + dirNode *lastDir = dir_head; + while (lastDir && lastDir->next_dir != NULL) + { + lastDir = lastDir->next_dir; + } + + while (lastDir != NULL) + { + if (lastDir->file_head) + { + int result = traverseFileNodesReverse(lastDir->file_head, target_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + if (lastDir->subdir_head) + { + int result = traverseDirNodesReverse(lastDir->subdir_head, target_path); + if (result == 1) + return 1; // 하위 디렉토리에서 파일 수정 발견 + } + lastDir = lastDir->prev_dir; + } + return -1; +} + +int check_modify_file(char *target_path) +{ + + if (traverseDirNodesReverse(commit_logs_list, target_path) == 1) + { + return 1; + } + else + { + return -1; + } +} + +// remove staging list에 있는 것과 같은 경로라면 1 반환 아니면 -1 + +int check_remove_staging_list(dirNode *dirNode, char *target_path) +{ + if (dirNode == NULL) + { + return -1; + } + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(target_path, bNode->origin_path)) + { + return 1; // 정확히 일치하는 경로 발견 + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 순회 + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + if (check_remove_staging_list(dirNode, target_path) == 1) + { + return 1; // 하위에서 일치하는 경로 발견 + } + dirNode = dirNode->next_dir; + } + + return -1; // 일치하는 경로를 찾지 못함 +} + +void bfs(char *path) +{ + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + // bfs위한 큐 + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + + dirNode_append(dirList, path, ""); + + curr_dir = dirList->next_dir; + + while (curr_dir != NULL) + { + // @todo -> 여기서 안됨! + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", curr_dir->dir_name); + return; + } + + if (S_ISREG(statbuf.st_mode)) + { + if (check_remove_staging_list(staging_remove_dir_list, curr_dir->dir_name) == 1) + { + return; + } + + // newfile 인경우 + if (check_new_file(commit_dir_list, curr_dir->dir_name) == 1) + { + dircnt++; + + backup_list_insert(current_commit_dir_list, "new file", curr_dir->dir_name, "", ""); + return; + } + // modify인경우 + else if (check_modify_file(curr_dir->dir_name) == 1) + { + dircnt++; + + backup_list_insert(current_commit_dir_list, "modified", curr_dir->dir_name, "", ""); + return; + } + } + else + { + + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", curr_dir->dir_name); + return; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX]; + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmp_path); + return; + } + + if (S_ISREG(statbuf.st_mode)) + { + if (check_remove_staging_list(staging_remove_dir_list, tmp_path) == 1) + { + return; + } + + // 파일인경우 커밋 로그에서 판단을 해준다. + // newfile 인경우 + if (check_new_file(commit_dir_list, tmp_path) == 1) + { + dircnt++; + + backup_list_insert(current_commit_dir_list, "new file", tmp_path, "", ""); + } + // modify인경우 + else if (check_modify_file(tmp_path) == 1) + { + dircnt++; + + backup_list_insert(current_commit_dir_list, "modified", tmp_path, "", ""); + } + } + else if (S_ISDIR(statbuf.st_mode)) + { + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } +} +// 스테이징 경로가 현재 작업경로에 존재하지 않으면 -1반환 +int check_exist_pwd(char *target_path) +{ + + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + dirNode_append(dirList, exePATH, ""); + int isAvailable = 0; // true + + curr_dir = dirList->next_dir; + int found = -1; + + while (curr_dir != NULL) + { + // @todo 여기 scandir에러 뜨는거 + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", curr_dir->dir_name); + return -1; + } + if (S_ISREG(statbuf.st_mode)) + { + if (S_ISREG(statbuf.st_mode) && !strcmp(target_path, curr_dir->dir_name)) + { + + return 1; + } + } + else if (S_ISDIR(statbuf.st_mode)) + { + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR!!: scandir error for %s\n", curr_dir->dir_name); + return -1; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX] = ""; + + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmp_path); + return -1; + } + + int len = strlen(target_path) < strlen(tmp_path) ? strlen(target_path) : strlen(tmp_path); + // 커밋경로랑 tmp_path가 같다는건 스테이징 경로에 해당 커밋 경로가 있다는거! 즉 안없어진걸 의미 + if (S_ISREG(statbuf.st_mode) && !strncmp(target_path, tmp_path, len)) + { + + return 1; + } + else if (S_ISDIR(statbuf.st_mode)) + { + + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } + return -1; +} + +// staging dirlist가 매개변수로 +void check_staging_list(dirNode *dirNode) +{ + struct stat statbuf; + if (dirNode == NULL) + return; + + // 파일 노드 순회 시작 + fileNode *fNode = dirNode->file_head->next_file; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head->next_backup; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + if (lstat(bNode->origin_path, &statbuf) < 0) + { + printf(stderr, "lstat error %s\n", bNode->origin_path); + } + + // bfs돌기전에 현재 디렉토리에 있는 파일인지 부터 확인 + + if (S_ISREG(statbuf.st_mode)) + { + if (check_exist_pwd(bNode->origin_path) > 0) + { + bfs(bNode->origin_path); + } + } + + bNode = bNode->next_backup; + } + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 순회 시작 + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + check_staging_list(dirNode); + + dirNode = dirNode->next_dir; + } +} + +int ssu_commit(char *commit_name) +{ + int fd; + if ((fd = open(commitLogPATH, O_WRONLY | O_CREAT | O_APPEND)) < 0) + { + fprintf(stderr, "ERROR: file open error %s\n", commitLogPATH); + } + + // 현재 커밋 담는 리스트 init + + check_staging_list(staging_dir_list); + + check_remove_file(commit_dir_list, exePATH); + + if (dircnt == 0) + { + printf("Nothing to commit\n"); + } + else + { + lookup_current_commit(current_commit_dir_list); + // @todo : commit 버전 레포 만들고 로그 쓰기 + backup_proccess(commit_name); + printf("commit to %s\n", commit_name); + printf("%d files changed, %d insertions(+), %d deletions(-)\n", change, insert, delete); + print_current_commit(current_commit_dir_list, commit_name, fd); + } +} + +// 같은 이름의 커밋이 있다면 -1 리턴 +int find_same_commit(dirNode *dirNode, char *commit_name) +{ + if (dirNode == NULL) + return 1; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0) + { + + remove_quotes_in_place(bNode->commit_message); + if (!strcmp(bNode->commit_message, commit_name)) + { + return -1; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + if (find_same_commit(dirNode, commit_name) == -1) + { + return -1; + } + dirNode = dirNode->next_dir; + } +} + +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + repoPATH = (char *)malloc(PATH_MAX); + stagingLogPATH = (char *)malloc(PATH_MAX); + commitLogPATH = (char *)malloc(PATH_MAX); + current_commit_dir_list = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(current_commit_dir_list); + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + return -1; + } + + // @todo : 해당 커밋이 이미 레포에 있는지 + if (find_same_commit(commit_dir_list, argv[1]) < 0) + { + printf("\"%s\" is already exist in repo\n", argv[1]); + exit(1); + } + + ssu_commit(argv[1]); + + exit(0); +} \ No newline at end of file diff --git a/p2/ssu_header.c b/p2/ssu_header.c new file mode 100644 index 0000000..5dc7c90 --- /dev/null +++ b/p2/ssu_header.c @@ -0,0 +1,395 @@ +#include "ssu_header.h" + +char *get_file_name(char *path) +{ + int i; + char tmp_path[PATH_MAX]; + char *file_name = (char *)malloc(sizeof(char) * FILE_MAX); + + strcpy(tmp_path, path); + for (i = 0; tmp_path[i]; i++) + { + if (tmp_path[i] == '/') + strcpy(file_name, tmp_path + i + 1); + } + + return file_name; +} + +char *cvt_time_2_str(time_t time) +{ + struct tm *time_tm = localtime(&time); + char *time_str = (char *)malloc(sizeof(char) * 32); + sprintf(time_str, "%02d%02d%02d%02d%02d%02d", + (time_tm->tm_year + 1900) % 100, + time_tm->tm_mon + 1, + time_tm->tm_mday, + time_tm->tm_hour, + time_tm->tm_min, + time_tm->tm_sec); + return time_str; +} + +char *substr(char *str, int beg, int end) +{ + char *ret = (char *)malloc(sizeof(char) * (end - beg + 1)); + + for (int i = beg; i < end && *(str + i) != '\0'; i++) + { + ret[i - beg] = str[i]; + } + ret[end - beg] = '\0'; + + return ret; +} + +char *c_str(char *str) +{ + return substr(str, 0, strlen(str)); +} + +int path_list_init(pathNode *curr_path, char *path) +{ + pathNode *new_path = curr_path; + char *ptr; + char *next_path = ""; + + if (!strcmp(path, "")) + return 0; + + if (ptr = strchr(path, '/')) + { + next_path = ptr + 1; + ptr[0] = '\0'; + } + + if (!strcmp(path, "..")) + { + new_path = curr_path->prev_path; + new_path->tail_path = new_path; + new_path->next_path = NULL; + new_path->head_path->tail_path = new_path; + + new_path->head_path->depth--; + + if (new_path->head_path->depth == 0) + return -1; + } + else if (strcmp(path, ".")) + { + new_path = (pathNode *)malloc(sizeof(pathNode)); + strcpy(new_path->path_name, path); + + new_path->head_path = curr_path->head_path; + new_path->tail_path = new_path; + + new_path->prev_path = curr_path; + new_path->next_path = curr_path->next_path; + + curr_path->next_path = new_path; + new_path->head_path->tail_path = new_path; + + new_path->head_path->depth++; + } + + if (strcmp(next_path, "")) + { + return path_list_init(new_path, next_path); + } + + return 0; +} + +char *cvt_path_2_realpath(char *path) +{ + pathNode *path_head; + pathNode *curr_path; + char *ptr; + char origin_path[PATH_MAX]; + char ret_path[PATH_MAX] = ""; + + path_head = (pathNode *)malloc(sizeof(pathNode)); + path_head->depth = 0; + path_head->tail_path = path_head; + path_head->head_path = path_head; + path_head->next_path = NULL; + + if (path[0] != '/') + { + sprintf(origin_path, "%s/%s", pwdPATH, path); + } + else + { + strcpy(origin_path, path); + } + + if (path_list_init(path_head, origin_path) == -1) + { + return NULL; + } + + curr_path = path_head->next_path; + while (curr_path != NULL) + { + strcat(ret_path, curr_path->path_name); + if (curr_path->next_path != NULL) + { + strcat(ret_path, "/"); + } + curr_path = curr_path->next_path; + } + + if (strlen(ret_path) == 0) + { + strcpy(ret_path, "/"); + } + + return c_str(ret_path); +} + +int make_dir_path(char *path) +{ + pathNode *path_head; + pathNode *curr_path; + char ret_path[PATH_MAX] = ""; + struct stat statbuf; + + path_head = (pathNode *)malloc(sizeof(pathNode)); + path_head->depth = 0; + path_head->tail_path = path_head; + path_head->head_path = path_head; + path_head->next_path = NULL; + + curr_path = path_head; + + if (path_list_init(path_head, c_str(path)) == -1) + { + return -1; + } + + curr_path = path_head->next_path; + while (curr_path != NULL) + { + strcat(ret_path, curr_path->path_name); + strcat(ret_path, "/"); + + curr_path = curr_path->next_path; + + if (!access(ret_path, F_OK)) + { + if (lstat(ret_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", ret_path); + return -1; + } + + if (!S_ISDIR(statbuf.st_mode)) + { + fprintf(stderr, "ERROR: %s is already exist as file\n", ret_path); + return -1; + } + + continue; + } + + if (mkdir(ret_path, 0755) == -1) + { + fprintf(stderr, "ERROR: mkdir error for '%s'\n", ret_path); + return -1; + }; + } + + return 0; +} + +int md5(char *target_path, char *hash_result) +{ + FILE *fp; + unsigned char hash[MD5_DIGEST_LENGTH]; + unsigned char buffer[SHRT_MAX]; + int bytes = 0; + MD5_CTX md5; + + if ((fp = fopen(target_path, "rb")) == NULL) + { + printf("ERROR: fopen error for %s\n", target_path); + return 1; + } + + MD5_Init(&md5); + + while ((bytes = fread(buffer, 1, SHRT_MAX, fp)) != 0) + MD5_Update(&md5, buffer, bytes); + + MD5_Final(hash, &md5); + + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(hash_result + (i * 2), "%02x", hash[i]); + hash_result[HASH_MD5 - 1] = 0; + + fclose(fp); + + return 0; +} + +int cvtHash(char *target_path, char *hash_result) +{ + md5(target_path, hash_result); +} + +int cmpHash(char *path1, char *path2) +{ + char *hash1 = (char *)malloc(sizeof(char) * HASH_MD5); + char *hash2 = (char *)malloc(sizeof(char) * HASH_MD5); + + cvtHash(path1, hash1); + cvtHash(path2, hash2); + + return strcmp(hash1, hash2); +} + +char *quote_check(char **str, char del) +{ + char *tmp = *str + 1; + int i = 0; + + while (*tmp != '\0' && *tmp != del) + { + tmp++; + i++; + } + if (*tmp == '\0') + { + *str = tmp; + return NULL; + } + if (*tmp == del) + { + for (char *c = *str; *c != '\0'; c++) + { + *c = *(c + 1); + } + *str += i; + for (char *c = *str; *c != '\0'; c++) + { + *c = *(c + 1); + } + } +} + +char *tokenize(char *str, char *del) +{ + int i = 0; + int del_len = strlen(del); + static char *tmp = NULL; + char *tmp2 = NULL; + + if (str != NULL && tmp == NULL) + { + tmp = str; + } + + if (str == NULL && tmp == NULL) + { + return NULL; + } + + char *idx = tmp; + + while (i < del_len) + { + if (*idx == del[i]) + { + idx++; + i = 0; + } + else + { + i++; + } + } + if (*idx == '\0') + { + tmp = NULL; + return tmp; + } + tmp = idx; + + while (*tmp != '\0') + { + if (*tmp == '\'' || *tmp == '\"') + { + quote_check(&tmp, *tmp); + continue; + } + for (i = 0; i < del_len; i++) + { + if (*tmp == del[i]) + { + *tmp = '\0'; + break; + } + } + tmp++; + if (i < del_len) + { + break; + } + } + + return idx; +} + +char **get_substring(char *str, int *cnt, char *del) +{ + *cnt = 0; + int i = 0; + char *token = NULL; + char *templist[100] = { + NULL, + }; + token = tokenize(str, del); + if (token == NULL) + { + return NULL; + } + + while (token != NULL) + { + templist[*cnt] = token; + *cnt += 1; + token = tokenize(NULL, del); + } + + char **temp = (char **)malloc(sizeof(char *) * (*cnt + 1)); + for (i = 0; i < *cnt; i++) + { + temp[i] = templist[i]; + } + return temp; +} +char* getRelativePath(char* absolutePath) { + + char cwd[1024]; + getcwd(cwd, 1024); + // absolutePath 내에서 cwd를 찾음 + char *found = strstr(absolutePath, cwd); + + // cwd 이후의 문자열 시작 위치 계산 + char *startAfterCwd = found + strlen(cwd); + + + // '/' 문자로 시작하면 건너뛰기 + if (*startAfterCwd == '/') startAfterCwd++; + char *newPath = (char *)malloc(STR_MAX); + memset(newPath, 0, STR_MAX); + + if(!strcmp(startAfterCwd,"")){ + strcat(newPath,"."); + }else{ + strcat(newPath,"./"); + strcat(newPath, startAfterCwd); + + } + return newPath; +} \ No newline at end of file diff --git a/p2/ssu_header.h b/p2/ssu_header.h new file mode 100644 index 0000000..e996908 --- /dev/null +++ b/p2/ssu_header.h @@ -0,0 +1,206 @@ +#ifndef SSU_HEADER_H +#define SSU_HEADER_H + +#define OPENSSL_API_COMPAT 0x10100000L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG printf("DEBUG\n"); + +#define PATH_MAX 4096 +#define BUF_MAX 4096 +#define FILE_MAX 255 +#define STR_MAX 255 + +#define CMD_ADD 0b0000000001 +#define CMD_REMOVE 0b0000000010 +#define CMD_STATUS 0b0000000100 +#define CMD_COMMIT 0b0000001000 +#define CMD_REVERT 0b0000010000 +#define CMD_LOG 0b0000100000 +#define CMD_HELP 0b0010000000 +#define CMD_EXIT 0b0100000000 +#define NOT_CMD 0b0000000000 + +#define OPT_D 0b000001 +#define OPT_R 0b000010 +#define OPT_Y 0b000100 +#define OPT_A 0b001000 +#define OPT_L 0b010000 +#define OPT_N 0b100000 + +#define ADD_SEP "add " +#define REMOVE_SEP "remove " +#define RECOVER_SEP "\" recovered to \"" +#define COMMIT_PREFIX "commit: " +#define NEWFILE_SEP " - new file: " +#define MODIFIED_SEP " - modified: " +#define REMOVED_SEP " - removed: " + +#define HASH_MD5 33 + +typedef struct command_parameter +{ + char *command; + char *filename; + char *tmpname; + int commandopt; + char *argv[10]; +} command_parameter; + +typedef struct _backupNode +{ + struct _dirNode *root_version_dir; + struct _fileNode *root_file; + + char command[13]; + char commit_message[STR_MAX]; + char origin_path[PATH_MAX]; + char backup_path[PATH_MAX]; + + struct _backupNode *prev_backup; + struct _backupNode *next_backup; +} backupNode; + +typedef struct _fileNode +{ + struct _dirNode *root_dir; + + int backup_cnt; + char file_name[FILE_MAX]; + char file_path[PATH_MAX]; + backupNode *backup_head; + + struct _fileNode *prev_file; + struct _fileNode *next_file; +} fileNode; + +typedef struct _dirNode +{ + struct _dirNode *root_dir; + + int file_cnt; + int subdir_cnt; + int backup_cnt; + char dir_name[FILE_MAX]; + char dir_path[PATH_MAX]; + fileNode *file_head; + struct _dirNode *subdir_head; + + struct _dirNode *prev_dir; + struct _dirNode *next_dir; +} dirNode; + +typedef struct _pathNode +{ + char path_name[FILE_MAX]; + int depth; + + struct _pathNode *prev_path; + struct _pathNode *next_path; + + struct _pathNode *head_path; + struct _pathNode *tail_path; +} pathNode; + +extern dirNode *backup_dir_list; +// add 된애들만 관리 +extern dirNode *staging_dir_list; +extern dirNode *version_dir_list; +// remove된애들만 관리 +extern dirNode *staging_remove_dir_list; +// commit log들 관리 +extern dirNode *commit_dir_list; +// 현재 프로세스에서 커밋될 애들 관리하는 리스트 +extern dirNode *current_commit_dir_list; +// 현재 디렉토리에 있는 파일들 관리하는 리스트 +extern dirNode *untrack_dir_list; +// 중복 제거 없이 모든 커밋로그들 관리 +extern dirNode *commit_logs_list; + +// 실행파일 이름 +extern char *exeNAME; +// 현재 실행 디렉 경로 +extern char *exePATH; +// HOME으로 지정한경로 +extern char *pwdPATH; +extern char *repoPATH; +extern char *stagingLogPATH; +extern char *commitLogPATH; +extern int hash; + +void help(); +void help_process(int argc, char *arg); + +char *quote_check(char **str, char del); +char **get_substring(char *str, int *cnt, char *del); +char *tokenize(char *str, char *del); +char *get_file_name(char *path); +char *cvt_time_2_str(time_t time); +char *substr(char *str, int beg, int end); +char *c_str(char *str); +char *cvt_path_2_realpath(char *path); +int make_dir_path(char *path); + +int md5(char *target_path, char *hash_result); +int cvtHash(char *target_path, char *hash_result); +int cmpHash(char *path1, char *path2); + +int path_list_init(pathNode *curr_path, char *path); +char *getRelativePath(char *absolutePath); + +// ssu_struct.c +void dirNode_init(dirNode *dir_node); +dirNode *dirNode_get(dirNode *dir_head, char *dir_name); +dirNode *dirNode_insert(dirNode *dir_head, char *dir_name, char *dir_path); +dirNode *dirNode_append(dirNode *dir_head, char *dir_name, char *dir_path); +void dirNode_remove(dirNode *dir_node); + +int add_cnt_root_dir(dirNode *dir_node, int cnt); + +void fileNode_init(fileNode *file_node); +fileNode *fileNode_get(fileNode *file_head, char *file_name); +fileNode *fileNode_insert(fileNode *file_head, char *file_name, char *dir_path); +void fileNode_remove(fileNode *file_node); + +void backupNode_init(backupNode *backup_node); +backupNode *backupNode_get(backupNode *backup_head, char *command); +backupNode *backupNode_insert(backupNode *backup_head, char *command, + char *file_name, char *dir_path, + char *backup_path, char *commit_message); +void backupNode_remove(backupNode *backup_node); + +dirNode *get_dirNode_from_path(dirNode *dirList, char *path); +fileNode *get_fileNode_from_path(dirNode *dirList, char *path); +backupNode *get_backupNode_from_path(dirNode *dirList, char *path, + char *command); + +// ssu_init.c +int init(char *path); +int init_backup_list(int log_fd); +void print_list(dirNode *dirList, int depth, int last_bit); +void print_depth(int depth, int is_last_bit); +int backup_list_insert(dirNode *dirList, char *command, char *path, + char *backup_path, char *commit_message); +int backup_list_remove(dirNode *dirList, char *command, char *path, + char *backup_path); + +// ssu_add.c +int check_already_add(dirNode *dirNode, char *absolutePath, char *relativePath); +// ssu_remove.c +int check_already_remove(dirNode *dirNode, char *absolutePath, + char *relativePath); +#endif \ No newline at end of file diff --git a/p2/ssu_help.c b/p2/ssu_help.c new file mode 100644 index 0000000..27a1688 --- /dev/null +++ b/p2/ssu_help.c @@ -0,0 +1,88 @@ +#include "ssu_header.h" + + +void help(int cmd_bit) +{ + if (!cmd_bit) + { + printf("Usage: \n"); + } + + if (!cmd_bit || cmd_bit & CMD_ADD) + { + printf("%s add : add files/directories to staging area\n", (cmd_bit ? "Usage:" : " >")); + } + + if (!cmd_bit || cmd_bit & CMD_REMOVE) + { + printf("%s remove : record path to staging area, path will not tracking modification\n", (cmd_bit ? "Usage:" : " >")); + } + + if (!cmd_bit || cmd_bit & CMD_STATUS) + { + printf("%s status : show staging area status\n", (cmd_bit ? "Usage:" : " >")); + } + + if (!cmd_bit || cmd_bit & CMD_COMMIT) + { + printf("%s commit : backup staging area with commit name\n", (cmd_bit ? "Usage:" : " >")); + } + + if (!cmd_bit || cmd_bit & CMD_REVERT) + { + printf("%s revert : recover commit version with commit name\n", (cmd_bit ? "Usage:" : " >")); + } + if (!cmd_bit || cmd_bit & CMD_LOG) + { + printf("%s log : show commit log\n", (cmd_bit ? "Usage:" : " >")); + } + if (!cmd_bit || cmd_bit & CMD_HELP) + { + printf("%s help : show commands for program\n", (cmd_bit ? "Usage:" : " >")); + } + if (!cmd_bit || cmd_bit & CMD_EXIT) + { + printf("%s exit : exit program\n", (cmd_bit ? "Usage:" : " >")); + } +} + +int main(int argc, char *argv[]) +{ + if (argc == 1) + { + help(0); + } + else if (!strcmp(argv[1], "add")) + { + help(CMD_ADD); + } + else if (!strcmp(argv[1], "remove")) + { + help(CMD_REMOVE); + } + else if (!strcmp(argv[1], "status")) + { + help(CMD_STATUS); + } + else if (!strcmp(argv[1], "commit")) + { + help(CMD_COMMIT); + } + else if (!strcmp(argv[1], "revert")) + { + help(CMD_REVERT); + } + else if (!strcmp(argv[1], "log")) + { + help(CMD_LOG); + } + else if (!strcmp(argv[1], "help")) + { + help(CMD_HELP); + } + else if (!strcmp(argv[1], "exit")) + { + help(CMD_EXIT); + } + exit(0); +} diff --git a/p2/ssu_help.h b/p2/ssu_help.h new file mode 100644 index 0000000..e50cc2e --- /dev/null +++ b/p2/ssu_help.h @@ -0,0 +1,33 @@ +#ifndef SSU_HELP_H +#define SSU_HELP_H + +#define OPENSSL_API_COMPAT 0x10100000L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMD_ADD 0b0000000001 +#define CMD_REMOVE 0b0000000010 +#define CMD_STATUS 0b0000000100 +#define CMD_COMMIT 0b0000001000 +#define CMD_REVERT 0b0000010000 +#define CMD_LOG 0b0000100000 +#define CMD_HELP 0b0010000000 +#define CMD_EXIT 0b0100000000 +#define NOT_CMD 0b0000000000 + +void help(); +void help_process(int argc, char *arg); + +#endif \ No newline at end of file diff --git a/p2/ssu_init.c b/p2/ssu_init.c new file mode 100644 index 0000000..47ec7b9 --- /dev/null +++ b/p2/ssu_init.c @@ -0,0 +1,689 @@ +#include "ssu_header.h" + +int backup_list_remove(dirNode *dirList, char *command, char *path, char *backup_path) +{ + if (dirList == NULL) + { + return 0; + } + + // 경로에서 다음 '/' 위치를 찾아 하위 디렉토리 또는 파일 이름을 분리 + char *slash_pos = strchr(path, '/'); + if (slash_pos != NULL) + { + // '/'를 기준으로 디렉토리 이름을 분리 + char dir_name[256]; + int dir_name_length = slash_pos - path; + strncpy(dir_name, path, dir_name_length); + dir_name[dir_name_length] = '\0'; + + // 해당 디렉토리 노드 찾기 + dirNode *subdir = dirNode_get(dirList->subdir_head, dir_name); + if (subdir != NULL) + { + // 재귀 호출로 하위 경로 처리 + if (backup_list_remove(subdir, command, slash_pos + 1, backup_path)) + { + // 하위 항목이 성공적으로 제거되면 상위 디렉토리의 카운터 감소 + if (subdir->file_cnt == 0 && subdir->subdir_cnt == 0 && subdir->backup_cnt == 0) + { + dirNode_remove(subdir); + dirList->subdir_cnt--; + return 1; + } + } + } + } + else + { + // 파일 노드 찾기 + fileNode *file = fileNode_get(dirList->file_head, path); + if (file != NULL) + { + // 파일의 모든 백업 노드 제거 + while (file->backup_head->next_backup != NULL) + { + backupNode_remove(file->backup_head->next_backup); + } + if (file->backup_cnt == 0) + { + fileNode_remove(file); + dirList->file_cnt--; + return 1; + } + } + } + return 0; +} + +int backup_list_insert(dirNode *dirList, char *command, char *path, + char *backup_path, char *commit_message) +{ + + char *ptr; + + if (ptr = strchr(path, '/')) + { + char *dir_name = substr(path, 0, strlen(path) - strlen(ptr)); + dirNode *curr_dir = + dirNode_insert(dirList->subdir_head, dir_name, dirList->dir_path); + backup_list_insert(curr_dir, command, ptr + 1, backup_path, commit_message); + curr_dir->backup_cnt++; + } + else + { + char *file_name = path; + fileNode *curr_file = + fileNode_insert(dirList->file_head, file_name, dirList->dir_path); + backupNode_insert(curr_file->backup_head, command, file_name, + dirList->dir_path, backup_path, commit_message); + } + + return 0; +} + +void print_depth(int depth, int is_last_bit) +{ + for (int i = 1; i <= depth; i++) + { + if (i == depth) + { + if ((1 << depth) & is_last_bit) + { + printf("┗ "); + } + else + { + printf("┣ "); + } + break; + } + if ((1 << i) & is_last_bit) + { + printf(" "); + } + else + { + printf("┃ "); + } + } +} + +void print_list(dirNode *dirList, int depth, int last_bit) +{ + dirNode *curr_dir = dirList->subdir_head->next_dir; + fileNode *curr_file = dirList->file_head->next_file; + + while (curr_dir != NULL && curr_file != NULL) + { + if (strcmp(curr_dir->dir_name, curr_file->file_name) < 0) + { + print_depth(depth, last_bit); + printf("%s/ %d %d %d\n", curr_dir->dir_name, curr_dir->file_cnt, + curr_dir->subdir_cnt, curr_dir->backup_cnt); + print_list(curr_dir, depth + 1, last_bit); + curr_dir = curr_dir->next_dir; + } + else + { + print_depth(depth, last_bit); + printf("%s %d\n", curr_file->file_name, curr_file->backup_cnt); + + backupNode *curr_backup = curr_file->backup_head->next_backup; + while (curr_backup != NULL) + { + print_depth(depth + 1, last_bit); + printf("%s\n", curr_backup->command); + curr_backup = curr_backup->next_backup; + } + curr_file = curr_file->next_file; + } + } + + while (curr_dir != NULL) + { + last_bit |= (curr_dir->next_dir == NULL) ? (1 << depth) : 0; + + print_depth(depth, last_bit); + printf("%s/ %d %d %d\n", curr_dir->dir_name, curr_dir->file_cnt, + curr_dir->subdir_cnt, curr_dir->backup_cnt); + print_list(curr_dir, depth + 1, last_bit); + curr_dir = curr_dir->next_dir; + } + + while (curr_file != NULL) + { + last_bit |= (curr_file->next_file == NULL) ? (1 << depth) : 0; + + print_depth(depth, last_bit); + printf("%s %d\n", curr_file->file_name, curr_file->backup_cnt); + + backupNode *curr_backup = curr_file->backup_head->next_backup; + while (curr_backup != NULL) + { + print_depth(depth + 1, (last_bit | ((curr_backup->next_backup == NULL) + ? (1 << depth + 1) + : 0))); + printf("%s\n", curr_backup->command); + curr_backup = curr_backup->next_backup; + } + curr_file = curr_file->next_file; + } +} + +void remove_quotes_in_place(char *str) +{ + int len = strlen(str); + int writeIndex = 0; + + for (int i = 0; i < len; i++) + { + // 큰따옴표가 아닌 문자만 복사 + if (str[i] != '\"') + { + str[writeIndex++] = str[i]; + } + } + str[writeIndex] = '\0'; +} + +int init_commit_list(int commit_fd) +{ + int len; + char buf[BUF_MAX]; + + char *command, *origin_path, *backup_path; + commit_dir_list = (dirNode *)malloc(sizeof(dirNode)); + commit_logs_list = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(commit_dir_list); + dirNode_init(commit_logs_list); + + while (len = read(commit_fd, buf, BUF_MAX)) + { + char *ptr = strchr(buf, '\n'); + ptr[0] = '\0'; + + lseek(commit_fd, -(len - strlen(buf)) + 1, SEEK_CUR); + + // commit 메시지 시작점 찾기 + char commitMessage[STR_MAX]; + memset(commitMessage, 0, STR_MAX); + char *commitStart = strstr(buf, COMMIT_PREFIX); + + commitStart += strlen(COMMIT_PREFIX); + char *commitEnd = strstr(commitStart, " - "); + if (commitEnd) + { + char *temp = substr(buf, commitStart - buf, commitEnd - buf); + strncpy(commitMessage, temp, STR_MAX - 1); + commitMessage[STR_MAX - 1] = '\0'; + free(temp); + } + + char fileName[PATH_MAX]; + memset(fileName, 0, sizeof(fileName)); + char *fileStart; + if ((fileStart = strstr(commitEnd, NEWFILE_SEP)) != NULL) + { + int start = fileStart - commitEnd + strlen(NEWFILE_SEP); // add sep 이후 인덱스 + int end = strlen(commitEnd) - 1; + char *path = substr(commitEnd, start, end); + remove_quotes_in_place(path); + char backupTmp[PATH_MAX] = ""; + strcpy(backupTmp, path); + char *backPtr = strstr(backupTmp, exePATH); + int backStart = backPtr - backupTmp + strlen(exePATH); + int backEnd = strlen(backupTmp); + char *backupPrefix = substr(backupTmp, backStart, backEnd); + char backup_path[PATH_MAX] = ""; + strcat(backup_path, repoPATH); + strcat(backup_path, "/"); + remove_quotes_in_place(commitMessage); + strcat(backup_path, commitMessage); + strcat(backup_path, backupPrefix); + + backup_list_insert(commit_dir_list, "new file", path, backup_path, commitMessage); + backup_list_insert(commit_logs_list, "new file", path, backup_path, commitMessage); + } + else if ((fileStart = strstr(commitEnd, MODIFIED_SEP))) + { + int start = fileStart - commitEnd + strlen(MODIFIED_SEP); // add sep 이후 인덱스 + int end = strlen(commitEnd) - 1; + char *path = substr(commitEnd, start, end); + remove_quotes_in_place(path); + char backupTmp[PATH_MAX] = ""; + strcpy(backupTmp, path); + char *backPtr = strstr(backupTmp, exePATH); + int backStart = backPtr - backupTmp + strlen(exePATH); + int backEnd = strlen(backupTmp); + char *backupPrefix = substr(backupTmp, backStart, backEnd); + char backup_path[PATH_MAX] = ""; + strcat(backup_path, repoPATH); + strcat(backup_path, "/"); + remove_quotes_in_place(commitMessage); + strcat(backup_path, commitMessage); + strcat(backup_path, backupPrefix); + + // backup_list_insert(commit_dir_list, "modified", path, backup_path, commitMessage); + + backup_list_insert(commit_logs_list, "modified", path, backup_path, commitMessage); + } + else if ((fileStart = strstr(commitEnd, REMOVED_SEP))) + { + int start = fileStart - commitEnd + strlen(REMOVED_SEP); // add sep 이후 인덱스 + int end = strlen(commitEnd) - 1; + char *path = substr(commitEnd, start, end); + remove_quotes_in_place(path); + char backupTmp[PATH_MAX] = ""; + strcpy(backupTmp, path); + char *backPtr = strstr(backupTmp, exePATH); + int backStart = backPtr - backupTmp + strlen(exePATH); + int backEnd = strlen(backupTmp); + char *backupPrefix = substr(backupTmp, backStart, backEnd); + char backup_path[PATH_MAX] = ""; + strcat(backup_path, repoPATH); + strcat(backup_path, "/"); + remove_quotes_in_place(commitMessage); + strcat(backup_path, commitMessage); + strcat(backup_path, backupPrefix); + + backup_list_remove(commit_dir_list, "delete", path, backup_path); + backup_list_insert(commit_logs_list, "removed", path, backup_path, commitMessage); + } + } + return 0; +} + +void bfs_backup(char *path, char *cmd) +{ + + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + // bfs위한 큐 + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + + dirNode_append(dirList, path, ""); + + curr_dir = dirList->next_dir; + + while (curr_dir != NULL) + { + // @todo -> 여기서 안됨! + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", curr_dir->dir_name); + return; + } + + if (S_ISREG(statbuf.st_mode)) + { + if (!strcmp(cmd, "add")) + { + backup_list_insert(backup_dir_list, "add", curr_dir->dir_name, "", ""); + backup_list_insert(staging_dir_list, "add", curr_dir->dir_name, "", ""); + backup_list_remove(staging_remove_dir_list, "add", curr_dir->dir_name, ""); + } + else if (!strcmp(cmd, "remove")) + { + backup_list_insert(backup_dir_list, "remove", curr_dir->dir_name, "", ""); + backup_list_insert(staging_remove_dir_list, "remove", curr_dir->dir_name, "", ""); + + backup_list_remove(staging_dir_list, "remove", curr_dir->dir_name, ""); + } + } + else + { + + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", curr_dir->dir_name); + return; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX]; + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmp_path); + return; + } + + if (S_ISREG(statbuf.st_mode)) + { + + if (!strcmp(cmd, "add")) + { + backup_list_insert(backup_dir_list, "add", tmp_path, "", ""); + backup_list_insert(staging_dir_list, "add", tmp_path, "", ""); + backup_list_remove(staging_remove_dir_list, "add", tmp_path, ""); + } + else if (!strcmp(cmd, "remove")) + { + + backup_list_insert(backup_dir_list, "remove", tmp_path, "", ""); + backup_list_insert(staging_remove_dir_list, "remove", tmp_path, "", ""); + + backup_list_remove(staging_dir_list, "remove", tmp_path, ""); + } + } + else if (S_ISDIR(statbuf.st_mode)) + + { + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } +} + +int init_backup_list(int log_fd) +{ + int len; + char buf[BUF_MAX]; + + char *command, *origin_path, *backup_path; + backup_dir_list = (dirNode *)malloc(sizeof(dirNode)); + staging_dir_list = (dirNode *)malloc(sizeof(dirNode)); + staging_remove_dir_list = (dirNode *)malloc(sizeof(dirNode)); + + dirNode_init(backup_dir_list); + dirNode_init(staging_dir_list); + dirNode_init(staging_remove_dir_list); + + while (len = read(log_fd, buf, BUF_MAX)) + { + char *ptr = strchr(buf, '\n'); + ptr[0] = '\0'; + + lseek(log_fd, -(len - strlen(buf)) + 1, SEEK_CUR); + struct stat statbuf; + + // add시 + if ((ptr = strstr(buf, ADD_SEP)) != NULL) + { + int start = ptr - buf + strlen(ADD_SEP); // add sep 이후 인덱스 + int end = strlen(buf); // 종료인덱스 + origin_path = substr(buf, start, end); + remove_quotes_in_place(origin_path); + backup_path = ""; + if (lstat(origin_path, &statbuf) < 0) + { + fprintf(stderr, "lstat error %s\n", origin_path); + exit(1); + } + if (S_ISDIR(statbuf.st_mode)) + { + backup_list_insert(backup_dir_list, "add", origin_path, backup_path, ""); + backup_list_insert(staging_dir_list, "add", origin_path, backup_path, ""); + backup_list_remove(staging_remove_dir_list, "add", origin_path, + backup_path); + + bfs_backup(origin_path, "add"); + } + else if (S_ISREG(statbuf.st_mode)) + { + backup_list_insert(backup_dir_list, "add", origin_path, backup_path, ""); + backup_list_insert(staging_dir_list, "add", origin_path, backup_path, ""); + backup_list_remove(staging_remove_dir_list, "add", origin_path, + backup_path); + } + } + else if ((ptr = strstr(buf, REMOVE_SEP)) != NULL) + { + int start = ptr - buf + strlen(REMOVE_SEP); // add sep 이후 인덱스 + int end = strlen(buf); // 종료인덱스 + origin_path = substr(buf, start, end); + remove_quotes_in_place(origin_path); + backup_path = ""; + // origin_path에서 가장 상위 실행 디렉토리도 삭제 + char originPathBuf[PATH_MAX] = ""; + strcpy(originPathBuf, origin_path); + ptr = strstr(originPathBuf, exePATH); + if (ptr != NULL) + { + + int exePathLength = strlen(exePATH); + + char *resultPath = ptr + exePathLength; + char topDir[2]; + char *firstSlash = strchr(resultPath, '/'); + + if (firstSlash != NULL && firstSlash[1] != '\0') + { + // 첫 번째 슬래시 다음 문자 추출 (상위 디렉토리만) + topDir[0] = firstSlash[1]; + topDir[1] = '\0'; + } + char removePath[PATH_MAX] = ""; + strcat(removePath, exePATH); + strcat(removePath, "/"); + strcat(removePath, topDir); + + if (lstat(origin_path, &statbuf) < 0) + { + fprintf(stderr, "lstat error %s\n", origin_path); + exit(1); + } + if (S_ISDIR(statbuf.st_mode)) + { + backup_list_insert(backup_dir_list, "remove", origin_path, backup_path, ""); + backup_list_insert(staging_remove_dir_list, "remove", origin_path, + backup_path, ""); + + backup_list_remove(staging_dir_list, "remove", origin_path, backup_path); + bfs_backup(origin_path, "remove"); + } + else if (S_ISREG(statbuf.st_mode)) + { + + backup_list_insert(backup_dir_list, "remove", origin_path, backup_path, ""); + backup_list_insert(staging_remove_dir_list, "remove", origin_path, + backup_path, ""); + + backup_list_remove(staging_dir_list, "remove", origin_path, backup_path); + } + } + } + } + + return 0; +} + +int get_version_list(dirNode *version_dir_head) +{ + int cnt; + int ret = 0; + struct dirent **namelist; + struct stat statbuf; + + if ((cnt = scandir(version_dir_head->dir_path, &namelist, NULL, alphasort))) + { + printf("cnt:%d\n", cnt); + fprintf(stderr, "ERROR: scandir error for %s\n", + version_dir_head->dir_path); + return -1; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX]; + strcpy(tmp_path, version_dir_head->dir_path); + strcat(tmp_path, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmp_path); + return -1; + } + + if (S_ISDIR(statbuf.st_mode)) + { + dirNode *version_subdir_node = + dirNode_insert(version_dir_head->subdir_head, namelist[i]->d_name, + version_dir_head->dir_path); + ret |= get_version_list(version_subdir_node); + } + } + return ret; +} + +int link_backup_node(dirNode *dirList) +{ + dirNode *curr_dir = dirList->subdir_head->next_dir; + fileNode *curr_file = dirList->file_head->next_file; + backupNode *curr_backup; + dirNode *curr_version_dir; + + while (curr_dir != NULL) + { + link_backup_node(curr_dir); + curr_dir = curr_dir->next_dir; + } + + while (curr_file != NULL) + { + curr_backup = curr_file->backup_head->next_backup; + while (curr_backup != NULL) + { + char *tmp_path = substr(curr_backup->backup_path, strlen(stagingLogPATH), + strlen(curr_backup->backup_path)); + char *ptr = strrchr(tmp_path, '/'); + ptr[0] = '\0'; + + // curr_version_dir = get_dirNode_from_path(version_dir_list, tmp_path); + curr_version_dir = NULL; + curr_backup->root_version_dir = curr_version_dir; + // add_cnt_root_dir(curr_backup->root_version_dir, 1); + + curr_backup = curr_backup->next_backup; + } + curr_file = curr_file->next_file; + } +} + +int init_version_list() +{ + version_dir_list = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(version_dir_list); + strcpy(version_dir_list->dir_path, stagingLogPATH); + get_version_list(version_dir_list); + + link_backup_node(backup_dir_list); + + // print_list(version_dir_list, 0, 0); +} +void print_backup_origin_paths(dirNode *dirNode) +{ + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + printf("%s\n", bNode->origin_path); + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + print_backup_origin_paths(dirNode); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} + +int init(char *path) +{ + int staging_fd; + int commit_fd; + // 사용자 정보 가지는 구조체 + struct passwd *pw; + char *loginName; + uid_t uid; + // 현재 사용자의 로그인 이름을 얻기 + loginName = getlogin(); + if (loginName == NULL) + { + // getlogin이 실패한 경우, 사용자 ID를 사용 + uid = getuid(); + pw = getpwuid(uid); + } + else + { + // 특정 사용자 이름(loginName)에 대한 사용자 계정 정보를 검색하고, 그 결과를 + // pw 변수에 저장 + pw = getpwnam(loginName); + } + + if (pw == NULL) + { + printf("ERROR: Failed to get user information\n"); + return 1; + } + + // 실행경로 /home/ubuntu/lsp/p2 + getcwd(exePATH, PATH_MAX); + // 현재 /home/ubuntu + sprintf(pwdPATH, "%s", pw->pw_dir); + + // .repo 경로 받아오기 + sprintf(repoPATH, "%s/.repo", pw->pw_dir); + // staging log경로 + sprintf(stagingLogPATH, "%s/.repo/.staging.log", pw->pw_dir); + // commit log 경로 + sprintf(commitLogPATH, "%s/.repo/.commit.log", pw->pw_dir); + + if (access(repoPATH, F_OK)) + { + if (mkdir(repoPATH, 0755) == -1) + { + return -1; + }; + } + + if ((staging_fd = open(stagingLogPATH, O_RDWR | O_CREAT, 0777)) == -1) + { + fprintf(stderr, "ERROR: open error for '%s'\n", stagingLogPATH); + return -1; + } + if ((commit_fd = open(commitLogPATH, O_RDWR | O_CREAT, 0777)) == -1) + { + fprintf(stderr, "ERROR: open error for '%s'\n", commitLogPATH); + return -1; + } + + init_backup_list(staging_fd); + + init_commit_list(commit_fd); + + close(staging_fd); + close(commit_fd); + + return 0; +} \ No newline at end of file diff --git a/p2/ssu_log.c b/p2/ssu_log.c new file mode 100644 index 0000000..89a928e --- /dev/null +++ b/p2/ssu_log.c @@ -0,0 +1,103 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; + +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; + +void lookup_one_commit(dirNode *dirNode, char *commit) +{ + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(bNode->commit_message, commit)) + { + printf(" - %s : \"%s\"\n", bNode->command, bNode->origin_path); + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + lookup_one_commit(dirNode, commit); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} + +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + repoPATH = (char *)malloc(PATH_MAX); + stagingLogPATH = (char *)malloc(PATH_MAX); + commitLogPATH = (char *)malloc(PATH_MAX); + + int cnt; + struct dirent **namelist; + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + return -1; + } + if (argv[1] != NULL) + { + if ((cnt = scandir(repoPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error %s\n", repoPATH); + } + for (int i = 0; i < cnt; i++) + { + if (!strcmp(argv[1], namelist[i]->d_name)) + { + printf("commit : \"%s\"\n", argv[1]); + lookup_one_commit(commit_logs_list, argv[1]); + } + } + } + else + { + // @todo 커밋 이름만 뽑아오기 + + if ((cnt = scandir(repoPATH, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error %s\n", repoPATH); + } + for (int i = 0; i < cnt; i++) + { + if (!strcmp(".", namelist[i]->d_name) || !strcmp("..", namelist[i]->d_name) || !strcmp(".commit.log", namelist[i]->d_name) || !strcmp(".staging.log", namelist[i]->d_name)) + { + continue; + } + printf("commit: \"%s\"\n", namelist[i]->d_name); + lookup_one_commit(commit_logs_list, namelist[i]->d_name); + } + } + exit(0); +} \ No newline at end of file diff --git a/p2/ssu_remove.c b/p2/ssu_remove.c new file mode 100644 index 0000000..0291a28 --- /dev/null +++ b/p2/ssu_remove.c @@ -0,0 +1,113 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; + +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; + +int check_already_remove(dirNode *dirNode, char *absolutePath, + char *relativePath) +{ + if (dirNode == NULL) + return 0; + + fileNode *fNode = dirNode->file_head->next_file; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head->next_backup; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(absolutePath, bNode->origin_path)) + { + printf("\"%s\" already removed from staging area \n", relativePath); + return 1; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + // 재귀호출하는데 유망한지 한번 더 확인해야함!! + if (check_already_remove(dirNode, absolutePath, relativePath) == 1) + { + return 1; + } + dirNode = dirNode->next_dir; + } +} + +int ssu_remove(char *path) +{ + int fd; + int len; + char buf[BUF_MAX + 200]; + char pathBuf[BUF_MAX]; + char absolutePathBuf[BUF_MAX]; + char relativePathBuf[BUF_MAX]; + char absolutePath[BUF_MAX]; + // stagingLogPath열기 + if ((fd = open(stagingLogPATH, O_WRONLY | O_APPEND)) == -1) + { + fprintf(stderr, "ERROR: open error for '%s'\n", stagingLogPATH); + return 1; + } + strcpy(pathBuf, path); + // 절대경로 + realpath(pathBuf, absolutePathBuf); + strcpy(absolutePath, absolutePathBuf); + // 상대경로 + char *relativePath = getRelativePath(absolutePathBuf); + + if (check_already_remove(staging_remove_dir_list, absolutePath, + relativePath) == 1) + { + return 1; + } + // log와 표준출력쓰기 + sprintf(buf, "remove \"%s\"\n", absolutePath); + if (write(fd, buf, strlen(buf)) == -1) + { + fprintf(stderr, "ERROR: write error for %s\n", stagingLogPATH); + return 1; + } + printf("remove \"%s\" \n", relativePath); + + return 0; +} + +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + repoPATH = (char *)malloc(PATH_MAX); + stagingLogPATH = (char *)malloc(PATH_MAX); + commitLogPATH = (char *)malloc(PATH_MAX); + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + return -1; + } + + ssu_remove(argv[1]); + exit(0); +} \ No newline at end of file diff --git a/p2/ssu_repo.c b/p2/ssu_repo.c new file mode 100644 index 0000000..04f7231 --- /dev/null +++ b/p2/ssu_repo.c @@ -0,0 +1,562 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; + +dirNode *get_dirNode_from_path(dirNode *dirList, char *path) +{ + char *ptr; + + if (dirList == NULL) + return NULL; + + if (ptr = strchr(path, '/')) + { + char *dir_name = substr(path, 0, strlen(path) - strlen(ptr)); + dirNode *curr_dir = dirNode_get(dirList->subdir_head, dir_name); + return get_dirNode_from_path(curr_dir, ptr + 1); + } + else + { + char *dir_name = path; + dirNode *curr_dir = dirNode_get(dirList->subdir_head, dir_name); + return curr_dir; + } +} + +fileNode *get_fileNode_from_path(dirNode *dirList, char *path) +{ + char *ptr; + dirNode *curr_dir; + fileNode *curr_file; + + ptr = strrchr(path, '/'); + + if ((curr_dir = get_dirNode_from_path( + dirList, substr(path, 0, strlen(path) - strlen(ptr)))) == NULL) + return NULL; + curr_file = fileNode_get(curr_dir->file_head, ptr + 1); + return curr_file; +} + +backupNode *get_backupNode_from_path(dirNode *dirList, char *path, + char *backup_time) +{ + fileNode *curr_file; + backupNode *curr_backup; + + if ((curr_file = get_fileNode_from_path(dirList, path)) == NULL) + return NULL; + curr_backup = backupNode_get(curr_file->backup_head, backup_time); + return curr_backup; +} + +int add_cnt_root_dir(dirNode *dir_node, int cnt) +{ + if (dir_node == NULL) + return 0; + dir_node->backup_cnt += cnt; + return add_cnt_root_dir(dir_node->root_dir, cnt); +} + +void removeExec(char *path) +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + if ((execl("./ssu_remove", "./ssu_remove", path, (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} + +void addExec(char *path) +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + if ((execl("./ssu_add", "./ssu_add", path, (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} +void commitExec(char *arg) +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + if ((execl("./ssu_commit", "./ssu_commit", arg, (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} + +void statusExec() +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + + if ((execl("./ssu_status", "./ssu_status", (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} + +void revertExec(char *arg) +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + + if ((execl("./ssu_revert", "./ssu_revert", arg, (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} + +void logExec(char *arg) +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + + if ((execl("./ssu_log", "./ssu_log", arg, (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} + +void helpExec(char *arg) +{ + pid_t pid; + + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + exit(1); + } + else if (pid == 0) + { + if ((execl("./ssu_help", "./ssu_help", arg, (char *)NULL) == -1)) + { + fprintf(stderr, "Error: execl error\n"); + exit(1); + } + } + else + { + pid = wait(NULL); + } +} + +// 예외처리 담당 +int parameter_processing(int argcnt, char **arglist, int command, + command_parameter *parameter) +{ + struct stat buf; + switch (command) + { + case CMD_ADD: + { + // 경로를 입력하지 않은 경우 + if (argcnt < 2) + { + fprintf(stderr, + "ERROR: is not include \nUsage : add : add " + "files/directories to staging area \n"); + return -1; + } + char *resolved = realpath(arglist[1], parameter->filename); + // 경로가 올바르지 않은경우 + if (resolved == NULL) + { + fprintf(stderr, "ERROR: %s is wrong filepath\n", parameter->filename); + return -1; + } + // 경로가 길이 제한을 넘는 경우 + if (strlen(resolved) > PATH_MAX) + { + fprintf(stderr, "ERROR : %s exceeds 4096 bytes \n", + parameter->filename); + return -1; + } + + // 파일이나 디렉토리가 존재하지 않는 경우 + if (lstat(parameter->filename, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", parameter->filename); + return -1; + } + // 일반 파일이거나 디렉토리가 아닌경우 + if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not regular file\n", parameter->filename); + return -1; + } + // 해당경로에 대한 접근 권한이 없는 경우 + if (access(parameter->filename, R_OK | W_OK) < 0) + { + fprintf(stderr, "ERROR: %s permission denied \n", parameter->filename); + return -1; + } + // 경로가 홈 디렉토리를 벗어나는 경우, 레포 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, exePATH, strlen(exePATH)) || + !strncmp(parameter->filename, repoPATH, strlen(repoPATH))) + { + fprintf(stderr, "ERROR: filename %s can't be add \n", + parameter->filename); + + return -1; + } + break; + } + case CMD_REMOVE: + { + // 경로를 입력하지 않은 경우 + if (argcnt < 2) + { + fprintf(stderr, + "ERROR: is not include \nUsage : remove : record " + "path to staging area, path will not tracking modification \n"); + return -1; + } + char *resolved = realpath(arglist[1], parameter->filename); + // 경로가 올바르지 않은경우 + if (resolved == NULL) + { + fprintf(stderr, "ERROR: %s is wrong filepath\n", parameter->filename); + return -1; + } + // 경로가 길이 제한을 넘는 경우 + if (strlen(resolved) > PATH_MAX) + { + fprintf(stderr, "ERROR : %s exceeds 4096 bytes \n", + parameter->filename); + return -1; + } + + // 파일이나 디렉토리가 존재하지 않는 경우 + if (lstat(parameter->filename, &buf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", parameter->filename); + return -1; + } + // 일반 파일이거나 디렉토리가 아닌경우 + if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) + { + fprintf(stderr, "ERROR: %s is not regular file\n", parameter->filename); + return -1; + } + // 해당경로에 대한 접근 권한이 없는 경우 + if (access(parameter->filename, R_OK | W_OK) < 0) + { + fprintf(stderr, "ERROR: %s permission denied \n", parameter->filename); + return -1; + } + // 경로가 홈 디렉토리를 벗어나는 경우, 레포 디렉토리에 백업되려고 할때 + if (strncmp(parameter->filename, exePATH, strlen(exePATH)) || + !strncmp(parameter->filename, repoPATH, strlen(repoPATH))) + { + fprintf(stderr, "ERROR: filename %s can't be add \n", + parameter->filename); + + return -1; + } + + break; + } + case CMD_COMMIT: + { + // 입력 안한 경우 + if (argcnt < 2) + { + fprintf( + stderr, + "Usage : commit : backup staging area with commit name\n"); + return -1; + } + // 이 길이 제한을 넘는 경우 + if (strlen(arglist[1]) > STR_MAX) + { + fprintf(stderr, "ERROR : %s exceeds 255 bytes \n", parameter->filename); + return -1; + } + break; + } + case CMD_REVERT: + { + // 입력 안한 경우 + if (argcnt < 2) + { + fprintf(stderr, + "ERROR: is not include\nUsage : revert : recover commit version with commit " + "name\n"); + return -1; + } + // 이 길이 제한을 넘는 경우 + if (strlen(arglist[1]) > STR_MAX) + { + fprintf(stderr, "ERROR : %s exceeds 255 bytes \n", parameter->filename); + return -1; + } + break; + } + case CMD_LOG: + { + // 이 길이 제한을 넘는 경우 + if (strlen(arglist[1]) > STR_MAX) + { + fprintf(stderr, "ERROR : %s exceeds 255 bytes \n", parameter->filename); + return -1; + } + break; + } + } +} + +void parameterInit(command_parameter *parameter) +{ + parameter->command = (char *)malloc(sizeof(char) * PATH_MAX); + parameter->filename = (char *)malloc(sizeof(char) * PATH_MAX); + parameter->tmpname = (char *)malloc(sizeof(char) * PATH_MAX); + // 명령어의 옵션 나타내는 플래그 + parameter->commandopt = 0; +} +int prompt() +{ + char input[STR_MAX]; + int argcnt = 0; + char **arglist = NULL; + int command; + command_parameter parameter = {(char *)0, (char *)0, (char *)0, 0}; + + while (1) + { + arglist = NULL; + printf("20221678> "); + if (fgets(input, sizeof(input), stdin) == NULL) + break; + + if (input[strlen(input) - 1] == '\n') + input[strlen(input) - 1] = '\0'; // 개행 문자 제거 + + arglist = get_substring(input, &argcnt, " \t"); + if (argcnt == 0) + continue; + + if (!strcmp(arglist[0], "add")) + { + command = CMD_ADD; + } + else if (!strcmp(arglist[0], "remove")) + { + command = CMD_REMOVE; + } + else if (!strcmp(arglist[0], "status")) + { + command = CMD_STATUS; + } + else if (!strcmp(arglist[0], "revert")) + { + command = CMD_REVERT; + } + else if (!strcmp(arglist[0], "commit")) + { + command = CMD_COMMIT; + } + else if (!strcmp(arglist[0], "log")) + { + command = CMD_LOG; + } + + else if (!strcmp(arglist[0], "help")) + { + command = CMD_HELP; + } + else if (!strcmp(arglist[0], "exit")) + { + command = CMD_EXIT; + fprintf(stdout, "Program exit(0)\n"); + exit(0); + } + else + { + command = NOT_CMD; + } + + if (command & (CMD_ADD | CMD_REMOVE | CMD_REVERT | CMD_COMMIT | CMD_LOG | CMD_STATUS)) + { + parameterInit(¶meter); + parameter.command = arglist[0]; + + if (parameter_processing(argcnt, arglist, command, ¶meter) == -1) + { + continue; + } + + if (command & CMD_ADD) + { + addExec(arglist[1]); + } + else if (command & CMD_REMOVE) + { + removeExec(arglist[1]); + } + else if (command & CMD_COMMIT) + { + commitExec(arglist[1]); + } + else if (command & CMD_REVERT) + { + revertExec(arglist[1]); + } + else if (command & CMD_STATUS) + { + statusExec(); + } + else if (command & CMD_LOG) + { + if (argcnt == 2) + { + logExec(arglist[1]); + } + else if (argcnt == 1) + { + logExec(NULL); + } + } + } + + else if (command & CMD_HELP || command == NOT_CMD) + { + if (argcnt == 1) + { + helpExec(NULL); + } + else if (argcnt == 2) + { + helpExec(arglist[1]); + } + } + } +} + +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + repoPATH = (char *)malloc(PATH_MAX); + stagingLogPATH = (char *)malloc(PATH_MAX); + commitLogPATH = (char *)malloc(PATH_MAX); + + char argvStr[PATH_MAX] = ""; + int argcnt = 0; + char **arglist = NULL; + command_parameter parameter = {(char *)0, (char *)0, (char *)0, 0}; + strcpy(exeNAME, argv[0]); + + for (int i = 0; i < argc; i++) + { + strcat(argvStr, argv[i]); + strcat(argvStr, " "); + } + + arglist = get_substring(argvStr, &argcnt, " "); + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + return -1; + } + + prompt(); + + return 0; +} \ No newline at end of file diff --git a/p2/ssu_revert.c b/p2/ssu_revert.c new file mode 100644 index 0000000..08b1600 --- /dev/null +++ b/p2/ssu_revert.c @@ -0,0 +1,23 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; + +int main(int argc, char *argv[]) +{ + printf(" %s revert옴\n", argv[0]); + exit(0); +} \ No newline at end of file diff --git a/p2/ssu_status.c b/p2/ssu_status.c new file mode 100644 index 0000000..e2555d3 --- /dev/null +++ b/p2/ssu_status.c @@ -0,0 +1,953 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *version_dir_list = NULL; +dirNode *staging_dir_list = NULL; +dirNode *commit_dir_list = NULL; +dirNode *staging_remove_dir_list = NULL; +dirNode *current_commit_dir_list = NULL; +dirNode *untrack_dir_list = NULL; +dirNode *commit_logs_list = NULL; + +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *repoPATH = NULL; +char *stagingLogPATH = NULL; +char *commitLogPATH = NULL; +int hash = 0; +int dircnt = 0; +int change = 0; +int insert = 0; +int delete = 0; + +void print_current_commit(dirNode *dirNode) +{ + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + char buf[BUF_MAX + 20]; + memset(buf, 0, sizeof(buf)); + char *relativePath = getRelativePath(bNode->origin_path); + + printf(" %s : %s\n", bNode->command, relativePath); + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + print_current_commit(dirNode); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} + +void lookup_current_commit(dirNode *dirNode) +{ + change = 0; + insert = 0; + delete = 0; + if (current_commit_dir_list == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(bNode->command, "new file")) + { + insert++; + } + else if (!strcmp(bNode->command, "modified")) + { + change++; + } + else if (!strcmp(bNode->command, "removed")) + { + delete ++; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + lookup_current_commit(dirNode); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} + +int check_new_file(dirNode *dirNode, char *target_path) +{ + // 더 이상 탐색할 요소가 없으므로, 새 파일이라고 가정 + if (dirNode == NULL) + return 1; + + // 파일 노드 순회 + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(target_path, bNode->origin_path)) + { + // 일치하는 경우, 새 파일이 아니므로 0 반환 + return 0; + } + } + + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 노드 순회 + int found = 1; // 초기값을 1로 설정하여, 새 파일이라고 가정 + dirNode = dirNode->subdir_head; + while (dirNode != NULL && found) + { + // 재귀 호출을 통해 하위 디렉토리 탐색 + found = check_new_file(dirNode, target_path); + if (found == 0) + { + // 일치하는 파일을 찾았으므로 0 반환 + return 0; + } + dirNode = dirNode->next_dir; + } + + // 전체 리스트를 순회했으나 target_path와 일치하는 경로를 찾지 못한 경우, 새 파일로 간주하여 1 반환 + return found; +} + +// staging에는 없고 commit에는 있으면 -1리턴 +// remove할거 없으면 1리턴(하나라도 둘다같은게 발견되면 1리턴) +// 커밋 리스트 path ,staging리스트 path +int remove_bfs(char *target_path, char *staging_path) +{ + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + dirNode_append(dirList, staging_path, ""); + int isAvailable = 0; // true + + curr_dir = dirList->next_dir; + int found = -1; + + while (curr_dir != NULL) + { + // @todo 여기 scandir에러 뜨는거 + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR1: lstat error for %s\n", curr_dir->dir_name); + return -1; + } + + if (S_ISDIR(statbuf.st_mode)) + { + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR!!: scandir error for %s\n", curr_dir->dir_name); + return -1; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX] = ""; + + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR2: lstat error for %s\n", tmp_path); + return -1; + } + + if (S_ISREG(statbuf.st_mode) && !strcmp(target_path, tmp_path)) + { + + return 1; + } + else if (S_ISDIR(statbuf.st_mode)) + { + + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } + return -1; +} +int traverseRemoveBackupNodesReverse(backupNode *backup_head, char *target_path) +{ + if (backup_head == NULL) + return -1; + + backupNode *lastBackup = backup_head; + while (lastBackup && lastBackup->next_backup != NULL) + { + lastBackup = lastBackup->next_backup; + } + + backupNode *backup = lastBackup; + while (backup != NULL) + { + if (backup && strlen(backup->origin_path) > 0) + { + // 커밋 리스트 path ,staging리스트 path + // 백업에만 남아있으면 + if (remove_bfs(backup->origin_path, target_path) < 0) + { + // 전역 currentlist에 추가 + dircnt++; + backup_list_insert(current_commit_dir_list, "removed", backup->origin_path, "", ""); + return 1; + } + else + { + + return -1; + } + backup = backup->prev_backup; + } + } +} +int traverseRemoveFileNodesReverse(fileNode *file_head, char *target_path) +{ + if (file_head == NULL) + return -1; + + fileNode *lastFile = file_head; + while (lastFile && lastFile->next_file != NULL) + { + lastFile = lastFile->next_file; + } + + while (lastFile != NULL) + { + if (lastFile->backup_head) + { + int result = traverseRemoveBackupNodesReverse(lastFile->backup_head, target_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + lastFile = lastFile->prev_file; + } + return -1; +} + +int traverseRemoveDirNodesReverse(dirNode *dir_head, char *target_path) +{ + if (dir_head == NULL) + return -1; + + dirNode *lastDir = dir_head; + while (lastDir && lastDir->next_dir != NULL) + { + lastDir = lastDir->next_dir; + } + + while (lastDir != NULL) + { + if (lastDir->file_head) + { + int result = traverseRemoveFileNodesReverse(lastDir->file_head, target_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + if (lastDir->subdir_head) + { + int result = traverseRemoveDirNodesReverse(lastDir->subdir_head, target_path); + if (result == 1) + return 1; // 하위 디렉토리에서 파일 수정 발견 + } + lastDir = lastDir->prev_dir; + } + return -1; +} +void check_remove_file(dirNode *dirNode, char *staging_path) +{ + // 역순으로 commit list순회 + if (traverseRemoveDirNodesReverse(commit_dir_list, staging_path) == 1) + { + } + else + { + return; + } +} +// 수정된게 있으면1 없으면 -1 +int traverseBackupNodesReverse(backupNode *backup_head, char *target_path) +{ + if (backup_head == NULL) + return -1; + + backupNode *lastBackup = backup_head; + // 마지막 백업 노드로 이동 + while (lastBackup && lastBackup->next_backup != NULL) + { + lastBackup = lastBackup->next_backup; + } + // 역순으로 노드를 검사 + backupNode *backup = lastBackup; + while (backup != NULL) + { + if (backup->origin_path && strlen(backup->origin_path) > 0) + { + + // 해시값이 다르다 -> 수정 됨 + if (!strcmp(backup->origin_path, target_path) && cmpHash(backup->backup_path, target_path)) + { + + return 1; + } + // 해시값이 같다 -> 수정 안됨 + if (!strcmp(backup->origin_path, target_path) && !cmpHash(backup->backup_path, target_path)) + { + + return -1; + } + } + backup = backup->prev_backup; // 이전 백업 노드로 이동 + } + return -1; // 수정된 파일 없음 +} +int traverseFileNodesReverse(fileNode *file_head, char *target_path) +{ + if (file_head == NULL) + return -1; + + fileNode *lastFile = file_head; + while (lastFile && lastFile->next_file != NULL) + { + lastFile = lastFile->next_file; + } + + while (lastFile != NULL) + { + if (lastFile->backup_head) + { + int result = traverseBackupNodesReverse(lastFile->backup_head, target_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + lastFile = lastFile->prev_file; + } + return -1; +} + +int traverseDirNodesReverse(dirNode *dir_head, char *target_path) +{ + if (dir_head == NULL) + return -1; + + dirNode *lastDir = dir_head; + while (lastDir && lastDir->next_dir != NULL) + { + lastDir = lastDir->next_dir; + } + + while (lastDir != NULL) + { + if (lastDir->file_head) + { + int result = traverseFileNodesReverse(lastDir->file_head, target_path); + if (result == 1) + return 1; // 파일 수정 발견 + } + if (lastDir->subdir_head) + { + int result = traverseDirNodesReverse(lastDir->subdir_head, target_path); + if (result == 1) + return 1; // 하위 디렉토리에서 파일 수정 발견 + } + lastDir = lastDir->prev_dir; + } + return -1; +} + +int check_modify_file(char *target_path) +{ + + if (traverseDirNodesReverse(commit_logs_list, target_path) == 1) + { + return 1; + } + else + { + return -1; + } +} +// remove staging list에 있는 것과 같은 경로라면 1 반환 아니면 -1 + +int check_remove_staging_list(dirNode *dirNode, char *target_path) +{ + if (dirNode == NULL) + { + return -1; + } + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(target_path, bNode->origin_path)) + { + return 1; // 정확히 일치하는 경로 발견 + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 순회 + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + if (check_remove_staging_list(dirNode, target_path) == 1) + { + return 1; // 하위에서 일치하는 경로 발견 + } + dirNode = dirNode->next_dir; + } + + return -1; // 일치하는 경로를 찾지 못함 +} + +int check_add_staging_list(dirNode *dirNode, char *target_path) +{ + if (dirNode == NULL) + { + return -1; + } + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(target_path, bNode->origin_path)) + { + return 1; // 정확히 일치하는 경로 발견 + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 순회 + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + if (check_add_staging_list(dirNode, target_path) == 1) + { + return 1; // 하위에서 일치하는 경로 발견 + } + dirNode = dirNode->next_dir; + } + + return -1; // 일치하는 경로를 찾지 못함 +} + +// 스테이징 경로가 현재 작업경로에 존재하지 않으면 -1반환 +// 스테이징 경로가 현재 작업경로에 존재하지 않으면 -1반환 +int check_exist_pwd(char *target_path) +{ + + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + dirNode_append(dirList, exePATH, ""); + int isAvailable = 0; // true + + curr_dir = dirList->next_dir; + int found = -1; + + while (curr_dir != NULL) + { + // @todo 여기 scandir에러 뜨는거 + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", curr_dir->dir_name); + return -1; + } + if (S_ISREG(statbuf.st_mode)) + { + if (S_ISREG(statbuf.st_mode) && !strcmp(target_path, curr_dir->dir_name)) + { + + return 1; + } + } + else if (S_ISDIR(statbuf.st_mode)) + { + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR!!: scandir error for %s\n", curr_dir->dir_name); + return -1; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX] = ""; + + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", tmp_path); + return -1; + } + + // 커밋경로랑 tmp_path가 같다는건 스테이징 경로에 해당 커밋 경로가 있다는거! 즉 안없어진걸 의미 + int len = strlen(target_path) < strlen(tmp_path) ? strlen(target_path) : strlen(tmp_path); + + if (S_ISREG(statbuf.st_mode) && !strncmp(target_path, tmp_path, len)) + { + + return 1; + } + else if (S_ISDIR(statbuf.st_mode)) + { + + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } + return -1; +} +// 이미 커밋된건지 판단 (이미 commit_dir_list에 있는 경로인지 판단) 이미 커밋된거면 1반환 +int is_commited(dirNode *dirNode, char *target_path) +{ + if (dirNode == NULL) + { + return -1; + } + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0) + { + + if (!strcmp(target_path, bNode->origin_path)) + { + return 1; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 순회 + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + if (check_remove_staging_list(dirNode, target_path) == 1) + { + return 1; // 하위에서 일치하는 경로 발견 + } + dirNode = dirNode->next_dir; + } + + return -1; // 일치하는 경로를 찾지 못함 +} +void bfs(char *path) +{ + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + // bfs위한 큐 + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + + dirNode_append(dirList, path, ""); + + curr_dir = dirList->next_dir; + + while (curr_dir != NULL) + { + // @todo -> 여기서 안됨! + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR3: lstat error for %s\n", curr_dir->dir_name); + return; + } + // staging 로그에서 remove된 애인지 판단 + if (check_remove_staging_list(staging_remove_dir_list, curr_dir->dir_name) > 0) + { + return; + } + + if (S_ISREG(statbuf.st_mode)) + { + + // newfile 인경우 + if (check_new_file(commit_dir_list, curr_dir->dir_name) == 1) + { + dircnt++; + backup_list_insert(current_commit_dir_list, "new file", curr_dir->dir_name, "", ""); + return; + } + // modify인경우 + else if (check_modify_file(curr_dir->dir_name) == 1) + { + dircnt++; + + backup_list_insert(current_commit_dir_list, "modified", curr_dir->dir_name, "", ""); + return; + } + } + else + { + + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", curr_dir->dir_name); + return; + } + + for (int i = 0; i < cnt; i++) + { + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + + char tmp_path[PATH_MAX]; + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR4: lstat error for %s\n", tmp_path); + return; + } + + int num = check_remove_staging_list(staging_remove_dir_list, tmp_path); + // staging 로그에서 remove된 애인지 판단 -> remove되어야 하는 애면 가차없이 리턴 + if (check_remove_staging_list(staging_remove_dir_list, tmp_path) > 0) + { + return; + } + + if (S_ISREG(statbuf.st_mode)) + { + + // 파일인경우 커밋 로그에서 판단을 해준다. + // newfile 인경우 + if (check_new_file(commit_dir_list, tmp_path) == 1) + { + dircnt++; + backup_list_insert(current_commit_dir_list, "new file", tmp_path, "", ""); + } + // modify인경우 + else if (check_modify_file(tmp_path) == 1) + { + dircnt++; + backup_list_insert(current_commit_dir_list, "modified", tmp_path, "", ""); + } + } + else if (S_ISDIR(statbuf.st_mode)) + { + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } +} +void check_staging_list(dirNode *dirNode) +{ + if (dirNode == NULL) + { + return; + } + + // 파일 노드 순회 시작 + fileNode *fNode = dirNode->file_head->next_file; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head->next_backup; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + if (check_exist_pwd(bNode->origin_path) > 0) + { + + bfs(bNode->origin_path); + } + + // 백업 파일에는 있는데 현재 디렉토리에는 없는 경우 + + bNode = bNode->next_backup; + } + } + fNode = fNode->next_file; + } + + // 하위 디렉토리 순회 시작 + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + check_staging_list(dirNode); + + dirNode = dirNode->next_dir; + } +} +int untrack_cnt = 0; +void untrack_bfs() +{ + struct dirent **namelist; + struct stat statbuf; + dirNode *dirList; + dirNode *curr_dir; + char sub_backup_path[PATH_MAX]; + int cnt; + int sub_backup_cnt = 0; + int backup_cnt = 0; + // bfs위한 큐 + dirList = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(dirList); + + dirNode_append(dirList, exePATH, ""); + + curr_dir = dirList->next_dir; + + while (curr_dir != NULL) + { + + if (lstat(curr_dir->dir_name, &statbuf) < 0) + { + fprintf(stderr, "ERROR5: lstat error for %s\n", curr_dir->dir_name); + return; + } + // staging 로그에서 remove된 애인지 판단 + if (check_remove_staging_list(staging_remove_dir_list, curr_dir->dir_name) > 0 || check_add_staging_list(staging_dir_list, curr_dir->dir_name) > 0) + { + continue; + } + + if (S_ISREG(statbuf.st_mode)) + { + untrack_cnt++; + backup_list_insert(untrack_dir_list, "new file", curr_dir->dir_name, "", ""); + } + else if (S_ISDIR(statbuf.st_mode)) + { + + if ((cnt = scandir(curr_dir->dir_name, &namelist, NULL, alphasort)) == -1) + { + fprintf(stderr, "ERROR: scandir error for %s\n", curr_dir->dir_name); + return; + } + + for (int i = 0; i < cnt; i++) + { + + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + { + continue; + } + + char tmp_path[PATH_MAX]; + memset(tmp_path, 0, PATH_MAX); + sprintf(tmp_path, "%s/%s", curr_dir->dir_name, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR6: lstat error for %s\n", tmp_path); + return; + } + + // // remove나 add된건지 판단 + if (check_remove_staging_list(staging_remove_dir_list, tmp_path) > 0 || check_add_staging_list(staging_dir_list, tmp_path) > 0) + { + continue; + } + + if (S_ISREG(statbuf.st_mode)) + { + untrack_cnt++; + backup_list_insert(untrack_dir_list, "new file", tmp_path, "", ""); + } + else if (S_ISDIR(statbuf.st_mode)) + { + dirNode_append(dirList, tmp_path, ""); + } + } + } + + curr_dir = curr_dir->next_dir; + } +} + +void print_current_untrack(dirNode *dirNode) +{ + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + char buf[BUF_MAX + 20]; + memset(buf, 0, sizeof(buf)); + char *relativePath = getRelativePath(bNode->origin_path); + + printf(" %s : %s\n", bNode->command, relativePath); + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head->next_dir; + while (dirNode != NULL) + { + print_current_untrack(dirNode); // 재귀 호출 + dirNode = dirNode->next_dir; + } +} +void print_untrack_files() +{ + untrack_bfs(); + printf("Untracked files:\n"); + if (untrack_cnt == 0) + { + return; + } + else + { + print_current_untrack(untrack_dir_list); + } +} +int ssu_status() +{ + int fd; + if ((fd = open(commitLogPATH, O_WRONLY | O_CREAT | O_APPEND)) < 0) + { + fprintf(stderr, "ERROR: file open error %s\n", commitLogPATH); + } + + // @현재작업경로 내의 모든 파일들 + check_staging_list(staging_dir_list); + check_remove_file(commit_dir_list, exePATH); + + if (dircnt == 0) + { + printf("Nothing to commit\n"); + } + else + { + // 개수만 세는 용도 + lookup_current_commit(current_commit_dir_list); + + printf("Changes to be committed:\n"); + print_current_commit(current_commit_dir_list); + + // @todo : untrack확인하기 + // 현재 작업경로 내에 있는 파일 들 중에 staging_dir_list와 remove_staging_dir_list에 없는애들 + print_untrack_files(); + } +} +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + repoPATH = (char *)malloc(PATH_MAX); + stagingLogPATH = (char *)malloc(PATH_MAX); + commitLogPATH = (char *)malloc(PATH_MAX); + untrack_dir_list = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(untrack_dir_list); + // 현재 커밋 담는 리스트 init + current_commit_dir_list = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(current_commit_dir_list); + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + return -1; + } + + ssu_status(); + exit(0); +} \ No newline at end of file diff --git a/p2/ssu_struct.c b/p2/ssu_struct.c new file mode 100644 index 0000000..ed3e5b8 --- /dev/null +++ b/p2/ssu_struct.c @@ -0,0 +1,317 @@ +#include "ssu_header.h" + +void dirNode_init(dirNode *dir_node) +{ + dir_node->root_dir = NULL; + + dir_node->file_cnt = 0; + dir_node->subdir_cnt = 0; + dir_node->backup_cnt = 0; + memset(dir_node->dir_path, 0, sizeof(dir_node->dir_path)); + memset(dir_node->dir_name, 0, sizeof(dir_node->dir_name)); + + dir_node->file_head = (fileNode *)malloc(sizeof(fileNode)); + dir_node->file_head->prev_file = NULL; + dir_node->file_head->next_file = NULL; + dir_node->file_head->root_dir = dir_node; + + dir_node->subdir_head = (dirNode *)malloc(sizeof(dirNode)); + dir_node->subdir_head->prev_dir = NULL; + dir_node->subdir_head->next_dir = NULL; + dir_node->subdir_head->root_dir = dir_node; + + dir_node->prev_dir = NULL; + dir_node->next_dir = NULL; +} + +dirNode *dirNode_get(dirNode *dir_head, char *dir_name) +{ + dirNode *curr_dir = dir_head->next_dir; + + while (curr_dir != NULL) + { + if (curr_dir != NULL && !strcmp(dir_name, curr_dir->dir_name)) + { + return curr_dir; + } + curr_dir = curr_dir->next_dir; + } + return NULL; +} + +dirNode *dirNode_insert(dirNode *dir_head, char *dir_name, char *dir_path) +{ + dirNode *curr_dir = dir_head; + dirNode *next_dir, *new_dir; + + while (curr_dir != NULL) + { + next_dir = curr_dir->next_dir; + + if (next_dir != NULL && !strcmp(dir_name, next_dir->dir_name)) + { + return curr_dir->next_dir; + } + else if (next_dir == NULL || strcmp(dir_name, next_dir->dir_name) < 0) + { + new_dir = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(new_dir); + + new_dir->root_dir = dir_head->root_dir; + + sprintf(new_dir->dir_name, "%s", dir_name); + sprintf(new_dir->dir_path, "%s%s/", dir_path, dir_name); + + new_dir->prev_dir = curr_dir; + new_dir->next_dir = next_dir; + + if (next_dir != NULL) + { + next_dir->prev_dir = new_dir; + } + curr_dir->next_dir = new_dir; + + dir_head->root_dir->subdir_cnt++; + + return curr_dir->next_dir; + } + curr_dir = next_dir; + } + return NULL; +} + +dirNode *dirNode_append(dirNode *dir_head, char *dir_name, char *dir_path) +{ + dirNode *curr_dir = dir_head; + dirNode *next_dir, *new_dir; + + while (curr_dir != NULL) + { + next_dir = curr_dir->next_dir; + if (next_dir == NULL) + { + new_dir = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(new_dir); + + new_dir->root_dir = dir_head->root_dir; + + sprintf(new_dir->dir_name, "%s", dir_name); + sprintf(new_dir->dir_path, "%s%s/", dir_path, dir_name); + + new_dir->prev_dir = curr_dir; + new_dir->next_dir = next_dir; + + if (next_dir != NULL) + { + next_dir->prev_dir = new_dir; + } + curr_dir->next_dir = new_dir; + + // dir_head->root_dir->subdir_cnt++; + + return curr_dir->next_dir; + } + curr_dir = next_dir; + } + return NULL; +} + +void dirNode_remove(dirNode *dir_node) +{ + dirNode *next_dir = dir_node->next_dir; + dirNode *prev_dir = dir_node->prev_dir; + + if (next_dir != NULL) + { + next_dir->prev_dir = dir_node->prev_dir; + prev_dir->next_dir = dir_node->next_dir; + } + else + { + prev_dir->next_dir = NULL; + } + + free(dir_node->subdir_head); + free(dir_node->file_head); + dir_node->subdir_head = NULL; + dir_node->file_head = NULL; + + free(dir_node); +} + +void fileNode_init(fileNode *file_node) +{ + file_node->root_dir = NULL; + + file_node->backup_cnt = 0; + memset(file_node->file_name, 0, sizeof(file_node->file_name)); + memset(file_node->file_path, 0, sizeof(file_node->file_path)); + + file_node->backup_head = (backupNode *)malloc(sizeof(backupNode)); + file_node->backup_head->prev_backup = NULL; + file_node->backup_head->next_backup = NULL; + file_node->backup_head->root_file = file_node; + + file_node->prev_file = NULL; + file_node->next_file = NULL; +} + +fileNode *fileNode_get(fileNode *file_head, char *file_name) +{ + fileNode *curr_file = file_head->next_file; + + while (curr_file != NULL) + { + if (curr_file != NULL && !strcmp(file_name, curr_file->file_name)) + { + return curr_file; + } + curr_file = curr_file->next_file; + } + return NULL; +} + +fileNode *fileNode_insert(fileNode *file_head, char *file_name, + char *dir_path) +{ + fileNode *curr_file = file_head; + fileNode *next_file, *new_file; + + while (curr_file != NULL) + { + next_file = curr_file->next_file; + + if (next_file != NULL && !strcmp(file_name, next_file->file_name)) + { + return curr_file->next_file; + } + else if (next_file == NULL || + strcmp(file_name, next_file->file_name) < 0) + { + new_file = (fileNode *)malloc(sizeof(fileNode)); + fileNode_init(new_file); + + new_file->root_dir = file_head->root_dir; + + strcpy(new_file->file_name, file_name); + strcpy(new_file->file_path, dir_path); + strcat(new_file->file_path, file_name); + + new_file->prev_file = curr_file; + new_file->next_file = next_file; + + if (next_file != NULL) + { + next_file->prev_file = new_file; + } + curr_file->next_file = new_file; + + file_head->root_dir->file_cnt++; + + return curr_file->next_file; + } + curr_file = next_file; + } + return NULL; +} + +void fileNode_remove(fileNode *file_node) +{ + fileNode *next_file = file_node->next_file; + fileNode *prev_file = file_node->prev_file; + + if (next_file != NULL) + { + next_file->prev_file = file_node->prev_file; + prev_file->next_file = file_node->next_file; + } + else + { + prev_file->next_file = NULL; + } + + free(file_node->backup_head); + file_node->backup_head = NULL; + + free(file_node); +} + +void backupNode_init(backupNode *backup_node) +{ + backup_node->root_version_dir = NULL; + backup_node->root_file = NULL; + memset(backup_node->origin_path, 0, sizeof(backup_node->origin_path)); + memset(backup_node->backup_path, 0, sizeof(backup_node->backup_path)); + memset(backup_node->command, 0, sizeof(backup_node->command)); + memset(backup_node->commit_message, 0, sizeof(backup_node->commit_message)); + + backup_node->prev_backup = NULL; + backup_node->next_backup = NULL; +} + +backupNode *backupNode_get(backupNode *backup_head, char *command) +{ + backupNode *curr_backup = backup_head->next_backup; + + while (curr_backup != NULL) + { + if (curr_backup != NULL) + { + return curr_backup; + } + curr_backup = curr_backup->next_backup; + } + + return NULL; +} + +backupNode *backupNode_insert(backupNode *backup_head, char *command, + char *file_name, char *dir_path, + char *backup_path, char *commit_message) +{ + backupNode *new_backup = (backupNode *)malloc(sizeof(backupNode)); + backupNode_init(new_backup); + new_backup->root_file = backup_head->root_file; + strcpy(new_backup->command, command); + strcpy(new_backup->origin_path, dir_path); + strcat(new_backup->origin_path, file_name); + strcpy(new_backup->backup_path, backup_path); + strcpy(new_backup->commit_message, commit_message); + + // 백업 리스트의 끝을 찾아 새 노드를 연결 + backupNode *curr_backup = backup_head; + while (curr_backup->next_backup != NULL) + { + curr_backup = curr_backup->next_backup; // 마지막 노드로 이동 + } + + // 마지막 노드와 새 노드 연결 + curr_backup->next_backup = new_backup; + new_backup->prev_backup = curr_backup; + + // root_file의 백업 카운트 업데이트 + if (backup_head->root_file != NULL) + { + backup_head->root_file->backup_cnt++; + } + + return new_backup; // 새로 삽입된 노드 반환 +} + +void backupNode_remove(backupNode *backup_node) +{ + backupNode *next_backup = backup_node->next_backup; + backupNode *prev_backup = backup_node->prev_backup; + + if (next_backup != NULL) + { + next_backup->prev_backup = backup_node->prev_backup; + prev_backup->next_backup = backup_node->next_backup; + } + else + { + prev_backup->next_backup = NULL; + } + + free(backup_node); +} diff --git a/p3/Makefile b/p3/Makefile new file mode 100644 index 0000000..4eaa908 --- /dev/null +++ b/p3/Makefile @@ -0,0 +1,58 @@ +CC = gcc + +HEADER = ssu_header +SYNCC = ssu_sync +HELP = ssu_help +INIT = ssu_init +STRUCT = ssu_struct +ADD = ssu_add +REMOVE = ssu_remove +LIST = ssu_list + +all : $(SYNCC) $(HELP) $(ADD) $(REMOVE) $(LIST) + +$(SYNCC) : $(SYNCC).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(HELP) : $(HELP).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(ADD) : $(ADD).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(REMOVE) : $(REMOVE).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + +$(LIST) : $(LIST).o $(HEADER).o $(INIT).o $(STRUCT).o + $(CC) -g -o $@ $^ -lcrypto + + +$(HEADER).o : $(HEADER).h $(HEADER).c + $(CC) -c -o $@ $(HEADER).c -lcrypto + +$(INIT).o : $(HEADER).h $(INIT).c + $(CC) -c -o $@ $(INIT).c -lcrypto + +$(STRUCT).o : $(HEADER).h $(STRUCT).c + $(CC) -c -o $@ $(STRUCT).c -lcrypto + +$(SYNCC).o : $(HEADER).h $(SYNCC).c + $(CC) -c -o $@ $(SYNCC).c -lcrypto + +$(HELP).o : $(HEADER).h $(HELP).c + $(CC) -c -o $@ $(HELP).c -lcrypto + +$(ADD).o : $(HEADER).h $(ADD).c + $(CC) -c -o $@ $(ADD).c -lcrypto + +$(REMOVE).o : $(HEADER).h $(REMOVE).c + $(CC) -c -o $@ $(REMOVE).c -lcrypto + +$(LIST).o : $(HEADER).h $(LIST).c + $(CC) -c -o $@ $(LIST).c -lcrypto + + + +clean : + rm -rf $(SYNCC) $(HELP) $(ADD) $(REMOVE) $(LIST) + rm -rf *.o \ No newline at end of file diff --git a/p3/a/a.txt b/p3/a/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/p3/a/b.txt b/p3/a/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/p3/a/b/a.txt b/p3/a/b/a.txt new file mode 100644 index 0000000..94557a7 --- /dev/null +++ b/p3/a/b/a.txt @@ -0,0 +1 @@ +sssddddd \ No newline at end of file diff --git a/p3/a/b/b.txt b/p3/a/b/b.txt new file mode 100644 index 0000000..69ec23d --- /dev/null +++ b/p3/a/b/b.txt @@ -0,0 +1 @@ +ddd \ No newline at end of file diff --git a/p3/a/b/e.txt b/p3/a/b/e.txt new file mode 100644 index 0000000..3b3db2e --- /dev/null +++ b/p3/a/b/e.txt @@ -0,0 +1 @@ +ddddddddddddddddㅇㅇㅇㅇㅇdddddd \ No newline at end of file diff --git a/p3/a/d.txt b/p3/a/d.txt new file mode 100644 index 0000000..e69de29 diff --git a/p3/d.txt b/p3/d.txt new file mode 100644 index 0000000..e69de29 diff --git a/p3/ssu_add.c b/p3/ssu_add.c new file mode 100644 index 0000000..89e062f --- /dev/null +++ b/p3/ssu_add.c @@ -0,0 +1,789 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *monitor_dir_list = NULL; +dirNode *print_create_dir_list = NULL; +dirNode *log_dir_list = NULL; +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *backupPATH = NULL; +char *monitorLogPATH = NULL; + +int hash = 0; + +volatile sig_atomic_t signalRecv = 0; + +// 시그널 핸들러 함수는 하나의 매개변수를 가져야 하며, +// 그 매개변수는 받은 시그널의 번호 따라서, 시그널 핸들러는 자동적으로 시그널 번호를 매개변수로 받게 됨 +void signalHandler(int signum) +{ + char signalerrorlog[PATH_MAX]; + sprintf(signalerrorlog, "Received signal %d (%s)\n", signum, strsignal(signum)); + log_error(signalerrorlog); + if (signum == SIGUSR1) + { + log_error("Received SIGUSR1 signal\n"); + signalRecv = 1; + } +} + +int createDaemon(void) +{ + pid_t pid; + int fd, maxfd; + + // 자식 프로세스 생성 + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + log_error("ERROR: fork error\n"); + exit(1); + } + // 부모 종료 + else if (pid != 0) + exit(0); + + pid = getpid(); + setsid(); + + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGUSR1, signalHandler); + + maxfd = getdtablesize(); + for (fd = 0; fd < maxfd; fd++) + close(fd); + + umask(0); + // chdir("/"); + + fd = open("/dev/null", O_RDWR); + dup(0); + dup(0); + + return getpid(); +} +// 백업리스트안에 backupFilePath가 있는지 확인 +// 없으면 생성 +// 있는데 수정시간 다르면 수정 +// 없으면 -1 반환 +int exist = -1; +int processFilesIndirectory(char *path, char *backupDirPath, dirNode *dirNode, char *daemonLogPath, int pid) +{ + char pid_string[PATH_MAX]; + sprintf(pid_string, "%d", pid); + char daemonbuf[PATH_MAX]; + char backupFilePath[PATH_MAX]; + struct stat statbuf; + + int backup_fd; + int origin_fd; + int daemonLog_fd; + int len; + char buf[PATH_MAX]; + char formattedTime[PATH_MAX]; + + if (dirNode == NULL) + { + + return -1; + } + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + + if (strlen(bNode->origin_path) > 0 && !strcmp(pid_string, bNode->pid)) + { + + if ((daemonLog_fd = open(daemonLogPath, O_WRONLY | O_CREAT | O_APPEND, 0777)) < 0) + { + fprintf(stderr, "ERROR: daemon log open error\n"); + + log_error("ERROR: daemon log open error1"); + } + + if (!strcmp(bNode->origin_path, path)) + { + + exist = 1; + + if (stat(path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: stat error\n"); + log_error("stat error"); + } + struct tm *mtime = localtime(&statbuf.st_mtime); + time_t fileModTime = mktime(mtime); + + struct tm lastBackupTm = {0}; + lastBackupTm.tm_isdst = -1; + + strptime(bNode->time, "%Y-%m-%d %H:%M:%S", &lastBackupTm); // 날짜 문자열을 struct tm으로 변환 + time_t lastBackupTime = mktime(&lastBackupTm); // struct tm을 time_t로 변환 + + // 원본 파일 수정시간이 백업시간보다 + if (fileModTime > lastBackupTime) + { + time_t now = time(NULL); + struct tm *tm_now = localtime(&now); + strftime(formattedTime, sizeof(formattedTime), "%Y-%m-%d %H:%M:%S", tm_now); + sprintf(daemonbuf, "[%s] [modify] [%s] \n", formattedTime, path); + if (write(daemonLog_fd, daemonbuf, strlen(daemonbuf)) < 0) + { + fprintf(stderr, "ERROR: write log error\n"); + log_error("ERROR: write log error\n"); + exit(1); + } + + // 백업로직 + // @todo: 백업파일 생성 + // 실행경로 제거 + char *removeExePath = remove_base_path(path, exePATH); + if (removeExePath == NULL || strlen(removeExePath) == 0) + { + log_error("Error: Path after removing base path is invalid.\n"); + } + + char removeExePathBuf[PATH_MAX]; + strcpy(removeExePathBuf, removeExePath); + + char *filename = strrchr(removeExePath, '/'); + if (filename != NULL) + { + filename++; // '/' 다음 문자로 포인터 이동 + } + else + { + // '/'가 없는 경우, 전체 문자열이 파일 이름임을 가정 + filename = removeExePath; + } + + char formattedbuf[PATH_MAX]; + now = time(NULL); + tm_now = localtime(&now); + + memset(formattedbuf, 0, strlen(formattedbuf)); + strftime(formattedbuf, sizeof(formattedbuf), "%Y%m%d%H%M%S", tm_now); + memset(backupFilePath, 0, PATH_MAX); + sprintf(backupFilePath, "%s/%s_%s", backupDirPath, filename, formattedbuf); + if ((backup_fd = open(backupFilePath, O_CREAT | O_WRONLY | O_TRUNC)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + log_error("ERROR: open error\n"); + exit(1); + } + if ((origin_fd = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + log_error("ERROR: open error\n"); + exit(1); + } + while (1) + { + len = read(origin_fd, buf, strlen(buf)); + if (len <= 0) + { + break; + } + write(backup_fd, buf, len); + } + close(origin_fd); + close(backup_fd); + init_backup_list(); + + return 1; // 파일이 수정됨 + } + // mtime이 같은 경우 hash랑 사이즈 값 비교하기 + else if (lastBackupTime == fileModTime) + { + char lastBackHash[PATH_MAX] = ""; + char fileModHash[PATH_MAX] = ""; + cvtHash(bNode->origin_path, lastBackHash); + cvtHash(path, fileModHash); + struct stat laststatbuf; + struct stat filestatbuf; + if (stat(bNode->origin_path, &laststatbuf) < 0) + { + log_error("stat error"); + } + if (stat(path, &filestatbuf) < 0) + { + log_error("stat error"); + } + // 둘의 해시값 다르면 수정된거 + if (strcmp(lastBackHash, fileModHash) && (laststatbuf.st_size != filestatbuf.st_size)) + { + return 1; + } + } + } + } + + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + init_backup_list(); + processFilesIndirectory(path, backupDirPath, dirNode, daemonLogPath, pid); // 재귀 호출 + dirNode = dirNode->next_dir; + } + if (exist == -1) + { + + return -1; + } +} + +// 매개변수로 받은 pid에 해당하는 백업파일이 원본 경로에 존재하지 않으면 삭제임 (-1리턴) +// 존재하면 1리턴 + +void findBackupFileByPidOptionD(dirNode *dirNode, int pid, char *inputPath, char *daemonLogPath) +{ + char pid_string[PATH_MAX]; + sprintf(pid_string, "%d", pid); + int cnt; + struct stat statbuf; + struct dirent **namelist; + int isExist = -1; + char formattedTime[PATH_MAX]; + char daemonbuf[PATH_MAX]; + int daemonLog_fd; + + if (dirNode == NULL) + return; + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + + if ((strlen(bNode->origin_path) > 0) && (!strcmp(pid_string, bNode->pid))) + { + + // 백업리스트의 경로가 매개변수의 하위 경로라면 + if (!strncmp(bNode->origin_path, inputPath, strlen(inputPath))) + { + if (access(bNode->origin_path, F_OK) != 0) + { + if ((daemonLog_fd = open(daemonLogPath, O_WRONLY | O_CREAT | O_APPEND, 0777)) < 0) + { + fprintf(stderr, "ERROR: daemon log open error\n"); + log_error("ERROR: daemon log open error1"); + } + time_t now = time(NULL); + struct tm *tm_now = localtime(&now); + strftime(formattedTime, sizeof(formattedTime), "%Y-%m-%d %H:%M:%S", tm_now); + sprintf(daemonbuf, "[%s] [remove] [%s] \n", formattedTime, bNode->origin_path); + if (write(daemonLog_fd, daemonbuf, strlen(daemonbuf)) < 0) + { + fprintf(stderr, "ERROR: write log error\n"); + log_error("ERROR: write log error\n"); + exit(1); + } + close(daemonLog_fd); + init_backup_list(); + return; + } + } + } + + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + findBackupFileByPidOptionD(dirNode, pid, inputPath, daemonLogPath); + + dirNode = dirNode->next_dir; + } +} + +// monitorPathByOptionD(tmp_path, daemon_pid, daemonLogPath, namelist[i]->d_name, backupDirPath); +void monitorPathByOptionD(char *path, int pid, char *daemonLogPath, char *backupDirPath) +{ + + struct stat statbuf; + + char formattedTime[20]; + char backupFilePath[PATH_MAX]; + char buf[BUF_MAX]; + int len; + char pid_string[BUF_MAX]; + int cnt; + struct dirent **namelist; + char tmp_path[PATH_MAX]; + char daemonbuf[PATH_MAX]; + int sub_cnt; + int backup_fd; + int origin_fd; + int daemonLog_fd; + time_t now = time(NULL); + struct tm *tm_now = localtime(&now); + + memset(formattedTime, 0, strlen(formattedTime)); + strftime(formattedTime, sizeof(formattedTime), "%Y-%m-%d %H:%M:%S", tm_now); + + // outer : 백업리스트를 돌면서 매개변수로 받아온 경로 하위 파일인지 판단 (strcmp) + // inner : 매개변수로 받아온 하위 경로가 맞는데 현재 원본경로에서는 사라지면 remove + + if ((cnt = scandir(path, &namelist, NULL, alphasort)) == -1) + { + + fprintf(stderr, "ERROR: scandir error for %s\n", path); + log_error("ERROR: scandir error\n"); + } + for (int i = 0; i < cnt; i++) + { + char tmp_path[PATH_MAX]; + memset(tmp_path, 0, PATH_MAX); + int sub_fd; + if (!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) + continue; + sprintf(tmp_path, "%s/%s", path, namelist[i]->d_name); + + if (lstat(tmp_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error\n"); + log_error("ERROR: lstat error\n"); + } + + if (S_ISREG(statbuf.st_mode)) + { + now = time(NULL); + tm_now = localtime(&now); + strftime(formattedTime, sizeof(formattedTime), "%Y-%m-%d %H:%M:%S", tm_now); + + exist = -1; + // @todo: 생성,수정,삭제 판단 + int result = processFilesIndirectory(tmp_path, backupDirPath, backup_dir_list, daemonLogPath, pid); + + // 생성임 + if (result == -1) + { + + if ((daemonLog_fd = open(daemonLogPath, O_WRONLY | O_CREAT | O_APPEND, 0777)) < 0) + { + fprintf(stderr, "ERROR: daemon log open error\n"); + log_error("ERROR: daemon log open error1"); + } + sprintf(daemonbuf, "[%s] [%s] [%s] \n", formattedTime, "create", tmp_path); + if (write(daemonLog_fd, daemonbuf, strlen(daemonbuf)) == -1) + { + fprintf(stderr, "ERROR: daemon log write error\n"); + log_error("ERROR: daemon log write error\n"); + } + close(daemonLog_fd); + + // @todo: 백업파일 생성 + // 실행경로 제거 + char *removeExePath = remove_base_path(tmp_path, exePATH); + char removeExePathBuf[PATH_MAX]; + strcpy(removeExePathBuf, removeExePath); + // 맨마지막 파일 경로 생성 /a/b/c.txt에서 c.txt만 추출 + char *filename = strrchr(removeExePath, '/'); + if (filename != NULL) + { + // 파일이름만 + filename++; + } + + now = time(NULL); + tm_now = localtime(&now); + memset(formattedTime, 0, strlen(formattedTime)); + strftime(formattedTime, sizeof(formattedTime), "%Y%m%d%H%M%S", tm_now); + memset(backupFilePath, 0, PATH_MAX); + sprintf(backupFilePath, "%s/%s_%s", backupDirPath, filename, formattedTime); + if ((backup_fd = open(backupFilePath, O_CREAT | O_WRONLY | O_TRUNC)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + log_error("ERROR: open error\n"); + } + if ((origin_fd = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + log_error("ERROR: open error\n"); + } + while (1) + { + len = read(origin_fd, buf, strlen(buf)); + if (len <= 0) + { + break; + } + write(backup_fd, buf, len); + } + close(origin_fd); + close(backup_fd); + init_backup_list(); + } + } + } + close(daemonLog_fd); + + // 삭제 판단 + + findBackupFileByPidOptionD(backup_dir_list, pid, path, daemonLogPath); +} +int monitorPath(char *path, int pid, char *daemonLogPath, char *backupDirPath) +{ + struct stat statbuf; + + char logBuffer[PATH_MAX + 100]; + char formattedTime[20]; + char backupFilePath[PATH_MAX]; + char buf[BUF_MAX]; + int len; + int backup_fd; + int origin_fd; + int daemonLog_fd; + char pid_string[BUF_MAX]; + char daemonbuf[PATH_MAX]; + time_t now = time(NULL); + struct tm *tm_now = localtime(&now); + + memset(formattedTime, 0, strlen(formattedTime)); + strftime(formattedTime, sizeof(formattedTime), "%Y-%m-%d %H:%M:%S", tm_now); + + init_backup_list(); + + int result = processFilesIndirectory(path, backupDirPath, backup_dir_list, daemonLogPath, pid); + + // 생성해야 + if (result == -1) + { + if ((daemonLog_fd = open(daemonLogPath, O_WRONLY | O_CREAT | O_APPEND, 0777)) < 0) + { + fprintf(stderr, "ERROR: daemon log open error\n"); + log_error("ERROR: daemon log open error1"); + } + sprintf(daemonbuf, "[%s] [%s] [%s] \n", formattedTime, "create", path); + if (write(daemonLog_fd, daemonbuf, strlen(daemonbuf)) == -1) + { + fprintf(stderr, "ERROR: daemon log write error\n"); + log_error("ERROR: daemon log write error\n"); + } + close(daemonLog_fd); + // 백업로직 + // @todo: 백업파일 생성 + // 실행경로 제거 + char *removeExePath = remove_base_path(path, exePATH); + if (removeExePath == NULL || strlen(removeExePath) == 0) + { + fprintf(stderr, "ERROR: invalid path\n"); + log_error("ERROR: invalid path\\n"); + return 0; + } + + char removeExePathBuf[PATH_MAX]; + strcpy(removeExePathBuf, removeExePath); + + char *filename = strrchr(removeExePath, '/'); + if (filename != NULL) + { + filename++; + } + else + { + // '/'가 없는 경우, 전체 문자열이 파일 이름임을 가정 + filename = removeExePath; + } + now = time(NULL); + tm_now = localtime(&now); + memset(formattedTime, 0, strlen(formattedTime)); + strftime(formattedTime, sizeof(formattedTime), "%Y%m%d%H%M%S", tm_now); + memset(backupFilePath, 0, PATH_MAX); + sprintf(backupFilePath, "%s/%s_%s", backupDirPath, filename, formattedTime); + if ((backup_fd = open(backupFilePath, O_CREAT | O_WRONLY | O_TRUNC)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + log_error("ERROR: open error"); + } + if ((origin_fd = open(path, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + log_error("ERROR: open error"); + } + while (1) + { + len = read(origin_fd, buf, strlen(buf)); + if (len <= 0) + { + break; + } + write(backup_fd, buf, len); + } + close(backup_fd); + close(origin_fd); + close(daemonLog_fd); + init_backup_list(); + } + + // 삭제 판단 + findBackupFileByPidOptionD(backup_dir_list, pid, path, daemonLogPath); + + return 0; +} + +int initDaemon(char *path, int option) +{ + int monitorList_fd; + int daemonLog_fd; + pid_t pid; + pid_t daemon_pid = 0; + char daemonLogPath[PATH_MAX]; + struct stat statbuf; + char formattedTime[20]; + char backupDirPath[PATH_MAX]; + int cnt; + struct dirent **namelist; + char backupFilePath[PATH_MAX]; + char pid_string[PATH_MAX]; + int path_fd; + int backupFile_fd; + char buf[PATH_MAX]; + + // 자식 프로세스 생성 + if ((pid = fork()) < 0) + { + fprintf(stderr, "ERROR: fork error\n"); + log_error("ERROR: fork error"); + } + // 자식을 데몬으로 + else if (pid == 0) + { + + if ((daemon_pid = createDaemon()) < 0) + { + fprintf(stderr, "ERROR: create daemon error\n"); + log_error("ERROR: create daemon error"); + exit(1); + } + char monitorbuf[PATH_MAX * 2]; + int len; + + // monitor_list에 로그 써주기 + if ((monitorList_fd = open(monitorLogPATH, O_WRONLY | O_APPEND | O_CREAT)) < 0) + { + fprintf(stderr, "ERROR: monitor log list open error\n"); + log_error("ERROR: monitor log list open error"); + } + sprintf(monitorbuf, "%d : %s\n", daemon_pid, path); + if (write(monitorList_fd, monitorbuf, strlen(monitorbuf)) == -1) + { + fprintf(stderr, "ERROR: monitor log list write error\n"); + log_error("ERROR: monitor log list write error"); + } + + // $(pid).log파일 생성 + sprintf(daemonLogPath, "%s/%d.log", backupPATH, daemon_pid); + + // 백업 디렉토리 생성 + sprintf(backupDirPath, "%s/%d", backupPATH, daemon_pid); + if (access(backupDirPath, F_OK)) + mkdir(backupDirPath, 0777); + + if (lstat(path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error\n"); + log_error("ERROR:lstat error"); + } + + if (S_ISDIR(statbuf.st_mode) && (option & OPT_D)) + { + while (!signalRecv) + { + monitorPathByOptionD(path, daemon_pid, daemonLogPath, backupDirPath); + sleep(1); + } + } + else if (option & OPT_R) + { + // 재귀 모니터링 로직 + } + else if (S_ISREG(statbuf.st_mode)) + { + while (!signalRecv) + { + + monitorPath(path, daemon_pid, daemonLogPath, backupDirPath); + sleep(1); + } + } + + return daemon_pid; + } + else + { + + int status; + waitpid(pid, &status, 0); + } +} + +int ssu_add(char *path, int option) +{ + + pid_t daemon_pid; + daemon_pid = initDaemon(path, option); + char realpathBuf[PATH_MAX]; + realpath(path, realpathBuf); + + printf("monitoring started (%s) : %d\n", realpathBuf, daemon_pid + 1); + + return 0; +} + +int check_monitorList(dirNode *dirNode, char *path) +{ + if (dirNode == NULL) + { + return -1; + } + + fileNode *fNode = dirNode->file_head; + while (fNode != NULL) + { + backupNode *bNode = fNode->backup_head; + while (bNode != NULL) + { + if (strlen(bNode->origin_path) > 0) + { + char resolved_origin_path[PATH_MAX]; + char resolved_input_path[PATH_MAX]; + + // 경로를 절대 경로로 변환 + realpath(bNode->origin_path, resolved_origin_path); + realpath(path, resolved_input_path); + + if (!strcmp(resolved_origin_path, resolved_input_path)) + { + return 1; + } + } + bNode = bNode->next_backup; + } + fNode = fNode->next_file; + } + + dirNode = dirNode->subdir_head; + while (dirNode != NULL) + { + int res = check_monitorList(dirNode, path); + if (res == 1) + { + return 1; + } + + dirNode = dirNode->next_dir; + } + + return -1; +} + +void init_monitorlist() +{ + int list_fd; + int len; + char buf[PATH_MAX]; + + if ((list_fd = open(monitorLogPATH, O_RDONLY)) < 0) + { + fprintf(stderr, "ERROR: open error\n"); + return; + } + + while ((len = read(list_fd, buf, BUF_MAX)) > 0) + { + char *ptr = strchr(buf, '\n'); + if (ptr != NULL) + { + ptr[0] = '\0'; + } + + char *pid_string = strtok(buf, " : "); + char *path_string = strtok(NULL, " : "); + + if (pid_string && path_string) + { + char resolved_path[PATH_MAX]; + realpath(path_string, resolved_path); // 경로를 절대 경로로 변환 + + backup_list_insert(monitor_dir_list, "", resolved_path, "", pid_string); + } + } + + close(list_fd); +} + +int main(int argc, char *argv[]) +{ + exePATH = (char *)malloc(PATH_MAX); + exeNAME = (char *)malloc(PATH_MAX); + pwdPATH = (char *)malloc(PATH_MAX); + backupPATH = (char *)malloc(PATH_MAX); + monitorLogPATH = (char *)malloc(PATH_MAX); + + monitor_dir_list = (dirNode *)malloc(sizeof(dirNode)); + dirNode_init(monitor_dir_list); + + char *filename = argv[1]; + int option = atoi(argv[2]); + struct stat statbuf; + + if (init(argv[0]) == -1) + { + fprintf(stderr, "ERROR: init error.\n"); + log_error("ERROR: init error "); + return -1; + } + if (lstat(filename, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s \n", filename); + log_error("ERROR: lstat error "); + return 1; + } + + // 디렉토리인데 옵션으로 -d 또는 -r로 지정되지 않았다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISDIR(statbuf.st_mode) && !((option & OPT_D) || (option & OPT_R))) + { + fprintf(stderr, "ERROR: %s is a directory \n", filename); + + return -1; + } + + // 파일인데 옵션으로 -d 또는 -r로 지정했다면 에러 메시지를 출력하고 -1을 반환 + if (S_ISREG(statbuf.st_mode) && ((option & OPT_D) || (option & OPT_R))) + { + fprintf(stderr, "ERROR: %s is a file \n", filename); + return -1; + } + + // @todo: 인자로 입력받은 경로에 대해 데몬 프로세스가 이미 존재한다면 에러 처리 후 프롬프트 재출력 + + init_monitorlist(); + int checkval = check_monitorList(monitor_dir_list, filename); + + if (checkval == 1) + { + fprintf(stderr, "ERROR: daemon process already in logs\n"); + exit(1); + } + ssu_add(filename, option); + + exit(0); +} \ No newline at end of file diff --git a/p3/ssu_header.c b/p3/ssu_header.c new file mode 100644 index 0000000..731eb31 --- /dev/null +++ b/p3/ssu_header.c @@ -0,0 +1,429 @@ +#include "ssu_header.h" + +char *get_file_name(char *path) +{ + int i; + char tmp_path[PATH_MAX]; + char *file_name = (char *)malloc(sizeof(char) * FILE_MAX); + + strcpy(tmp_path, path); + for (i = 0; tmp_path[i]; i++) + { + if (tmp_path[i] == '/') + strcpy(file_name, tmp_path + i + 1); + } + + return file_name; +} + +char *cvt_time_2_str(time_t time) +{ + struct tm *time_tm = localtime(&time); + char *time_str = (char *)malloc(sizeof(char) * 32); + sprintf(time_str, "%02d%02d%02d%02d%02d%02d", + (time_tm->tm_year + 1900) % 100, + time_tm->tm_mon + 1, + time_tm->tm_mday, + time_tm->tm_hour, + time_tm->tm_min, + time_tm->tm_sec); + return time_str; +} + +char *substr(char *str, int beg, int end) +{ + char *ret = (char *)malloc(sizeof(char) * (end - beg + 1)); + + for (int i = beg; i < end && *(str + i) != '\0'; i++) + { + ret[i - beg] = str[i]; + } + ret[end - beg] = '\0'; + + return ret; +} + +char *c_str(char *str) +{ + return substr(str, 0, strlen(str)); +} + +int path_list_init(pathNode *curr_path, char *path) +{ + pathNode *new_path = curr_path; + char *ptr; + char *next_path = ""; + + if (!strcmp(path, "")) + return 0; + + if (ptr = strchr(path, '/')) + { + next_path = ptr + 1; + ptr[0] = '\0'; + } + + if (!strcmp(path, "..")) + { + new_path = curr_path->prev_path; + new_path->tail_path = new_path; + new_path->next_path = NULL; + new_path->head_path->tail_path = new_path; + + new_path->head_path->depth--; + + if (new_path->head_path->depth == 0) + return -1; + } + else if (strcmp(path, ".")) + { + new_path = (pathNode *)malloc(sizeof(pathNode)); + strcpy(new_path->path_name, path); + + new_path->head_path = curr_path->head_path; + new_path->tail_path = new_path; + + new_path->prev_path = curr_path; + new_path->next_path = curr_path->next_path; + + curr_path->next_path = new_path; + new_path->head_path->tail_path = new_path; + + new_path->head_path->depth++; + } + + if (strcmp(next_path, "")) + { + return path_list_init(new_path, next_path); + } + + return 0; +} + +char *cvt_path_2_realpath(char *path) +{ + pathNode *path_head; + pathNode *curr_path; + char *ptr; + char origin_path[PATH_MAX]; + char ret_path[PATH_MAX] = ""; + + path_head = (pathNode *)malloc(sizeof(pathNode)); + path_head->depth = 0; + path_head->tail_path = path_head; + path_head->head_path = path_head; + path_head->next_path = NULL; + + if (path[0] != '/') + { + sprintf(origin_path, "%s/%s", pwdPATH, path); + } + else + { + strcpy(origin_path, path); + } + + if (path_list_init(path_head, origin_path) == -1) + { + return NULL; + } + + curr_path = path_head->next_path; + while (curr_path != NULL) + { + strcat(ret_path, curr_path->path_name); + if (curr_path->next_path != NULL) + { + strcat(ret_path, "/"); + } + curr_path = curr_path->next_path; + } + + if (strlen(ret_path) == 0) + { + strcpy(ret_path, "/"); + } + + return c_str(ret_path); +} + +int make_dir_path(char *path) +{ + pathNode *path_head; + pathNode *curr_path; + char ret_path[PATH_MAX] = ""; + struct stat statbuf; + + path_head = (pathNode *)malloc(sizeof(pathNode)); + path_head->depth = 0; + path_head->tail_path = path_head; + path_head->head_path = path_head; + path_head->next_path = NULL; + + curr_path = path_head; + + if (path_list_init(path_head, c_str(path)) == -1) + { + return -1; + } + + curr_path = path_head->next_path; + while (curr_path != NULL) + { + strcat(ret_path, curr_path->path_name); + strcat(ret_path, "/"); + + curr_path = curr_path->next_path; + + if (!access(ret_path, F_OK)) + { + if (lstat(ret_path, &statbuf) < 0) + { + fprintf(stderr, "ERROR: lstat error for %s\n", ret_path); + return -1; + } + + if (!S_ISDIR(statbuf.st_mode)) + { + fprintf(stderr, "ERROR: %s is already exist as file\n", ret_path); + return -1; + } + + continue; + } + + if (mkdir(ret_path, 0755) == -1) + { + fprintf(stderr, "ERROR: mkdir error for '%s'\n", ret_path); + return -1; + }; + } + + return 0; +} + +int md5(char *target_path, char *hash_result) +{ + FILE *fp; + unsigned char hash[MD5_DIGEST_LENGTH]; + unsigned char buffer[SHRT_MAX]; + int bytes = 0; + MD5_CTX md5; + + if ((fp = fopen(target_path, "rb")) == NULL) + { + printf("ERROR: fopen error for %s\n", target_path); + return 1; + } + + MD5_Init(&md5); + + while ((bytes = fread(buffer, 1, SHRT_MAX, fp)) != 0) + MD5_Update(&md5, buffer, bytes); + + MD5_Final(hash, &md5); + + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(hash_result + (i * 2), "%02x", hash[i]); + hash_result[HASH_MD5 - 1] = 0; + + fclose(fp); + + return 0; +} + +int cvtHash(char *target_path, char *hash_result) +{ + md5(target_path, hash_result); +} + +int cmpHash(char *path1, char *path2) +{ + char *hash1 = (char *)malloc(sizeof(char) * HASH_MD5); + char *hash2 = (char *)malloc(sizeof(char) * HASH_MD5); + + cvtHash(path1, hash1); + cvtHash(path2, hash2); + + return strcmp(hash1, hash2); +} + +char *quote_check(char **str, char del) +{ + char *tmp = *str + 1; + int i = 0; + + while (*tmp != '\0' && *tmp != del) + { + tmp++; + i++; + } + if (*tmp == '\0') + { + *str = tmp; + return NULL; + } + if (*tmp == del) + { + for (char *c = *str; *c != '\0'; c++) + { + *c = *(c + 1); + } + *str += i; + for (char *c = *str; *c != '\0'; c++) + { + *c = *(c + 1); + } + } +} + +char *tokenize(char *str, char *del) +{ + int i = 0; + int del_len = strlen(del); + static char *tmp = NULL; + char *tmp2 = NULL; + + if (str != NULL && tmp == NULL) + { + tmp = str; + } + + if (str == NULL && tmp == NULL) + { + return NULL; + } + + char *idx = tmp; + + while (i < del_len) + { + if (*idx == del[i]) + { + idx++; + i = 0; + } + else + { + i++; + } + } + if (*idx == '\0') + { + tmp = NULL; + return tmp; + } + tmp = idx; + + while (*tmp != '\0') + { + if (*tmp == '\'' || *tmp == '\"') + { + quote_check(&tmp, *tmp); + continue; + } + for (i = 0; i < del_len; i++) + { + if (*tmp == del[i]) + { + *tmp = '\0'; + break; + } + } + tmp++; + if (i < del_len) + { + break; + } + } + + return idx; +} + +char **get_substring(char *str, int *cnt, char *del) +{ + *cnt = 0; + int i = 0; + char *token = NULL; + char *templist[100] = { + NULL, + }; + token = tokenize(str, del); + if (token == NULL) + { + return NULL; + } + + while (token != NULL) + { + templist[*cnt] = token; + *cnt += 1; + token = tokenize(NULL, del); + } + + char **temp = (char **)malloc(sizeof(char *) * (*cnt + 1)); + for (i = 0; i < *cnt; i++) + { + temp[i] = templist[i]; + } + return temp; +} +char *getRelativePath(char *absolutePath) +{ + + char cwd[1024]; + getcwd(cwd, 1024); + // absolutePath 내에서 cwd를 찾음 + char *found = strstr(absolutePath, cwd); + + // cwd 이후의 문자열 시작 위치 계산 + char *startAfterCwd = found + strlen(cwd); + + // '/' 문자로 시작하면 건너뛰기 + if (*startAfterCwd == '/') + startAfterCwd++; + char *newPath = (char *)malloc(STR_MAX); + memset(newPath, 0, STR_MAX); + + if (!strcmp(startAfterCwd, "")) + { + strcat(newPath, "."); + } + else + { + strcat(newPath, "./"); + strcat(newPath, startAfterCwd); + } + return newPath; +} + +void log_error(const char *message) +{ + char logPath[PATH_MAX]; + sprintf(logPath, "%s/log/log.txt", pwdPATH); + FILE *log_file = fopen(logPath, "a"); + if (log_file) + { + fprintf(log_file, "%s\n", message); + fclose(log_file); + } +} + +// /home/ubuntu/a/b/c.txt에서 /home/ubuntu만 제거 +char *remove_base_path(char *full_path, const char *base_path) +{ + + char *pos = strstr(full_path, base_path); + + if (pos != NULL) + { + + size_t len = strlen(base_path); + + if (pos[len] == '/') + len++; + return pos + len; + } + + return full_path; +} diff --git a/p3/ssu_header.h b/p3/ssu_header.h new file mode 100644 index 0000000..818a964 --- /dev/null +++ b/p3/ssu_header.h @@ -0,0 +1,196 @@ +#ifndef SSU_HEADER_H +#define SSU_HEADER_H + +#define OPENSSL_API_COMPAT 0x10100000L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG printf("DEBUG\n"); + +#define PATH_MAX 4096 +#define BUF_MAX 4096 +#define FILE_MAX 255 +#define STR_MAX 255 + +#define CMD_ADD 0b0000000001 +#define CMD_REMOVE 0b0000000010 +#define CMD_LIST 0b0000000100 +#define CMD_HELP 0b0000001000 +#define CMD_EXIT 0b0000010000 +#define NOT_CMD 0b0000000000 + +#define OPT_D 0b000001 +#define OPT_R 0b000010 +#define OPT_Y 0b000100 +#define OPT_A 0b001000 +#define OPT_T 0b010000 +#define OPT_N 0b100000 + +#define ADD_SEP "add " +#define REMOVE_SEP "remove " +#define RECOVER_SEP "\" recovered to \"" +#define COMMIT_PREFIX "commit: " +#define NEWFILE_SEP " - new file: " +#define MODIFIED_SEP " - modified: " +#define REMOVED_SEP " - removed: " + +#define CREATE_SEP "[create]" +#define MODIFY_SEP "[modify]" +#define DELETE_SEP "[remove]" + +#define HASH_MD5 33 + +typedef struct command_parameter +{ + char *command; + char *filename; + char *tmpname; + int commandopt; + char *argv[10]; +} command_parameter; + +typedef struct _backupNode +{ + struct _dirNode *root_version_dir; + struct _fileNode *root_file; + + char time[STR_MAX]; + + char origin_path[PATH_MAX]; + char backup_path[PATH_MAX]; + char pid[PATH_MAX]; + + struct _backupNode *prev_backup; + struct _backupNode *next_backup; +} backupNode; + +typedef struct _fileNode +{ + struct _dirNode *root_dir; + + int backup_cnt; + char file_name[FILE_MAX]; + char file_path[PATH_MAX]; + backupNode *backup_head; + + struct _fileNode *prev_file; + struct _fileNode *next_file; +} fileNode; + +typedef struct _dirNode +{ + struct _dirNode *root_dir; + + int file_cnt; + int subdir_cnt; + int backup_cnt; + char dir_name[FILE_MAX]; + char dir_path[PATH_MAX]; + fileNode *file_head; + struct _dirNode *subdir_head; + + struct _dirNode *prev_dir; + struct _dirNode *next_dir; +} dirNode; + +typedef struct _pathNode +{ + char path_name[FILE_MAX]; + int depth; + + struct _pathNode *prev_path; + struct _pathNode *next_path; + + struct _pathNode *head_path; + struct _pathNode *tail_path; +} pathNode; + +extern dirNode *backup_dir_list; +extern dirNode *monitor_dir_list; +// list명령어시 create된 시간만 담는 리스트 +extern dirNode *print_create_dir_list; + +extern dirNode *log_dir_list; +// 실행파일 이름 +extern char *exeNAME; +// 현재 실행 디렉 경로 +extern char *exePATH; +// HOME으로 지정한경로 +extern char *pwdPATH; +extern char *backupPATH; +extern char *monitorLogPATH; + +extern int hash; + +void help(); +void help_process(int argc, char *arg); + +char *quote_check(char **str, char del); +char **get_substring(char *str, int *cnt, char *del); +char *tokenize(char *str, char *del); +char *get_file_name(char *path); +char *cvt_time_2_str(time_t time); +char *substr(char *str, int beg, int end); +char *c_str(char *str); +char *cvt_path_2_realpath(char *path); +int make_dir_path(char *path); + +int md5(char *target_path, char *hash_result); +int cvtHash(char *target_path, char *hash_result); +int cmpHash(char *path1, char *path2); + +int path_list_init(pathNode *curr_path, char *path); +char *getRelativePath(char *absolutePath); +char *remove_base_path(char *full_path, const char *base_path); + +// ssu_struct.c +void dirNode_init(dirNode *dir_node); +dirNode *dirNode_get(dirNode *dir_head, char *dir_name); +dirNode *dirNode_insert(dirNode *dir_head, char *dir_name, char *dir_path); +dirNode *dirNode_append(dirNode *dir_head, char *dir_name, char *dir_path); +void dirNode_remove(dirNode *dir_node); + +int add_cnt_root_dir(dirNode *dir_node, int cnt); + +void fileNode_init(fileNode *file_node); +fileNode *fileNode_get(fileNode *file_head, char *file_name); +fileNode *fileNode_insert(fileNode *file_head, char *file_name, char *dir_path); +void fileNode_remove(fileNode *file_node); + +void backupNode_init(backupNode *backup_node); +backupNode *backupNode_get(backupNode *backup_head, char *command); +backupNode *backupNode_insert(backupNode *backup_head, char *command, + char *file_name, char *dir_path, + char *backup_path, char *commit_message); +void backupNode_remove(backupNode *backup_node); + +dirNode *get_dirNode_from_path(dirNode *dirList, char *path); +fileNode *get_fileNode_from_path(dirNode *dirList, char *path); +backupNode *get_backupNode_from_path(dirNode *dirList, char *path, + char *command); + +// ssu_init.c +int init(char *path); +int init_backup_list(); +void print_list(dirNode *dirList, int depth, int last_bit); +void print_depth(int depth, int is_last_bit); +int backup_list_insert(dirNode *dirList, char *command, char *path, + char *backup_path, char *commit_message); + +#endif \ No newline at end of file diff --git a/p3/ssu_help.c b/p3/ssu_help.c new file mode 100644 index 0000000..4a8fc5b --- /dev/null +++ b/p3/ssu_help.c @@ -0,0 +1,78 @@ +#include "ssu_header.h" + +dirNode *backup_dir_list = NULL; +dirNode *monitor_dir_list = NULL; +dirNode *print_create_dir_list = NULL; +dirNode *log_dir_list = NULL; +char *exeNAME = NULL; +char *exePATH = NULL; +char *pwdPATH = NULL; +char *backupPATH = NULL; +char *monitorLogPATH = NULL; + +int hash = 0; + +void help(int cmd_bit) +{ + if (!cmd_bit) + { + printf("Usage: \n"); + } + + if (!cmd_bit || cmd_bit & CMD_ADD) + { + printf("%s add [OPTION]... : add new daemon process of if is file\n", (cmd_bit ? "Usage:" : " >")); + printf("%s -d : add new daemon process of if is directory\n", (!cmd_bit ? " " : "")); + printf("%s -r : add new daemon process of recursive if is directory\n", (!cmd_bit ? " " : "")); + printf("%s -t