COMP 3000 Lab 4 2012

From Soma-notes
Revision as of 02:01, 12 November 2012 by Soma (talk | contribs) (→‎Answers)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

A few guidelines:

  • Submit your solutions for both Part A and Part B via cuLearn by Friday, October 19th at 11:55 pm. There are 32 points.
  • Please answer using a single text file (with a .txt extension). Do not submit doc, docx, pdf, or other formats. Also, please do not submit an archive (zip, tarball, rar) of multiple text files, just upload one. Anything other than a single text file will receive no credit.
  • Show all your work. Short answers are not sufficient; you should list any websites or individuals you consult and should document any experiments you conducted. For any question that you could just answer without any external reference or experiment, write "(no work required)" after your answer.
  • All submitted code and commands should compile and run. Partial code fragments or explanations may not be given credit. While code may use standard C and UNIX/Linux libraries, no code should rely on external binaries.
  • The exercises in this lab require root access on a Linux system. In the labs, you can use the COMP2401-2404 VM with the admin account (password "nimda1234!"). To get a root shell (rather than having to type sudo for each command, run sudo su .

Part A

Be sure to do the following questions in order.

  1. [2] Run the command truncate -s 1G foo. What is the logical size of foo, and how much space does it consume on disk?
  2. [2] Run mkfs.ext4 foo. (Say "yes" to operating on a regular file.) What is the logical size of foo now, and how much space does it now consume on disk?
  3. [1] What command do you run to check the filesystem in foo for errors?
  4. [1] Run mount foo /mnt. What does this command do?
  5. [2] Run df. What device is mounted on /mnt? What is this device?
  6. [2] Run rsync -a -v /etc /mnt. What does this command do? Explain the arguments as well.
  7. [2] Run umount /mnt. What files can you still access, and what have gone away?
  8. [1] Run dd if=/dev/zero of=foo conv=notrunc count=10 bs=512. What does this command do?
  9. [1] Run mount foo /mnt. What error do you get?
  10. [2] What command can you run to make foo mountable again? What characteristic of the file system enables this command to work?

Part B

  1. [6] Create a simple C program to generate a file consisting of the word "Hello", one thousand bytes of blank space (null bytes), and then "world\n", and then another thousand null bytes, such that the file produced consumes the maximum amount of space given its contents.
  2. [6] Create a C program "mycopy-blocks.c" that takes three arguments: a block size and two filenames A and B as command line arguments. This program should copy the contents of A to B, making B a byte-for-byte copy of A, except that the length of B should be evenly divisible by the block size. The file should be padded with nulls as necessary. Zero bytes in A should consume little to no space in B, however, including the padding.
  3. [4] How could loopback mounts be useful when working with virtual machines? What conditions must the VM software meet for this to work?

Answers

Part A

  1. The logical size of the file is 1GB, but it consumes 0 space on disk. Can use ls -s to find the disk usage.
  2. The logical size has remained the same at 1GB, but it now consumes 33MB on disk. Can use ls -s to find the disk usage and can multiply the size of the blocks by the block count to get the size. Can use sudo tune2fs -l <file with filesystem> to get block info.
  3. fsck.ext4 foo
  4. This will mount the foo filesystem on the /mnt directory.
  5. /dev/loop0 is mounted on /mnt. It allows a file to be accessed as if it were a block device.
  6. rsync is a fast and versatile file copying tool. It uses incremental copies to improve speed, only copying files not present in the destination that are present in the source and only sending file changes rather than entire files. With respect to the command rsync -a -v /etc /mnt, rsync will be executed locally and will copy the contents of the /etc directory to /mnt. It will do this with increased verbosity (-v) and in archive mode (-a), ensuring that symbolic links, devices, attributes, permissions, ownerships etc are preserved. It will also leave files present in the destination that are not present in the source. To delete these you must specify with --delete --force.
  7. By executing umount /mnt, all access to the files via /mnt has been lost.
  8. This command copies 10 blocks, 512 bytes at a time, from /dev/zero to foo. It is essentially destroying the first blocks of data in foo, corrupting the filesystem.
  9. "mount: you must specify the filesystem type."
  10. Can use fsck.ext4 to make foo mountable again. This is possible due to the redundant copies of the superblock that were created when foo was made a filesystem. (The previous command "dd" effectively destroyed the main superblock of foo)


Part B

  1. See code for hello-world-nulls.c below.
  2. See code for mycopy-blocks.c below.
  3. Loopback mounts could be useful to mount the VM filesystem on the hosts physical disk as a block device. To be able to do this, the VM software must use the same format for its disk images as that of an actual physical disk (i.e., just a sequence of blocks). The QEMU "raw" disk type satisfies these requirements. VMware and Virtualbox default disks do not. (Some VM disk formats are raw device dumps preceded by a header. On those, you just need to specify an offset value to the loopback mount so that it ignores the first part of the file.)


/* hello-world-nulls.c */

#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
	int file = open("helloworld", O_RDWR | O_CREAT);

	write(file, "Hello", 5);

	int i;
	for(i=0; i<1000; i++)
	{
		write(file, "\0", 1);
	}
	
	write(file, "world\n", 6);

	for(i=0; i<1000; i++)
	{
		write(file, "\0", 1);
	}
	
	close(file);
	return 0;
}


/* mycopy-blocks.c */
/* Usage: mycopy-blocks <block-size> <source> <dest>
/* Note that this version only preserves holes that are at least block size
   in length */

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

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

        int in_count, source, dest, nonzero, i, out_count, result;
        size_t size;
        char *source_fn, *dest_fn;
        unsigned char *buf;

        if( argc < 4 ) {
                printf( "Insufficient arguments.\n" );
                return -1;
        }

        size = (size_t) atoi(argv[1]);
        source_fn = argv[2];
        dest_fn = argv[3];

        if (! (buf = (unsigned char *) malloc(size))) {
                fprintf(stderr,
                        "Unable to allocate buffer of %d bytes\n", (int) size);
                exit(-1);
        }

        source = open(source_fn, O_RDONLY);
        if (!source) {
                fprintf(stderr,
                        "unable to open %s for reading\n", source_fn);
                exit(-2);
        }
        
        /* if we create the file, then just give read and write perms */
        dest = open(dest_fn, O_WRONLY|O_CREAT|O_TRUNC, 00666);

        if (!dest) {
                fprintf(stderr,
                        "unable to open %s for writing\n", dest_fn);
                exit(-3);
        }

        memset(buf, '\0', size);
        in_count = read(source, buf, size);
        nonzero = -1;
        while (in_count > 0) {
                nonzero = 0;
                for (i=0; i<in_count; i++) {
                        if (buf[i] !=0) {
                                nonzero = 1;
                                break;
                        }
                }

                if (nonzero) {
                        /* note we're padding here with nulls on every read */
                        /* we are assuming every read is for a full buffer
                           except for the last */
                        /* to do a normal copy, use in_count */
                        out_count = write(dest, buf, size);
                } else {
                        lseek(dest, size, SEEK_CUR);
                        out_count = size;
                }
                
                if (out_count != size) {
                        fprintf(stderr, "Error in writing copy.\n");
                        exit(-4);
                }

                memset(buf, '\0', size);
                in_count = read(source, buf, size);
        }

        if (nonzero == 0) {
                /* last block was not written, so write a trailing zero */
                lseek(dest, -1, SEEK_CUR);
                result = write(dest, buf, 1);
                if (result != 1) {
                        fprintf(stderr, "Error in writing trailing byte.\n");
                        exit(-5);
                }
        }
        
        if (close(source) != 0 || close(dest) != 0) {
                fprintf(stderr, "Error closing files\n");
                exit(-5);
        }

        return(0);
}