uni

University stuff
git clone git://git.christosmarg.xyz/uni-assignments.git
Log | Files | Refs | README | LICENSE

mfproc (4036B)


      1 #!/bin/sh
      2 
      3 # ΧΡΗΣΤΟΣ ΜΑΡΓΙΩΛΗΣ - [REDACTED]
      4 
      5 # Exit codes:
      6 # 0     Success
      7 # 1     No user
      8 # 2     No process
      9 # 3     Usage
     10 
     11 main() {
     12         # Using getopts(1) because it's easier to parse
     13         # optional command line options. getopts(1) works
     14         # by passing it all the available options. If an option
     15         # is followed by a ':', that means that getopts(1) is
     16         # expecting an argument as well. If no argument is passed,
     17         # it exits with an error code of 1. In this case I've
     18         # passed it the string "u:s:" which means that the options
     19         # are '-u' and '-s' and both of them take an argument.
     20         # The argument is always stored in the `OPTARG` environmental
     21         # variable.
     22         while getopts u:s: arg; do
     23         case "${arg}" in
     24                 # In order for `username` to really be a user's name, it
     25                 # has to exist in `/etc/passwd`.
     26                 u) test ! -z "$(grep -w "^${OPTARG}" /etc/passwd)" ||
     27                    err "'${OPTARG}' is not in /etc/passwd" 1
     28                    # Get UID from username.
     29                    uid=$(id -u ${OPTARG}) ;;
     30                 s) state="${OPTARG}"
     31                    # `state` needs to be either S, R or Z in order
     32                    # to be valid.
     33                    validstate ${state} || usage ;;
     34                 *) usage
     35         esac
     36         done
     37 
     38         count=0
     39         # Print header. expand(1) will will expand \t to 16 spaces
     40         # in order for the output to be aligned.
     41         printf "Name\tPID\tPPID\tUID\tGID\tState\n" | expand -t 16
     42 
     43         # Parse each process in `/proc`. This is a slow way to parse it though.
     44         for proc in /proc/*/status; do
     45                 procuid=$(getfield "Uid:\s*${uid}" ${proc})
     46                 procgid=$(getfield "Gid" ${proc})
     47                 # If the `-u` option was passed, ignore every UID that
     48                 # does not match the UID we gave it as an argument.
     49                 # We're using a `continue` command here so that it
     50                 # stop searching for anything else in this file since
     51                 # we don't need it.
     52                 test -z ${uid} || [ "${procuid}" == "${uid}" ] || continue
     53 
     54                 procstate=$(getfield "State:\s*${state}" ${proc})
     55 
     56                 # Ignore any state other than S|R|Z
     57                 validstate ${procstate} || continue
     58 
     59                 # If the `-s` option was passed, ignore every state that does
     60                 # not match the one we gave it as an argument.
     61                 test -z ${state} || [ "${procstate}" == "${state}" ] || continue
     62 
     63                 procname=$(getfield "Name" ${proc})
     64                 procpid=$(getfield "Pid" ${proc})
     65                 procppid=$(getfield "PPid" ${proc})
     66 
     67                 # Print everything in a top(1)-like format.
     68                 printf "%s\t%s\t%s\t%s\t%s\t%s\n" \
     69                 ${procname} ${procpid} ${procppid} ${procuid} ${procgid} ${procstate} |
     70                 expand -t 16
     71 
     72                 # Increment counter by 1 to keep track of how many processes
     73                 # we have printed. If `count` is 0 after the loop, the script
     74                 # will exit with error code 2, as is done below.
     75                 count=$(expr ${count} + 1)
     76         done
     77 
     78         # We didn't print any process
     79         test ${count} -eq 0 && exit 2
     80 
     81         # Success!
     82         exit 0
     83 }
     84 
     85 usage() {
     86         # `{0##*/}` means the first argument (i.e the script's name) with
     87         # its path stripped, if it exists. 
     88         echo "usage: ${0##*/} [-u username] [-s S|R|Z]" 1>&2
     89         exit 3
     90 }
     91 
     92 err() {
     93         echo "${0##*/}: $@" 1>&2
     94         exit ${2}
     95 }
     96 
     97 # Check if process is either S, R or Z.
     98 validstate() {
     99         [ "${1}" = "S" ] || [ "${1}" = "R" ] || [ "${1}" = "Z" ]
    100 }
    101 
    102 # Get wanted field. grep(1) will return the line
    103 # that begins with the first argument we passed.
    104 # awk(1) is used to extract the 2nd field in the string.
    105 getfield() {
    106         grep -w "^${1}" ${2} | awk '{print $2}'
    107 }
    108 
    109 # Pass all command line arguments to `main`.
    110 main "$@"