本文结合当前仓库 FtpNextServer,说明如何将开源 FTP 服务器 ProFTPD 从 Linux 平台移植为可在 OpenHarmony 5.0+ 设备上运行的 HAP 应用。核心结论:通过 OHOS 交叉编译工具链将 ProFTPD 及其依赖库(OpenSSL、libssh2、zlib)编译为 arm64-v8a 架构的原生库与可执行文件,再由 NAPI 桥接层启动 ProFTPD 子进程,实现 ArkUI 界面与 FTP 服务进程的分离运行。

移植成功后的运行效果(FTP客户端连接登录):
在这里插入图片描述
移植后的开源地址:https://gitcode.com/OpenHarmonyPCDeveloper/FtpNextServer


一、为什么鸿蒙上的 FTP 服务端和传统 Linux 不一样

维度 传统 Linux FTP 服务端 鸿蒙上的 FTP 服务端(本方案)
运行环境 root 或普通用户,完整的 Linux 用户空间 应用沙箱,无 root 权限,seccomp 过滤
C 库 glibc musl libc
系统调用 chroot/setuid/setgid/wtmp 均可使用 全部被沙箱拦截,需源码绕过
端口权限 可绑定 21/22 等特权端口 无 root 权限,实际使用 60021/60022/60023
进程模型 独立守护进程 fork()+execvp() 作为应用子进程运行
文件路径 /etc/, /var/run/, /home/ 等标准路径 全部限制在应用沙箱 /data/storage/el2/base/haps/entry/files/

因此,移植的关键不在于把代码编译过,而在于:让一个假定自己运行在标准 Unix 用户空间的 FTP 服务器,在 OHOS 的沙箱限制下正常工作。


二、待移植的 ProFTPD 介绍

ProFTPD 是一款类 Unix 平台上的开源 FTP 服务器软件,具有以下特点:

  • 支持 FTP、FTPS(FTP over TLS)、SFTP(SSH File Transfer Protocol)三种协议
  • 采用模块化架构,通过动态加载模块(mod_tls、mod_sftp、mod_vroot 等)扩展功能
  • 使用 autoconf 构建体系,可配置性强
  • 单配置文件管理,类似 Apache 的配置风格

ProFTPD 开源地址:http://www.proftpd.org/

本方案选用 ProFTPD 1.3.8 版本,并为其打上 OHOS 适配补丁,使其能在 OpenHarmony 应用沙箱中稳定运行。


三、本仓库中的代码布局

  • 鸿蒙工程根目录FtpNextServer/(DevEco Studio 打开的工程)。

  • ArkTS 侧entry/src/main/ets/,包含服务控制、配置页面、用户管理等界面。

  • 原生 C/C++ 侧(NAPI 桥接)

    entry/src/main/cpp/
    ├── napi_init.cpp                   ← NAPI 总入口,注册导出给 ArkTS 的函数
    └── FLog.h                          ← 日志工具
    
  • 三方库源码native_c/ 目录下存放所有 C 源码

    native_c/
    ├── zlib-1.3.1/                     ← 压缩库
    ├── openssl-3.0.0/                  ← TLS/SSL 与加密库
    ├── libssh2-1.10.0/                 ← SFTP 协议库
    ├── gettext-0.26/                   ← 国际化库
    ├── proftpd-1.3.8-ohos/             ← 已打 OHOS 补丁的 ProFTPD 源码
    │   └── OHOS配置文件/               ← proftpd_ohos.conf、ftpd.passwd、SSH 密钥
    └── out/                            ← 预编译产物
        ├── bin/     proftpd, ftpasswd, openssl 等可执行文件
        ├── lib/     libcrypto.so.3, libssl.so.3, libssh2.so.1, libz.so.1
        ├── etc/     proftpd_ohos.conf
        └── ssl/     ftpd.pem(自签名 TLS 证书)
    

ArkTS 中调用原生库的方式(节选):

// entry/src/main/ets/pages/Index.ets
import { startServer, stopServer, cryptPasswd } from 'libentry.so';

// 启动 FTP 服务
startServer(this.binPath, (logMsg: string) => {
  // 实时接收 ProFTPD 子进程的日志输出
  this.logLines += logMsg + '\n';
}, () => {
  // 服务启动完成回调
  this.isRunning = true;
});

// 停止服务
stopServer(this.binPath, () => {
  this.isRunning = false;
});

// 生成密码哈希
cryptPasswd(password, salt, (hash: string) => {
  // 写入 ftpd.passwd 文件
});

NAPI 模块在原生侧的注册(节选):

// entry/src/main/cpp/napi_init.cpp
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"startServer", nullptr, StartServer, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopServer", nullptr, StopServer, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"cryptPasswd", nullptr, CryptPasswd, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",      // ← 对应 ArkTS 侧 'libentry.so'
    .nm_priv = ((void *)0),
    .reserved = {0},
};

nm_modname = "entry" 即对应 ArkTS 侧 'libentry.so' 这个导入字符串,CMake 产物名 libentry.sonm_modnameimport 路径三者必须对齐。


四、移植总流程

  1. 准备交叉编译环境

    • OpenHarmony SDK(含 native 工具链,clang/LLVM for aarch64-linux-ohos
    • 配置环境变量脚本 native_c/prepare_clang.sh
  2. 按依赖顺序编译三方库

    zlib-1.3.1 → openssl-3.0.0 → libssh2-1.10.0
                                         ↘ proftpd-1.3.8
    gettext-0.26 ──────────────────────↗
    

    四个库各自编译方式不同,核心是让构建系统使用 --target=aarch64-linux-ohos 参数。

  3. 编译 ProFTPD 主程序

    • autoconf 配置:./configure --host=aarch64-linux,指定交叉编译后的 OpenSSL/zlib/libssh2 头文件与库路径
    • 启用模块:--with-modules=mod_tls:mod_sftp:mod_vroot
    • 产出:proftpd 可执行文件及 ftpasswd 管理工具
  4. 修改 ProFTPD 源码适配 OHOS 沙箱

    • src/fsio.c:禁用 chroot,路径映射到沙箱目录
    • src/privs.c:跳过 setuid/setgid
    • src/wtmp.c:跳过 wtmp 日志
    • 配置文件路径全部指向沙箱私有目录
  5. 开发 NAPI 桥接层

    • ArkUI/TS 通过 NAPI 调用 C++ 代码
    • C++ 代码 fork()+execvp() 启动 ProFTPD 子进程
    • 子进程日志通过 pipe + napi_threadsafe_function 回传 TS 层
  6. 编译 HAP → 签名 → 安装到鸿蒙设备

    • 使用 DevEco Studio 打包
    • 将 ProFTPD 可执行文件、配置文件、依赖库部署到设备对应路径

五、CMake/Configure 侧关键改动(编译 .so 与可执行文件)

5.1 交叉编译工具链配置

native_c/prepare_clang.sh 中统一指定:

export OHOS_SDK=/opt/ohos-sdk
export CC="${OHOS_SDK}/native/llvm/bin/clang --target=aarch64-linux-ohos"
export CXX="${OHOS_SDK}/native/llvm/bin/clang++ --target=aarch64-linux-ohos"
export AR="${OHOS_SDK}/native/llvm/bin/llvm-ar"
export LD="${OHOS_SDK}/native/llvm/bin/ld.lld"
export CFLAGS="-fPIC -D__MUSL__=1 --target=aarch64-linux-ohos"
export LDFLAGS="--target=aarch64-linux-ohos -fuse-ld=lld"

aarch64-linux-ohos 是 OHOS 特有的目标三元组,-D__MUSL__=1 是因为 OHOS 使用 musl 而非 glibc。

5.2 四个三方库的编译

四个库按依赖顺序依次编译,方式各不相同:

  • zlib:autoconf 体系,./configure --archs="-arch aarch64",通过环境变量 CC/CFLAGS 传入 OHOS 工具链
  • OpenSSL./config linux-aarch64 shared zlib,利用环境变量中指定的 CC 与 CFLAGS 完成交叉编译
  • libssh2:CMake 体系,cmake -DOPENSSL_ROOT_DIR=../../out 指向已编译好的 OpenSSL 产物(必须指向交叉编译后的版本,否则会链接到宿主机的 x86_64 符号表导致 crash)
  • gettext:autoconf 体系,./configure --host=aarch64-linux 后通过 sed 修补 libtool 加入 --target=aarch64-linux-ohos

5.3 ProFTPD 的 configure 配置

./configure \
  --host=aarch64-linux \
  --build=x86_64-linux \
  --enable-static --disable-shared \
  --disable-ipv6 \
  --with-modules=mod_tls:mod_sftp:mod_vroot \
  --with-includes=/path/to/out/include \
  --with-libraries=/path/to/out/lib \
  CC="clang --target=aarch64-linux-ohos" \
  CFLAGS="-fPIC -D__MUSL__=1" \
  LDFLAGS="-L/path/to/out/lib" \
  LIBS="-lcrypto -lssl -lz -lssh2"

5.4 ProFTPD 的 OHOS 适配

ProFTPD 假定自己在标准 Unix 环境运行,OHOS 应用沙箱限制不同,需要五处修改:

  1. fsio.cchroot() 被拦截,跳过并将根路径 / 映射到沙箱目录
  2. privs.c:setuid/setgid 无权限,#if 0 禁掉
  3. wtmp.c:OHOS 无 /var/log/wtmp,直接 return 0
  4. 配置文件:路径指向沙箱私有目录,端口改用 60021(FTP)/60022(SFTP)/60023(FTPS)
  5. 用户配置:使用 ohosuser/ohosgroup + RequireValidShell off

5.5 entry/src/main/cpp/CMakeLists.txt

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(SuperredFTPServer)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

link_directories(${NATIVERENDER_ROOT_PATH}/../../../libs/${OHOS_ARCH})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)

六、与 DevEco 工程对接时要注意什么

  1. 可执行文件通过 HNP 部署
    ProFTPD 二进制以 HNP形式分发,打包命令:

    hnpcli pack -i .\hnp_proftpd -o .\hnp\arm64-v8a -n proftpd -v 1.5
    

    module.json5 中声明 hnpPackages 引入,napi_init.cppexecvp() 使用的路径需与此一致。
    注意:HNP 包已存在时,若版本号不变直接重新打包可能会失败,需先删除旧包或更新版本号再执行打包命令。

  2. 端口配置
    真机测试时确保 proftpd_ohos.conf 中配置的端口(60021/60022/60023)未被占用。可使用 netstat 命令检查。

  3. WiFi 网络拦截
    设备连接的 WiFi 路由器可能开启 AP 隔离或端口过滤,导致同一局域网下的客户端无法连接 FTP 服务。需确保路由器允许局域网设备互访,或在路由器管理后台放行对应端口。


七、开发过程中遇到的问题

  1. 执行 HNP 包中的二进制需使用绝对路径
    通过 execvp() 调用 HNP 包内的二进制文件时,必须使用绝对路径(如 /data/app/proftpd.org/proftpd_1.5/bin/proftpd),不能依赖相对路径或环境变量。

八、小结

  • 传统 ProFTPD:autoconf 配置 → make独立守护进程,运行在标准 Linux 用户空间。
  • 鸿蒙上的 ProFTPD:同一套 ProFTPD 源码 → OHOS 交叉编译工具链编译 → arm64 ELF 可执行文件 + 依赖 .so → NAPI 桥接层拉起子进程 → HAP 应用内运行

当前仓库已包含:

  • 全部三方的 OHOS 交叉编译产物(native_c/out/
  • 打上 OHOS 适配补丁的 ProFTPD 源码(native_c/proftpd-1.3.8-ohos/
  • NAPI 桥接层 C++ 代码(entry/src/main/cpp/
  • OHOS 配置文件模板(native_c/proftpd-1.3.8-ohos/OHOS配置文件/

参考链接

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐