Lab Solution

Assignment Task: xargs (Lab1-w2)

Objective: Read lines from standard input and run a command for each line, appending the input as arguments.

1. Full Code Solution (user/xargs.c)

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

/* Reads a single line from file descriptor 'fd' into 'buf' 
   Stops at a newline or when 'max' is reached. */
static int
readline(int fd, char *buf, int max)
{
  int n = 0; // characters stored
  char c;    // temporary character holder

  while (n < max - 1) {
    int r = read(fd, &c, 1);
    if (r < 1) {
      break; // End of file or error
    }
    if (c == '\n') {
      break; // Stop at newline but don't include it in buf
    }
    buf[n] = c;
    n++;
  }
  buf[n] = '\0'; // Null-terminate the string
  return n;
}

/* Parses string 's' into individual tokens separated by spaces/tabs.
   Modifies 's' in-place by inserting null terminators. */
static int
split_args(char *s, char *out[], int maxout)
{
  int argc = 0;
  while (*s != '\0') {
    // Skip leading whitespace (spaces or tabs)
    while (*s == ' ' || *s == '\t') {
      s++;
    }
    if (*s == '\0') {
      break;
    }
    // Stop if the output pointer array is full
    if (argc >= maxout) {
      break;
    }
    out[argc] = s; // Save the start of the word
    argc++;

    // Find the end of the current word
    while (*s != '\0' && *s != ' ' && *s != '\t') {
      s++;
    }
    if (*s == '\0') {
      break;
    }
    *s = '\0'; // Replace trailing whitespace with null terminator
    s++;
  }

  return argc;
}

/* Combines fixed base arguments and stdin arguments, then forks and execs. */
static void
run_cmd(char *base[], int basec, char *extra[], int stdinCount)
{
  char *final[MAXARG];
  int finalCount = 0;

  // 1. Copy base command arguments (e.g., "echo", "-n")
  for (int i = 0; i < basec && finalCount < MAXARG - 1; i++) {
    final[finalCount] = base[i];
    finalCount++;
  }

  // 2. Append extra arguments gathered from stdin
  for (int i = 0; i < stdinCount && finalCount < MAXARG - 1; i++) {
    final[finalCount] = extra[i];
    finalCount++;
  }

  final[finalCount] = 0; // Exec requires a null-terminated pointer array

  int pid = fork();
  if (pid < 0) {
    fprintf(2, "xargs: fork failed\n");
    exit(1);
  }
  if (pid == 0) {
    exec(final[0], final);
    // If exec returns, it failed
    fprintf(2, "xargs: exec %s failed\n", final[0]);
    exit(1);
  } else {
    wait(0); // Parent waits for the command to finish before continuing
  }
}

int
main(int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(2, "usage: xargs [-n maxargs] command [args...]\n");
    exit(1);
  }

  int max_per_exec = 0;
  int arg_start = 1;

  // Handle optional "-n" flag for limiting arguments per execution
  if (argc >= 4 && strcmp(argv[1], "-n") == 0) {
    max_per_exec = atoi(argv[2]);
    if (max_per_exec < 1) {
      max_per_exec = 0; // Default to no limit if invalid number provided
    }
    arg_start = 3; // Shift start of command to skip "-n" and its value
  }

  if (arg_start >= argc) {
    fprintf(2, "xargs: missing command\n");
    exit(1);
  }

  // Store the fixed base command and its arguments
  char *base[MAXARG];
  int basec = 0;
  for (int i = arg_start; i < argc && basec < MAXARG; i++) {
    base[basec++] = argv[i];
  }

  char argsbuf[1024];  // Buffer to store stdin words safely in memory
  int buf_used = 0;
  char *extra[MAXARG]; // Array of pointers into argsbuf
  int extrac = 0;
  char line[512];

  // Process stdin line by line
  while (1) {
    int len = readline(0, line, sizeof(line));
    if (len == 0) {
      break; // End of stdin reached
    }

    // Split the current line into individual word tokens
    char *tokens[MAXARG];
    int tokc = split_args(line, tokens, MAXARG);

    for (int i = 0; i < tokc; i++) {
      int tlen = strlen(tokens[i]) + 1;

      // Flush and execute if we run out of buffer space or argument slots
      if (buf_used + tlen > sizeof(argsbuf) ||
          basec + extrac + 1 >= MAXARG) {
        if (extrac > 0) {
          run_cmd(base, basec, extra, extrac);
          extrac = 0;
          buf_used = 0;
        }
      }

      if (tlen > sizeof(argsbuf)) {
        continue; // Word is too large for the buffer, skip it
      }

      // Copy the token into stable memory in argsbuf
      char *dest = argsbuf + buf_used;
      strcpy(dest, tokens[i]);
      buf_used += tlen;
      extra[extrac++] = dest;

      // Trigger execution if the -n limit is reached
      if (max_per_exec > 0 && extrac >= max_per_exec) {
        run_cmd(base, basec, extra, extrac);
        extrac = 0; // Reset for next batch
        buf_used = 0;
      }
    }
  }

  // Execute any remaining arguments left in the buffer
  if (extrac > 0) {
    run_cmd(base, basec, extra, extrac);
  }

  exit(0);
}

2. Setup Instructions

  • Modify Makefile: Add _xargs to the UPROGS list.

Last updated