nuaa2023操作系统实验报告
Job6/sh4
题目要求:
重新编写makefile;
实现连接多条命令;
编写测试用例。
解决思路:
重新编写makefile:
根据教学视频所讲述模板进行编写,得到自动生成依赖关系makefile文件。在 Makefile.dep 中定义依赖关系,然后在 Makefile 文件中引用依赖关系进行编 译等操作。
实现连接多条命令:
创建管道,前一个命令的输出作为下一个命令的输入,管道的创建和关闭依靠函数对命令数组进行遍历,将命令的输入输出连接起来,在子进程中,函数执行当前命令,并将输出写入管道;在父进程中,函数等待子进程执行完毕,并将管道的输入端设置为下一个命令的输入。
编写测试用例:
对几种测试用例进行实现。
关键代码:
重写makefile:
OBJS= parse.o cmd.o utest.o main.o
all : $(OBJS)
cc -o sh4 $(OBJS)
%.o: %.c
cc -c -o $@ $<
run:
./sh4 -utest
clean:
rm -f sh4 *.o
include Makefile.dep
exec_cmd函数实现
void exec_cmd(struct cmd *cmd)
{
int fd;
int error;
if (cmd->input) {
fd = open(cmd->input, O_RDONLY);
if (fd < 0) {
perror("open");
return ;
}
dup2(fd, 0);
close(fd);
}
if (cmd->output) {
fd = open(cmd->output, O_CREAT | O_WRONLY, 0666);
if (fd < 0) {
perror("open");
return ;
}
dup2(fd, 1);
close(fd);
}
error = execvp(cmd->argv[0], cmd->argv);
if (error < 0 )
perror("execvp");
return ;
}
exec_pipe_cmd函数
void exec_pipe_cmd(int cmdc, struct cmd *cmdv)
{
if (cmdc == 1)
{
exec_cmd(cmdv+0);
return ;
}
pid_t pid;
int fds[2];
pipe(fds);
pid = fork();
if (pid == -1) {
perror("fork");
return ;
}
if (pid == 0) {
dup2(fds[1], 1);
close(fds[1]);
close(fds[0]);
exec_pipe_cmd(cmdc-1 ,cmdv);
exit(0);
}
wait(NULL);
dup2(fds[0], 0);
close(fds[1]);
close(fds[0]);
exec_cmd(&cmdv[cmdc-1]);
}
// cat <test.in | sort | uniq | cat >test.out
void test_exec_pipe_cmd()
{
unlink("test.out");
pid_t pid = fork();
if (pid == 0) {
// echo "3\n1\n2\n3\n2" | cat >log.txt
struct cmd cmdv[4] = {{1, {"cat", NULL}, "test.in", NULL},
{1, {"sort", NULL}, NULL, NULL},
{1, {"uniq", NULL}, NULL, NULL},
{1, {"cat", NULL}, NULL, "test.out"}};
exec_pipe_cmd(4, cmdv);
}
wait(NULL);
read_and_cmp("test.out", "1\n2\n3\n");
}
void parse_cmd(char *line, struct cmd *cmd)
{
char* word;
cmd->argc = 0;
cmd->input = NULL;
cmd ->output = NULL;
word = strtok(line, " ");
while (word) {
if (word[0] == '>') {
if (strlen(word) == 1) {
word = strtok(NULL, " ");
cmd->output = word;
}
else
cmd->output = word+1;
}
else if (word[0] == '<') {
if( strlen(word) == 1) {
word = strtok(NULL, " ");
cmd->input =word;
}
else
cmd->input = word+1;
}
else {
cmd->argv[cmd->argc] = word;
cmd->argc++;
}
word = strtok(NULL, " ");
}
cmd->argv[cmd->argc] = NULL;
}
void dump_cmd(struct cmd *cmd)
{
printf("\nargc = %d\n", cmd->argc);
int i;
for (i = 0; i < cmd->argc; i++) {
printf("argv[%d] = (%s)\n", i, cmd->argv[i]);
}
if (cmd->input)
printf("input = (%s)\n", cmd->input);
else
printf("input = null\n");
if (cmd->output)
printf("output = (%s)\n", cmd->output);
else
printf("output = null\n");
}
int parse_pipe_cmd(char *line, struct cmd *cmdv)
{
int cmdc = 0;
char* save;
char* part;
part = strtok_r(line, "|", &save);
while (part) {
parse_cmd(part, cmdv+cmdc);
cmdc++;
part = strtok_r(NULL, "|", &save);
}
//dump_pipe_cmd(cmdc, cmdv);
return cmdc;
}
void dump_pipe_cmd(int cmdc, struct cmd *cmdv)
{
int i;
printf("pipe cmd, cmdc = %d\n", cmdc);
for (i = 0; i < cmdc; i++) {
struct cmd *cmd = cmdv + i;
dump_cmd(cmd);
}
}
// echo abc
void test_parse_cmd_1()
{
struct cmd cmd;
char line[] = "echo abc xyz";
parse_cmd(line, &cmd);
assert(cmd.argc == 3);
assert(strcmp(cmd.argv[0], "echo") == 0);
assert(strcmp(cmd.argv[1], "abc") == 0);
assert(strcmp(cmd.argv[2], "xyz") == 0);
assert(cmd.argv[3] == NULL);
}
// echo abc >log
void test_parse_cmd_2()
{
struct cmd cmd;
char line[] = "echo abc >log";
parse_cmd(line, &cmd);
assert(cmd.argc == 2);
assert(strcmp(cmd.argv[0], "echo") == 0);
assert(strcmp(cmd.argv[1], "abc") == 0);
assert(cmd.argv[2] == NULL);
assert(strcmp(cmd.output, "log") == 0);
}
// cat /etc/passwd | wc -l
void test_parse_pipe_cmd_1()
{
struct cmd cmdv[MAX_TEST_CMDV];
char line[] = "cat /etc/passwd | wc -l";
int cmdc = 0;
cmdc = parse_pipe_cmd(line, cmdv);
assert(cmdc == 2);
assert(cmdv[0].argc == 2);
assert(strcmp(cmdv[0].argv[0], "cat") == 0);
assert(strcmp(cmdv[0].argv[1], "/etc/passwd") == 0);
assert(cmdv[0].argv[2] == NULL);
assert(cmdv[1].argc == 2);
assert(strcmp(cmdv[1].argv[0], "wc") == 0);
assert(strcmp(cmdv[1].argv[1], "-l") == 0);
assert(cmdv[1].argv[2] == NULL);
}
// cat <input | sort | cat >output
void test_parse_pipe_cmd_2()
{
struct cmd cmdv[MAX_TEST_CMDV];
char line[] = "cat <input | sort | cat >output";
int cmdc = 0;
cmdc = parse_pipe_cmd(line, cmdv);
assert(cmdc == 3);
assert(cmdv[0].argc == 1);
assert(strcmp(cmdv[0].argv[0], "cat") == 0);
assert(cmdv[0].argv[1] == NULL);
assert(strcmp(cmdv[0].input, "input") == 0);
assert(cmdv[1].argc == 1);
assert(strcmp(cmdv[1].argv[0], "sort") == 0);
assert(cmdv[1].argv[1] == NULL);
assert(cmdv[2].argc == 1);
assert(strcmp(cmdv[2].argv[0], "cat") == 0);
assert(cmdv[2].argv[1] == NULL);
assert(strcmp(cmdv[2].output, "output") == 0);
}
运行结果:
test_parse_cmd_1 pass
test_parse_cmd_2 pass
test_parse_pipe_cmd_1 pass
test_parse_pipe_cmd_1 pass
test_exec_cmd pass
test_exec_pipe_cmd pass
All test have passed!
>pwd
current working directory:/root/os/job6/sh4
>ls
Makefile Makefile.dep cmd.c cmd.h cmd.o main.c main.o parse.c
parse.h parse.o readme.md sh4 test test.in test.out
>exit
Job8/pc1.c
题目要求:
- 系统中有3个线程:生产者、计算者、消费者
- 系统中有2个容量为4的缓冲区:buffer1、buffer2
- 生产者
- 生产’a’、‘b’、‘c’、‘d’、‘e’、‘f’、‘g’、'h’八个字符
- 放入到buffer1
- 打印生产的字符
- 计算者
- 从buffer1取出字符
- 将小写字符转换为大写字符,按照 input:OUTPUT 的格式打印
- 放入到buffer2
- 消费者
- 从buffer2取出字符
- 打印取出的字符
- 程序输出结果(实际输出结果是交织的)
a
b
c
…
a:A
b:B
c:C
…
A
B
C
…
解决思路:
定义一个buffer结构体,用来存储buffer1与buffer2状态,定义buffer_is_empty(struct array a)
、buffer_is_full(struct array a)
、get_item(struct array *a)
、put_item(struct array *a, char item)
函数。
总共有三个角色,他们的关系是:计算者相当于生产者的消费者,“消费者”相当于计算者的消费者。
1.生产者产生字符后,查看buffer1的锁状态,加锁,之后判断buffer1是否为满,满则阻塞等待,反之放入缓冲区buffer1,之后唤醒等待的计算者线程,解开锁。
2.计算者被唤醒后,获取buffer1的锁,加锁,判断buffer1是否为空,如果空则阻塞等待,反之取出一个字符,转化为大写形式。之后再唤醒生产者,释放锁。接下来,它作为消费者的生产者,查看buffer2的锁状态,加锁,之后判断buffer2是否为满,满则阻塞等待,反之将字符放入缓冲区buffer2,之后唤醒等待的消费者线程,解开锁。
3.消费者被唤醒后,获取buffer2的锁,加锁,判断buffer2是否为空,如果空则阻塞等待,反之取出一个字符,输出到屏幕上,之后唤醒消费者,解开锁。
关键代码:
消费者:
void *consume(void *arg) {
char item;
int i;
for (i = 0; i < ITEM_COUNT; i++) {
pthread_mutex_lock(&mutex);
while (buffer_is_empty(buffer2))
pthread_cond_wait(&wait_full_buffer2, &mutex);
item = get_item(&buffer2);
printf(" consume item: %c\n", item);
pthread_cond_signal(&wait_empty_buffer2);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
计算者:
void *calculator(void *arg) {
char item;
int i;
for (i = 0; i < ITEM_COUNT; i++) {
pthread_mutex_lock(&mutex);
while (buffer_is_empty(buffer1))
pthread_cond_wait(&wait_full_buffer1, &mutex);
while (buffer_is_full(buffer2))
pthread_cond_wait(&wait_empty_buffer2, &mutex);
item = get_item(&buffer1);
printf("%c:%c\n", item, item - 32);
item -= 32;
put_item(&buffer2,item);
pthread_cond_signal(&wait_full_buffer2);
pthread_cond_signal(&wait_empty_buffer1);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
生产者:
void *produce() {
char item;
int i;
for (i = 0; i < ITEM_COUNT; i++) {
pthread_mutex_lock(&mutex);
while (buffer_is_full(buffer1))
pthread_cond_wait(&wait_empty_buffer1, &mutex);
item = i + 'a';
put_item(&buffer1, item);
printf("produce item: %c\n", item);
pthread_cond_signal(&wait_full_buffer1);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
主函数:
int main() {
pthread_t consumer_tid, calculator_tid;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&wait_empty_buffer1, NULL);
pthread_cond_init(&wait_empty_buffer2, NULL);
pthread_cond_init(&wait_full_buffer1, NULL);
pthread_cond_init(&wait_full_buffer2, NULL);
pthread_create(&calculator_tid, NULL, calculator, NULL);
pthread_create(&consumer_tid, NULL, consume, NULL);
produce();
pthread_join(consumer_tid, NULL);
pthread_join(calculator_tid, NULL);
return 0;
}
运行结果:
produce item: a
produce item: b
a:A
produce item: c
consume item: A
b:B
produce item: d
consume item: B
c:C
produce item: e
consume item: C
d:D
produce item: f
consume item: D
e:E
produce item: g
consume item: E
f:F
produce item: h
consume item: F
g:G
consume item: G
h:H
consume item: H
Job8/pc2.c
题目要求:
使用信号量解决生产者、计算者、消费者问题
功能与 job8/pc1.c
相同
解决思路:
引入信号量结构体,并且定义多个信号量,通过sema_init()
对信号量进行初始化,sema_wait()
进行等待,sema_signal()
进行释放。
总共有三个角色,他们的关系是:计算者相当于生产者的消费者,“消费者”相当于计算者的消费者,按一定的逻辑执行PV
操作。
生产者首先执行 sema_wait(&empty_buffer1_sema)
函数,如果buffer1
存在空闲位置, empty_buffer1_sema
减1,对buffer1
进行加锁,加入字符后进行解锁。之后执行sema_signal(&full_buffer1_sema);
进行加1操作后,唤起计算者进程。
唤起计算者进程后,首先执行sema_wait(&full_buffer1_sema);
函数,如果buffer1
存在值,则full_buffer1_sema
减1,加buffer1
的锁,取字符转换为大写形式,解开锁,之后执行sema_signal(&empty_buffer1_sema);
此时empty_buffer1_sema
加1,唤醒等待的生产者线程。之后作为生产者,向buffer2
写字符,首先执行sema_wait(&full_buffer2_sema)
函数,判断是否buffer2
有值,如果等到有值的情况,则full_buffer2_sema
减1,加buffer1
的锁,取字符转换为大写形式,解开锁,再执行ema_signal(&empty_buffer2_sema)
,此时empty_buffer2_sema
加1,唤醒等待的消费者线程。
消费者首先执行sema_wait(&full_buffer2_sema);
对buffer2
进行判断,如果有值,则full_buffer2_sema
减1,加buffer2
的锁,取字符打印输出,解开锁,再执行sema_signal(&empty_buffer2_sema)
,此时empty__buffer2_sema
加1,唤醒等待的计算者线程。
关键代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define CAPACITY 4
#define ITEM_COUNT 8
int buffer1[CAPACITY], buffer2[CAPACITY];
int in1, in2;
int out1, out2;
typedef struct {
int value;
pthread_mutex_t mutex;
pthread_cond_t cond;
} sema_t;
sema_t mutex1_sema, mutex2_sema;
sema_t empty_buffer1_sema, empty_buffer2_sema;
sema_t full_buffer1_sema, full_buffer2_sema;
// buffer1
int buffer1_is_empty() {
return in1 == out1;
}
int buffer1_is_full() {
return (in1 + 1) % CAPACITY == out1;
}
int get_item1() {
int item = buffer1[out1];
out1 = (out1 + 1) % CAPACITY;
return item;
}
void put_item1(int item) {
buffer1[in1] = item;
in1 = (in1 + 1) % CAPACITY;
}
// buffer2
int buffer2_is_empty() {
return in2 == out2;
}
int buffer2_is_full() {
return (in2 + 1) % CAPACITY == out2;
}
int get_item2() {
int item = buffer2[out2];
out2 = (out2 + 1) % CAPACITY;
return item;
}
void put_item2(int item) {
buffer2[in2] = item;
in2 = (in2 + 1) % CAPACITY;
}
// sema
void sema_init(sema_t *sema, int value) {
sema->value = value;
pthread_mutex_init(&sema->mutex, NULL);
pthread_cond_init(&sema->cond, NULL);
}
void sema_wait(sema_t *sema) {
pthread_mutex_lock(&sema->mutex);
while(sema->value <= 0)
pthread_cond_wait(&sema->cond, &sema->mutex);
--sema->value;
pthread_mutex_unlock(&sema->mutex);
}
void sema_signal(sema_t *sema) {
pthread_mutex_lock(&sema->mutex);
++sema->value;
pthread_cond_signal(&sema->cond);
pthread_mutex_unlock(&sema->mutex);
}
// 生产者
void *produce(void *arg) {
int item, i;
for(i = 0; i < ITEM_COUNT; i++) {
sema_wait(&empty_buffer1_sema);
sema_wait(&mutex1_sema);
item = 'a' + i;
printf("produce item: %c\n", item);
put_item1(item);
sema_signal(&mutex1_sema);
sema_signal(&full_buffer1_sema);
}
return NULL;
}
// 计算者
void *compute(void *arg) {
int item, i;
for(i = 0; i < ITEM_COUNT; i++) {
sema_wait(&full_buffer1_sema);
sema_wait(&mutex1_sema);
item = get_item1();
printf("\t\tcompute item before: %c\n", item);
item = item - 32;
printf("\t\tcompute item after : %c\n", item);
sema_signal(&mutex1_sema);
sema_signal(&empty_buffer1_sema);
sema_wait(&empty_buffer2_sema);
sema_wait(&mutex2_sema);
put_item2(item);
sema_signal(&mutex2_sema);
sema_signal(&full_buffer2_sema);
}
return NULL;
}
// 消费者
void *consume(void *arg) {
int item, i;
for(i = 0; i < ITEM_COUNT; i++) {
sema_wait(&full_buffer2_sema);
sema_wait(&mutex2_sema);
item = get_item2();
printf("\t\t\t\t\tconsume item: %c\n", item);
sema_signal(&mutex2_sema);
sema_signal(&empty_buffer2_sema);
}
return NULL;
}
int main() {
pthread_t computer_tid, consumer_tid;
sema_init(&mutex1_sema, 1);
sema_init(&mutex2_sema, 1);
sema_init(&empty_buffer1_sema, CAPACITY - 1);
sema_init(&empty_buffer2_sema, CAPACITY - 1);
sema_init(&full_buffer1_sema, 0);
sema_init(&full_buffer2_sema, 0);
pthread_create(&computer_tid, NULL, compute, NULL);
pthread_create(&consumer_tid, NULL, consume, NULL);
produce(NULL);
pthread_join(computer_tid, NULL);
pthread_join(consumer_tid, NULL);
return 0;
}
运行结果:
produce item: a
produce item: b
produce item: c
compute item before: a
compute item after : A
produce item: d
consume item: A
compute item before: b
compute item after : B
produce item: e
consume item: B
compute item before: c
compute item after : C
produce item: f
consume item: C
compute item before: d
compute item after : D
produce item: g
consume item: D
compute item before: e
compute item after : E
produce item: h
consume item: E
compute item before: f
compute item after : F
compute item before: g
compute item after : G
consume item: F
compute item before: h
compute item after : H
consume item: G
consume item: H
Job9/pfind.c
题目要求:
功能与 sfind 相同
要求使用多线程完成
主线程创建若干个子线程
主线程负责遍历目录中的文件
遍历到目录中的叶子节点时
将叶子节点发送给子线程进行处理
两者之间使用生产者消费者模型通信
主线程生成数据
子线程读取数据
解决思路:
分不同情况,如果路径为文件,则对文件进行查找是否有包含指定的字符串,输出行。如果路径为目录,则遍历目录与其子目录,将得到的文件路径与搜索的字符串添加到任务队列中。
创建WORKER_NUMBER
个子进程,不同子进程从任务队列中获取任务并执行,即对指定文件进行查找。当任务队列为空时,等待新的任务加入。
主线程完成遍历后,根据WORKER_NUMBER
添加标记,表示所有任务完成,子线程得到标记后,自动退出线程。
关键代码:
void find_file(char *path,char *target)
{
FILE *file = fopen(path,"r");
char line[256];
while (fgets(line,sizeof(line),file)){
if(strstr(line,target))
printf("%s: %s",path,line);
}
fclose(file);
}
void *worker_entry(void *arg)
{
while(1){
struct task task;
task=get_item();
if(task.is_end)
{
struct task tm;
tm.is_end=1;
put_item(tm);
break;
}
find_file(task.path,task.string);
}
}
void find_dir(char *path,char *target)
{
DIR *dir = opendir(path);
struct dirent *entry;
struct task buffer;
while(entry = readdir(dir)){
if(strcmp(entry->d_name,".") == 0)
continue;
if(strcmp(entry->d_name,"..") == 0)
continue;
if(entry->d_type == DT_DIR)
{
char base[80];
memset(base,'\0',sizeof(base));
strcpy(base,path);
strcat(base,"/");
strcat(base,entry->d_name);
find_dir(base,target);
}
if(entry->d_type == DT_REG)
{
char base[80];
memset(base,'\0',sizeof(base));
strcpy(base,path);
strcat(base,"/");
strcat(base,entry->d_name);
buffer.is_end=0;
strcpy(buffer.path,base);
strcpy(buffer.string,target);
put_item(buffer);
break;
}
}
closedir(dir);
}
main函数
int main(int argc,char *argv[])
{
char *path = argv[1];
char *string = argv[2];
if(isfile(path))
{
find_file(path,string);
return 0;
}
pthread_t consumer_tid[WORKER_NUMBER];
find_dir(path,string);
struct task buf;
buf.is_end=1;
put_item(buf);
for(int i=0;i<WORKER_NUMBER;i++)
{
pthread_create(&consumer_tid[i],NULL,worker_entry,NULL);
}
for(int i=0;i<WORKER_NUMBER;i++)
{
pthread_join(consumer_tid[i],NULL);
}
return 0;
运行结果:
root@lisongqi-virtual-machine:/home/lisongqi/桌面/operating-system-experiment-ma
ster/job10# cc pfind.c -o pfind -lpthread
root@lisongqi-virtual-machine:/home/lisongqi/桌面/operating-system-experiment-ma
ster/job10# ./pfind test main
test/hello/hello.c: int main() {
test/world/world.c: int main()
root@lisongqi-virtual-machine:/home/lisongqi/桌面/operating-system-experiment-ma
ster/job10# ./pfind test int
test/hello/hello.c: int main() {
test/hello/hello.c: printf("hello");
test/world/world.c: int main()
test/world/world.c: printf("world");
Job11/http服务器
题目要求:
完成多进程版本的 httpd
放置在 job10/http/concurrent 目录下
解决思路:
主函数首先解析命令行参数,获取要监听的 IP 地址和端口号。然后使用 tcp_listen 函数创 建一个 TCP 服务器,该服务器会监听指定的 IP 地址和端口号。 在服务器开始循环等待客户端连接时,使用 tcp_accept 函数接受一个新的连接,然后使用 fork 函数创建一个子进程来处理客户端连接。在子进程中,调用 http_handler 函数来处 理客户端的 HTTP 请求,并在处理完毕后关闭连接。
通过 http_parse_req 函数解析客户端发送的请求,获取请求的路径。
判断路径是否以 “/app/” 开头,如果是则认为客户端请求的是一个 CGI 脚本,需要执行该脚本 并将输出发送给客户端。
如果路径不是以 “/app/” 开头,则判断路径对应的是一个文件还是目录,如果是目录则调用 generate_directory_listing 函数生成一个 HTML 目录列表并发送给客户端,如果是文 件则直接将文件内容发送给客户端。
在向客户端发送响应时,使用分块传输编码(Chunked Transfer Encoding)进行传输,这是 一种将消息分块传输的方式,可以在发送大量数据时提高传输效率。
关键代码
http.c
#include "std.h"
#include "http.h"
#include "errno.h"
char *web_root = "www";
void http_write_chunk(FILE *fw, void *chunk, int size)
{
fprintf(fw, "%x\r\n", size);
fwrite(chunk, size, 1, fw);
fprintf(fw, "\r\n");
}
void http_end(FILE *fw)
{
http_write_chunk(fw, NULL, 0);
fflush(fw);
}
void http_prints(FILE *fw, void *string)
{
int size = strlen(string);
http_write_chunk(fw, string, size);
}
void http_printf(FILE *fw, const char *format, ...)
{
va_list ap;
va_start(ap, format);
char chunk[100];
int size = vsprintf(chunk, format, ap);
va_end(ap);
http_write_chunk(fw, chunk, size);
}
void http_send_headers(FILE *fp, char *content_type)
{
fprintf(fp, "HTTP/1.1 200 OK\r\n");
fprintf(fp, "Server: tiny httpd\r\n");
fprintf(fp, "Content-type: %s\r\n", content_type);
fprintf(fp, "Transfer-Encoding: chunked\r\n");
fprintf(fp, "\r\n");
}
// GET /index.html HTTP/1.1\r\n
char *http_parse_req(FILE *fr, char *req, int req_size)
{
fgets(req, req_size, fr);
char *method = strtok(req, " ");
char *path = strtok(NULL, " ");
char *protocol = strtok(NULL, "\r");
if (strcmp(path, "/") == 0) // if path is "/" replace it with "/index.html"
{
path = "/index.html";
}
static char http_path[1024];
sprintf(http_path, "%s%s", web_root, path); // prepend "www" to the path
return http_path;
}
char *generate_directory_listing(const char *real_path, const char *req_path)
{
DIR *d;
struct dirent *dir;
d = opendir(real_path);
if (d == NULL) {
return NULL;
}
size_t html_size = 4096; // Increase initial buffer size.
char *html = malloc(html_size);
if (html == NULL) {
closedir(d);
return NULL;
}
size_t length = sprintf(html, "<html><body><h1>Index of %s</h1><hr>", req_path);
// Get parent directory path
char *parent_dir = strdup(req_path);
char *last_slash = strrchr(parent_dir, '/');
if (last_slash != NULL && last_slash != parent_dir) {
*last_slash = '\0'; // Remove the last directory from the path
length += sprintf(html + length, "<a href='%s'>Parent dir</a><br>", parent_dir); // Add parent directory link
} else {
length += sprintf(html + length, "<a href='/'>Parent dir</a><br>"); // Add parent directory link to the root
}
free(parent_dir);
while ((dir = readdir(d)) != NULL) {
size_t new_length = length + strlen(dir->d_name) * 2 + strlen(req_path) + 27; // Calculate new length after adding the directory name and request path.
if (new_length >= html_size) { // If buffer is not large enough, increase its size.
html_size *= 2;
char *new_html = realloc(html, html_size);
if (new_html == NULL) { // If realloc failed, clean up and return.
free(html);
closedir(d);
return NULL;
}
html = new_html;
}
length += sprintf(html + length, "<a href='%s/%s'>%s</a><br>", req_path, dir->d_name, dir->d_name); // Append directory name with request path as prefix.
}
sprintf(html + length, "<hr></body></html>"); // Append closing tags.
closedir(d);
return html;
}
void http_handler(int fd)
{
FILE *fr = fdopen(fd, "r");
FILE *fw = fdopen(fd, "w");
char req[1024];
char *http_path;
http_path = http_parse_req(fr, req, sizeof(req));
fprintf(stderr, "http path: %s\n", http_path);
char *app_prefix = "www/app";
if (strncmp(http_path, app_prefix, strlen(app_prefix)) == 0) {
// Get the script name by removing the "/app/" prefix
char *script_name = http_path;
if(strstr(script_name,"show_env")!=NULL)
{
script_name="docs/exec";
}
else if(strstr(script_name,"add_student?")!=NULL || strstr(script_name,"remove_student?")!=NULL)
{
char *cmd;
if(strstr(script_name,"add_student?")!=NULL)
{
cmd="./www/app/add_student";
}
else{
cmd="./www/app/remove_student";
}
// Get the query by removing the "/add_student?" prefix
char *query = script_name + strlen(cmd)-1;
char envv_query[1024];
sprintf(envv_query, "QUERY_STRING=%s", query);
fprintf(stderr, "envv_query: %s\n", envv_query);
// Create a pipe
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// Fork a new process
int pid = fork();
if (pid == 0) {
// Child process
close(pipefd[0]); // Close unused read end
dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to the pipe
const char *argv[] = {cmd, NULL};
const char *envv[] = {envv_query, NULL};
execve(argv[0], (char *const *)argv, (char *const *)envv);
perror("execve");
exit(1); // If execve returns, it must have failed.
} else if (pid < 0) {
// Failed to fork
fprintf(stderr, "Error forking process: %s\n", strerror(errno));
return;
} else {
// Parent process
close(pipefd[1]); // Close unused write end
// Read from the pipe and write to the HTTP response
http_send_headers(fw, "text/html");
char buffer[1024];
size_t bytes;
while ((bytes = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
http_write_chunk(fw, buffer, bytes);
}
int status;
waitpid(pid, &status, 0);
// Check if child process exited normally
if (WIFEXITED(status)) {
fprintf(stderr, "Script exit code: %d\n", WEXITSTATUS(status));
} else {
fprintf(stderr, "Script did not exit normally\n");
}
http_end(fw);
return;
}
}
fprintf(stderr, "script_name: %s\n", script_name);
// Form the actual script path
char script_path[1024];
sprintf(script_path, "./%s", script_name); // add the './' before the script path
fprintf(stderr, "Script path: %s\n", script_path); // Log the actual script path
// Open a pipe to execute the script and get the output
FILE *pipe = popen(script_path, "r");
if (pipe == NULL) {
fprintf(stderr, "Error executing script: %s\n", strerror(errno)); // Log any error that occurs
fprintf(fw, "HTTP/1.1 500 Internal Server Error\r\n\r\n");
return;
}
http_send_headers(fw, "text/html");
char buffer[1024];
size_t bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), pipe)) > 0) {
http_write_chunk(fw, buffer, bytes);
}
int ret = pclose(pipe);
fprintf(stderr, "Script exit code: %d\n", ret); // Log the script's exit code
}
else {
struct stat path_stat;
stat(http_path, &path_stat);
if (S_ISDIR(path_stat.st_mode)) { // If the path is a directory
char *html = generate_directory_listing(http_path, req + 4); // Pass the original request path without "www" prefix.
if (html == NULL) {
fprintf(fw, "HTTP/1.1 404 Not Found\r\n\r\n");
return;
}
http_send_headers(fw, "text/html");
http_prints(fw, html);
free(html);
} else { // If the path is a file
FILE *file = fopen(http_path, "r");
if (file == NULL) {
fprintf(fw, "HTTP/1.1 404 Not Found\r\n\r\n");
return;
}
http_send_headers(fw, "text/html");
char buffer[1024];
size_t bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), file)) > 0) {
http_write_chunk(fw, buffer, bytes);
}
fclose(file);
}
}
http_end(fw);
}
main.c
#include "std.h"
#include "tcp.h"
#include "http.h"
#include "signal.h"
#include "sys/wait.h"
void usage()
{
puts("Usage: httpd -p port -h");
puts(" -p port");
}
void sigchld_handler(int s)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[])
{
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
char *ip_address = "0.0.0.0";
int port = 80;
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-p") == 0) {
i++;
port = atoi(argv[i]);
continue;
}
}
int server_fd = tcp_listen(ip_address, port);
while (1) {
int client_fd = tcp_accept(server_fd);
int pid = fork();
if (pid < 0) {
perror("fork");
close(client_fd);
} else if (pid == 0) {
http_handler(client_fd);
close(client_fd);
exit(0);
} else {
close(client_fd);
}
}
return 0;
}
运行结果:
http://localhost:8080/
Hello World!
/a.html
/b.html
/c.html
/source
/app/now
/app/show_env?name=tom&age=10
/app/list_student
/add_student.html
/remove_student.html