ml3m's journey

Operating Systems Lab Exam 1 - Solutions.

Exercise 1: File Handling and Error Checking

Original Code

int main(int argc, char argv)
{
    int f = open(argv, O_RDONLY), c, total;
    while ((n == read(f, &c, 1)) > 0)
        if (isalpha(c))
            total++;
    close(f);
    printf("Found %uc alphanumeric elements in file!\n", total);
}

Solution

#include <fcntl.h>   // For open()
#include <unistd.h>  // For read() and close()
#include <ctype.h>   // For isalnum()
#include <stdio.h>   // For printf()

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        return 1;
    }

    int f = open(argv[1], O_RDONLY);
    if (f < 0) {
        perror("Error opening file");
        return 1;
    }

    int c, total = 0;
    ssize_t n; // ssize_t is the correct type for the return value of read()

    while ((n = read(f, &c, 1)) > 0) {
        // Cast c to unsigned char before passing to isalnum
        if (isalnum((unsigned char)c)) {
            total++;
        }
    }

    if (n < 0) {
        perror("Error reading file");
        close(f);
        return 1;
    }

    close(f);
    printf("Found %d alphanumeric elements in file!\n", total);

    return 0;
}

Explanation


Exercise 2: Process Creation

Original Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include 
int main(int argc, char **argv)
{
n = atoi(argv);
int pids[n];
for (i = 0; i < n; ++i) {
    if ((pids[i] = fork()) < 0) {
        print("Errors\n");
    } else if (pids[i] > 0) {
        printf("Child PID %d\n",getpid());
        exit(i);
    }
    }
print("Done spawning %d subprocesses", n);
}

Solution

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h> // For wait()

int main(int argc, char **argv) {
    if (argc != 2) {
        perror("error need a number as arg");
        return 1;
    }

    int n = atoi(argv[1]);
    int pids[n];

    for (int i = 0; i < n; i++) {
        pids[i] = fork();
        if (pids[i] < 0) {
            printf("Errors\n");
        } else if (pids[i] == 0) {
            // Child process
            exit(0);
        }
    }

    // we need to wait for all of them. 
    for (int i = 0; i < n; i++) {
        wait(NULL);
    }

    printf("Done spawning %d subprocesses\n", n);
    return 0;
}

Explanation


Exercise 3: Inter-Process Communication with Pipes

Original Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char **argv){
int pipe_fd[2];
int n = open(argv[1], O_WRONLY);
pid_t pid=fork();
if (pid<0){
perror("Issue with fork");
exit(EXIT_FAILURE);}
if (pid==0){
n = read(pipe_fd[0],buffer,sizeof(buffer));
printf("Total characters read: %d",n);
else{
char buffer[512];
read(fd,buffer,sizeof(buffer));
write(pipe_fd[1],buffer,sizeof(buffer));
wait(NULL);
}

Solution

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

#define BUFFER_SIZE 512

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("error: please provide a filename\n");
        return 1;
    }

    int pipe_fd[2];
    if (pipe(pipe_fd) == -1) {
        perror("pipe error");
        return 1;
    }

    int fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("open error");
        return 1;
    }

    pid_t pid = fork();

    if (pid < 0) {
        perror("fork error");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {  // Child process
        close(pipe_fd[1]);
        char buffer[BUFFER_SIZE];
        ssize_t total_bytes = 0;
        ssize_t bytes_read;

        while ((bytes_read = read(pipe_fd[0], buffer, BUFFER_SIZE)) > 0) {
            total_bytes += bytes_read;
        }

        if (bytes_read == -1) {
            perror("read child error");
            exit(EXIT_FAILURE);
        }

        printf("Total characters read: %zd\n", total_bytes);
        close(pipe_fd[0]);
        exit(EXIT_SUCCESS);
    } else {  // Parent process
        char buffer[BUFFER_SIZE];
        close(pipe_fd[0]);
        ssize_t bytes_read;

        while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) {
            if (write(pipe_fd[1], buffer, bytes_read) == -1) {
                perror("write parent error");
                exit(EXIT_FAILURE);
            }
        }

        if (bytes_read == -1) {
            perror("read parent error");
            exit(EXIT_FAILURE);
        }

        close(pipe_fd[1]);
        close(fd);
        wait(NULL);
    }
    return 0;
}

Explanation


Exercise 4: Process Memory and Fork

Original Code

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h> 
#define SIZE 5
int nums[SIZE] = {0,1,2,3,4};
int main()
{
int i;
pid t pid;
pid = fork();
if (pid == 0) {
for (i = 0; i < SIZE; i++) {
nums[i] *= -i;
printf("%d ",nums[i]);LINE X 
}
}
else if (pid > 0) {
for (i = 0; i < SIZE; i++)
printf("PARENT: %d ",nums[i]); LINE Y 
}
return 0;
}

Solution

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h> 
#define SIZE 5
int nums[SIZE] = {0,1,2,3,4};
int main()
{
    int i;
    pid_t pid;
    pid = fork();
    if (pid == 0) {
        for (i = 0; i < SIZE; i++) {
            nums[i] *= -i;
            printf("%d ",nums[i]); /* LINE X */
        }
    }
    else if (pid > 0) {
        for (i = 0; i < SIZE; i++)
            printf("PARENT: %d ",nums[i]); /* LINE Y */
    }
    return 0;
}

Explanation


Exercise 5: File Opening and Permissions

Original Code

#include <unistd.h>
#include <fcntl.h>
int main(int argc, char argv)
{
int f = open(argv O_RDONLY);
printf("Opened file");
return 0;
}

Solution

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv){
    if (argc != 2) {
        perror("arg err");
    }

    int f = open(argv[1], O_RDONLY);
    if (f != -1) {
        printf("Opened file");
    }else {
        perror("err");
    }

    return 0;
}

Explanation


Exercise 6: File Permissions and Modes

Original Code

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char **argv)
{
    int file = open(argv, O_WRONLY);
    struct stat fileStat;
    if(lstat(file,&stat) < 0)    
    {
        return 1;
    } 
    printf( (S_ISDIR(stat.st_mode)) ? "d" : (S_ISLNK(stat.st_mode)) ? "l" : "-");
    printf( (stat.st_mode & S_IRUSR) ? "w" : "-");
    printf( (stat.st_mode & S_IWUSR) ? "x" : "-");
    printf( (stat.st_mode & S_IXUSR) ? "r" : "-");
    printf( (stat.st_mode & S_IRGRP) ? "w" : "-");
    printf( (stat.st_mode & S_IWGRP) ? "x" : "-");
    printf( (stat.st_mode & S_IXGRP) ? "r" : "-");
    printf( (stat.st_mode & S_IROTH) ? "w" : "-");
    printf( (stat.st_mode & S_IWOTH) ? "x" : "-");
    printf( (stat.st_mode & S_IXOTH) ? "r" : "-");
    close(file);
    return 0;
}

Solution

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return 1;
    }

    const char *filename = argv[1];
    /*
    mode_t permissions = S_IRUSR | S_IRGRP | S_IROTH |  // Read permissions for all
                         S_IXGRP | S_IXOTH |           // Execute permissions for group and others
                         S_IWOTH;                      // Write permission for others

    // or using octa:
    */
    //mode_t permissions = 0457;
    int permissions = 0457;

    // Open the file with O_CREAT and the desired permissions
    int file = open(filename, O_WRONLY | O_CREAT, permissions);
    if (file < 0) {
        perror("open");
        return 1;
    }
    close(file);

    // Explicitly set the permissions using chmod to override umask
    if (chmod(filename, permissions) < 0) {
        perror("chmod");
    }

    // lstat for file metadata
    struct stat fileStat;
    if (lstat(filename, &fileStat) < 0) {
        perror("lstat");
    }

    // File type
    // it is fileStat.st_mode 
    printf((S_ISDIR(fileStat.st_mode)) ? "d" :
           (S_ISLNK(fileStat.st_mode)) ? "l" : "-");
    // Owner permissions
    printf((fileStat.st_mode & S_IRUSR) ? "r" : "-");
    printf((fileStat.st_mode & S_IWUSR) ? "w" : "-");
    printf((fileStat.st_mode & S_IXUSR) ? "x" : "-");
    // Group permissions
    printf((fileStat.st_mode & S_IRGRP) ? "r" : "-");
    printf((fileStat.st_mode & S_IWGRP) ? "w" : "-");
    printf((fileStat.st_mode & S_IXGRP) ? "x" : "-");
    // Other permissions
    printf((fileStat.st_mode & S_IROTH) ? "r" : "-");
    printf((fileStat.st_mode & S_IWOTH) ? "w" : "-");
    printf((fileStat.st_mode & S_IXOTH) ? "x" : "-");
    printf("\n");
    return 0;
}

Explanation


Exercise 7: Process Spawn Based on Executable Name

Original Code

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>

// Function to check if a character is a vowel
int isvowel(char ch) {
    char lower = tolower(ch);
    return (lower == 'a' || lower == 'e' || lower == 'i' || lower == 'o' || lower == 'u');
}

// Function to check if a character is a consonant
int isconsonant(char ch) {
    char lower = tolower(ch);
    return (lower >= 'a' && lower <= 'z' && !isvowel(lower));
}

// Function to extract the executable name from the full path
char *get_executable_name(char *path) {
    char *name = strrchr(path, '/'); // Find the last '/' in the path
    return (name == NULL) ? path : name + 1; // Return the name after the last '/'
}

int main(int argc, char **argv) {
    if (argc < 1) {
        fprintf(stderr, "Error: No executable name provided.\n");
        return 1;
    }

    char *path = argv[0];
    char *name = get_executable_name(path); // Extract the executable name
    int count_v = 0, count_c = 0;

    // Check if the executable name starts with a vowel
    if (isvowel(name[0])) {
        pid_t c1 = fork();

        if (c1 == 0) {
            // Child process: Count vowels
            for (int i = 0; i < strlen(name); i++) {
                if (isvowel(name[i])) {
                    count_v++;
                }
            }
            printf("Number of vowels: %d\n", count_v);
            return 0;
        } else if (c1 == -1) {
            perror("Error creating child process");
            return 2;
        } else {
            // Parent process
            wait(NULL);
            printf("Testing executable name starting with a vowel: %s\n", path);
        }
    } else {
        // Executable name does not start with a vowel: Create 2 child processes
        pid_t c2 = fork();

        if (c2 == 0) {
            // First child process: Count consonants
            for (int i = 0; i < strlen(name); i++) {
                if (isconsonant(name[i])) {
                    count_c++;
                }
            }
            printf("Number of consonants: %d\n", count_c);
            return 0;
        } else if (c2 == -1) {
            perror("Error creating first child process");
            return 3;
        } else {
            // Parent process creates the second child
            pid_t c3 = fork();

            if (c3 == 0) {
                // Second child process: Count consonants
                for (int i = 0; i < strlen(name); i++) {
                    if (isconsonant(name[i])) {
                        count_c++;
                    }
                }
                printf("Number of consonants: %d\n", count_c);
                return 0;
            } else if (c3 == -1) {
                perror("Error creating second child process");
                return 4;
            } else {
                // Parent process waits for both children
                wait(NULL);
                wait(NULL);
                printf("Testing executable name not starting with a vowel: %s\n", path);
            }
        }
    }

    return 0;
}

Explanation


Exercise 8: Reading from Standard Input

Original Code

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    unsigned int c;
    read(STDIN_FILENO, &c, 4);
    printf("Read character %x", c);
}

Explanation


Exercise 9: Signal Handling

Original Code

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void handle_sigstp(int signum)
{
    sa.sa_handler = &handle_sigstp;
    sa.sa_flags = SA_RESTART;
    sigaction(SIGSTOP, &sa, NULL);
}

int main()
{
    printf("STOP signal received\n");
    return 0;
}

Solution

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void handle_sigstp(int signum){
    printf("STOP sig recv\n");
}

int main(){
    struct sigaction sa;

    sa.sa_handler = &handle_sigstp;
    sa.sa_flags = SA_RESTART;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGTSTP, &sa, NULL) == -1) {
        perror("error sigaction");
        return 1;
    }

    while (1) {
        pause();
    }

    return 0;
}

Explanation


Exercise 10: Thread-Based File Reading

Original Code

// Structure to hold thread-specific data
typedef struct 
{
 int start; 
 int end; 
 char *filename; 
} thread_info;

void *countCharacters(void *threadarg)
{
    thread_info *threadData;
    int start, end;
    FILE *file;
    char ch;
    threadData = (thread_info *)threadarg;
    start = threadData->start;
    file = fopen(threadData->filename, "r");
    if (!file) {
        perror("Error opening file");
        pthread_exit(NULL);
    }
    fseek(file, start, SEEK_SET);
    fclose(file);
    totalCount++
    pthread_exit(NULL);
}

Solution

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>

// long is used for file chunking.
typedef struct {
    long start;
    long end;
    const char *filename;
    long count; // thread individual count
} thread_info;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
long totalCount = 0; // global for all threads accesable..

void *countCharacters(void *threadarg) {
    thread_info *threadData = (thread_info *)threadarg;
    FILE *file;
    int ch;
    long localCount = 0;

    file = fopen(threadData->filename, "r");
    if (!file) {
        perror("Error opening file");
        pthread_exit(NULL);
    }

    if (fseek(file, threadData->start, SEEK_SET) != 0) {
        perror("Error seeking in file");
        fclose(file);
        pthread_exit(NULL);
    }

    // iterating through chunks.
    while (ftell(file) < threadData->end && (ch = fgetc(file)) != EOF) {
        localCount++;
    }

    threadData->count = localCount;

    // mutex are needed here to counter race conditions.
    pthread_mutex_lock(&mutex);
    totalCount += localCount;
    pthread_mutex_unlock(&mutex);

    fclose(file);
    pthread_exit(NULL);
}

Explanation



To solve exercises you need to understand the following concepts:

General Concepts:

  1. System Calls and Library Functions:

    • fork(), open(), read(), write(), close(), wait(), pipe(), fseek(), fopen(), fgetc(), fclose(), sigaction(), pause()).
  2. Error Handling:

    • Using perror()
    • Since the proff doesn't care to see this. you can just use printf() and return or exit
  3. Process Management:

    • fork() and wait() logic
  4. Inter-process (pipes):

    • fd and pipes, read, write to buffer, and closing pipes.
  5. File Management:

    • Opening files with appropriate flags (O_RDONLY, O_WRONLY).
    • or OCTAL representation.
    • Understanding file permissions and how to set them using chmod().
    • keep in mind the restrictions by umask
  6. Signal Handling:

    • Struct Signals.
    • Setting up signal handlers using `sigaction().
    • Understanding signal types (e.g., SIGTSTP) and their handling.
    • SIGSTOP and SIGTSTP (force termination and pause)
  7. Thread Programming:

    • pthread_create().
    • mutexes and semaphores.
    • advanced pointers for casting and using structs for passing information with threads to working function.
  8. Character and String Operations:

    • isalnum().
    • <ctype.h>
  9. Endianness and Data Representation:

    • Understanding how data is stored in memory (little-endian vs. big-endian).
    • Handling byte order conversions.

concepts and Traps per exercise:

  1. Exercise 1:

    • File handling with open(), read(), and close().
    • TRAP was isalpha() which checks for alphabetic only!
    • request was alphanumeric which is:
    • isalnum() to check for alphanumeric characters.
  2. Exercise 2:

    • fork().
    • Synchronizing with wait().
  3. Exercise 3:

    • pipe().
    • read and write to pipes
  4. Exercise 4:

    • manual compile and see results.
    • memory separation in parent and child processes.
  5. Exercise 5:

    • Opening files with appropriate permissions.
    • Basic error handling for file operations.
    • ALL PERMISION FLAGS
  6. Exercise 6:

    • file permissions using chmod().
    • Retrieving file metadata with lstat().
    • Displaying file type and permissions.
    • the umask trap where you can't add permisions, use chmod().
  7. Exercise 7:

    • pass trough args
    • checking if a character is a vowel or consonant. you have to write your own functions for this.
    • A LOT of logic, and you need to keep track of processes and logic for consonants and vowels.
  8. Exercise 8:

    • read().
    • understanding endianness and data representation.
    • ASCII / hexa
    • you can print a lot in other exercises fields, if you have time.
  9. Exercise 9:

    • Handling signals with `sigaction().
    • Handling signals with STRUCTS!
    • Using pause() to wait for signals.
    • Setting up signal handlers.
    • Trap: was the SIGSTOP and SIGTSTP (which are not the same thing.)
  10. Exercise 10:

    • Using threads to read different parts of a file.
    • file chunking with struct info carry threads.
    • Synchronizing threads with mutexes.
    • thread-specific data structures, casting void pointers for struct info carry threads.

END OF exam