Lab Solution
Assignment Task: xargs (Lab1-w2)
#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);
}Last updated