typecho重新升级至1.2.0(实际上是重新删除后安装)后,typecho始终无法登录后台。

PHP这种Web编程语言实在没接触过,花了两天来玩一下。

博客网站使用的技术:nginx+php-fpm+typecho(sqlite),nginx与php实际运行一个ARM电视盒子上。

正常运行的网站,各种调试、日志都是关闭的,因此,首先打开php-fpm的日志捕获开关:

;catch_workers_output=no

修改为yes,并将首字符;去掉。

此开关打开后,php的error_log就可以输出信息了。

同时将php.ini的显示错误开关打开:

;display_errors=off

修改为display_errors=on。

修改好这些开关后,重启php-fpm的服务。

之后就可以在typecho的php代码中增加error_log以观察代码的运行轨迹。

登录链接代码在typecho的admin目录login.php,但实际提交的代码为:

https://XXX.XXX.net/blog/index.php/action/login?_=7cf73d0584c577c96833bd2e3a58e0f0

_=后面的字符为一串md5字符,可以不管。

而这个链接首先会被nginx的fastcgi_split_path_info功能拆分,对于blog的fastcgi_split_path_info代码如下:

fastcgi_split_path_info ^(.+?\.php)(\/?.+)$;

fastcgi_split_path_info后面的正则表达式,将/blog/index.php/action/login?_=7cf73d0584c577c96833bd2e3a58e0f0链接拆分成两个group

分别是:

/blog/index.php

/action/login?_=7cf73d0584c577c96833bd2e3a58e0f0

然后用$fastcgi_script_name与$fastcgi_path_info存放。

在其后的

set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;

将后部内容传递至PATH_INFO(这个变量在typecho中,即可通过从request头部中获取PATH_INFO变量来取得)

实际执行脚本的是/blog/index.php

但从index.php来看,代码很简单:

<?php
/**
 * Typecho Blog Platform
 *
 * @copyright  Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license    GNU General Public License 2.0
 * @version    $Id: index.php 1153 2009-07-02 10:53:22Z magike.net $
 */

/** 载入配置支持 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once 'config.inc.php') {
    file_exists('./install.php') ? header('Location: install.php') : print('Missing Config File');
    exit;
}

/** 初始化组件 */
\Widget\Init::alloc();

/** 注册一个初始化插件 */
\Typecho\Plugin::factory('index.php')->begin();

/** 开始路由分发 */
\Typecho\Router::dispatch();

/** 注册一个结束插件 */
\Typecho\Plugin::factory('index.php')->end();

从网络中,搜索一堆资料,了解到,关键代码在\Typecho\Router::dispatch()

主要内容即根据数据库中的路由表,分别调用创建对应的Widget,然后调用相应Widget的action函数。

在Router.php的dispatch函数中加上相应的日志输出对应的widget。

    public static function dispatch()
    {
        /** 获取PATHINFO */
        $pathInfo = Request::getInstance()->getPathInfo();

        foreach (self::$routingTable as $key => $route) {
            if (preg_match($route['regx'], $pathInfo, $matches)) {
                self::$current = $key;
                error_log('route widget ' . $route['widget']);

                try {
                    /** 载入参数 */
                    $params = null;

                    if (!empty($route['params'])) {
                        unset($matches[0]);
                        $params = array_combine($route['params'], $matches);
                    }

                    $widget = Widget::widget($route['widget'], null, $params);

                    if (isset($route['action'])) {
                        $widget->{$route['action']}();
                    }

                    return;

                } catch (\Exception $e) {
                    if (404 == $e->getCode()) {
                        Widget::destroy($route['widget']);
                        continue;
                    }

                    throw $e;
                }
            }
        }

        /** 载入路由异常支持 */
        throw new RouterException("Path '{$pathInfo}' not found", 404);
    }

从日志看出/action/login?_=7cf73d0584c577c96833bd2e3a58e0f0对应的widget为\Widget\Action

对于var\Widget\Action.php的代码见内:

<?php

namespace Widget;

use Typecho\Widget;

if (!defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}

/**
 * 执行模块
 *
 * @package Widget
 */
class Action extends Widget
{
    /**
     * 路由映射
     *
     * @access private
     * @var array
     */
    private $map = [
        'ajax'                     => '\Widget\Ajax',
        'login'                    => '\Widget\Login',
        'logout'                   => '\Widget\Logout',
        'register'                 => '\Widget\Register',
        'upgrade'                  => '\Widget\Upgrade',
        'upload'                   => '\Widget\Upload',
        'service'                  => '\Widget\Service',
        'xmlrpc'                   => '\Widget\XmlRpc',
        'comments-edit'            => '\Widget\Comments\Edit',
        'contents-page-edit'       => '\Widget\Contents\Page\Edit',
        'contents-post-edit'       => '\Widget\Contents\Post\Edit',
        'contents-attachment-edit' => '\Widget\Contents\Attachment\Edit',
        'metas-category-edit'      => '\Widget\Metas\Category\Edit',
        'metas-tag-edit'           => '\Widget\Metas\Tag\Edit',
        'options-discussion'       => '\Widget\Options\Discussion',
        'options-general'          => '\Widget\Options\General',
        'options-permalink'        => '\Widget\Options\Permalink',
        'options-reading'          => '\Widget\Options\Reading',
        'plugins-edit'             => '\Widget\Plugins\Edit',
        'themes-edit'              => '\Widget\Themes\Edit',
        'users-edit'               => '\Widget\Users\Edit',
        'users-profile'            => '\Widget\Users\Profile',
        'backup'                   => '\Widget\Backup'
    ];

    /**
     * 入口函数,初始化路由器
     *
     * @throws Widget\Exception
     */
    public function execute()
    {
        /** 验证路由地址 **/
        $action = $this->request->action;

        /** 判断是否为plugin */
        $actionTable = array_merge($this->map, unserialize(Options::alloc()->actionTable));

        if (isset($actionTable[$action])) {
            $widgetName = $actionTable[$action];
        }

        if (isset($widgetName) && class_exists($widgetName)) {
            $widget = self::widget($widgetName);

            if ($widget instanceof ActionInterface) {
                $widget->action();
                return;
            }
        }

        throw new Widget\Exception(_t('请求的地址不存在'), 404);
    }
}

基本思想来看,查map表,然后转到对应的类。对于当前动作,login显然对应到\Widget\Login.php。

\Widget\Login.php的代码如下:

<?php

namespace Widget;

use Typecho\Cookie;
use Typecho\Validate;
use Widget\Base\Users;

if (!defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}

/**
 * 登录组件
 *
 * @category typecho
 * @package Widget
 * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license GNU General Public License 2.0
 */
class Login extends Users implements ActionInterface
{
    /**
     * 初始化函数
     *
     * @access public
     * @return void
     */
    public function action()
    {
        // protect
        $this->security->protect();

        /** 如果已经登录 */
        if ($this->user->hasLogin()) {
            /** 直接返回 */
            $this->response->redirect($this->options->index);
        }

        /** 初始化验证类 */
        $validator = new Validate();
        $validator->addRule('name', 'required', _t('请输入用户名'));
        $validator->addRule('password', 'required', _t('请输入密码'));
        $expire = 30 * 24 * 3600;

        /** 记住密码状态 */
        if ($this->request->remember) {
            Cookie::set('__typecho_remember_remember', 1, $expire);
        } elseif (Cookie::get('__typecho_remember_remember')) {
            Cookie::delete('__typecho_remember_remember');
        }

        /** 截获验证异常 */
        if ($error = $validator->run($this->request->from('name', 'password'))) {
            Cookie::set('__typecho_remember_name', $this->request->name);

            /** 设置提示信息 */
            Notice::alloc()->set($error);
            $this->response->goBack();
        }

        /** 开始验证用户 **/
        $valid = $this->user->login(
            $this->request->name,
            $this->request->password,
            false,
            1 == $this->request->remember ? $expire : 0
        );

        /** 比对密码 */
        if (!$valid) {
            /** 防止穷举,休眠3秒 */
            sleep(3);

            self::pluginHandle()->loginFail(
                $this->user,
                $this->request->name,
                $this->request->password,
                1 == $this->request->remember
            );

            Cookie::set('__typecho_remember_name', $this->request->name);
            Notice::alloc()->set(_t('用户名或密码无效'), 'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }

        self::pluginHandle()->loginSucceed(
            $this->user,
            $this->request->name,
            $this->request->password,
            1 == $this->request->remember
        );

        /** 跳转验证后地址 */
        if (!empty($this->request->referer)) {
            /** fix #952 & validate redirect url */
            if (
                0 === strpos($this->request->referer, $this->options->adminUrl)
                || 0 === strpos($this->request->referer, $this->options->siteUrl)
            ) {
                $this->response->redirect($this->request->referer);
            }
        } elseif (!$this->user->pass('contributor', true)) {
            /** 不允许普通用户直接跳转后台 */
            $this->response->redirect($this->options->profileUrl);
        }

        $this->response->redirect($this->options->adminUrl);
    }
}

在此action函数中,加上相应日志,问题定位到此处代码:

$this->security->protect();

代码在执行到此行,即已经返回,也就根本没有再执行后面的校验用户名、密码等动作。

protect函数的代码如下,相当简单

    /**
     * 保护提交数据
     */
    public function protect()
    {
        if ($this->enabled && $this->request->get('_') != $this->getToken($this->request->getReferer())) {
            $this->response->goBack();
        }
    }

在此函数中加上日志定位到,此处的request->get('_')获取即为请求中的7cf73d0584c577c96833bd2e3a58e0f0,

而getToken函数如下:

    /**
     * 获取token
     *
     * @param string|null $suffix 后缀
     * @return string
     */
    public function getToken(?string $suffix): string
    {
        return md5($this->token . '&' . $suffix);
    }

根据token与request的getReferer计算的一个md5内容。

token及相应计算规则应该是没有问题的,因此怀疑点定位到getReferer函数的返回内容。

在网上搜索一番后,发现已经有人记录了此种问题及相应解决办法,链接:https://blog.warhut.cn/dmbj/423.html

引用如下:

------------------------------------------------------------------------------------------------------------------

如果加入了no-referrer,将会导致typecho无法登录后台,原因如下:

<input type="hidden" name="referrer" value="<?php echo htmlspecialchars($request->get('referrer')); ?>" />
由于typecho是通过referrer登录的后台地址,传输参数,所以当加入no-referrer之后相当于删除了提交的地址。

通过下面即可解决,加在<head></head>中。

<meta name="referrer" content="same-origin" />

------------------------------------------------------------------------------------------------------------------

解决办法,先在admin\Header.php中加入上面的referer行,如下:

<?php
if (!defined('__TYPECHO_ADMIN__')) {
    exit;
}

$header = '<link rel="stylesheet" href="' . $options->adminStaticUrl('css', 'normalize.css', true) . '">
<link rel="stylesheet" href="' . $options->adminStaticUrl('css', 'grid.css', true) . '">
<link rel="stylesheet" href="' . $options->adminStaticUrl('css', 'style.css', true) . '">';

/** 注▒~F~L▒~@个▒~H~]▒~K▒~L~V▒~O~R件 */
$header = \Typecho\Plugin::factory('admin/header.php')->header($header);

?><!DOCTYPE HTML>
<html>
    <head>
        <meta charset="<?php $options->charset(); ?>">
        <meta name="renderer" content="webkit">
        <meta name="referrer" content="same-origin" />
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
        <title><?php _e('%s - %s - Powered by Typecho', $menu->title, $options->title); ?></title>
        <meta name="robots" content="noindex, nofollow">
        <?php echo $header; ?>
    </head>
    <body<?php if (isset($bodyClass)) {echo ' class="' . $bodyClass . '"';} ?>>

加入后,登录功能即正常,但blog首页中的退出登录还有问题,点击退出类似于登录异常的情形,直接返回。

采取在首页相应的header处,添加相应代码。

themes中的header.php中代码如下(默认theme):

<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="<?php $this->options->charset(); ?>">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title><?php $this->archiveTitle([
            'category' => _t('分类 %s 下的文章'),
            'search'   => _t('包含关键字 %s 的文章'),
            'tag'      => _t('标签 %s 下的文章'),
            'author'   => _t('%s 发布的文章')
        ], '', ' - '); ?><?php $this->options->title(); ?></title>

    <!-- 使用url函数转换相关路径 -->
    <link rel="stylesheet" href="<?php $this->options->themeUrl('normalize.css'); ?>">
    <link rel="stylesheet" href="<?php $this->options->themeUrl('grid.css'); ?>">
    <link rel="stylesheet" href="<?php $this->options->themeUrl('style.css'); ?>">

    <!-- 通过自有函数输出HTML头部信息 -->
    <?php $this->header(); ?>
</head>
<body>

这儿首页的header()函数,实际对应到var/Widget/Archive.php中的header函数,

    /**
     * 输出头部元数据
     *
     * @param string|null $rule 规则
     */
    public function header(?string $rule = null)
    {
        $rules = [];
        $allows = [
            'description'  => htmlspecialchars($this->description),
            'keywords'     => htmlspecialchars($this->keywords),
            'generator'    => $this->options->generator,
            'template'     => $this->options->theme,
            'pingback'     => $this->options->xmlRpcUrl,
            'xmlrpc'       => $this->options->xmlRpcUrl . '?rsd',
            'wlw'          => $this->options->xmlRpcUrl . '?wlw',
            'rss2'         => $this->feedUrl,
            'rss1'         => $this->feedRssUrl,
            'commentReply' => 1,
            'antiSpam'     => 1,
            'atom'         => $this->feedAtomUrl
        ];

        /** 头部是否输出聚合 */
        $allowFeed = !$this->is('single') || $this->allow('feed') || $this->makeSinglePageAsFrontPage;

        if (!empty($rule)) {
            parse_str($rule, $rules);
            $allows = array_merge($allows, $rules);
        }

        $allows = self::pluginHandle()->headerOptions($allows, $this);
        $title = (empty($this->archiveTitle) ? '' : $this->archiveTitle . ' &raquo; ') . $this->options->title;

        $header = '<meta name="referrer" content="same-origin" />';
        if (!empty($allows['description'])) {
            $header .= '<meta name="description" content="' . $allows['description'] . '" />' . "\n";
        }

        if (!empty($allows['keywords'])) {
            $header .= '<meta name="keywords" content="' . $allows['keywords'] . '" />' . "\n";
        }

        if (!empty($allows['generator'])) {
            $header .= '<meta name="generator" content="' . $allows['generator'] . '" />' . "\n";
        }

        if (!empty($allows['template'])) {
            $header .= '<meta name="template" content="' . $allows['template'] . '" />' . "\n";
        }

        if (!empty($allows['pingback']) && 2 == $this->options->allowXmlRpc) {
            $header .= '<link rel="pingback" href="' . $allows['pingback'] . '" />' . "\n";
        }

        if (!empty($allows['xmlrpc']) && 0 < $this->options->allowXmlRpc) {
            $header .= '<link rel="EditURI" type="application/rsd+xml" title="RSD" href="'
                . $allows['xmlrpc'] . '" />' . "\n";
        }
        .....................

即可解决首页退出登录情况。

 

个人网站上搭建了有Blog、Git等服务,均是通过nginx反代,以子目录形式提供服务。因此这些服务是可以共用一个https证书的。

而cloudreve就麻烦了,目前没有找到以子目录形式提供服务的办法,只能先以子域名的方式搭建。

证书的申请也得添加一个子域名证书,好在acme.sh申请Let's Encrypt时,支持多域名证书。

申请了Let's Encrypt后,关键是后续要定时更新,而cloudreve服务也不能停,因此只能在nginx的配置上想办法了。

如下是cloudreve在nginx的反代配置,应该支持同时运行cloudreve服务、证书在后台自动续期。

server {
    listen 80;
    listen [::]:80;
    server_name cloudreve.XXX.cn;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl      http2;
    listen [::]:443 ssl http2;
    server_name cloudreve.XXX.cn;

    include /YYY/etc/nginx/sites-conf/https.conf;
    include /YYY/etc/nginx/sites-conf/common.conf;
	
	# 支持Let's Encrypt申请证书 
    location ^~ /.well-known {
	    # 使用与主网站一样的路径
		root  /YYY/web;
        location /.well-known/acme-challenge    { try_files $uri $uri/ =404; }
        location /.well-known/pki-validation    { try_files $uri $uri/ =404; }
        return 404;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://127.0.0.1:5212;

        # 如果您要使用本地存储策略,请将下一行注释符删除,并更改大小为理论最大文件尺寸
        # client_max_body_size 20000m;
    }
}

 

Git的交叉编译

gitea的服务启动是不依赖于Git的。但在初始化时会检测Git程序是否存在,同时也是创建、克隆仓库所必需。

默认安装系统提供的Git库也是可行,但依赖很多,感觉很不实在。

因此直接从官方下载源代码过来,交叉编译一个定制版本出来。

1.环境

Host环境:Ubuntu 18.04.5 (PC)

编译工具链:arm-himix200-linux(解包自arm-himix200-linux.tgz,据说来自Hi3516dv300SDK),海思提供的arm编译工具链

环境变量:执行命令:export PATH=/opt/hisi-linux/x86-arm/arm-himix200-linux/bin:$PATH

编译器arm-himix200-linux默认输出的ELF格式为private flags = 5000200: [Version5 EABI] [soft-float ABI],与ubuntu-armhf的格式private flags = 5000400: [Version5 EABI] [hard-float ABI]不兼容(soft-float ABI与hard-float ABI的传参规则不一样,因此会不兼容)。

通过gcc的print-multi-lib查询

xxx@xxx-HP-ProDesk-480-G5-MT:~/BigEye$ arm-himix200-linux-gcc --print-multi-lib
.;
armv5te_arm9;@mcpu=arm926ej-s
a9;@mcpu=cortex-a9
a7;@mcpu=cortex-a7
a17;@mcpu=cortex-a17
a17_a7;@mcpu=cortex-a17.cortex-a7
a53;@mcpu=cortex-a53
a73;@mcpu=cortex-a73
a73_a53;@mcpu=cortex-a73.cortex-a53
armv5te_arm9_soft;@mcpu=arm926ej-s@mfloat-abi=soft
armv5te_arm9_vfp;@mcpu=arm926ej-s@mfloat-abi=softfp@mfpu=vfp
a9_soft;@mcpu=cortex-a9@mfloat-abi=soft
a9_softfp_vfp;@mcpu=cortex-a9@mfloat-abi=softfp@mfpu=vfp
a9_softfp_vfpv3-d16;@mcpu=cortex-a9@mfloat-abi=softfp@mfpu=vfpv3-d16
a9_vfpv3;@mcpu=cortex-a9@mfloat-abi=softfp@mfpu=vfpv3
a9_vfpv3_neon;@mcpu=cortex-a9@mfloat-abi=softfp@mfpu=neon
a9_hard_neon;@mcpu=cortex-a9@mfloat-abi=hard@mfpu=neon
a7_soft;@mcpu=cortex-a7@mfloat-abi=soft
a7_softfp_vfpv4;@mcpu=cortex-a7@mfloat-abi=softfp@mfpu=vfpv4
a7_softfp_neon-vfpv4;@mcpu=cortex-a7@mfloat-abi=softfp@mfpu=neon-vfpv4
a7_hard_neon-vfpv4;@mcpu=cortex-a7@mfloat-abi=hard@mfpu=neon-vfpv4
a17_soft;@mcpu=cortex-a17@mfloat-abi=soft
a17_softfp_vfpv4;@mcpu=cortex-a17@mfloat-abi=softfp@mfpu=vfpv4
a17_softfp_neon-vfpv4;@mcpu=cortex-a17@mfloat-abi=softfp@mfpu=neon-vfpv4
a17_hard_neon-vfpv4;@mcpu=cortex-a17@mfloat-abi=hard@mfpu=neon-vfpv4
a17_a7_soft;@mcpu=cortex-a17.cortex-a7@mfloat-abi=soft
a17_a7_softfp_vfpv4;@mcpu=cortex-a17.cortex-a7@mfloat-abi=softfp@mfpu=vfpv4
a17_a7_softfp_neon-vfpv4;@mcpu=cortex-a17.cortex-a7@mfloat-abi=softfp@mfpu=neon-vfpv4
a17_a7_hard_neon-vfpv4;@mcpu=cortex-a17.cortex-a7@mfloat-abi=hard@mfpu=neon-vfpv4
a53_soft;@mcpu=cortex-a53@mfloat-abi=soft
a53_softfp_vfpv4;@mcpu=cortex-a53@mfloat-abi=softfp@mfpu=vfpv4
a53_softfp_neon-vfpv4;@mcpu=cortex-a53@mfloat-abi=softfp@mfpu=neon-vfpv4
a53_hard_neon-vfpv4;@mcpu=cortex-a53@mfloat-abi=hard@mfpu=neon-vfpv4
a73_soft;@mcpu=cortex-a73@mfloat-abi=soft
a73_softfp_vfpv4;@mcpu=cortex-a73@mfloat-abi=softfp@mfpu=vfpv4
a73_softfp_neon-vfpv4;@mcpu=cortex-a73@mfloat-abi=softfp@mfpu=neon-vfpv4
a73_hard_neon-vfpv4;@mcpu=cortex-a73@mfloat-abi=hard@mfpu=neon-vfpv4
a73_a53_soft;@mcpu=cortex-a73.cortex-a53@mfloat-abi=soft
a73_a53_softfp_vfpv4;@mcpu=cortex-a73.cortex-a53@mfloat-abi=softfp@mfpu=vfpv4
a73_a53_softfp_neon-vfpv4;@mcpu=cortex-a73.cortex-a53@mfloat-abi=softfp@mfpu=neon-vfpv4
a73_a53_hard_neon-vfpv4;@mcpu=cortex-a73.cortex-a53@mfloat-abi=hard@mfpu=neon-vfpv4

所以,所有库的编译都需要修改默认CFLAGS,添加上:

CFLAGS="-mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4"

2.依赖

git 2.34.1编译时,会依赖于如下一些包:openssl、zlib、pcre2、curl、expat等等(git想要支持https的推送与拉取,需要curl及expat的支持)。

env CURL_CONFIG=/data/app/bin/curl-config CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " CPPFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -I/data/app/include" LDFLAGS="-L/data/app/lib -Wl,--rpath=/data/app/lib"  ./configure --prefix=/data/app --host=arm-himix200-linux --target=arm-himix200-linux --without-iconv --with-expat=/data/app --without-tcltk --with-curl=/data/app ac_cv_lib_curl_curl_global_init=yes ac_cv_prog_CURL_CONFIG=/data/app/bin/curl-config NO_ICONV=true ac_cv_fread_reads_directories=true ac_cv_snprintf_returns_bogus=false
make V=1
make DESTDIR=/XXX install

执行命令后,将安装git相应的共享库、头文件等至/XXX目录下。

Gitea如需要支持此版本,则需要修改配置文件,

2.1 openssl

下载链接:https://www.openssl.org/source/old/1.1.1/openssl-1.1.1k.tar.gz

编译命令如下(生成的Makefile不太正确,需要用sed命令修正):

./Configure --prefix=/data/app --openssldir=/data/app/etc/ssl --libdir=lib  --with-zlib-include=/data/app/include --with-zlib-lib=/data/app/lib/ no-asm no-async shared zlib-dynamic no-ssl3-method linux-armv4 --cross-compile-prefix=arm-himix200-linux-
sed -i 's|arm-himix200-linux-gcc|gcc|g' Makefile
sed -i 's|-O3|-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4|g' Makefile
sed -i 's|./demoCA|/data/app/etc/ssl|g' apps/CA.pl.in
sed -i 's|./demoCA|/data/app/etc/ssl|g' apps/openssl.cnf
make
make DESTDIR=/XXX install

执行命令后,将安装openssl相应的共享库、头文件等至/XXX目录下。

2.2 zlib

下载链接:http://www.zlib.net/zlib-1.2.11.tar.gz

编译命令如下(默认会是O3,建议用O2):

//export CC=arm-himix200-linux-gcc
env CC=arm-himix200-linux-gcc CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " ./configure --prefix=/data/app
make
make DESTDIR=/XXX install

执行命令后,将安装zlib相应的共享库、头文件等至/XXX目录下。

2.3 libpcre2

下载链接:https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.bz2

编译命令如下:

env  CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/ ./configure --prefix=/data/app --enable-pcre2-16 --enable-pcre2-32 --enable-jit  --host=arm-himix200-linux --target=arm-himix200-linux 
make V=1
make DESTDIR=/XXX install

执行命令后,将安装libpcre2相应的共享库、头文件等至/XXX目录下。

2.4 curl

下载链接:https://curl.haxx.se/download/curl-7.80.0.tar.gz

编译命令如下:

env PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/ LDFLAGS="-L/data/app/lib -Wl,--rpath-link=/data/app/lib"  CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " ./configure --prefix=/data/app --host=arm-himix200-linux --target=arm-himix200-linux --disable-ldap --disable-ldaps --disable-manual --enable-ipv6 --enable-versioned-symbols --enable-threaded-resolver --with-openssl --with-libssh2 --with-random='/dev/urandom' 
//sed -i -e 's/ -shared / -Wl,-O1,--as-needed\0/g' libtool
make V=1
make DESTDIR=/XXX install

执行命令后,将安装curl相应的共享库、头文件等至/XXX目录下。
编译安装完成后的curl,某些使用curl-config脚本来链接libcurl的,可能会有链接问题,则可以直接修改/data/app/bin/curl-config脚本,将166行修改为

echo ${CURLLIBDIR}-lcurl -lnghttp2 -lssh2 -L/data/app/lib -lssh2 -lssl -lcrypto -lssl -lcrypto -lzstd -lbrotlidec -lbrotlicommon -lz -pthread

2.4.1 libnghttp2

下载链接:https://github.com/nghttp2/nghttp2/releases/download/v1.46.0/nghttp2-1.46.0.tar.gz

编译命令如下:

autoreconf -i
env PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/  CFLAGS="-g -O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " CXXFLAGS="-g -O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " LDFLAGS="-mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4" ./configure --prefix=/data/app --disable-examples --disable-python-bindings --enable-lib-only --host=arm-himix200-linux --target=arm-himix200-linux
make V=1
make DESTDIR=/XXX install

执行命令后,将安装libnghttp2相应的共享库、头文件等至/XXX目录下。

2.4.2 brotli

下载链接:https://github.com/google/brotli/archive/refs/tags/v1.0.9.tar.gz

拷贝libzip的cross.cmake文件过来

编译命令如下:

mkdir build && cd build
env PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/  CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " cmake -DCMAKE_TOOLCHAIN_FILE=../cross.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/data/app -DCMAKE_INSTALL_LIBDIR=lib -DBUILD_SHARED_LIBS=True ..
make VERBOSE=1
make DESTDIR=/XXX install

执行命令后,将安装brotli相应的共享库、头文件等至/XXX目录下。

2.4.3 libssh2

下载链接:https://www.libssh2.org/download/libssh2-1.10.0.tar.gz

编译命令如下:

env PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/  CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " LDFLAGS="-L/data/app/lib -Wl,--rpath-link=/data/app/lib" ./configure --prefix=/data/app --host=arm-himix200-linux --target=arm-himix200-linux --disable-static --disable-examples-build
make V=1
make DESTDIR=/XXX install

执行命令后,将安装libssh2相应的共享库、头文件等至/XXX目录下。

2.5 expat

下载链接:https://github.com/libexpat/libexpat/releases/download/R_2_4_2/expat-2.4.2.tar.gz

拷贝libzip的cross.cmake文件过来

编译命令如下(由于Host环境的cmake版本很老,expat的CMakeLists.txt脚本需要较新的cmake,因此编译时去掉了生成pkg-config文件):

mkdir build && cd build
env PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/ LDFLAGS="-L/data/app/lib -Wl,--rpath-link=/data/app/lib"  CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " cmake -DCMAKE_INSTALL_PREFIX=/data/app -DCMAKE_TOOLCHAIN_FILE=../cross.cmake -DCMAKE_BUILD_TYPE=None -DEXPAT_BUILD_TOOLS=OFF -DEXPAT_BUILD_EXAMPLES=OFF -DEXPAT_BUILD_TESTS=OFF -DEXPAT_BUILD_DOCS=OFF -DEXPAT_BUILD_PKGCONFIG=OFF ../
make VERBOSE=1
make DESTDIR=/XXX install

或者

env PKG_CONFIG_LIBDIR=/data/app/lib/pkgconfig/ LDFLAGS="-L/data/app/lib -Wl,--rpath-link=/data/app/lib"  CFLAGS="-O2 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 " ./configure --host=arm-himix200-linux --target=arm-himix200-linux --prefix=/data/app --without-xmlwf --without-examples --without-tests --disable-static --without-docbook
make V=1
make DESTDIR=/XXX install

执行命令后,将安装expat相应的共享库、头文件等至/XXX目录下。

如果使用cmake编译,由于未生成.pc文件,所以需要手工添加相应文件data/app/lib/pkgconfig/expat.pc

prefix=/data/app
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: expat
Version: 2.4.2
Description: expat XML parser
URL: https://libexpat.github.io/
Libs: -L${libdir} -lexpat -lm
Cflags: -I${includedir}

使用OpenGL显示简单的视频,对于一些纹理需要旋转并水平翻转,找到一个示例代码,做少量修改

// RotateByVector.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <iostream>
#include <math.h>

using namespace std;
#define CV_PI 3.1415926

//定义返回结构体
struct Point3f
{
    Point3f()
    {
        x = 0.0f;
        y = 0.0f;
        z = 0.0f;
    }

    Point3f(double _x, double _y, double _z)
    {
        x = _x;
        y = _y;
        z = _z;
    }
    double x;
    double y;
    double z;
};

//点绕任意向量旋转,右手系
//输入参数old_x,old_y,old_z为旋转前空间点的坐标
//vx,vy,vz为旋转轴向量
//theta为旋转角度角度制,范围在-180到180
//返回值为旋转后坐标点
Point3f RotateByVector(double old_x, double old_y, double old_z, double vx, double vy, double vz, double theta)
{
    double r = theta * CV_PI / 180;
    double c = cos(r);
    double s = sin(r);
    double new_x = (vx*vx*(1 - c) + c) * old_x + (vx*vy*(1 - c) - vz * s) * old_y + (vx*vz*(1 - c) + vy * s) * old_z;
    double new_y = (vy*vx*(1 - c) + vz * s) * old_x + (vy*vy*(1 - c) + c) * old_y + (vy*vz*(1 - c) - vx * s) * old_z;
    double new_z = (vx*vz*(1 - c) - vy * s) * old_x + (vy*vz*(1 - c) + vx * s) * old_y + (vz*vz*(1 - c) + c) * old_z;
    return Point3f(new_x, new_y, new_z);
}

// 可类比glRotate
Point3f RotateByVector(Point3f in_, double vx, double vy, double vz, double theta)
{
    double r = theta * CV_PI / 180;
    double c = cos(r);
    double s = sin(r);
    double new_x = (vx*vx*(1 - c) + c) * in_.x + (vx*vy*(1 - c) - vz * s) * in_.y + (vx*vz*(1 - c) + vy * s) * in_.z;
    double new_y = (vy*vx*(1 - c) + vz * s) * in_.x + (vy*vy*(1 - c) + c) * in_.y + (vy*vz*(1 - c) - vx * s) * in_.z;
    double new_z = (vx*vz*(1 - c) - vy * s) * in_.x + (vy*vz*(1 - c) + vx * s) * in_.y + (vz*vz*(1 - c) + c) * in_.z;
    return Point3f(new_x, new_y, new_z);
}

int main()
{
    // 原始
    Point3f A[] = {
        Point3f(1.0f,  1.0f, 0.0f),
        Point3f(1.0f,  -1.0f, 0.0f),
        Point3f(-1.0f, -1.0f, 0.0f),
        Point3f(-1.0f,  1.0f, 0.0f)
    };
    Point3f B[sizeof(A)/sizeof(Point3f)];
    Point3f C[sizeof(A)/sizeof(Point3f)];

    memset(&B, 0, sizeof(B));
    memset(&C, 0, sizeof(B));

    for(unsigned int idx = 0; idx < sizeof(A)/sizeof(Point3f); idx ++){
        // 围绕Z轴,旋转90度
        B[idx] = RotateByVector(A[idx], 0, 0, -1, 90);
        printf("idx: %d, x: %.05f, y: %.05f, z: %.05f\n", idx, B[idx].x, B[idx].y, B[idx].z);
    }
    printf("\n\n");
    for(unsigned int idx = 0; idx < sizeof(A)/sizeof(Point3f); idx ++){
        // 再水平翻转
        C[idx] = RotateByVector(B[idx], 0, 1, 0, 180);
        printf("idx: %d, x: %.05f, y: %.05f, z: %.05f\n", idx, C[idx].x, C[idx].y, C[idx].z);
    }

    system("pause");
    return 0;
}