APUE course source code
guowenxue
yesterday 7b55c92f8d1401a93c8fd8e342da271dce742000
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
/*********************************************************************************
 *      Copyright:  (C) 2025 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  exec.c
 *    Description:  This file is exec*() example program
 *
 *        Version:  1.0.0(10/27/2025)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "10/27/2025 05:42:26 PM"
 *
 ********************************************************************************/
 
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
 
// 标准输出重定向的文件, /tmp路径是在Linux系统在内存里做的一个文件系统,放在这里不用写硬盘程序运行会快些。
#define TMP_FILE         "/tmp/.ifconfig.log"
 
int main(int argc, char **argv)
{
    pid_t          pid;
    int            fd;
    char           buf[1024];
    int            rv;
    char          *ptr;
    FILE          *fp;
    char          *ip_start;
    char          *ip_end;
    char           ipaddr[16];
 
    // 父进程打开这个文件,子进程将会继承父进程打开的这个文件描述符,这样父子进程都可以通过各自的文件描述符访问同一个文件了
    if( (fd=open(TMP_FILE, O_RDWR|O_CREAT|O_TRUNC, 0644)) < 0 )
    {
        printf("Redirect standard output to file failure: %s\n", strerror(errno));
        return -1;
    }
 
    // 父进程开始创建进程
    pid = fork();
    if(pid < 0)
    {
        printf("fork() create child process failure: %s\n", strerror(errno));
        return -1;
    }
    else if( pid == 0 )   //子进程开始运行
    {
        printf("Child process start excute ifconfig program\n");
 
        // 子进程会继承父进程打开的文件描述符,此时子进程重定向标准输出到父进程所打开的文件里
        dup2(fd, STDOUT_FILENO);
 
        /*
           下面这句execl(...)函数是让子进程开始执行带参数的ifconfig命令: ifconfig eth0
           execl()会导致子进程彻底丢掉父进程的文本段、数据段,并加载/sbin/ifconfig这个程序的文本段、数据段重新建立进程内存空间。
           execl()函数的第一个参数是所要执行程序的路径,ifconfig命令(程序)的路径是/sbin/ifconfig;
           接下来的参数是命令及其相关选项、参数,每个命令、选项、参数都用双引号("")扩起来,并以NULL结束。
           */
        /*
           ifconfig eth0命令在执行时会将命令的执行的结果输出到标准输出上,而这时子进程已经重定向标准输出到文件中去了,所以ifconfig
           命令的打印结果会输出到文件中去,这样父进程就会从该文件里读到子进程执行该命令的结果;
           */
        execl("/sbin/ifconfig", "ifconfig", "enp2s0", NULL);
 
 
        /* execl()函数并不会返回,因为他去执行另外一个程序了。如果execl()返回了,说明该系统调用出错了。 */
        printf("Child process excute another program, will not return here. Return here means execl() error\n");
        return -1;
    }
    else
    {
        // 父进程等待一会,让子进程先执行
        sleep(1);
    }
 
    // 子进程因为调用了execl(), 它会丢掉父进程的文本段,所以子进程不会执行到这里了。只有父进程会继续执行这后面的代码
 
    memset(buf, 0, sizeof(buf));
 
    // 父进程这时候读是读不到内容的,这时因为子进程往文件里写内容时已经将文件偏移量修改到文件尾了
    rv=read(fd, buf, sizeof(buf));
    printf("Read %d bytes data dierectly read after child process write\n", rv);
 
    // 父进程如果要读则需要将文件偏移量设置到文件头才能读到内容
    memset(buf, 0, sizeof(buf));
    lseek(fd, 0, SEEK_SET);
    rv=read(fd, buf, sizeof(buf));
    printf("Read %d bytes data after lseek():\n %s", rv, buf);
 
    // 如果使用read()读的话,一下子就读 N 多个字节进buffer,但有时我们希望一行一行地读取文件的内容,这时可以使用fdopen()函数将文件描述符fd转成文件流fp
    fp = fdopen(fd, "r");
    fseek(fp, 0, SEEK_SET); // 重新设置文件偏移量到文件头
 
    while( fgets(buf, sizeof(buf), fp) )  // fgets()从文件里一下子读一行,如果读到文件尾则返回NULL
    {
        /* 包含IP地址的那一行包含有netmask关键字,如果在该行中找到该关键字就可以从这里面解析出IP地址了。
           inet 192.168.2.17  netmask 255.255.255.0  broadcast 192.168.2.255
           inet6 fe80::ba27:ebff:fee1:95c3  prefixlen 64  scopeid 0x20<link> */
        if( strstr(buf, "netmask") )
        {
            // 查找"inet"关键字,inet关键字后面跟的就是IP地址;
            ptr=strstr(buf, "inet");
            if( !ptr )
            {
                break;
            }
            ptr += strlen("inet");
 
            // inet 关键字后面是空白符,我们不确定是空格还是TAB,所以这里使用isblank()函数判断如果字符还是空白符就往后跳过;
            while( isblank(*ptr) )
                ptr++;
 
            // 跳过空白符后跟着的就是IP地址的起始字符;
            ip_start = ptr;
 
            // IP地址后面又是跟着空白字符,跳过所有的非空白字符,即IP地址部分:xxx.xxx.xxx.xxx
            while( !isblank(*ptr) )
                ptr++;
 
            // 第一个空白字符的地址也就是IP地址终止的字符位置
            ip_end = ptr;
 
            // 使用memcpy()函数将IP地址拷贝到存放IP地址的buffer中,其中ip_end-ip_start就是IP地址的长度,ip_start就是IP地址的起始位置;
            memset(ipaddr, 0, sizeof(ipaddr));
            memcpy(ipaddr, ip_start, ip_end-ip_start);
 
            break;
        }
    }
 
    printf("Parser and get IP address: %s\n", ipaddr);
 
    fclose(fp);
    unlink(TMP_FILE);
 
    return 0;
}