gethostname函数在C中为什么会导致PATH环境变量被清除?

huangapple go评论106阅读模式
英文:

Why gethostname function in C causes the PATH env to be cleared?

问题

这段代码是一个名为imcsh的小型shell程序。在运行ls命令时显示"command not found"错误。但是当注释掉获取主机名的部分后,ls命令可以正常运行。您尝试使用setenv("PATH", "/bin:/usr/bin:/usr/local/bin", 1);重新设置了PATH变量,但问题仍然存在。您想知道问题出在哪里。

英文:

This is a code for a small shell called imcsh:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. #include <signal.h>
  8. #define MAX_INPUT_SIZE 1024
  9. #define MAX_TOKEN_SIZE 64
  10. #define MAX_NUM_TOKENS 64
  11. // Tokenizes the input string based on whitespace
  12. void tokenizeInput(char *input, char **tokens, int *numTokens)
  13. {
  14. char *token = strtok(input, " \t\n");
  15. *numTokens = 0;
  16. while (token != NULL && *numTokens < MAX_NUM_TOKENS)
  17. {
  18. tokens[*numTokens] = token;
  19. (*numTokens)++;
  20. token = strtok(NULL, " \t\n");
  21. }
  22. }
  23. // Executes the given command and waits for it to finish
  24. void executeCommand(char **tokens, int numTokens, int background)
  25. {
  26. pid_t pid = fork();
  27. if (pid == 0)
  28. { // Child process
  29. if (background)
  30. {
  31. // Ignore SIGINT signal for background processes
  32. signal(SIGINT, SIG_IGN);
  33. }
  34. // Execute the command
  35. execvp(tokens[0], tokens);
  36. // If execvp returns, there was an error
  37. printf("imcsh: command not found: %s\n", tokens[0]);
  38. exit(1);
  39. }
  40. else if (pid > 0)
  41. { // Parent process
  42. if (!background)
  43. {
  44. // Wait for the child process to finish
  45. waitpid(pid, NULL, 0);
  46. }
  47. else
  48. {
  49. // Print the PID of the background process
  50. printf("Background process PID: %d\n", pid);
  51. }
  52. }
  53. else
  54. { // Error occurred
  55. printf("Failed to fork()\n");
  56. exit(1);
  57. }
  58. }
  59. // Prints the version of imcsh
  60. void showVersion()
  61. {
  62. printf("IMCSH Version 1.1 created by me.\n");
  63. }
  64. // Redirects the output to a file
  65. void redirectOutput(char **tokens, int numTokens)
  66. {
  67. // Find the ">" symbol in the tokens
  68. int i;
  69. for (i = 0; i < numTokens; i++)
  70. {
  71. if (strcmp(tokens[i], ">") == 0)
  72. {
  73. break;
  74. }
  75. }
  76. // If ">" symbol found and there is a file name after it
  77. if (i < numTokens - 1)
  78. {
  79. char *filename = tokens[i + 1];
  80. // Open the file for writing (create if it doesn't exist, append if it does)
  81. FILE *file = fopen(filename, "a");
  82. if (file != NULL)
  83. {
  84. // Duplicate the file descriptor of the opened file
  85. int fd = fileno(file);
  86. // Redirect the stdout to the file descriptor
  87. dup2(fd, STDOUT_FILENO);
  88. fclose(file);
  89. }
  90. else
  91. {
  92. printf("imcsh: failed to open file: %s\n", filename);
  93. }
  94. }
  95. else
  96. {
  97. printf("imcsh: invalid command syntax\n");
  98. }
  99. }
  100. int main()
  101. {
  102. char input[MAX_INPUT_SIZE];
  103. char *tokens[MAX_NUM_TOKENS];
  104. int numTokens;
  105. char *username = getlogin();
  106. if (username == NULL)
  107. {
  108. printf("Failed to get username.\n");
  109. }
  110. char hostname[1024];
  111. if (gethostname(hostname, sizeof(hostname)) != 0)
  112. {
  113. printf("Failed to get hostname.\n");
  114. exit(1);
  115. }
  116. while (1)
  117. {
  118. // Print the command prompt
  119. // printf("%s@%s> ", username, hostname);
  120. printf("user@host> ");
  121. // Read user input
  122. fgets(input, sizeof(input), stdin);
  123. // Tokenize the input
  124. tokenizeInput(input, tokens, &numTokens);
  125. if (numTokens > 0)
  126. {
  127. // Check for internal commands
  128. if (strcmp(tokens[0], "exec") == 0)
  129. {
  130. int background = 0;
  131. // Check for "&" symbol at the end (for background execution)
  132. if (numTokens > 1 && strcmp(tokens[numTokens - 1], "&") == 0)
  133. {
  134. background = 1;
  135. tokens[numTokens - 1] = NULL; // Remove "&" symbol
  136. }
  137. // Execute the command
  138. executeCommand(tokens + 1, numTokens - 1, background);
  139. }
  140. else if (strcmp(tokens[0], "globalusage") == 0)
  141. {
  142. showVersion();
  143. }
  144. else if (strcmp(tokens[0], "quit") == 0)
  145. {
  146. // Check for running processes
  147. int status;
  148. pid_t result;
  149. do
  150. {
  151. result = waitpid(-1, &status, WNOHANG);
  152. if (result > 0)
  153. {
  154. printf("Running process: %d\n", result);
  155. }
  156. } while (result > 0);
  157. // Prompt for confirmation
  158. printf("The following processes are running, are you sure you want to quit? [Y/n]: ");
  159. char confirm[5];
  160. fgets(confirm, sizeof(confirm), stdin);
  161. if (confirm[0] == 'Y' || confirm[0] == 'y')
  162. {
  163. // Terminate all running processes and exit the shell
  164. kill(0, SIGTERM);
  165. break;
  166. }
  167. }
  168. else
  169. {
  170. // External command, execute it
  171. executeCommand(tokens, numTokens, 0);
  172. }
  173. }
  174. }
  175. return 0;
  176. }

if I run this and enter a command, for example, ls, it says command not found. But if I comment this section:

  1. if (gethostname(hostname, sizeof(hostname)) != 0)
  2. {
  3. printf("Failed to get hostname.\n");
  4. exit(1);
  5. }

ls will work. I tried to set PATH variable again using setenv("PATH", "/bin:/usr/bin:/usr/local/bin", 1);
But the problem still persists. What is the problem?

答案1

得分: 3

你没有正确构建令牌列表。

execv 函数的第二个参数是一个字符串数组,预期应以 NULL 指针终止。你没有设置这个空指针,所以 execv 会继续读取参数,直到找到一个 NULL。这将导致读取未初始化的数组元素,可能超出数组的末尾,触发未定义的行为。

在填充令牌列表后,需要在末尾添加一个 NULL 指针:

  1. void tokenizeInput(char *input, char **tokens, int *numTokens)
  2. {
  3. char *token = strtok(input, " \t\n");
  4. *numTokens = 0;
  5. while (token != NULL && *numTokens < MAX_NUM_TOKENS - 1)
  6. {
  7. tokens[*numTokens] = token;
  8. (*numTokens)++;
  9. token = strtok(NULL, " \t\n");
  10. }
  11. tokens[*numTokens] = NULL;
  12. }
英文:

You're not constructing the token list correctly.

The second argument to the execv function is an array of strings which is expected to be terminated by a NULL pointer. You don't set this null pointer, to execv keeps reading arguments until it finds one. This will read into uninitialized array elements and possibly past the end of the array, triggering undefined behavior.

After you've populated the token list, add a NULL pointer to the end:

  1. void tokenizeInput(char *input, char **tokens, int *numTokens)
  2. {
  3. char *token = strtok(input, &quot; \t\n&quot;);
  4. *numTokens = 0;
  5. while (token != NULL &amp;&amp; *numTokens &lt; MAX_NUM_TOKENS - 1)
  6. {
  7. tokens[*numTokens] = token;
  8. (*numTokens)++;
  9. token = strtok(NULL, &quot; \t\n&quot;);
  10. }
  11. tokens[*numTokens] = NULL;
  12. }

huangapple
  • 本文由 发表于 2023年6月29日 22:53:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/76582222.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定