Operating Systems 2026F: Assignment 3

From Soma-notes

Please submit the answers to the following questions via Brightspace by November 23, 2026. There are 20 points in 7 questions.

Submit your answers as a plain text file following this template. Name your answer file "comp3000-a3-<username>.txt" (where username is your MyCarletonOne username). Please make sure to use your correct student ID number otherwise your grades may not be properly recorded.

Your answers will be parsed by a script in order to help with grading so please preserve the format of the template. No other formats will be accepted.

Note that the file should be a UNIX text file, not a Windows text file (so LF line endings). See the Wikipedia page on text files to learn more. Most modern text editors can convert between different types of text files. Alternately, install the program dos2unix on the VM to convert.

You may use this validator page to make sure your answer file is properly named and formatted.

Don't forget to include what outside resources you used to complete each of your answers, including other students, man pages, and web resources. You do not need to list help from the instructor, TA, or information found in the textbook.

Questions

Please answer the following questions about the code below. Note that your code changes should make use of existing program functionality, either from this assignment's code or code from the Tutorials, and should require adding as little code new as possible. Solutions that implement functionality from scratch where not needed will not receive credit.

  1. [2] The 3000cp programs copy a file from source to destination. The current versions overwrite the destination file if it already exists. How could you make 3000cp-fifo generate an error if the destination already exists?
  2. [2] On lines 128 and 131, 3000cp-fifo calls close. Are these calls important? Why?
  3. [2] How could you change 3000cp-fifo so the destination file has the same permissions as the source file, always?
  4. [4] How could you make 3000cp-fifo, with the -u option, make the destination file's owner and group be the same as the original file, no matter what those were for the original file? How could you make this version work properly if called by any user?
  5. [4] How could you change 3000cp-fifo so that with the -x option it only copies a file if the destination is a different filesystem than the source? Why does your solution work? How did you test it?
  6. [2] Does 3000cp-rendezvous work consistently? How did you test it? Note that you can make arbitrarily sized random files using the dd command. For example, dd if=/dev/urandom of=testfile bs=4096 count=4 will produce a file with exactly 16,384 random bytes.
  7. [4] How could you modify 3000cp-rendezvous so it used the same timeout strategy as 3000pc-rendezvous-timeout? Does the modified version behave significantly differently from the original? At minimum, present timing test results.

Code

Source: .tar.gz, .zip

3000cp-fifo.c

/* 3000cp-fifo.c  Simple producer-consumer with a fifo
 * Original Version Copyright (C) 2026  Anil Somayaji
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>. */

/* You really shouldn't be incorporating parts of this in any other code,
   it is meant for teaching, not production */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>

#define BUFSIZE 4096

void report_error(char *error)
{
        fprintf(stderr, "Error: %s\n", error);
}

void usage_exit(char *progname)
{
        fprintf(stderr,
                "Usage: %s <source file> <dest file>\n",
                progname);
        exit(-1);
}

void read_file(char *fn, int out_fd)
{
        char buf[BUFSIZE];
        int in_fd;
        ssize_t read_count, write_count;

        in_fd = open(fn, O_RDONLY);

        if (in_fd == -1) {
                report_error("Couldn't open file for reading\n");
                close(out_fd);
                exit(-1);
        }

        while ((read_count = read(in_fd, buf, BUFSIZE)) > 0) {
                write_count = write(out_fd, buf, read_count);
                if (write_count != read_count) {
                        report_error("Problem sending data to writer.");
                        close(out_fd);
                        exit(-1);
                }
        }

        close(in_fd);
        close(out_fd);
        printf("File successfully read and sent.\n");
        exit(0);
}

void write_file(char *fn, int in_fd)
{
        char buf[BUFSIZE];
        int out_fd;
        ssize_t read_count, write_count;

        out_fd = creat(fn, 0666);

        if (out_fd == -1) {
                report_error("Couldn't open file for writing\n");
                close(in_fd);
                exit(-1);
        }

        while ((read_count = read(in_fd, buf, BUFSIZE)) > 0) {
                write_count = write(out_fd, buf, read_count);
                if (write_count != read_count) {
                        report_error("Problem writing file to disk.");
                        exit(-1);
                }
        }

        close(in_fd);
        close(out_fd);
        printf("File successfully received and copied.\n");
        exit(0);
}

int main(int argc, char *argv[])
{
        int pid;
        int pipefd[2];
        char *source_fn, *dest_fn;

        if (argc < 3) {
                if (argc < 2) {
                        report_error("no command line");
                        usage_exit(argv[0]);
                } else {
                        report_error("Not enough arguments");
                        usage_exit(argv[0]);
                }
        }
        
        source_fn = argv[1];
        dest_fn = argv[2];

        if (pipe(pipefd)) {
                report_error("Unable to open pipe");
                exit(-1);
        }
        
        pid = fork();
        
        if (pid == 0) {
                close(pipefd[0]);
                read_file(source_fn, pipefd[1]);
        } else {
                close(pipefd[1]);
                write_file(dest_fn, pipefd[0]);
        }

        return -1;
}

3000cp-rendezvous.c

/* 3000cp-rendezvous.c  Simple producer-consumer with shared memory
 * Copyright (C) 2026  Anil Somayaji
 * based on code Copyright (C) 2020  William Findlay
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>. */

/* You really shouldn't be incorporating parts of this in any other code,
   it is meant for teaching, not production */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>

#define QUEUESIZE 32
#define BUFSIZE 4096

typedef struct entry {
        char buf[BUFSIZE];
        ssize_t count;
        sem_t lock;
} entry;

typedef struct shared {
        pthread_mutex_t cond_mutex;
        pthread_cond_t  queue_nonempty;
        pthread_cond_t  queue_nonfull;
        entry queue[QUEUESIZE];
        int last_produced;
        int last_consumed;
        pid_t prod_pid;
        pid_t con_pid;
        int prod_count;
        int con_count;
} shared;


void report_error(char *error)
{
        fprintf(stderr, "Error: %s\n", error);
}

void usage_exit(char *progname)
{
        fprintf(stderr,
                "Usage: %s <source file> <dest file>\n",
                progname);
        exit(-1);
}

void wait_for_producer(shared *s)
{
        if (s->prod_pid != 0) {
                pthread_mutex_lock(&s->cond_mutex);
                pthread_cond_wait(&s->queue_nonempty, &s->cond_mutex);
                pthread_mutex_unlock(&s->cond_mutex);
        }
}

void wait_for_consumer(shared *s)
{
        if (s->con_pid != 0) {
                pthread_mutex_lock(&s->cond_mutex);
                pthread_cond_wait(&s->queue_nonfull, &s->cond_mutex);
                pthread_mutex_unlock(&s->cond_mutex);
        }
}

int queue_buffer(char *buf, ssize_t count, shared *s)
{
        entry *e;
        int current, retval;

        current = (s->last_produced + 1) % QUEUESIZE;

        e = &s->queue[current];

        sem_wait(&e->lock);

        if (e->count != 0) {
                sem_post(&e->lock);
                wait_for_consumer(s);
                sem_wait(&e->lock);
        }

        if (e->count != 0) {
                fprintf(stderr, "ERROR: No room for producer after waiting!\n");
                retval = -1;
                goto done;
        } else {
                memcpy(e->buf, buf, count);
                e->count = count;
                s->last_produced = current;
                s->prod_count++;
                /* Notify that queue is nonempty */
                pthread_cond_signal(&s->queue_nonempty);
                retval = 0;
                goto done;
        }

 done:
        sem_post(&e->lock);
        return retval;

}

ssize_t get_next_buffer(char *buf, shared *s)
{
        entry *e;
        int current;
        ssize_t count;

        current = (s->last_consumed + 1) % QUEUESIZE;

        e = &s->queue[current];

        sem_wait(&e->lock);

        if (e->count == 0) {
                /* producer hasn't filled in this entry yet */
                sem_post(&e->lock);
                wait_for_producer(s);
                sem_wait(&e->lock);
        }

        if (e->count == 0 && s->prod_pid != 0) {
                fprintf(stderr, "ERROR: Nothing for consumer after waiting!\n");
                count = -1;
                goto done;
        } else {
                count = e->count;
                memcpy(buf, e->buf, count);
                e->count = 0;
                s->last_consumed = current;
                s->con_count++;
                pthread_cond_signal(&s->queue_nonfull);
                goto done;
        }

 done:
        sem_post(&e->lock);
        return count;
}

void read_file(char *fn, shared *s)
{
        char buf[BUFSIZE];
        int in_fd;
        ssize_t read_count;
        int found_data = 0;

        in_fd = open(fn, O_RDONLY);

        if (in_fd == -1) {
                report_error("Couldn't open file for reading\n");
                s->prod_pid = 0;
                exit(-1);
        }

        while ((read_count = read(in_fd, buf, BUFSIZE)) > 0) {
                found_data = 1;
                queue_buffer(buf, read_count, s);
        }

        close(in_fd);
        s->prod_pid = 0;
        if (found_data > 0) {
                printf("File successfully read and sent.\n");
        } else {
                printf("No data read from file.\n");                
        }
        exit(0);
}

void write_file(char *fn, shared *s)
{
        char buf[BUFSIZE];
        int out_fd;
        ssize_t read_count, write_count;

        out_fd = creat(fn, 0666);
        
        if (out_fd == -1) {
                report_error("Couldn't open file for writing\n");
                s->con_pid = -1;
                exit(-1);
        }

        read_count = 1;
        while (read_count > 0) {
                read_count = get_next_buffer(buf, s);

                if (read_count == 0) {
                        if (s->prod_pid == 0) {
                                break;
                        }
                } else {
                        write_count = write(out_fd, buf, read_count);

                        if (write_count != read_count) {
                                report_error("Problem writing file to disk.");
                                s->con_pid = 0;
                                exit(-1);
                        }
                }
        }
        
        s->con_pid = 0;
        close(out_fd);
        printf("File successfully received and copied.\n");
        exit(0);
}

void init_shared(shared *s)
{
        int i;

        pthread_mutexattr_t mattr = {};
        pthread_mutexattr_setpshared(&mattr, 1);
        pthread_mutex_init(&s->cond_mutex, &mattr);

        pthread_condattr_t cattr = {};
        pthread_condattr_setpshared(&cattr, 1);
        pthread_cond_init(&s->queue_nonempty, &cattr);
        pthread_cond_init(&s->queue_nonfull, &cattr);

        s->last_consumed = -1;
        s->last_produced = -1;

        s->prod_pid = -1;
        s->con_pid  = -1;

        s->prod_count = 0;
        s->con_count  = 0;

        for (i=0; i<QUEUESIZE; i++) {
                s->queue[i].count = 0;
                sem_init(&s->queue[i].lock, 1, 1);
        }
}

int main(int argc, char *argv[])
{
        int pid;
        shared *s;
        char *source_fn, *dest_fn;

        if (argc < 3) {
                if (argc < 1) {
                        report_error("no command line");
                        usage_exit(argv[0]);
                } else {
                        report_error("Not enough arguments");
                        usage_exit(argv[0]);
                }
        }
        
        source_fn = argv[1];
        dest_fn = argv[2];

        s = (shared *) mmap(NULL, sizeof(shared),
                             PROT_READ|PROT_WRITE,
                             MAP_SHARED|MAP_ANONYMOUS, -1, 0);

        if (s == MAP_FAILED)
        {
                fprintf(stderr, "Error: Unable to mmap: %s\n", strerror(errno));
                exit(-1);
        }

        init_shared(s);

        pid = fork();

        if (pid == 0) {
                s->prod_pid = getpid();
                read_file(source_fn, s);
        } else {
                s->con_pid = getpid();
                write_file(dest_fn, s);
        }

        return -1;
}