2

I'm writing a program that will read from /etc/passwd and output the username and shell.

For example, here is the first line of the /etc/passwd file:

root:x:0:0:root:/root:/bin/bash

I need to only output the user and the shell. In this instance it would print:

root:/bin/bash

The values are separated by ':' so I just need to print the string before the first ':' and the string after the 6th ':'

Here is the code I have so far:

#include <string.h>

#define BUFFERSIZE 4096

int printf(const char *text, ...);

int main(void) {
    int fd;
    int buff_size = 1;
    char buff[BUFFERSIZE];
    int size;

    fd = open("/etc/passwd", O_RDONLY);
    if (fd < 0) {
        printf("Error opening file \n");
        return -1;
    }

    size = strlen(buff - 17);
    size = size + 1;

    while ((size = read(fd, buff, 1)) > 0) {
        buff[1] = '\0';
        write(STDOUT_FILENO, buff, size);
    }
}

(I am creating prototypes for printf because one of the requirements was to write the program without including <stdio.h> or <stdlib.h>)

6
  • 2
    Why are you using file-descriptors and open() instead of a file-stream FILE* and fopen() which makes reading line-oriented input much much easier. Reading with fgets() instead of read() and then using strtok() (or just strchr() and a counter) would make the task quite easy. Commented Oct 25, 2022 at 5:31
  • 1
    Off the bat, size = strlen(buff-17); passes an address that is not yours to play with to a library function expecting an address of a null terminated string... Off to a bad start... The constraint "don't use these header files" probably means "don't use the functions found in these libraries"... Dangerous to provide your own "hand made" function prototypes for standard library functions... Commented Oct 25, 2022 at 5:32
  • (not to mention buff in uninitialized which would invoke Undefined Behavior in your call with strlen() (for a 2nd reason...)) Commented Oct 25, 2022 at 5:33
  • If/when you figure out how to read "lines" of input from /etc/passwd, research strchr() and strrchr() to find the first and the last 'colon' in the buffer you've loaded. Those are the two that are interesting to this assignment... Commented Oct 25, 2022 at 5:38
  • You can check your output against awk -F: '{print $1":"$7}' /etc/passwd Commented Oct 25, 2022 at 5:57

2 Answers 2

3

Another approach is to use a single loop and a state variable to track the state of where you are in each line based on the number of colons read. The state-variable ncolon does that below. Essentially you read every character and check whether the loop is in a state where you should write the character as output or not. You condition the write on the number of colons, whether you are before the 1st or after the last.

Putting it altogether, you could do:

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

int main (int argc, char **argv) {
  
  int fd,                   /* file descriptor */
      ofd = STDOUT_FILENO,  /* output file descriptor */
      ncolon = 0;           /* counter - number of colons seen */
  
  /* open file given on command line or read from stdin otherwise */
  if ((fd = argc > 1 ? open (argv[1], O_RDONLY) : STDIN_FILENO) == -1) {
    return 1;
  }
  
  for (;;) {                              /* loop continually */
    unsigned char c;                      /* storage for character */
    int rtn;                              /* var to save return */
  
    if ((rtn = read (fd, &c, 1)) < 1) {   /* validate read of 1 char */
      if (rtn == -1) {                    /* return on error */
        return 1;
      }
      break;                              /* break read loop on EOF */
    }
    
    if (ncolon < 1 || ncolon == 6) {      /* if before 1st or after last */
      write (ofd, &c, 1);                 /* output char */
    }
    
    if (c == '\n') {                      /* reset ncolon on newline */
      ncolon = 0;
    }
    else if (c == ':') {                  /* increment on colon */
      ncolon += 1;
    }
  }
  
  if (fd != STDIN_FILENO) {   /* close file */
    close (fd);
  }
}

Example Use/Output

$ ./read_etc-passwd /etc/passwd
root:/bin/bash
messagebus:/usr/bin/false
systemd-network:/usr/sbin/nologin
systemd-timesync:/usr/sbin/nologin
nobody:/bin/bash
mail:/usr/sbin/nologin
chrony:/usr/sbin/nologin
...

Confirm the Format

$ diff <(./read_etc-passwd /etc/passwd) <(awk -F: '{print $1":"$7}' /etc/passwd)

(no output means program output and awk output were identical)

Sign up to request clarification or add additional context in comments.

Comments

2

Your program has undefined behavior when you evaluate strlen(buff - 17). It is unclear why you do this.

You can solve the problem with these simple steps:

  • read one byte at a time
  • count the ':' on the line
  • output the byte if the count is equal to 0 or equal to 6.
  • reset the count at newline (and print the newline)

Note that read(fd, &b, 1) and write(1, &b, 1) return -1 in case of error or interruption and should be restarted if errno is EINTR.

Here is a modified version:

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

int main(void) {
    int fd;
    unsigned char b;
    int count;
    ssize_t ret;

    fd = open("/etc/passwd", O_RDONLY);
    if (fd < 0) {
        write(2, "Error opening /etc/password\n", 28);
        return 1;
    }

    count = 0;
    for (;;) {
        ret = read(fd, &b, 1);
        if (ret == 0) { // end of file
            break;
        }
        if (ret < 0) { // error
            if (errno == EINTR)
                continue;
            write(2, "Read error on /etc/password\n", 28);
            return 1;
        }
        if (b == '\n') {
            // reset count, print b
            count = 0;
        } else
        if (b == ':') {
            // increment count, print ':' only if count == 1
            count = count + 1;
            if (count != 1)
                continue;
        } else
        if (count != 0 && count != 6) {
            // print b only if count is 0 or 6
            continue;
        }
        for (;;) {
            ret = write(1, &b, 1);
            if (ret == 1)
                break;
            if (ret < 0 && errno = EINTR)
                continue;
            write(2, "Write error\n", 12);
            return 1;
        }
    }
    close(fd);
    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.