User Tools

Site Tools


Sidebar

projects

spi0 (due 20200123)
wcp1 (due 20200123)
wpf1 (bonus; due 20200123)
wcp2 (due 20200129)
wpf2 (due 20200129)
wcp3 (due 20200205)
wpf3 (due 20200205)
wcp4 (due 20200212)
wpf4 (due 20200212)
wcp5 (due 20200226)
wpf5 (due 20200226)
bwp0 (due 20200226)
eoce (due 20200513)
haas:spring2020:sysprog:projects:sci1

Corning Community College

CSCS2730 Systems Programming

~~TOC~~

Project: System Command Implementation -- LS (sci1)

Objective

To implement a programmatic solution of an existing system command. Our first task will be to implement command-line arguments to our version of the ls(1) command.

Background

The ls(1) command allows one to change the permissions of a file (that they have permission to change permissions on).

As we've started exploring system calls, we see that in many cases there is a surprising similarity between the executable command and the system call name.

It turns out that is also the case here with ls:

lab46:~$ apropos ls
chmod (1)            - change file mode bits
chmod (2)            - change permissions of a file

We're interested in that section 2 manual page. We see the function prototype for chmod is:

int chmod(const char *pathname, mode_t mode);

Setting permissions

Although we've largely operated without direct attention to it, our data and our access to it very much depends upon the permissions set on them.

You cannot view any source code written without the read permission being set; you cannot save changes unless you have write permission enabled; and you cannot run your compiled programs if the execution permission was not applied.

Different operating systems and different filesystems manifest file permissions differently; for this problem we will specifically explore the UNIX file permissions, by writing a program that accepts a value and a file from the command-line and does the pre-processing necessary to convert that value into a form compatible with the mechanism for actually changing the permission.

UNIX file permissions are represented as a 3-digit octal (base 8!) value, and each octal value can have the following values:

value description
4 apply read permission to that particular mode
2 apply write permission to that particular mode
1 apply execute permission to that particular mode
0 apply no permissions to that particular mode

Being an octal value, we can express results ranging from 0-7, and that is precisely how many variations we need to specify all the possible combinations here.

For example, if we wanted read (4) and write (2) permission, we'd add them together… 4+2 is 6; if we wanted read, write, AND execute: 4+2+1 = 7.

There are 3 'tiers' of permissions to consider:

tier description
user permissions applied to the assigned owner of the file
group permissions applied to the assigned group of the file
other permissions applied to anyone else (the world)

More specifically:

user group other
read 0400 0040 0004
write 0200 0020 0002
execute 0100 0010 0001
none 0000 0000 0000

To form the octal permission, we figure out what permissions to apply to the user (a combination of read, write, execute, or none), and then for the group, and finally for the world. That is the 3-digit octal value we need.

The problem is that the mode as specified on the command-line will be available as a string (argv[1], an array of characters), so your main task will be to convert that character data into base 8 data (or binary, or hex, the same thing in the end).

Your program should expect data to be provided as follows:

  • argv[0]: program name
  • argv[1]: 3-digit permission (as a string)
  • argv[2-n]: file to which we want to apply permissions

This can be done rather effectively using logic operations (almost crazy easily).

To get a better handle on this, you may run the following commands when logged into lab46:

lab46:~$ ls -l /etc/motd /etc/shadow /bin/ls
-rwxr-xr-x 1 root root   118280 Mar 14 11:47 /bin/ls
-rw-r--r-- 1 root root   859 Mar 14 12:16 /etc/motd
-rw-r----- 1 root shadow 729 Oct 21 04:55 /etc/shadow
lab46:~$ 

Ignoring the leading '-' (that refers to file type), we see that /bin/ls has rwx (7) applied to the user root, r-x (5) applied to the group root, and r-x applied to everyone else. That means its octal permission is 0755.

/etc/motd has permissions of 0644, while /etc/shadow has permissions of 0640.

Program

It is your task to write the program that will use the chmod(2) system call to provide 3- or 4-digit octal values to appropriately change the permissions on the indicated file(s).

Your program should:

  • accept as command-line arguments:
    • the 3- or 4-digit octal value
    • the file(s)
  • perform the task (process)
    • as stated, you are not allowed to use any of the strtol(3) family of functions to do the base conversion for you.
      • This isn't to say you cannot use them; you may, you just cannot use them to do any of the conversion work (beyond ASCII to base 10).
  • display error or usage if applicable.
    • if error or usage, make sure you return a 1 instead of 0.

FAQs/Hints

0x0: Octal

In argv[1] the number input is an array of characters but it needs conversion to a singular octal value. Why?

As argv[1] is an array of characters… if you give it 640, it'll actually be “640\0”, that is, ASCII character '6', followed by ASCII character '4', followed by ASCII character '0', followed by the NULL terminator.

'6' has a numeric value of 54 (decimal).

If you were to convert “640” to an integer value 640, that would be 640 in base 10; 640(10) to base 8 would be: 1200

If you pass that decimal 640 to the chmod() function, you'd end up with the sticky bit being set (T in other) along with user write, and NOTHING else. Not 0640 as we desire, but instead 01200.

So, entering 640 on the command-line would not result in a direct conversion to octal 0640… some converting will be in order.

0x1: more octal

Since octal values start with a leading zero, if I insert an ASCII '0' at the start of the string and then convert it using atoi(3), wouldn't that work? It doesn't seem to be the case.

That leading zero is only a convenience, implemented on a case by case basis. It would appear atoi(3) does not implement it. Look at this:

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char **argv)
{
        int result = atoi(argv[1]);  // take the first argument and convert it to an integer
 
        printf("argv[1] is \"%s\"\n", argv[1]); // display the original string, for comparison
 
        // display the result in octal, decimal, and hex
        printf("result is %o in octal, %d in decimal, and %x in hexadecimal\n", result, result, result);
 
        return(0);
}

And look at the output:

lab46:~$ ./testatoi 640
argv[1] is "640"
result is 1200 in octal, 640 in decimal, and 280 in hexadecimal
lab46:~$ ./testatoi 0640
argv[1] is "0640"
result is 1200 in octal, 640 in decimal, and 280 in hexadecimal

As you can see, even if you had a 0640, the leading zero would be dropped in the conversion, because atoi(3) is apparently only cognizant of decimal values (and good, because that would have taken the fun out of this particular problem… you stand to learn some important things by working through this process).

And also, do you see that regardless of displaying it in octal, decimal, or hex, it is the same value? They're all being sourced from an integer variable called result… a regular old int… so it ultimately is up to how we instruct the computer to interpret it… after all, EVERYTHING is in binary, even if we are thinking through the problem exclusively in a different base.

Why doesn't adding the leading zero make it octal?

The leading zero is a convenient way to identify an octal value. It is a means to mark one, but by no means a required form. That C and other facilities on the computer support a leading 0 for octal and a leading 0x for hex makes our lives easier, but only goes as far as support for such things has been implemented.

We do the same in language. If I were to say the following value is in base 8: 72033

You would understand because I identified it as such… note the lack of the leading zero. If I wanted to be more brief, instead of saying “the following value is in base 8” I could just prefix a 0 on, because that shortcut is generally understood (within the context of assignable values in C syntax). But it is by no means the only way to do it.

atoi

And note, there is nothing magical about atoi(3)… it is just a function. It takes an array currently filled with ASCII-equivalents of single digit decimal numbers and coalesces those separate digits into one. We've played with things like this in our early labs this semester (there are advantages to having a number broken up into separate digits, there are also advantages to having a number combined as a single value).

The overall scope of this problem presents you with a desired-octal value currently represented as a string– each 'digit' is a separate entity, and we want to combine them into a single value, only preserving the octal value (where many tools assume decimal).

And as we know: 031 is not 31. 031 is 25.

So, if we read in 031 as a decimal 31 yet desire to then represent it as octal, we'd instead have 037.

the neatness of binary and octal (and hex)

There are certain advantages when working in similar bases that are all powers of two. Quite advantageous things.

That base 8 is one of those bases means this problem can take advantage of some very simple and very effective logic operations that would not be as simple or direct in decimal (10 is not a power of 2).

That each octal digit represents three binary bits should be kept in mind. This problem entirely plays off how well binary values and octal values just sync up (because, well, they do).

We would experience similar neatness with decimal if we started playing with base 10, base 100, and base 1000 values (in such a case, decimal would be to base 100 and 1000 what binary is to bases 8 and 16).

0x2: when a number isn't a number but a representation of a number we'd like it to be

Does chmod(2) have to be in octal or are there other ways that it can work.

No, you can think of it as being in binary, octal, decimal, or hex… or any base, really, so long as that value, when converted to octal, matches the desired permissions.

After all:

  • 0640 in binary is: 000110100000
  • 0640 in hex is: 1A0
  • 0640 in decimal is: 416

Once the number is in the variable, it can instantly and effortlessly be represented in base 8, 10, or 16. It can be thought of as any one of those, and it really doesn't make a difference, because they're all the same (in that 0640 == 0x1A0 == 000110100000 == 416). That's just how numbers work (on or off the computer).

The only difference is when we choose to visualize them… when you SEE a number, it has to take a form (and abide by a base)… when you input a number, we apply the same notions. But once stored in a single variable on the computer, its original form is unimportant.

The value provided on the command line has to conform with the octal permissions, just as the chmod command does.

Converting argv[1]'s “640” to 0640 seems confusing because they are two completely different values.

The command-line “640” (the string) isn't a decimal; it isn't one number (and as such shouldn't be considered an automatically intelligible number, that's where our program comes in, to make sense of it)… it is an ASCII representation of a 3-digit number (or, a sequence of three two-digit decimal numbers that represent the ASCII character being displayed). Due to the context of how we're interpreting it, we desire that number to be ultimately represented as a single octal quantity (of 3-digits), because that is what the chmod(2) function requires.

So, if the first digit of argv[1] is a '6' (that's what, a decimal 54?), we know for the three bits that correspond with that field (the user field), we want to apply read (4, or 100 binary) and write (2, or 010 binary) for a total of 6 (110 binary). For the user field, read is 0400 (XXX 000 000 in binary (marked with the X's)). For the group field, read is 0040. Look at where they end up lining up in binary.

Execution

If you run without proper arguments:

lab46:~/src/sysprog/sci0$ ./mychmod 
./mychmod <MODE> <FILE> [FILE...]
lab46:~/src/sysprog/sci0$ 

Run with invalid mode:

lab46:~/src/sysprog/sci0$ ./mychmod 0987 file1
ERROR: invalid mode

Run with valid mode (success means you just get your prompt back):

lab46:~/src/sysprog/sci0$ ./myls -la /tmp

Submission

To submit this program to me using the submit tool, run the following command at your lab46 prompt:

$ submit sysprog sci1 myls.c
Submitting sysprog project "sci1":
    -> myls.c(OK)

SUCCESSFULLY SUBMITTED

You should get some sort of confirmation indicating successful submission if all went according to plan. If not, check for typos and or locational mismatches.

haas/spring2020/sysprog/projects/sci1.txt · Last modified: 2017/02/14 16:59 by 127.0.0.1