diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ce7538..e34cbab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,4 +16,5 @@ endfunction() define_lab(lab2) define_lab(lab3) define_lab(lab4) +define_lab(lab5) define_lab(lab7) \ No newline at end of file diff --git a/lab5/.execme b/lab5/.execme new file mode 100755 index 0000000..901d422 --- /dev/null +++ b/lab5/.execme @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +echo "TODO!" +exit 1 \ No newline at end of file diff --git a/lab5/CMakeLists.txt b/lab5/CMakeLists.txt new file mode 100644 index 0000000..2451264 --- /dev/null +++ b/lab5/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.16) +set(CMAKE_C_STANDARD 11) + +# Lab name +set(LAB_NAME "lab5") + +# Lab tasks +list(APPEND SOURCE_FILES + task1.c + task2.c + mytail.c + mystat.c + myls.c + task6.c + ) +list(APPEND NON_COMPILABLE_SRC + .execme + ) + +### Here goes the template + +project("${LAB_NAME}" C) + +add_custom_target("${LAB_NAME}") + +foreach (file IN LISTS SOURCE_FILES) + add_executable("${LAB_NAME}_${file}_run" "${file}") + add_dependencies("${LAB_NAME}" "${LAB_NAME}_${file}_run") +endforeach () + +foreach (file IN LISTS NON_COMPILABLE_SRC) + add_custom_command( + TARGET "${LAB_NAME}" POST_BUILD + DEPENDS "${file}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/${file}" + "${CMAKE_CURRENT_BINARY_DIR}/${file}" + ) +endforeach () \ No newline at end of file diff --git a/lab5/README.md b/lab5/README.md new file mode 100644 index 0000000..448b995 --- /dev/null +++ b/lab5/README.md @@ -0,0 +1,3 @@ +# Лабораторная работа №5 + +## Задание 1 diff --git a/lab5/myls.c b/lab5/myls.c new file mode 100644 index 0000000..88de390 --- /dev/null +++ b/lab5/myls.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +5. Разработать собственную версию (myls) команды ls, которая выводит список файлов в заданной директории. +С ключом -l она выводит информацию о каждом файле, включая собственника, группу, разрешения и т.д., получаемые из системного +вызова stat(). +Формат: myls -l directory (или текущую директорию, если параметр не задан) +Использовать: stat(), opendir(), readdir(), getcwd(), ... + */ + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define STR_EQ(a, b) (!strcmp((a),(b))) +#define FATAL(fmt, ...) do {\ + fprintf(stderr,fmt,##__VA_ARGS__);\ + exit(1);\ +} while (0) + +char *path = NULL; +bool long_listing = false; +bool flag_end = false; + +void cleanup_on_exit() { + free(path); +} + +void print_entry(char *entry) { + if (long_listing) { + struct stat fstat; + if (lstat(entry, &fstat) == -1) { + perror("stat()"); + FATAL("Couldn't stat %s\n", entry); + } + + // Print permissions + char entry_type; + // setuid, setgid, sticky are not in count + const char* perm_sym[] = {"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"}; + int perm = fstat.st_mode & 0777; + struct passwd *pw_data; + struct group *gp_data; + + char user_name[33] = ""; + char group_name[33] = ""; + char last_mtime[256] = ""; + + switch (fstat.st_mode & S_IFMT) { + case S_IFREG: + entry_type = '-'; + break; + case S_IFDIR: + entry_type = 'd'; + break; + case S_IFLNK: + entry_type = 'l'; + break; + case S_IFBLK: + entry_type = 'b'; + break; + case S_IFSOCK: + entry_type = 's'; + break; + case S_IFCHR: + entry_type = 'c'; + break; +#ifdef S_IFIFO + case S_IFIFO: + entry_type = 'p'; + break; +#endif /* S_IFIFO */ + default: + entry_type = '?'; + break; + } + + if ((pw_data = getpwuid(fstat.st_uid)) == NULL) { + perror("Could not get owner username"); + errno = 0; + snprintf(user_name, 33, "%d", fstat.st_uid); + } else { + snprintf(user_name, 33, "%s", pw_data->pw_name); + } + + if ((gp_data = getgrgid(fstat.st_gid)) == NULL) { + perror("Could not get owner group name"); + errno = 0; + snprintf(group_name, 33, "%d", fstat.st_gid); + } else { + snprintf(group_name, 33, "%s", gp_data->gr_name); + } + + time_t mtime = fstat.st_mtime; + struct tm* tm_info = localtime(&mtime); + strftime(last_mtime, 256, "%Y-%b-%d %T %Z", tm_info); + + printf("%c%s%s%s\t%4lu\t%s %s\t%6ld\t%s\t", + entry_type, + perm_sym[(perm & S_IRWXU) >> 6], + perm_sym[(perm & S_IRWXG) >> 3], + perm_sym[perm & S_IRWXO], + fstat.st_nlink, + user_name, + group_name, + fstat.st_size, + last_mtime); + + } + printf("%s\n", entry); +} + +int main(int argc, char *argv[]) { + atexit(cleanup_on_exit); + + for (int i = 1; i < argc; ++i) { + if (argv[i][0] == '-' && !flag_end) { + char *flag = argv[i] + 1; + if (STR_EQ(flag, "l")) { + long_listing = true; + } + else if (STR_EQ(flag, "-")) { + flag_end = true; + } + else { + FATAL("Usage: %s [-l] [--] [path]\n", argv[0]); + } + } else { + path = strdup(argv[i]); + break; + } + } + + if (!path) { + /* + * As an extension to the POSIX.1-2001 standard, glibc's getcwd() + * allocates the buffer dynamically using malloc(3) if buf is NULL. In + * this case, the allocated buffer has the length size unless size is + * zero, when buf is allocated as big as necessary. The caller should + * free(3) the returned buffer. + */ + if ((path = getcwd(NULL, 0)) == NULL) { + perror("getcwd()"); + FATAL("Couldn't get current working directory\n"); + } + } + + struct stat path_stat; + DIR *dir = NULL; + if (stat(path, &path_stat) == -1) { + perror("stat()"); + FATAL("Couldn't stat %s\n", path); + } + + bool is_dir = S_ISDIR(path_stat.st_mode); + + if (is_dir) { + if ((dir = opendir(path)) == NULL) { + perror("opendir()"); + FATAL("Couldn't open directory %s\n", path); + } + if (chdir(path) == -1) { + perror("chdir()"); + FATAL("Could not chdir to directory %s\n", path); + } + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + print_entry(ent->d_name); + } + closedir(dir); + } else { + print_entry(path); + } + + return 0; +} \ No newline at end of file diff --git a/lab5/mystat.c b/lab5/mystat.c new file mode 100644 index 0000000..363f468 --- /dev/null +++ b/lab5/mystat.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +4. Разработать собственную версию (mystat) команды stat, которая просто осуществляет системный вызов stat(), выводит +размер, число задействованных блоков, число ссылок и т.д. Отследить, как меняется число ссылок, когда изменяется +содержимое директории. + */ + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define FATAL(fmt, ...) do {\ + fprintf(stderr,fmt,##__VA_ARGS__);\ + exit(1);\ +} while (0) + +int main(int argc, char *argv[]) { + if (argc != 2) { + FATAL("Usage: %s \n", argv[0]); + } + + char *path = argv[1]; + struct stat sb; + + if (lstat(path, &sb) == -1) { + perror("lstat()"); + FATAL("Couldn't stat %s\n", path); + } + + printf("File: %s\n", path); + printf("Size: %ld\tBlocks: %ld\tIO Block: %ld\t", sb.st_size, sb.st_blocks, sb.st_blksize); + switch(sb.st_mode & S_IFMT) { + case S_IFBLK: + puts("block device"); + break; + case S_IFCHR: + puts("character device"); + break; + case S_IFDIR: + puts("directory"); + break; + case S_IFIFO: + puts("FIFO/pipe"); + break; + case S_IFLNK: + puts("symlink"); + break; + case S_IFREG: + puts("regular file"); + break; + case S_IFSOCK: + puts("socket"); + break; + default: + puts("unknown?"); + break; + } + + printf("Device: [%04x:%04x]\t", major(sb.st_dev), minor(sb.st_dev)); + printf("Inode: %lu\t", sb.st_ino); + printf("Links: %lu\n", sb.st_nlink); + + printf("Mode: 0%o\tUid: %d\tGid: %d\n", sb.st_mode, sb.st_uid, sb.st_gid); + printf("Access (atime): %s", ctime(&sb.st_atime)); + printf("Modify (mtime): %s", ctime(&sb.st_mtime)); + printf("Change (ctime): %s", ctime(&sb.st_ctime)); + return 0; +} \ No newline at end of file diff --git a/lab5/mytail.c b/lab5/mytail.c new file mode 100644 index 0000000..ede3bd5 --- /dev/null +++ b/lab5/mytail.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + 3. Разработать собственную версию (mytail) команды tail. + Формат: mytail -n file + Она читает блок из конца файла, просматривает его с конца до заданного количества строк n и печатает эти строки + в соответствующем порядке. + Использовать: stat(), lseek(), open(), read(), close(), ... + */ +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define FATAL(fmt, ...) do {\ + fprintf(stderr,fmt,##__VA_ARGS__);\ + exit(1);\ +} while (0) + +int main(int argc, char *argv[]) { + const int BUFF_SIZE = 4096; + long n = 10; + char *filename = NULL; + + // Parse arguments (skip 0th arg) + for (int i = 1; i < argc; ++i) { + if ((argv[i][0] == '-') && (i < argc - 1)) { + // -n flag (e.g. -20) + n = strtol(argv[i], NULL, 10) * -1; + if (n < 1 || errno == ERANGE) { + FATAL("Usage: %s [-] \n", argv[0]); + } + } else { + filename = argv[i]; + break; + } + } + if (filename == NULL) FATAL("No file name specified.\n"); + + struct stat sb; + int fd; + + if (stat(filename, &sb) == -1) { + perror("stat()"); + FATAL("Couldn't stat %s\n", filename); + } + if ((fd = open(filename, O_RDONLY)) == -1) { + perror("open()"); + FATAL("Couldn't open %s\n", filename); + } + + char *buf = calloc(BUFF_SIZE, 1); + int lf_cnt = 0; + size_t read_sz = 0; + off_t file_offset = MAX(sb.st_size - BUFF_SIZE, 0); + bool seek_stop = false; + + lseek(fd, -BUFF_SIZE, SEEK_END); + + do { + int data_sz = read(fd, buf, BUFF_SIZE); + for (int i = data_sz - 1; i >= 0; --i) { + if (buf[i] == '\n') { + if (++lf_cnt >= n) { + file_offset += i + 1; + goto print_lines; + } + } + } + file_offset -= BUFF_SIZE; + lseek(fd, -BUFF_SIZE, SEEK_CUR); + } while ((lf_cnt <= n) && (file_offset > 0)); + +print_lines: + lseek(fd, file_offset, SEEK_SET); + do { + read_sz = read(fd, buf, BUFF_SIZE); + write(STDOUT_FILENO, buf, read_sz); + } while (read_sz == BUFF_SIZE); + + free(buf); + close(fd); + + return 0; +} \ No newline at end of file diff --git a/lab5/task1.c b/lab5/task1.c new file mode 100644 index 0000000..9720c8c --- /dev/null +++ b/lab5/task1.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BENCHMARK(name, payload) \ + do {\ + struct timeval t0, t1;\ + gettimeofday(&t0, NULL);\ + payload \ + gettimeofday(&t1, NULL);\ + struct timeval result;\ + timersub(&t1, &t0, &result);\ + printf(name ": %ld.%06lds\n", result.tv_sec, result.tv_usec);\ + } while (0) + +/* +1. Используя creat(), write(), fflush(), close(), gettimeofday(), разработать программу, которая +открывает файл, +записывает туда 300 KB, +очищает все записи, +закрывает и +удаляет файл, +а также измеряет и выводит время, затраченное на каждое действие. +*/ + +int main() { + int f = -1; + + BENCHMARK("creat() call",{ + f = creat("task1.txt", S_IWUSR); + }); + + if (f == -1) { + perror("creat()"); + return 1; + } + + int bytes_written = -1; + const size_t data_size = 307200; // bytes, 300KiB + char *random_data = malloc(data_size); + + BENCHMARK("write 300KiB", { + bytes_written = write(f, random_data, data_size); + }); + free(random_data); + + if (bytes_written == -1) { + perror("write()"); + return 1; + } + + BENCHMARK("fsync() call", { + fsync(f); + }); + + BENCHMARK("close() call", { + close(f); + }); + + return 0; +} \ No newline at end of file diff --git a/lab5/task2.c b/lab5/task2.c new file mode 100644 index 0000000..b7c8a85 --- /dev/null +++ b/lab5/task2.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BENCHMARK(name, payload) \ + do {\ + struct timeval __t0, __t1;\ + gettimeofday(&__t0, NULL);\ + payload \ + gettimeofday(&__t1, NULL);\ + struct timeval __result;\ + timersub(&__t1, &__t0, &__result);\ + printf(name ": %ld.%06lds\n", __result.tv_sec, __result.tv_usec);\ + } while (0) + +/* +2. Разработать программу, которая замеряет время для 300,000 однобайтовых записей с использованием +a) напрямую POSIX: creat(), write(), close(). +b) с использованием библиотеки stdio (напр., fopen(), fwrite(), and fclose()). +Сравнить и объяснить результаты. +*/ + +int main() { + BENCHMARK("POSIX API", { + int f = -1; + BENCHMARK("creat() call",{ + f = creat("task2_posix.txt", 0644); + }); + + if (f == -1) { + perror("creat()"); + return 1; + } + + int bytes_written = -1; + const size_t data_size = 300000; + char *random_data = malloc(data_size); + + BENCHMARK("write() call", { + bytes_written = write(f, random_data, data_size); + }); + free(random_data); + + if (bytes_written == -1) { + perror("write()"); + return 1; + } + + BENCHMARK("close() call", { + close(f); + }); + }); + + BENCHMARK("stdio.h API",{ + FILE *f = NULL; + BENCHMARK("fopen() call", { + f = fopen("task2_stdio.txt", "w"); + }); + if (f == NULL) { + perror("fopen()"); + return 1; + } + + int bytes_written = -1; + const size_t data_size = 300000; + char *random_data = malloc(data_size); + BENCHMARK("fwrite() call", { + bytes_written = fwrite(random_data, 1, 300000, f); + }); + free(random_data); + if (bytes_written != data_size) { + perror("fwrite()"); + return 1; + } + + BENCHMARK("fclose() call",{ + fclose(f); + }); + }); + + return 0; +} \ No newline at end of file diff --git a/lab5/task6.c b/lab5/task6.c new file mode 100644 index 0000000..8f10385 --- /dev/null +++ b/lab5/task6.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +6. Разработать программу, которая выводит имена каждого файла и директории, начиная с заданной точки в дереве каталогов. +a) Без аргументов: сначала текущая директория и ее содержимое, затем поддиректории и т.д. (пока не закончится дерево, +root в качестве CWD). +b) С одним аргументом (который есть имя директории): все поддерево, начиная с заданной директории. +c) А также еще один какой-либо интересный вариант (см. опции для find). + */ + +#define USAGE_STRING "Usage: %s [-d ] [-L] [--] [path]\n" +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define STR_EQ(a, b) (!strcmp((a),(b))) +#define FATAL(fmt, ...) do {\ + fprintf(stderr,fmt,##__VA_ARGS__);\ + exit(1);\ +} while (0) +#define WARNING(fmt, ...) fprintf(stderr,fmt,##__VA_ARGS__) + +bool flag_end = false; +bool follow_symlinks = false; +long max_depth = LONG_MAX; + +void traverse_path(char *path, long depth) { + struct stat pstat; + DIR *dir = NULL; + struct dirent *ent; + int stat_status; + + printf("%s\n", path); + + if (depth > max_depth) + return; + + if (follow_symlinks) { + stat_status = stat(path, &pstat); + } else { + stat_status = lstat(path, &pstat); + } + + if (stat_status == -1) { + perror("stat()"); + FATAL("Couldn't stat %s\n", path); + } + + if (!S_ISDIR(pstat.st_mode)) + return; + + if ((dir = opendir(path)) == NULL) { + perror("opendir()"); + FATAL("Couldn't open directory %s\n", path); + } + + while ((ent = readdir(dir)) != NULL) { + if (STR_EQ(ent->d_name, ".") || STR_EQ(ent->d_name, "..")) { + continue; + } + + char path_next[PATH_MAX]; + strcpy(path_next, path); + + if (path_next[strlen(path_next) - 1] != '/') { + strcat(path_next, "/"); + } + + strcat(path_next, ent->d_name); + + traverse_path(path_next, depth + 1); + } + + closedir(dir); +} + +int main(int argc, char *argv[]) { + char *path = "."; + + for (int i = 1; i < argc; ++i) { + if (argv[i][0] == '-' && !flag_end) { + char *flag = argv[i] + 1; + if (STR_EQ(flag, "d")) { + if (i + 1 < argc) { + max_depth = strtol(argv[++i], NULL, 10); + if (errno == ERANGE) { + WARNING("Max depth level is out of range, %ld is set\n", max_depth); + errno = 0; + } + if (max_depth < 0) { + FATAL("Max depth must be non-negative\n"); + } + } else { + FATAL(USAGE_STRING, argv[0]); + } + } + else if (STR_EQ(flag, "L")) { + follow_symlinks = true; + } + else if (STR_EQ(flag, "-")) { + flag_end = true; + } + else { + FATAL(USAGE_STRING, argv[0]); + } + } else { + path = argv[i]; + break; + } + } + + traverse_path(path, 1); +} \ No newline at end of file