Gio DBus 中创建复合类型的参数
2024-12-18 21:18:31+08:00

Gio::DBus 中创建复合类型的参数

首先,需要知道两个基本知识:

  • 每一个参数都需要被包装为 Variant。
  • 全体参数需要被整体包装成一个 Tuple。

即使只有一个参数也需要包装为 Tuple,例如一个 string:

const auto arg1 = Glib::Variant<Glib::ustring>::create("hello world");
Glib::VariantContainerBase args = Glib::VariantContainerBase::create_tuple(arg1);

Glib::Variant 是模板,是不完全的类型,所以要使用它的基类 Glib::VariantBase 来表示任意类型的 Variant 对象。

因此,一个 a{sv} 类型的参数类型为 Glib::Variant<std::map<Glib::ustring, Glib::VariantBase>>

Linux C API 获取广播地址
2024-12-18 21:18:31+08:00

Linux C API 获取广播地址

广播地址通常为网段的最后一个地址

通过对一个 socket 进行 ioctl(SIOCGIFBRDADDR) 即可获取广播地址。

struct ifreq req;
strcpy(req.ifr_name, "网卡名");
ioctl(sock, SIOCGIFBRDADDR, &req);
inet_ntoa(((struct sockaddr_in*)&(req.ifr_addr))->sin_addr)

遍历网卡的方法可以参考 Linux C API 获取本机的 IP 地址

示例:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>

#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>

bool getBoardCastAddress(const char* iface, char* buffer, size_t n)
{
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0)
    {
        fprintf(stderr, "socket: %s\n", strerror(errno));
        return false;
    }


    struct ifreq req;
    strcpy(req.ifr_name, iface);
    if (ioctl(sock, SIOCGIFBRDADDR, &req) < 0)
    {
        fprintf(stderr, "ioctl: %s\n", strerror(errno));
        close(sock);
        return false;
    }

    strncpy(buffer, inet_ntoa(((struct sockaddr_in*)&(req.ifr_addr))->sin_addr), n);
    close(sock);
    return true;
}

int main(void)
{
    char addr[INET_ADDRSTRLEN];
    if (!getBoardCastAddress("enp2s0", addr, INET_ADDRSTRLEN))
    {
        return EXIT_FAILURE;
    }

    printf("broadcast %s\n", addr);
    return EXIT_SUCCESS;
}
Linux C API 获取本机的 IP 地址
2024-12-18 21:18:31+08:00

Linux C API 获取本机的 IP 地址

方法1:遍历网卡

#include <stdio.h>      
#include <ifaddrs.h>
#include <arpa/inet.h>

int main (void) 
{
    struct ifaddrs* ifAddrStruct = NULL;
    getifaddrs(&ifAddrStruct); // 获得网卡的链表

    while (ifAddrStruct!=NULL) 
    {
        if (ifAddrStruct->ifa_addr->sa_family==AF_INET) 
        {
            // IPv4
            char address[INET_ADDRSTRLEN];
            void* inAddr = &((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
            inet_ntop(AF_INET, inAddr, address, INET_ADDRSTRLEN);
            
            char netMask[INET_ADDRSTRLEN];
            void* inNetMask = &((struct sockaddr_in *)ifAddrStruct->ifa_netmask)->sin_addr;
            inet_ntop(AF_INET, inNetMask, netMask, INET_ADDRSTRLEN);
            
            char broadAddr[INET_ADDRSTRLEN];
            void* inBroadAddr = &((struct sockaddr_in *)ifAddrStruct->ifa_ifu.ifu_broadaddr)->sin_addr;
            inet_ntop(AF_INET, inBroadAddr, broadAddr, INET_ADDRSTRLEN);
            
            printf("IPv4 %8s: %31s \t%31s \t%31s\n", ifAddrStruct->ifa_name, address, netMask, broadAddr); 
        } 
        else if (ifAddrStruct->ifa_addr->sa_family==AF_INET6) 
        {
            // IPv6
            char address[INET6_ADDRSTRLEN];
            void* inAddr = &((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
            inet_ntop(AF_INET6, inAddr, address, INET6_ADDRSTRLEN);
            
            char prefixMask[INET6_ADDRSTRLEN];
            void* inPrefixMask = &((struct sockaddr_in *)ifAddrStruct->ifa_netmask)->sin_addr;
            inet_ntop(AF_INET6, inPrefixMask, prefixMask, INET6_ADDRSTRLEN);
            
            printf("IPv6 %8s: %31s \t%31s\n", ifAddrStruct->ifa_name, address, prefixMask); 
        } 
        ifAddrStruct=ifAddrStruct->ifa_next;
    }
    return 0;
}

方法2:gethostbyname

这个方法不能支持 IPv6

#include <stdio.h>    
#include <unistd.h>  
#include <netdb.h>
#include <arpa/inet.h>

int main (void) 
{
    // 获取 host name
    char hostName[_SC_HOST_NAME_MAX];
    gethostname(hostName, _SC_HOST_NAME_MAX);

    // 通过 host name 获取 host entry
    struct hostent* hostEntry = gethostbyname(hostName);

    // 打印 IP 地址
    for(int i = 0; hostEntry->h_addr_list[i]; i++) 
    {
        printf("%s\n", inet_ntoa(*(struct in_addr*)(hostEntry->h_addr_list[i])));
    }

    return 0;
}

netdb.h 中还有一个 gethostent 函数,能够遍历 host entry,但是它只能查询 /etc/hosts 里的值。因此,它通常只能返回 127.0.0.1

#include <stdio.h>    
#include <unistd.h>  
#include <netdb.h>
#include <arpa/inet.h>

int main(void)
{
    struct hostent* hostEntry;
    while((hostEntry = gethostent()) != NULL)
    {
        for(int i = 0; hostEntry->h_addr_list[i]; i++) 
        {
            printf("%s\n", inet_ntoa(*(struct in_addr*)(hostEntry->h_addr_list[i])));
        }
    }
    return 0;
}
通过 ALSA 显示音频设备的声道
2024-12-18 21:18:31+08:00

通过 ALSA 显示音频设备的声道

// apt install libasound2-dev
// LDFLAGS := -lasound
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s <device>\n", argv[0]);
        fprintf(stderr, "       %s hw:0,0\n", argv[0]);
        return EXIT_FAILURE;
    }

    snd_pcm_t* pcm = NULL;
    if(snd_pcm_open(&pcm, argv[1], SND_PCM_STREAM_PLAYBACK, 0) < 0)
    {
        fprintf(stderr, "snd_pcm_open failed\n");
        return EXIT_FAILURE;
    }

    snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(pcm);
    if(chmaps == NULL)
    {
        fprintf(stderr, "snd_pcm_query_chmaps failed\n");
        return EXIT_FAILURE;
    }

    for(int i = 0; chmaps[i] != NULL; i++)
    {
        snd_pcm_chmap_query_t* mapping = chmaps[i];
        printf("Mapping-%d\n", i);
        for(int ch = 0; ch < mapping->map.channels; ch++)
        {
            printf("\tchannel-%d %s\n", ch, snd_pcm_chmap_long_name(mapping->map.pos[ch]));
        }
    }

    snd_pcm_free_chmaps(chmaps);

    return EXIT_SUCCESS;
}
通过 PulseAudio 播放和录制声音
2024-12-18 21:18:31+08:00

通过 PulseAudio 播放和录制声音

// libs: -lpulse 
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <pulse/pulseaudio.h>

#ifdef DEBUG
    #define LOG(...) printf(__VA_ARGS__)
#else
    #define LOG(...)
#endif

typedef enum Method
{
    METHOD_PLAY,
    METHOD_RECORD
}Method;

typedef struct UserData
{
    pa_context* context;
    pa_stream* stream;
    pa_mainloop_api* api;
    FILE* fp;
    Method method;
}UserData;

static void mainloop_quit(UserData* userdata, int ret);
static void context_state_callback(pa_context* context, void* userdata) ;
static void stream_write_callback(pa_stream* stream, size_t length, void* userdata);
static void stream_read_callback(pa_stream* stream, size_t length, void* userdata);
static void stream_drain_complete(pa_stream* stream, int success, void* userdata);
static void context_drain_complete(pa_context* c, void* userdata);

int main(int argc, char* argv[]) 
{
    // 参数检查
    if(argc != 3)
    {
        printf("pademo [play|record] <sound-file>\n");
        return EXIT_FAILURE;
    }

    // 创建一个线程,并在该线程中创建 mainloop
    pa_mainloop* mainloop = pa_mainloop_new();
    if(mainloop == NULL)
    {
        fprintf(stderr, "pa_threaded_mainloop_new failed.\n");
        return EXIT_FAILURE;
    }

    // 获取API
    pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
    if(api == NULL)
    {
        pa_mainloop_free(mainloop);
        fprintf(stderr, "pa_threaded_mainloop_get_api failed.\n");
        return EXIT_FAILURE;
    }

    // 创建上下文
    pa_context* context = pa_context_new(api, "demo");
    if(context == NULL)
    {
        pa_mainloop_free(mainloop);
        fprintf(stderr, "pa_context_new failed.\n");
        return EXIT_FAILURE;
    }

    UserData data;
    if(strcmp(argv[1],"play") == 0)
    {
        data.fp = fopen(argv[2], "rb");
        data.method = METHOD_PLAY;
    } 
    else if(strcmp(argv[1], "record") == 0)
    {
        data.fp = fopen(argv[2], "wb");
        data.method = METHOD_RECORD;
    }
    else
    {
        fprintf(stderr, "Unknown method %s\n", argv[1]);
        // 释放
        pa_context_unref(context);
        pa_mainloop_free(mainloop);
        return EXIT_FAILURE;
    }

    // 设置状态变化的回调函数,这是主入口
    data.context = context;
    data.api = api;
    pa_context_set_state_callback(context, context_state_callback, (void*)(&data));

    // 开始建立连接
    if(pa_context_connect(context, NULL, PA_CONTEXT_NOFAIL, NULL) < 0)
    {
        pa_context_unref(context);
        pa_mainloop_free(mainloop);
        fprintf(stderr, "pa_context_connect failed.\n");
        return EXIT_FAILURE;
    }
    
    // 运行mainloop
    int ret = pa_mainloop_run(mainloop, NULL);

    // 退出
    pa_context_unref(context);
    pa_mainloop_free(mainloop);
    return ret;
}

// 退出主循环
static void mainloop_quit(UserData* userdata, int ret)
{
    LOG("mainloop_quit\n");
    userdata->api->quit(userdata->api, ret);
}

// 状态变化的回调函数
static void context_state_callback(pa_context* context, void* userdata) 
{
    UserData* data = (UserData*)(userdata);
    pa_context_state_t state = pa_context_get_state(context);
    switch (state) 
    {
    case PA_CONTEXT_READY: // 上下文就绪
    {
        LOG("PA_CONTEXT_READY\n");

        // 创建spec
        pa_sample_spec sampleSpec;
        sampleSpec.rate = 44100;
        sampleSpec.format = PA_SAMPLE_S16LE;
        sampleSpec.channels = 2;

        // 创建channel map
        pa_channel_map channelMap;
        pa_channel_map_init_stereo(&channelMap);

        // 创建stream
        pa_stream* stream = pa_stream_new(context, "demo-stream", &sampleSpec, &channelMap);
        data->stream = stream;

        if(data->method == METHOD_PLAY) // 播放
        {
            pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
            pa_stream_set_write_callback(stream, stream_write_callback, userdata);
        }
        else if(data->method == METHOD_RECORD) // 录音
        {
            pa_stream_connect_record(stream, NULL, NULL, PA_STREAM_NOFLAGS);
            pa_stream_set_read_callback(stream, stream_read_callback, userdata);
        }
        break;
    }

    case PA_CONTEXT_TERMINATED: // 结束
        LOG("PA_CONTEXT_TERMINATED\n");
        mainloop_quit(data, EXIT_SUCCESS);
        break;

    default:
        LOG("context state %d\n", state);
    }
}

// 播放的回调
static void stream_write_callback(pa_stream* stream, size_t length, void* userdata)
{
    UserData* data = (UserData*)(userdata);
    void* buffer;
    while(true)
    {
        // 给buffer分配空间,不需要手动释放
        pa_stream_begin_write(stream, &buffer, &length); 
        
        // 读取文件,写入stream
        length = fread(buffer, 1, length, data->fp);
        pa_stream_write(stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE); // 会自动释放buffer
        LOG("play %zu bytes\n", length);

        // 文件读取完毕
        if(feof(data->fp))
        {
            pa_stream_cancel_write(stream);
            pa_stream_set_write_callback(stream, NULL, NULL); //清除回调
            pa_operation* o = pa_stream_drain(stream, stream_drain_complete, data); // 设置播放完毕时的回调
            if(o == NULL)
            {
                mainloop_quit(data, EXIT_FAILURE);
            }
            pa_operation_unref(o);
            break;
        }
    }
}

// 录音的回调
static void stream_read_callback(pa_stream* stream, size_t length, void* userdata)
{
    UserData* data = (UserData*)(userdata);
    const void *buffer;
    while(pa_stream_readable_size(stream) > 0)
    {
        pa_stream_peek(stream, &buffer, &length);
        if(buffer == NULL || length <= 0)
        {
            continue;
        }

        fwrite(buffer, length, 1, data->fp);
        fflush(data->fp);
        LOG("record %zu bytes\n", length);
        pa_stream_drop(stream);
    }
}

// 播放完毕的回调
static void stream_drain_complete(pa_stream* stream, int success, void* userdata) 
{
    (void)(success);
    LOG("stream_drain_complete\n");
    UserData* data = (UserData*)(userdata);

    // 释放stream
    pa_stream_disconnect(stream); 
    pa_stream_unref(stream);
    data->stream = NULL;

    // 设置上下文结束的回调
    pa_operation* o = pa_context_drain(data->context, context_drain_complete, NULL);
    if (o == NULL)
    {
        pa_context_disconnect(data->context);
    }
    else 
    {
        pa_operation_unref(o);
    }
}

// 上下文结束的回调
static void context_drain_complete(pa_context* context, void* userdata)
{
    (void)(userdata);
    LOG("context_drain_complete\n");
    pa_context_disconnect(context);
}
Linux Hook 技术
2024-12-18 20:03:48+08:00

Linux Hook 技术

Hook 是一种覆盖重写进程中符号的技术,在 Linux 中,通过环境变量 LD_PRELOAD 预加载包含同名符号的动态库即可实现。

覆盖 malloc 和 free 检查内存泄漏

// 文件名: memcheck.c
// 编译命令: gcc -o memcheck.so memcheck.c --shared -fPIC
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <execinfo.h>

// 打印调用栈的最大深度
#define MAX_STACK_DEPTH 16

typedef struct RecordNode RecordNode;

struct RecordNode
{
    RecordNode* next;
    void* ptr;
    size_t size;
    void* stack_trace[MAX_STACK_DEPTH];
    size_t stack_depth;
};

static RecordNode* head = NULL;                 // 此处头指针不存数据,head->next 才是第一个结点
static RecordNode* tail = NULL;
static void* (*real_malloc)(size_t) = NULL;     // 原 malloc 函数的地址
static void (*real_free)(void*) = NULL;         // 原 free 函数的地址
static bool ignore = false;                     // 忽略内部调用的 malloc

// 打印调用栈
static void printStack(const RecordNode* node)
{
    char** symbols = backtrace_symbols(node->stack_trace, node->stack_depth);
    for (size_t i = 0; i < node->stack_depth; ++i) {
        fprintf(stderr, " [%zu] %s \r\n", i, symbols[i]);
    }
    real_free(symbols);
}

// 打印内存泄漏记录
static void printRecord(void)
{
    ignore = true;
    for (RecordNode* node = head->next; node != NULL; node = node->next)
    {
        fprintf(stderr, "Leak %zu bytes at %p\n", node->size, node->ptr);
        printStack(node);
    }
    ignore = false;
}

// 初始化
static void init()
{
    // 通过 RTLD_NEXT 查找当前进程空间的下一个同名符号来获取原函数地址
    real_malloc = (void*(*)(size_t))dlsym(RTLD_NEXT, "malloc");
    real_free = (void(*)(void*))dlsym(RTLD_NEXT, "free");

    head = (RecordNode*)real_malloc(sizeof(RecordNode));
    head->next = NULL;
    tail = head;
    atexit(printRecord);
}

// 添加记录
static RecordNode* addRecord(void* ptr, size_t size)
{
    RecordNode* node = (RecordNode*)real_malloc(sizeof(RecordNode));
    node->next = NULL;
    node->ptr = ptr;
    node->size = size;
    node->stack_depth = 0;

    tail->next = node;
    tail = node;
    return node;
}

// 删除记录
static void delRecord(void* ptr)
{
    RecordNode* prev = head;
    for (RecordNode* node = head->next; node != NULL; node = node->next)
    {
        if (node->ptr == ptr)
        {
            prev->next = node->next;
            if (node == tail)
                tail = prev;
            real_free(node);
            break;
        }
        prev = node;
    }
}

// hook malloc
void* malloc(size_t size)
{
    if (real_malloc == NULL)
        init();

    void* ptr = real_malloc(size);

    if (!ignore) // 防止内部调用 malloc 导致死循环
    {
        ignore = true;
        RecordNode* node = addRecord(ptr, size);
        node->stack_depth = backtrace(node->stack_trace, MAX_STACK_DEPTH);
        ignore = false;
    }
    return ptr;
}

// hook free
void free(void* ptr)
{
    real_free(ptr);
    delRecord(ptr);
}

预加载 memcheck.so 来检查内存泄漏:

构建程序(即下述的 test)时在链接选项中添加 -rdynamic 选项导出符号表才能显示函数名,否则只能显示地址。

$ LD_PRELOAD=./memcheck.so ./test 
Leak 233 bytes at 0x559e563c8350
 [0] ./memcheck.so(malloc+0x7b) [0x7f6546b064a7] 
 [1] ./test(func1+0x12) [0x559e5579215b] 
 [2] ./test(func3+0x12) [0x559e55792185] 
 [3] ./test(main+0x12) [0x559e557921a4] 
 [4] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f6546829d90] 
 [5] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f6546829e40] 
 [6] ./test(_start+0x25) [0x559e55792085] 
Leak 666 bytes at 0x559e563c9560
 [0] ./memcheck.so(malloc+0x7b) [0x7f6546b064a7] 
 [1] ./test(func2+0x12) [0x559e55792170] 
 [2] ./test(func3+0x1c) [0x559e5579218f] 
 [3] ./test(main+0x12) [0x559e557921a4] 
 [4] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f6546829d90] 
 [5] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f6546829e40] 
 [6] ./test(_start+0x25) [0x559e55792085]
怎样 detach 子进程
2024-12-18 20:03:48+08:00

怎样 detach 子进程

通过 fork 创建进程后,父进程可以使用 waitpid 来获取子进程的结束状态。 如果子进程先于父进程退出,而父进程没有调用 waitpid 的话,子进程将一直存续下去以保持结束状态可以被获取。 此时,子进程被称为僵尸进程

如果子进程没有被调用 waitpid 而父进程退出了,此时子进程被称为孤儿进程,它会被 init进程(PID为1) 领养并调用waitpid回收。

有时我们并不关心子进程的结束状态,希望像 pthread_detach 一样示释放子进程。有以下两种方法可以实现:

  1. 父进程调用 fork 创建子进程A并对其调用 waitpid子进程A再次 fork 创建子进程B并退出。

这样一来,子进程A 由父进程回收,子进程B 成为孤儿进程被init进程回收。

  1. 父进程调用 signal(SIGCHLD, SIG_IGN)

SIGCHLD 是子进程状态发生变化时产生的信号,默认就是忽略的,但是必须显示调用才会释放子进程。

守护进程

上述方法一中创建孤儿进程的方法常被用于创建守护进程。常用步骤如下:

  1. 调用 fork 创建子进程,然后父进程退出。
  2. 子调用 setsid 创建新的会话和进程组,从而避免被原先的会话、进程组以及会话终端影响。
  3. 调用 chdir 设置工作目录。
  4. 调用 umask 设置文件掩码(通常设为 umask(0))。
  5. 关闭不需要的资源(从父进程继承的文件描述符等)。

除第一步外的后续步骤并不是必须的,但大部分情况下是必要的。

调用 PAM 进行认证
2024-12-18 20:03:48+08:00

调用 PAM 进行认证

// gcc main.c -lpam

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termio.h>
#include <security/pam_appl.h>

/***************************************
 * @brief 开关回显 
 * @param[in] fd 文件描述符
 * @param[in] off 1-关闭回显,0-开启回显
 * *************************************/
static void echoOff(int fd, int off)
{
    struct termio tty;
    (void) ioctl(fd, TCGETA, &tty);
    if (off) 
    {
        tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
        (void) ioctl(fd, TCSETAF, &tty);
    }
    else
    {
        tty.c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL);
        (void) ioctl(fd, TCSETAW, &tty);
    }
}

/***************************************
 * @brief 关闭标准输入的回显 
 * *************************************/
static void echoOffStdin()
{
    echoOff(fileno(stdin), 1);
}

/***************************************
 * @brief 开启标准输入的回显 
 * *************************************/
static void echoOnStdin()
{
    echoOff(fileno(stdin), 0);
}

/***************************************
 * @brief 读取一行输入
 * @return 输入的字符串
 * *************************************/
static char* readline()
{
    struct termio tty;
    char input[PAM_MAX_RESP_SIZE];

    /* 读取字符直到回车 */
    flockfile(stdin);
    int i = 0;
    for (; i < PAM_MAX_RESP_SIZE; i++)
    {
        int ch = getchar_unlocked();
        if (ch == '\n' || ch == '\r' ||ch == EOF)
            break;
        input[i] = ch;
    }
    funlockfile(stdin);
    input[i] = '\0';

    return (strdup(input));
}

/**************************************************
 * @brief PAM对话回调函数
 * @param[in] num_msg PAM发送过来的消息数量
 * @param[in] msg PAM发送过来的消息数据 
 * @param[out] resp 发回给PAM的应答
 * @param[in] appdata_ptr 用户参数 
 * @return 状态 
 * ************************************************/
static int conversation(int num_msg, const struct pam_message** msg, struct pam_response **resp, void *appdata_ptr)
{
    // 检查参数
    if (num_msg <= 0 || num_msg >= PAM_MAX_MSG_SIZE)
    {
        fprintf(stderr, "invalid num_msg(%d)\n", num_msg);
        return PAM_CONV_ERR;
    }

    // 给回复消息分配内存,这里分配的内存,由外层的 PAM 框架释放
    // TODO: 发生错误的时候需要手动释放
    if ((resp[0] = malloc(num_msg * sizeof(struct pam_response))) == NULL)
    {
        fprintf(stderr, "bad alloc\n");
        return PAM_BUF_ERR;
    }

    // 处理PAM发来的消息并应答
    for(int i = 0; i < num_msg; i++)
    {
        const struct pam_message* m = *msg + i;
        struct pam_response* r = *resp + i;
        r->resp_retcode = 0;    // 这个是保留属性,固定为0
        switch (m->msg_style)
        {
        case PAM_PROMPT_ECHO_OFF:   // 请求输入,不回显,例如请求输入用户名
            printf("%s", m->msg);
            echoOffStdin();         // 关闭回显
            r->resp = readline();   // 读取密码
            echoOnStdin();          // 开启回显
            printf("\n");           // 补个换行
            break;

        case PAM_PROMPT_ECHO_ON:    // 请求输入,回显,例如请求输入密码
            printf("%s", m->msg);
            r->resp = readline();
            break;

        case PAM_TEXT_INFO:         // 打印普通消息
            printf("%s\n", m->msg);
            break;

        case PAM_ERROR_MSG:         // 打印错误消息
            fprintf(stderr, "%s\n", m->msg);
            break;

        default:
            printf("DEFAULT\n");
            break;
        }
    }
    return PAM_SUCCESS;
}

int main()
{
    atexit(echoOnStdin); // 退出时开启回显

    struct pam_conv pam_conv = {conversation, NULL};
    pam_handle_t *pamh;
    if (PAM_SUCCESS != pam_start("login", NULL, &pam_conv, &pamh))
    {
        fprintf(stderr, "pam_start failed\n");
        return EXIT_FAILURE;
    }

    if (PAM_SUCCESS != pam_authenticate(pamh, 0))
    {
        fprintf(stderr, "pam_authenticate failed\n");
        pam_end(pamh, 0);
        return EXIT_FAILURE;
    }
    
    pam_end(pamh, 0);
    return EXIT_SUCCESS;
}
通过 V4L2 读取摄像头
2024-12-18 20:03:48+08:00

通过 V4L2 读取摄像头


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h> // libv4l-dev


#define VIDEO_DEV "/dev/video0"

int main()
{
    // 打开设备
    int videoFd = open(VIDEO_DEV, O_RDWR);
    if(videoFd < 0)
    {
        fprintf(stderr, "open %s failed: %s\n", VIDEO_DEV, strerror(errno));
        return EXIT_FAILURE;
    }

    // 读取设备属性
    struct v4l2_capability videoCap;
    if(ioctl(videoFd, VIDIOC_QUERYCAP, &videoCap) < 0)
    {
        close(videoFd);
        fprintf(stderr, "ioctl VIDIOC_QUERYCAP failed: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    printf("%s\n", videoCap.card);

    // 判断是否是摄像头
    if(videoCap.capabilities & V4L2_CAP_VIDEO_CAPTURE != V4L2_CAP_VIDEO_CAPTURE)
    {
        fprintf(stderr, "%s doesn't support video recording\n", VIDEO_DEV);
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 读取支持的格式
    bool supportYUYV = false;
    struct v4l2_fmtdesc fmtdesc;
    fmtdesc.index=0;
    fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("Support format:\n");
    while(ioctl(videoFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
    {
        printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
        fmtdesc.index++;
        if(fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV)
        {
            supportYUYV = true;
        }
    }

    if(supportYUYV == false)
    {
        fprintf(stderr, "YUYV 4:2:2 not supported\n");
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 设置帧格式
    struct v4l2_format videoFormat;
    videoFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    videoFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    if(ioctl(videoFd, VIDIOC_S_FMT, &videoFormat) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_S_FMT failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 申请缓冲
    struct v4l2_requestbuffers request;
    request.count = 3; //三帧缓冲
    request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    request.memory = V4L2_MEMORY_MMAP;
    if(ioctl(videoFd, VIDIOC_REQBUFS, &request) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_REQBUFS failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 获取缓冲
    struct v4l2_buffer buffer;
    memset(&buffer, 0, sizeof(buffer));
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_MMAP;
    buffer.index = 0;
    if(ioctl(videoFd, VIDIOC_QUERYBUF, &buffer) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_QUERYBUF failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }
    void* bufPtr = mmap(NULL, buffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, videoFd, buffer.m.offset);
    if(bufPtr == NULL)
    {
        fprintf(stderr, "mmap failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 创建一个帧缓冲
    if(ioctl(videoFd, VIDIOC_QBUF, &buffer) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_QBUF failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 开始采集
    enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
    if(ioctl(videoFd, VIDIOC_STREAMON, &bufType) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_STREAMON failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 等待采集完成
    fd_set fds;
    FD_ZERO(&fds); 
    FD_SET(videoFd,  &fds); 
    struct timeval   tv; 
    tv.tv_sec = 2;
    tv.tv_usec = 0;
    select(1, &fds, NULL, NULL, &tv);

    // 读取一帧图像并删除帧缓冲
    if(ioctl(videoFd, VIDIOC_DQBUF, &buffer) < 0)
    {
        fprintf(stderr, "ioctl VIDIOC_DQBUF failed: %s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 停止采集
    ioctl(videoFd, VIDIOC_STREAMOFF, &bufType);

    // 创建一个PPM文件
    FILE* ppmFptr = fopen("output.ppm", "wb");
    if(ppmFptr == NULL)
    {
        fprintf(stderr, "%s\n", strerror(errno));
        close(videoFd);
        return EXIT_FAILURE;
    }

    // 写入PPM header
    fprintf(ppmFptr, "P3\n%d %d\n255\n", videoFormat.fmt.pix.width, videoFormat.fmt.pix.height);

    // 读取像素,写入PPM文件
    for(size_t i = 0; i+3 < buffer.length; i+=4)
    {
        uint8_t Y1 = ((uint8_t*)bufPtr)[i];
        uint8_t U = ((uint8_t*)bufPtr)[i+1];
        uint8_t Y2 = ((uint8_t*)bufPtr)[i+2];
        uint8_t V = ((uint8_t*)bufPtr)[i+3];

        uint8_t B =  1.164 * (Y1 - 16) +  2.018 * (U - 128);
        uint8_t G =  1.164 * (Y1 - 16) -  0.391 * (U - 128) - 0.813 * (V - 128);
        uint8_t R =  1.164 * (Y1 - 16)                      + 1.596 * (V - 128);
        fprintf(ppmFptr, "%u %u %u ", R, G, B);

        B =  1.164 * (Y2 - 16) +  2.018 * (U - 128);
        G =  1.164 * (Y2 - 16) -  0.391 * (U - 128) - 0.813 * (V - 128);
        R =  1.164 * (Y2 - 16)                      + 1.596 * (V - 128);
        fprintf(ppmFptr, "%u %u %u ", R, G, B);
    }
    
    close(videoFd);
    fclose(ppmFptr);
    return EXIT_SUCCESS;
}