Repository navigation
Expand file tree
/
Copy pathNotes_lin
More file actions
457 lines (371 loc) · 13.1 KB
/
Copy pathNotes_lin
File metadata and controls
457 lines (371 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
git add .
git commit -m "Finish: new lexer"
git push origin lin
git push myrepo lin
#push my own
--------------------
git checkout main
git pull origin main
git merge lin
git add .
git commit -m ""
git push all
#merge and push to main
------------------------
REPL stands for Read-Eval-Print Loop:
Read: Read a line of input from the user.
Eval: Evaluate or process the input
Print: Output/echo the result.
Loop: Repeat this process for the next line, until you encounter EOF (Ctrl-D).
-------------------------
Ctrl-C in the terminal sends a SIGINT "interrupt" signal to the current program.
- Real shells do not quit when you press Ctrl-C while waiting for input, they just clear the line or print a new prompt.
Ctrl-\ sends a SIGQUIT "quit" signal.
-------------------------
// Test 1: Simple Command
Input: echo hello world
Expected:
[TOKEN_WORD:echo][TOKEN_WORD:hello][TOKEN_WORD:world]
// Test 2: Pipe and Redirection
Input: cat < file | grep foo > out.txt
Expected:
[TOKEN_WORD:cat][TOKEN_REDIR_IN:<][TOKEN_WORD:file][TOKEN_PIPE:|]
[TOKEN_WORD:grep][TOKEN_WORD:foo][TOKEN_REDIR_OUT:>][TOKEN_WORD:out.txt]
// Test 3: Strings Quoted with Single/Double Quotes
Input: echo "hello world" 'foo bar'
Expected:[TOKEN_WORD:echo][TOKEN_WORD:hello world:quoted=2][TOKEN_WORD:foo bar:quoted=1]
// Test 4: Operators Inside Quotes
Input: echo "a|b" | grep "c>d"
Expected:
[TOKEN_WORD:echo][TOKEN_WORD:a|b:quoted=2][TOKEN_PIPE:|]
[TOKEN_WORD:grep][TOKEN_WORD:c>d:quoted=2]
// Test 5: Escaped Characters
Input: echo \"hello\"
Expected:
[TOKEN_WORD:echo][TOKEN_WORD:"hello"]
// Test 6: Multiple Consecutive Operators
Input: cat << heredoc >> out
Expected:
[TOKEN_WORD:cat][TOKEN_HEREDOC:<<][TOKEN_WORD:heredoc]
[TOKEN_REDIR_APPEND:>>][TOKEN_WORD:out]
// Test 7: Only Spaces
Input: " "
Expected:
[TOKEN_WORD: :quoted=2]
// Test 8: Empty String
Input: (empty)
Expected:
(NULL or empty list)
// Test 9: Unclosed Quotes
Input: echo "hello world
Expected: Error or TOKEN_UNKNOWN (lexer should detect the error)
// Test 10: Token Containing Special Characters
Input: echo ab\$c
Expected:[TOKEN_WORD:echo][TOKEN_WORD:ab$c]
(The lexer should correctly interpret the escaped character)
-----------------------------"
void *safe_malloc(size_t size)
{
void *ptr;
ptr = malloc(size);
if (!ptr)
exit_error("malloc failed\n");
return (ptr);
}
------------------------
three issue:
1\safe malloc, fixed
2\strndup, fixed
3\tokenizer issue: the contents inside quotes are not being captured as a single token. Instead, they’re being split up or including stray quote characters.
---------------------
void print_redirects(t_redirect *redir)
{
while (redir)
{
printf(" Redirect: type=%d, file=%s\n", redir->type, redir->filename);
redir = redir->next;
}
}
void print_cmd_list(t_cmd *cmd)
{
int i, j = 0;
while (cmd)
{
printf("=== Command #%d ===\n", ++j);
printf(" argv:");
for (i = 0; cmd->argv && cmd->argv[i]; i++))
printf(" '%s'", cmd->argv[i]);
printf("\n");
print_redirects(cmd->redirects);
cmd = cmd->next;
}
}
-----------------
static void print_token_list(t_token *token_list)
{
int i = 0;
while (token_list)
{
printf("Token %d: %s (type=%d) (quoted=%d)\n", i, token_list->str, token_list->t_type, token_list->quote_type);
token_list = token_list->next;
i++;
}
}
void repl_loop(void)
{
char *line;
t_token *token_list;
while(1)
{
line = readline("minishell$ "); //should free line
if (!line) //Ctrl+D
{
printf("exit\n");
break ;
}
if(*line)
{
add_history(line); //no need to care about the history memory
token_list = get_token_list(line);
printf("You enter: %s\n", line);
print_token_list(token_list);
free_token_list(token_list);
free(line);
}
}
}
int main(void)
{
setup_signals();
repl_loop();
return (0);
}
----------------------------
✅ When should you use perror?
File open/read/write errors (e.g., fopen, open, read, write fail)
Memory allocation failures (malloc, calloc, etc.)
System call failures (like fork, pipe, dup2, execve, etc.)
Any library/system function documented to set errno on failure
❌ When should you NOT use perror?
Logic errors in your own code that have nothing to do with system/library calls
Checking if a command string matches something (no error from a system call)
User input validation errors
------------------
!!!!!!!! should fix 0730;
9\can we use errno ??
10\
lin@EricdeMacBook-Pro minishell % cat out > wc -l
cat: -l: No such file or directory
lin@EricdeMacBook-Pro minishell % ./minishell
minishell$ cat out > wc -l
minishell: -l : command not found
minishell$ ^D%
---------------
need fix:
1\ if i delibrately embedded error in open heredoc file, i should press Ctrl+d twice to exit instead of exit directly, why??
minishell$ cat << 1
heredoc open failed: Bad address
minishell$ ^D%
2\
[父进程]
|
|--- pipe1[0/1] (cat | grep)
|--- fork -> [cat进程]
|
|--- pipe2[0/1] (grep | wc)
|--- fork -> [grep进程]
|
|--- fork -> [wc进程]
|
|--- 关闭所有pipe,只等wait
eg. cat file.txt | grep foo | wc -l
每一轮循环父进程关闭/保留的 pipe fd
第一轮(cat)
父进程建 pipe1:[pipe1[0] (读), pipe1[1] (写)]
fork cat 进程
cat 子进程:
stdin = 默认
stdout = pipe1[1](写入 pipe1,给 grep 用)
父进程操作:
prev_pipe == -1,什么都不关
有下一个命令(grep),所以:
close(pipe1[1]) 只关 pipe1 的写端(父进程自己不用写了!)
prev_pipe = pipe1[0],把 pipe1[0](读端)留给下一轮(grep 的 stdin)
->
第二轮(grep)
父进程建 pipe2:[pipe2[0] (读), pipe2[1] (写)]
fork grep 进程
grep 子进程:
stdin = prev_pipe = pipe1[0]
stdout = pipe2[1]
父进程操作:
prev_pipe == pipe1[0],现在可以关掉 pipe1[0](父进程自己不用了,baton 交完)
有下一个命令(wc),所以:
close(pipe2[1]) 只关 pipe2 的写端(父进程不用写)
prev_pipe = pipe2[0],把 pipe2[0](读端)留给下一轮(wc 的 stdin)
->
第三轮(wc)
没有新 pipe 了(这是最后一段)
fork wc 进程
wc 子进程:
stdin = prev_pipe = pipe2[0]
stdout = 1(终端)
父进程操作:
prev_pipe == pipe2[0],现在可以关掉 pipe2[0](baton 完全传递完)
没有下一个命令(这是最后一段),不需要新 pipe,prev_pipe = -1
---------
子进程只保留自己要用的那一头(输入/输出),其它全部及时关闭
------------
!!!should fix
1\
export and echo:
-export:
export
export 123=minishell
export foo
-echo:
echo -nnnn -nnnn -nnnn minishell
----------
父进程
│
├── 调用 waitpid(pid, &status, 0)
│
├── 等待子进程结束...
│
├──[信号来了!]──► waitpid 被中断
│ │
│ ├── 返回值: -1
│ └── errno: EINTR (Interrupted system call)
│
├── 检查: (返回值 == -1 && errno == EINTR) ?
│ ├── 是 → 再次调用 waitpid (重试等待)
│ └── 否 → 继续处理退出状态
│
├── 最终拿到子进程状态
│
└── 根据 WIFEXITED / WIFSIGNALED 设置 exit_status
--------------
解析:单/双引号、转义、连续空格、结尾空格、管道/重定向组合、<< heredoc(含变量展开开关、Ctrl-C 取消、Ctrl-D 提前 EOF)。
展开:$VAR、$?、未定义变量、引号内/外的差异。
内建:
echo -n -nnn(连续 -n 应该也算数)、echo 的换行逻辑;
cd(无参、~、-、不存在路径/无权限时的返回码与环境更新);
export(无参时的输出格式、排序、带或不带 = 的键处理);
unset(不存在键不报错);
env(只打印带值的环境);
exit(无参、单参、非数字、多参时返回码与提示)。
执行与错误码:命令不存在=127、没有执行权限=126、把目录当可执行要报错。
信号:前台程序被 Ctrl-C/Ctrl-\ 终止时的消息与 $?,提示符下的互动。
--------------------
echo hello > out1 > out2
cat < infile > outfile
-----------
mimic the behavior of fork failed:
errno = ENOMEM;
pid = -1;
get_path, modify status
pipeline, modify fork failed, dup2 failed
single, modify fork failed
builtin, modify restore
------------------------------
Once execve() succeeds, it never returns. If it fails, you're already in a dying child, so:
✅ You do not need to call free_two(cmd, env_list) in the child, as long as the parent process will clean up everything later.
Memory in the child process is automatically cleaned up when it exits (no zombie mallocs). The only reason to free() manually in a child is if:
You're not using execve() and doing more logic.
Or you're looping/forking repeatedly without exiting (e.g., in a shell built-in).
But in your case — you're running execve(), then exiting. So no leak danger, and freeing is redundant.
------------------
//---------------------------exec part------------------------------------//
//execute builtins
int is_builtin(char *cmd);
int execute_builtin_cmd(char **argv, t_env **env,\
t_exec_path *exec_cmd);
void run_builtin_with_redir(t_exec_path *exec_cmd, t_env **env_list);
//exec_single
void execute_single_cmd(t_exec_path *cmd, t_env **env_list);
void print_error_and_exit(t_cmd *cmd, t_exec_path *exec_cmd,\
t_env **env_list);
void precheck_path_or_exit(char *path, t_exec_path *cmd,\
t_env **env_list);
//exec_pipline
void execute_pipeline(t_exec_path *exec_cmd, t_env **env_list);
//exec_pipeline_utils
int create_pipe_or_exit(t_exec_path *exec_cmd, t_pipe_ex *pinfo);
int set_up_stdin(t_pipe_ex *pinfo, t_exec_path *cmd, t_env **env_list);
int set_up_stdout(t_cmd *cmd, t_pipe_ex *pinfo, t_exec_path *exec_cmd,\
t_env **env_list);
void wait_exit(t_exec_path *exec_cmd, pid_t last_pid);
//exec_utils
void handle_execve_or_exit_inchild(t_exec_path *exec_cmd,\
t_cmd *cmd, t_env **env_list);
char *cleanup_heredoc(int fd, int saved_stdin, char *tmp_file,\
char *err_msg);
void free_all_and_exit(t_exec_path *cmd, t_env **env_list, int status);
void free_all_and_exit_pipe(t_exec_path *cmd, t_env **env_list,\
int status, t_pipe_ex *pinfo);
//redirections
int check_and_apply_redirections(t_cmd *cmd);
//heredoc
char *creat_heredoc_file(char *delim, int quoted, t_exec_path *cmd);
//get_path
char *get_env(t_env *env, char *key);
char *get_cmd_path(char *cmd, t_env *env_list, t_exec_path *exec_cmd);
//---------------------------built_in part--------------------------------//
//7 builtin cmds:
//builtins_opera1
int ft_echo(char **argv);
int ft_exit(char **argv, t_exec_path *exec_cmd, t_env **env_list);
void free_two(t_exec_path *exec_cmd, t_env **env_list);
int ft_pwd(t_env **env);
//builtins_opera2
int ft_cd(char **argv, t_env **env);
int ft_env(t_env **env);
int ft_unset(char **argv, t_env **env);
//builtins_opera3
int ft_export(char **argv, t_env **env);
//builtins_utils
void set_env(t_env **env, char *key, char *value);
long long ft_atoll(char *str);
int ft_is_numeric(char *str);
int env_count(t_env *env);
void sort_copy_list(t_env **copy_list, int size);
//---------------------------env part---------------------------------------//
// NOTES
// in minishell,
// we might change the elements of the original env(impletment unset, export),
// so we have to copy the oringal env, and change them in copy version
// first step is converting every envp[i] to a signal node,
// and connect these nodes to a linked list,
// in which we can add or delete env elements more easily than in array,
// after modifying the env list, we should convert linked list to array
//get env list
t_env *env_new_node(char *key, char *value);
t_env *env_list_init(char **envp);
void free_env_list(t_env *head);
//env list to envp
// second used in preexpander
char **env_list_to_envp(t_env *head);
char *get_env_value(char **envp, const char *key);
//---------------------------signal part-------------------------------------//
//signals.c
// 2 + 2 static inside
void signal_init(void);
void signal_default(void);
// signal_utils.c
void signal_heredoc(void);
void signal_ignore(void);
int hd_is_interrupted(void);
-----------------------------------------
SRCS := \
$(SRC_DIR)/main.c $(SRC_DIR)/main_helper.c\
$(SRC_DIR)/builtins/builtins_opera1.c $(SRC_DIR)/builtins/get_env_list.c \
$(SRC_DIR)/builtins/envlist_to_envp.c $(SRC_DIR)/builtins/builtins_opera2.c \
$(SRC_DIR)/builtins/builtins_opera3.c $(SRC_DIR)/builtins/builtins_utils.c \
$(SRC_DIR)/exec/exec_single.c $(SRC_DIR)/exec/exec_builtin.c \
$(SRC_DIR)/exec/exec_utils.c $(SRC_DIR)/exec/heredoc.c $(SRC_DIR)/exec/exec_pipeline_utils.c\
$(SRC_DIR)/exec/exec_pipeline.c $(SRC_DIR)/exec/get_path.c $(SRC_DIR)/exec/redirections.c \
$(SRC_DIR)/parsing/preexpanding.c $(SRC_DIR)/parsing/preexpanding_utils.c \
$(SRC_DIR)/parsing/lexing.c $(SRC_DIR)/parsing/lexing_w_token.c $(SRC_DIR)/parsing/lexing_utils.c \
$(SRC_DIR)/parsing/parsing.c $(SRC_DIR)/parsing/parsing_utils.c $(SRC_DIR)/parsing/parsing_to_cmd_list.c \
$(SRC_DIR)/utils/free.c $(SRC_DIR)/utils/str_utils.c $(SRC_DIR)/utils/err_msg.c \
$(SRC_DIR)/signals/signals.c $(SRC_DIR)/signals/signal_utils.c