嵌入式Qt,U盘升级程序

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

        近期在完成一个U盘升级功能,对于Arm嵌入式设备来说应该算是必备的了。


        参考链接:

https://blog.csdn.net/newnewman80/article/details/8766657icon-default.png?t=N7T8https://blog.csdn.net/newnewman80/article/details/8766657

一、实现过程

1.检测U盘中的更新程序

结合netlink捕获USB的热拔插,我们需要去检测U盘的挂载点,通常情况下都是挂载在/media下。 

bool checkConsoleAppExists() 
{
    DIR *dir = opendir("/run/media"); // 打开 /run/media 目录 具体的挂载点根据设备决定
    if (dir) 
    {
        struct dirent *entry;
        while ((entry = readdir(dir)) != NULL) 
        {
            if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) 
            {
                devpath = "/run/media/" + std::string(entry->d_name);
                path = "/run/media/" + std::string(entry->d_name) + "/updateFileName";

                std::string signatureFilePath = devpath +"/sigFileName.sig"; 
                std::string publicKeyPath = devpath + "/key.pub" ;

                if (access(path.c_str(), F_OK) != -1) 
                {
                    //检测到更新程序,可以做一些校验,我这里的话是验证数字签名,根据具体情况决定
                    return checkUpdate(signatureFilePath,publicKeyPath,path);
                }
            }
        }
        closedir(dir);
    }
    return false;
}
                

2. 数字验证

/*检测是否需要更新,是否为正确的更新程序*/
bool checkUpdate(const std::string& signatureFile, const std::string& publicKeyFile, const std::string& dataFile) {
    // 打开更新文件
    // 这里是想要去检测版本号,去判断是否需要升级的,后面由于使用了数字签名就没有实现这部分。
    FILE *updateFile = fopen(dataFile.c_str(), "rb");
    if (!updateFile) {
        perror("Error opening update file\n");
        return false;
    }
    // 验证数字签名
    return verifySignature(signatureFile,publicKeyFile,dataFile);
}

// 函数用于验证数字签名是否有效
bool verifySignature(const std::string& signatureFile, const std::string& publicKeyFile, const std::string& dataFile) 
{
    // 打开公钥文件以读取公钥信息
    FILE* fp = fopen(publicKeyFile.c_str(), "r");
    if (!fp) {
        std::cerr << "Error opening public key file." << std::endl;
        return false;
    }

    // 从公钥文件中读取 RSA 公钥信息
    RSA* rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
    fclose(fp);

    // 检查是否成功读取公钥信息
    if (!rsa) {
        std::cerr << "Error reading public key." << std::endl;
        return false;
    }

    // 创建一个用于存储 RSA 公钥的 EVP_PKEY 对象
    EVP_PKEY* evpKey = EVP_PKEY_new();

    // 将 RSA 公钥赋值给 EVP_PKEY 对象
    if (!EVP_PKEY_assign_RSA(evpKey, rsa)) {
        std::cerr << "Error assigning RSA key." << std::endl;
        RSA_free(rsa);
        return false;
    }

    // 创建一个用于消息摘要计算的 EVP_MD_CTX 对象
    EVP_MD_CTX* ctx = EVP_MD_CTX_new();
    if (!ctx) {
        std::cerr << "Error creating context." << std::endl;
        RSA_free(rsa);
        return false;
    }

    // 打开数据文件以进行签名验证
    std::ifstream fileStream(dataFile, std::ios::binary | std::ios::ate);
    if (!fileStream.is_open()) {
        std::cerr << "Error opening data file." << std::endl;
        EVP_MD_CTX_free(ctx);
        RSA_free(rsa);
        return false;
    }

    // 读取数据文件的大小和内容
    std::streamsize fileSize = fileStream.tellg();
    fileStream.seekg(0, std::ios::beg);
    std::vector<unsigned char> fileData(fileSize);
    if (!fileStream.read(reinterpret_cast<char*>(fileData.data()), fileSize)) {
        std::cerr << "Error reading data file." << std::endl;
        fileStream.close();
        EVP_MD_CTX_free(ctx);
        RSA_free(rsa);
        return false;
    }
    fileStream.close();

    // 打开签名文件以进行签名验证
    std::ifstream signatureFileStream(signatureFile, std::ios::binary);
    if (!signatureFileStream.is_open()) {
        std::cerr << "Error opening signature file." << std::endl;
        EVP_MD_CTX_free(ctx);
        RSA_free(rsa);
        return false;
    }

    // 读取签名文件的大小和内容
    signatureFileStream.seekg(0, std::ios::end);
    size_t signatureFileSize = signatureFileStream.tellg();
    signatureFileStream.seekg(0, std::ios::beg);

    std::vector<unsigned char> signatureData(signatureFileSize);
    if (!signatureFileStream.read(reinterpret_cast<char*>(signatureData.data()), signatureFileSize)) {
        std::cerr << "Error reading signature file." << std::endl;
        signatureFileStream.close();
        EVP_MD_CTX_free(ctx);
        RSA_free(rsa);
        return false;
    }
    signatureFileStream.close();

    // 更新消息摘要的内容,计算数据文件的哈希值
    if (!EVP_VerifyUpdate(ctx, fileData.data(), fileSize)) {
        std::cerr << "Error updating verification." << std::endl;
        EVP_MD_CTX_free(ctx);
        RSA_free(rsa);
        return false;
    }

    // 验证签名的有效性
    int result = EVP_VerifyFinal(ctx, signatureData.data(), signatureFileSize, evpKey);
    EVP_MD_CTX_free(ctx);
    RSA_free(rsa);

    // 根据验证结果返回相应的布尔值
    if (result != 1) {
        std::cerr << "Signature verification failed." << std::endl;
        return false;
    }
    std::cout << "Signature verification successful." << std::endl;
    return true;
}

3.升级程序

        升级程序的话,实际上就是一个拷贝的过程。将设备上的备份,U盘中的更新程序拷贝出来。

/* 更新程序 */
bool copyFile(const char *sourcePath, const char *destinationPath) {
    
    //system("mv updateFileName  updateFileName.back"); //这里是一个备份,后续可以进行一个升级失败,备份还原。

    FILE *sourceFile = fopen(sourcePath, "rb");
    FILE *destinationFile = fopen(destinationPath, "wb");

    if (sourceFile == NULL) {
        printf("Error opening sourceFile files.\n");
        return false;
    }
    if(destinationFile == NULL){
        printf("Error opening  destinationFile files.\n");
        return false;
    }
    char buffer[1024];
    size_t bytesRead;
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), sourceFile)) > 0) {
        printf("start copy file........................\n");
        fwrite(buffer, 1, bytesRead, destinationFile);
    }

    /*更简单一点就是用system命令,直接拷贝
      不过有一点需要注意,程序在运行的时候,有时候会拷贝失败。
      可以先用system命令删除或者备份,然后再进行拷贝,再用system给个权限。
    */

    fclose(sourceFile);
    fclose(destinationFile);
    return true;
}

4.主函数

int main(void)
{
    struct sockaddr_nl client;
    struct timeval tv;
    int CppLive, rcvlen, ret;
    fd_set fds;
    int buffersize = 1024;
    CppLive = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    memset(&client, 0, sizeof(client));
    client.nl_family = AF_NETLINK;
    client.nl_pid = getpid();
    client.nl_groups = 1; /* receive broadcast message*/
    setsockopt(CppLive, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
    bind(CppLive, (struct sockaddr*)&client, sizeof(client));
    while (1) {
        char buf[UEVENT_BUFFER_SIZE] = { 0 };
        FD_ZERO(&fds);
        FD_SET(CppLive, &fds);
        tv.tv_sec = 0;
        tv.tv_usec = 100 * 1000;
        ret = select(CppLive + 1, &fds, NULL, NULL, &tv);
        if(ret < 0)
            continue;
        if(!(ret > 0 && FD_ISSET(CppLive, &fds)))
            continue;
        /* receive data */
        rcvlen = recv(CppLive, &buf, sizeof(buf), 0);
        if (rcvlen > 0) { 
            printf("%s\n", buf);
            //检测更新程序
            //升级程序
            //重启....
        }
    }
    close(CppLive);
    return 0;
}

总结

        以上就是今天要讲的内容,本文仅仅简单介绍了结合netlink的u盘升级,后期可以通过Qt实现U盘升级,实现一个程序,通过界面可以判断更新状态。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值