Computer Systems Fundamentals

7 ways to print Hello, stdio!
#include <stdio.h>

int main(void) {
    char bytes[] = "Hello, stdio!\n"; // 15 bytes

    // write 14 bytes so we don't write (terminating) 0 byte
    for (int i = 0; i < (sizeof bytes) - 1; i++) {
        fputc(bytes[i], stdout);
    }

    // or as we know bytes is 0-terminated
    for (int i = 0; bytes[i] != '\0'; i++) {
        fputc(bytes[i], stdout);
    }

    // or if you prefer pointers
    for (char *p = &bytes[0]; *p != '\0'; p++) {
        fputc(*p, stdout);
    }

    // fputs relies on bytes being 0-terminated
    fputs(bytes, stdout);

    // write 14 1 byte items
    fwrite(bytes, 1, (sizeof bytes) - 1, stdout);

    // %s relies on bytes being 0-terminated
    fprintf(stdout, "%s", bytes);
    
    // %s relies on bytes being 0-terminated
    printf("%s", bytes);

    return 0;
}
create file "hello.txt" containing 1 line: Hello, Zac! if the file already exists it will truncate the data in the file, (bascially overwriting it)
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    FILE *output_stream = fopen("hello.txt", "w");
    if (output_stream == NULL) {
        perror("hello.txt");
        return 1;
    }

    fprintf(output_stream, "Hello, Zac!\n");

    // fclose will flush data to file, best to close file ASAP
    // optional here as fclose occurs automatically on exit
    fclose(output_stream);

    return 0;
}
create file "hello.txt" containing 1 line: Hello, Zac! if the file already exists it will append the data to the end of the file
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    FILE *output_stream = fopen("hello.txt", "a");
    if (output_stream == NULL) {
        perror("hello.txt");
        return 1;
    }

    fprintf(output_stream, "Hello, Zac!\n");

    // fclose will flush data to file, best to close file ASAP
    // optional here as fclose occurs automatically on exit
    fclose(output_stream);

    return 0;
}

Opens file "hello.txt" and read and print out one byte
#include <stdio.h>

int main(void){
    FILE *in = fopen("hello.txt", "r");
    if (in == NULL) {
        perror("hello.txt");
        return 1;
    }
    
    int c = fgetc(in); 
    if ( c == EOF ){
        printf("There is nothing to read\n");
    } else {
        printf("I just read: ");
	fputc(c,stdout); //Could just use putchar(ch);
	printf("\n");
    }
    
    fclose(in);
    return 0;
}

Opens a file named "hello.txt" and reads each byte from the file and prints it to stdout
The program closes the file at the end
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>

int main(void){
    FILE *in = fopen("hello.txt", "r");
    if (in == NULL) {
        perror("hello.txt");
        return 1;
    }
    int ch;
    while ((ch = fgetc(in)) != EOF)
        fputc(ch,stdout); //Could just use putchar(ch);
   
   fclose(in);
   return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>

//stdin     0
//stdout    1
//stderr    2 
//1021
//1024

int main(void){

   FILE * in;  
   int ch;
   int counter = 0;
   while (1) {
      printf("File %d\n",counter);
      if ((in = fopen("abc", "r")) == NULL){
         perror("");
         exit(1);
      }
      while ((ch = fgetc(in)) != EOF)
         fputc(ch, stdout);
      
      counter++;
      //fclose(in);
   }
   return 0;
}
cp <file1> <file2> implemented with fgetc
#include <stdio.h>

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

    FILE *input_stream = fopen(argv[1], "r");
    if (input_stream == NULL) {
        perror(argv[1]);  // prints why the open failed
        return 1;
    }

    FILE *output_stream = fopen(argv[2], "w");
    if (output_stream == NULL) {
        perror(argv[2]);
        return 1;
    }

    int c; // not char!
    while ((c = fgetc(input_stream)) != EOF) {
        fputc(c, output_stream);
    }


    fclose(input_stream);  // optional here as fclose occurs
    fclose(output_stream); // automatically on exit

    return 0;
}
#include <stdio.h>
//Fine for text files
//Breaks binary files!!!!!
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source file> <destination file>\n", argv[0]);
        return 1;
    }

    FILE *input_stream = fopen(argv[1], "r");
    if (input_stream == NULL) {
        perror(argv[1]);  // prints why the open failed
        return 1;
    }

    FILE *output_stream = fopen(argv[2], "w");
    if (output_stream == NULL) {
        perror(argv[2]);
        return 1;
    }
    char buffer[BUFSIZ];
     
    // '\n' 
    while (fgets(buffer,BUFSIZ,input_stream) != NULL) {
        fputs(buffer, output_stream);
    }

    // close occurs automatically on exit
    // so these lines not needed
    fclose(input_stream);
    fclose(output_stream);

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>


//stdout is line buffered
//setbuf(stdout,NULL); //helpful for debugging
//fflush(stdout);



int main(void){  
   printf("Hello\n");         //stored
   printf("Goodbye");
   printf("Potato");
   
   int *p = NULL;             // 
   *p = *p + 1;               //Crash!!!! Runtime error 
                       
   return 0;                  //anything in buffer gets printed
}
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>

// Try running this and check the contents of MyFile.txt
// Then uncomment the //fflush(fp) line recompile and rerun, then check
// the contents of MyFile.txt
// Writing to a file is fully buffered

int main(void){ 
   FILE * fp = fopen("MyFile.txt","w");
   fprintf(fp,"Important information\n");  
   //fflush(fp);
   int *p = NULL;
   *p = *p + 1;
   
   fclose(fp); //flush the buffer
   return 0;
}
use fseek to access diferent bytes of a file with no error checking
the return value of the calls to fopen, fseek and fgetc should be checked to see if they worked!
#include <stdio.h>

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

    FILE *input_stream = fopen(argv[1], "r");

    // move to a position 1 byte from end of file
    // then read 1 byte
    fseek(input_stream, -1, SEEK_END);
    printf("last byte of the file is 0x%02x\n", fgetc(input_stream));

    // move to a position 0 bytes from start of file
    // then read 1 byte
    fseek(input_stream, 0, SEEK_SET);
    printf("first byte of the file is 0x%02x\n", fgetc(input_stream));

    // move to a position 41 bytes from start of file
    // then read 1 byte
    fseek(input_stream, 41, SEEK_SET);
    printf("42nd byte of the file is 0x%02x\n", fgetc(input_stream));

    // move to a position 58 bytes from current position
    // then read 1 byte
    fseek(input_stream, 58, SEEK_CUR);
    printf("100th byte of the file is 0x%02x\n", fgetc(input_stream));

    return 0;
}
use fseek to change a random bit in a file supplied as a command-line argument for simplicty no error checking is done good code would check the return values of the calls to fopen, fseek, fgetc, fputc
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

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


    FILE *f = fopen(argv[1], "r+");    // open for reading and writing

    fseek(f, 0, SEEK_END);             // move to end of file

    long n_bytes = ftell(f);           // get number of bytes in file

    srandom(time(NULL));               // initialize random number
                                       // generator with current time

    long target_byte = random() % n_bytes; // pick a random byte

    fseek(f, target_byte, SEEK_SET);  // move to byte

    int byte = fgetc(f);              // read byte


    int bit = random() % 8;           // pick a random bit

    int new_byte = byte ^ (1 << bit); // flip the bit

    fseek(f, -1, SEEK_CUR);           // move back to same position


    fputc(new_byte, f);               // write the byte

    fclose(f);

    printf("Changed byte %ld of %s from %02x to %02x\n",target_byte, argv[1], byte, new_byte);
    return 0;
}
hello world implemented with a direct syscall

This isn't portable or readable but shows us what system calls look like.
#include <unistd.h>

int main(void) {
    char bytes[13] = "Hello, Zac!\n";

    // argument 1 to syscall is the system call number, 1 is write
    // remaining arguments are specific to each system call

    // write system call takes 3 arguments:
    //   1) file descriptor, 1 == stdout
    //   2) memory address of first byte to write
    //   3) number of bytes to write

    syscall(1, 1, bytes, 12); // prints Hello, Zac! on stdout

    return 0;
}

#include <unistd.h>

#define O_RDONLY 00
#define O_WRONLY 01
#define O_CREAT  0100
#define O_TRUNC  01000

// cp <file1> <file2> with syscalls and no error handling
int main(int argc, char *argv[]) {

    // system call number 2 is open, takes 3 arguments:
    //   1) address of zero-terminated string containing file pathname
    //   2) bitmap indicating whether to write, read, ... file
    //      O_WRONLY | O_CREAT == 0x41 == write to file, creating if necessary
    //   3) permissions if file will be newly created
    //      0644 == readable to everyone, writeable by owner

    long read_file_descriptor  = syscall(2, argv[1], O_RDONLY,           0);
    long write_file_descriptor = syscall(2, argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

    while (1) {

        // system call number 0 is read -  takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to put bytes read
        //   3) maximum number of bytes read
        // returns number of bytes actually read
        char bytes[4096];
        long bytes_read = syscall(0, read_file_descriptor, bytes, 4096);

        if (bytes_read <= 0) {
            break;
        }

        // system call number 1 is write - takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to take bytes from
        //   3) number of bytes to written
        // returns number of bytes actually written

        syscall(1, write_file_descriptor, bytes, bytes_read);
    }

    return 0;
}
hello world implemented with libc
#include <unistd.h>

int main(void) {
    char bytes[13] = "Hello, Zac!\n";

    // write takes 3 arguments:
    //   1) file descriptor, 1 == stdout
    //   2) memory address of first byte to write
    //   3) number of bytes to write

    write(1, bytes, 12); // prints Hello, Zac! on stdout

    return 0;
}

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

// cp <file1> <file2> implemented with libc and no error handling
int main(int argc, char *argv[]) {

    // open takes 3 arguments:
    //   1) address of zero-terminated string containing pathname of file to open
    //   2) bitmap indicating whether to write, read, ... file
    //   3) permissions if file will be newly created
    //      0644 == readable to everyone, writeable by owner
    int read_file_descriptor = open(argv[1], O_RDONLY);
    int write_file_descriptor = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

    while (1) {

        // read takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to put bytes read
        //   3) maximum number of bytes read
        // returns number of bytes actually read

        char bytes[4096];
        ssize_t bytes_read = read(read_file_descriptor, bytes, 4096);
        if (bytes_read <= 0) {
            break;
        }

        // write takes 3 arguments:
        //   1) file descriptor
        //   2) memory address to take bytes from
        //   3) number of bytes to written
        // returns number of bytes actually written

        write(write_file_descriptor, bytes, bytes_read);
    }

    // good practice to close file descriptions as soon as finished using them
    // not necessary needed here as program about to exit
    close(read_file_descriptor);
    close(write_file_descriptor);

    return 0;
}
cp <file1> <file2> implemented with libc and *zero* error handling
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[]) {
    // copy bytes one at a time from  pathname passed as
    // command-line argument 1 to pathname given as argument 2
    int read_file_descriptor = open(argv[1], O_RDONLY);
    int write_file_descriptor = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    while (1) {
        char bytes[1];
        ssize_t bytes_read = read(read_file_descriptor, bytes, 1);
        if (bytes_read <= 0) {
            break;
        }
        write(write_file_descriptor, bytes, 1);
    }

    return 0;
}

An example showing how to use stat and extract information about the file type and access permissions
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char* argv[]){
    struct stat buf;  
  
    if(stat("testDir",&buf) < 0){
        perror("");
        return 1;
    }
    printf("size: %ld\n", buf.st_size );
    
    printf("mode %d %o\n",buf.st_mode,buf.st_mode);
       
    //man inode for more details                    
    if ( buf.st_mode & S_IRUSR){
        printf("Owner can read\n");
    } else {
        printf("Owner can't read\n");
    }
  
    
    if(S_ISREG(buf.st_mode)){
        printf("This is a file\n");
    } else {
        printf("This is not a file\n");
    }
    
    if(S_ISDIR(buf.st_mode)){
        printf("This is a dir\n");
    } else {
        printf("This is not a dir\n");
    }
    
    return 0;
}
call stat on each command line argument as simple example of its use
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

void stat_file(char *pathname);

int main(int argc, char *argv[]) {
    for (int arg = 1; arg < argc; arg++) {
        stat_file(argv[arg]);
    }
    return 0;
}

void stat_file(char *pathname) {
    printf("stat(\"%s\", &s)\n", pathname);

    struct stat s;
    if (stat(pathname, &s) != 0) {
        perror(pathname);
        exit(1);
    }

    printf("ino =  %10ld # Inode number\n", s.st_ino);
    printf("mode = %10o # File mode \n", s.st_mode);
    printf("nlink =%10ld # Link count \n", (long)s.st_nlink);
    printf("uid =  %10u # Owner uid\n", s.st_uid);
    printf("gid =  %10u # Group gid\n", s.st_gid);
    printf("size = %10ld # File size (bytes)\n", (long)s.st_size);

    printf("mtime =%10ld # Modification time (seconds since 1/1/70)\n",
           (long)s.st_mtime);
}
call lstat on each command line argument as simple example of its use lstat differs from stat only for symbolic links, where it will get the information about the link itself instead of the file it links to
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

void lstat_file(char *pathname);

int main(int argc, char *argv[]) {
    for (int arg = 1; arg < argc; arg++) {
        lstat_file(argv[arg]);
       
    }
    return 0;
}

void lstat_file(char *pathname) {
    printf("stat(\"%s\", &s)\n", pathname);

    struct stat s;
  
    if (lstat(pathname, &s) != 0) {
        perror(pathname);
        exit(1);
    }
   

    printf("ino =  %10ld # Inode number\n", s.st_ino);
    printf("mode = %10o # File mode \n", s.st_mode);
    printf("nlink =%10ld # Link count \n", (long)s.st_nlink);
    printf("uid =  %10u # Owner uid\n", s.st_uid);
    printf("gid =  %10u # Group gid\n", s.st_gid);
    printf("size = %10ld # File size (bytes)\n", (long)s.st_size);

    printf("mtime =%10ld # Modification time (seconds since 1/1/70)\n",
           (long)s.st_mtime);
}

$ dcc mkdir.c
$ ./a.out new_dir
$ ls -ld new_dir
drwxr-xr-x 2 z5555555 z5555555 60 Oct 29 16:28 new_dir
$

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

// create the directories specified as command-line arguments
int main(int argc, char *argv[]) {

    for (int arg = 1; arg < argc; arg++) {
        if (mkdir(argv[arg], 0755) != 0) {
            perror(argv[arg]);  // prints why the mkdir failed
            return 1;
        }
    }

    return 0;
}

$ dcc chmod.c
$ ls -l chmod.c
-rw-r--r-- 1 z5555555 z5555555 746 Nov  4 08:20 chmod.c
$ ./a.out 600 chmod.c
$ ls -l chmod.c
-rw------- 1 z5555555 z5555555 787 Nov  4 08:22 chmod.c
$ ./a.out 755 chmod.c
chmod.c 755
$ ls -l chmod.c
-rwxr-xr-x 1 z5555555 z5555555 787 Nov  4 08:22 chmod.c
$

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

// change permissions of the specified files
int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <mode> <files>\n", argv[0]);
        return 1;
    }

    char *end;
    // first argument is mode in octal
    mode_t mode = strtol(argv[1], &end, 8);

    // check first argument was a valid octal number
    if (argv[1][0] == '\0' || end[0] != '\0') {
        fprintf(stderr, "%s: invalid mode: %s\n", argv[0], argv[1]);
        return 1;
    }

    for (int arg = 2; arg < argc; arg++) {
        if (chmod(argv[arg], mode) != 0) {
            perror(argv[arg]);  // prints why the chmod failed
            return 1;
        }
    }

    return 0;
}
$ dcc rm.c
$ ./a.out rm.c
$ ls -l rm.c
ls: cannot access 'rm.c': No such file or directory
$

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

// remove the specified files
int main(int argc, char *argv[]) {

    for (int arg = 1; arg < argc; arg++) {
        if (unlink(argv[arg]) != 0) {
            perror(argv[arg]);  // prints why the unlink failed
            return 1;
        }
    }

    return 0;
}
$ dcc rename.c
$ ./a.out rename.c renamed.c
$ ls -l  renamed.c
renamed.c
$

#include <stdio.h>

// rename the specified file
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <old-filename> <new-filename>\n",
                argv[0]);
        return 1;
    }
    char *old_filename = argv[1];
    char *new_filename = argv[2];
    if (rename(old_filename, new_filename) != 0) {
        fprintf(stderr, "%s rename %s %s:", argv[0], old_filename,
                new_filename);
        perror("");
        return 1;
    }

    return 0;
}
a non working attempt at cd chdir() affects only this process and any it runs
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc > 1 && chdir(argv[1]) != 0) {
        perror("chdir");
        return 1;
    }
    return 0;
}
broken attempt to implement cd chdir() affects only this process and any it runs this versions prints out the current working directory while this process is running
#include <unistd.h>
#include <stdio.h>
#include <limits.h>

int main(int argc, char *argv[]) {
    if (argc > 1 && chdir(argv[1]) != 0) {
        perror("chdir");
        return 1;
    }
    char pathname[PATH_MAX];
    if (getcwd(pathname, sizeof pathname) == NULL) {
        perror("getcwd");
        return 1;
    }
    printf("getcwd() returned %s\n", pathname);

    return 0;
}
getcwd and chdir example
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    // use repeated chdir("..") to climb to root of the file system
    char pathname[PATH_MAX];
    while (1) {
        if (getcwd(pathname, sizeof pathname) == NULL) {
            perror("getcwd");
            return 1;
        }
        printf("getcwd() returned %s\n", pathname);

        if (strcmp(pathname, "/") == 0) {
            return 0;
        }

        if (chdir("..") != 0) {
            perror("chdir");
            return 1;
        }
    }
    return 0;
}
silly program which creates a 1000-deep directory hierarchy
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>


int main(void) {

    for (int i = 0; i < 1000;i++) {
        char dirname[256];
        snprintf(dirname, sizeof dirname, "d%d", i);

        if (mkdir(dirname, 0755) != 0) {
            perror(dirname);
            return 1;
        }

        if (chdir(dirname) != 0) {
            perror(dirname);
            return 1;
        }

        char pathname[1000000];
        if (getcwd(pathname, sizeof pathname) == NULL) {
            perror("getcwd");
            return 1;
        }
        printf("\nCurrent directory now: %s\n", pathname);
    }

    return 0;
}
silly program which create a 1000 links to file in effect there are 1001 names for the file
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char pathname[256] = "hello.txt";

    // create a target file
    FILE *f1;
    if ((f1 = fopen(pathname, "w")) == NULL) {
        perror(pathname);
        return 1;
    }
    fprintf(f1, "Hello Andrew!\n");
    fclose(f1);

    for (int i = 0; i < 1000; i++) {
        printf("Verifying '%s' contains: ", pathname);
        FILE *f2;
        if ((f2 = fopen(pathname, "r")) == NULL) {
            perror(pathname);
            return 1;
        }
        int c;
        while ((c = fgetc(f2)) != EOF) {
            fputc(c, stdout);
        }
        fclose(f2);

        char new_pathname[256];
        snprintf(new_pathname, sizeof new_pathname,
                 "hello_%d.txt", i);

        printf("Creating a link %s -> %s\n",
               new_pathname, pathname);
        if (link(pathname, new_pathname) != 0) {
            perror(pathname);
            return 1;
        }
    }

    return 0;
}
silly program which attempts to creates a long chain of symbolic links
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>


int main(int argc, char *argv[]) {
    char pathname[256] = "hello.txt";

    // create target file
    FILE *f1;
    if ((f1 = fopen(pathname, "w")) == NULL) {
        perror(pathname);
        return 1;
    }
    fprintf(f1, "Hello Andrew!\n");
    fclose(f1);

    for (int i = 0; i < 1000;i++) {
        printf("Verifying '%s' contains: ", pathname);
        FILE *f2;
        if ((f2 = fopen(pathname, "r")) == NULL) {
            perror(pathname);
            return 1;
        }
        int c;
        while ((c = fgetc(f2)) != EOF) {
            fputc(c, stdout);
        }
        fclose(f2);

        char new_pathname[256];
        snprintf(new_pathname, sizeof new_pathname, "hello_%d.txt", i);

        printf("Creating a symbolic link %s -> %s\n", new_pathname, pathname);
        if (symlink(pathname, new_pathname) != 0) {
            perror(pathname);
            return 1;
        }

        strcpy(pathname, new_pathname);

    }

    return 0;
}

$ dcc list_directory.c
$ ./a.out .
list_directory.c
a.out
.
..
$
#include <stdio.h>
#include <dirent.h>

// list the contents of directories specified as command-line arguments
int main(int argc, char *argv[]) {

    for (int arg = 1; arg < argc; arg++) {
        DIR *dirp = opendir(argv[arg]);
        if (dirp == NULL) {
            perror(argv[arg]);  // prints why the open failed
            return 1;
        }

        struct dirent *de;

        while ((de = readdir(dirp)) != NULL) {
            printf("%ld %s\n", de->d_ino, de->d_name);
        }

        closedir(dirp);
    }
    return 0;
}