某Linux发行版本无法使用nodejs程序重命名文件问题的研究

本文主要研究某国产Linux操作系统无法使用nodejs程序重命名指定目录文件的问题。

起因

最近遇到一个业务程序迁移不同操作系统的问题:该业务程序使用nodejs语言编写的web应用,并且要设置为开机自启动。运行过程正常,网页正常打开,但当设置并保存网卡信息到/etc目录指定文件时,就提示权限不足。

排查及解决

问题表现

该nodejs程序在开机自动启动后,使用浏览器访问相应端口,保存参数信息,错误信息如下:

Error: EACCES: permission denied, rename '/etc/network/interfaces.d/eth0.tmp' -> '/etc/network/interfaces.d/eth0' at Object.renameSync (node:fs:1026:3) at saveConfig

但是,在命令行终端下,停掉进程,再手动启动,保存过程十分顺利。

尝试解决

分析代码,保存参数的过程,是将配置参数写到一个.tmp的临时文件,再将其重命名为原文件,如此完成保存的操作。此处不评价这种做法优劣,因为已经在生产环境(x86平台)中用着了。

从错误信息看,问题出在fs.renameSync函数。从错误的结果反推,尝试了几个方法。既然无法使用API函数解决,则其它方式,比如使用child_process 模块,在nodejs中调用shell,即执行mv xx.tmp xx命令,但失败。既然是/etc目录的权限问题,那好,修改/etc/network/interfaces.d/权限为777,但失败。

进一步分析,如果真的是权限问题,为何能够创建xx.tmp文件,且内容是正确的?如果不是权限问题,为何无法将其重命名为原文件?

检查过权限和用户组,是正常的。因为普通用户在命令行终端手动执行程序,是正常的,但开机自启动却失败。下面分别是手动启动和开机启动的进程状态:

latelee       35811  7.5  0.2 590640 43696 pts/1    Sl   10:18   0:00 node /server_test.js

latelee       17573  0.0  0.2 624000 45728 ?        Sl   12:28   0:00 node /server_test.js

从结果看,手动启动的那一项多了pts/1,这是没毛病的,因为就是在终端里启动的。

解决方法

后来因其它任务中断,此事不再继续。经过一段较长时间后,本着问题是自己的,始终要解决的精神,重拾起来。

回想想先前在安装该操作系统时,遇到一些权限问题,如用ssh远程连接后,执行脚本会卡住跑不起来(经查,是因为安全机制阻止了非法应用程序执行);明明监听了某端口,但外部机器无法访问(经查,是因为防火墙阻止了端口的访问)。

于是往这方向想,终于搜索到一个方法,在命令终端执行:

$ sudo setstatus softmode -p

设置后,再进行测试,参数保存成功。

原来问题出现操作系统的安全保存措施上。

现场重演

下面模拟实际工程关于保存配置文件的关键代码,并做测试。

模拟代码

var express = require('express');
var path = require('path');
const fs = require('fs');
const moment = require("moment");


var g_port = 5000
var testnetfile="/etc/network/interfaces.d/eth0_test"
var g_netstr = ""

function readMyFile(filename, ret) {
    var tmpstr = fs.readFileSync(filename, "utf8");

    g_netstr = tmpstr;
    writeToLog("read net file " + filename + " ret: \r\n" + g_netstr)

    return ret;
}

function saveToFile(filename, str) {
  var tempfile = filename + ".tmp";
  fs.writeFileSync(tempfile, str);
  fs.renameSync(tempfile, filename);
}

function testRename(filename) {
    var ret = {};
    ret = readMyFile(filename, ret)
    interfaces = ret
    str = g_netstr + "\n"
    str += moment().format("YYYY-MM-DDTHH:mm:ss") + " change by me" + "\n"

    saveToFile(filename, str)
}

///
var app = express();

app.get('/', function (req, res) {
    res.render("index.html");
})

app.get('/test', function (req, res) {

    writeToLog("================start test rename=================")

    if (req.query.test_num == 1) {
        testRename(testnetfile)
    }

    g_netstr = "";
    g_netstr = fs.readFileSync(testnetfile, "utf8")
    writeToLog("after rename \r\n" + g_netstr)

   var response = {
       "result": g_netstr,
   };

//    console.log(response);
   res.end(JSON.stringify(response));
})

app.set('views', path.join(__dirname, '.'));

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

var server = app.listen(g_port, function () {
    var port = server.address().port
    console.log("listening on %s", port)
})



function writeToLog(log) {
    const datestr = moment().format("YYYY-MM-DDTHH:mm:ss");
    logstr = "[" + datestr+ "] " + log;
    console.log(logstr);
    fs.appendFile('node_log.txt', logstr + '\n', (err) => {
    if (err) {
    //   console.error('写入日志失败', err);
    } else {
    //   console.log('日志写入成功');
    }
    });
}

启动脚本

编写执行脚本/home/latelee/mystart.sh

#!/bin/bash
LOGFILE=/tmp/mystart_log.txt

cd /home/latelee/plat_test/node_server_test

date >> $LOGFILE
echo "begin start nodejs test" >> $LOGFILE

#node ./server.js &

node /home/latelee/plat_test/node_server_test/server_test.js  &

脚本要添加可执行属性:

chmod +x /home/latelee/mystart.sh

否则启动时会提示:

mystart.service: Failed at step EXEC spawning /home/latelee/mystart.sh: Permission denied

开机自启动

编写systemd启动所需要的配置文件/etc/systemd/system/mystart.service

[Unit]
Description=Start my demo after tty1 is ready , in case of interrupting plymouth, that cause cannot enter into tty1

[Service]
Type=forking
User=latelee

ExecStart=/home/latelee/mystart.sh  &
#ExecStartPre=/home/latelee/mystart_pre.sh  &
#ExecStartPost=/home/latelee/mystart_after.sh  &

[Install]
WantedBy=multi-user.target

使能之:

$ sudo -s systemctl enable mystart
Created symlink /etc/systemd/system/multi-user.target.wants/mystart.service → /etc/systemd/system/mystart.service.

注意,配置文件里可以指定多个执行脚本,按先后顺序有ExecStartPreExecStartExecStartPost。一般用ExecStart即可,如果的确有多个依赖且有先后顺序的脚本,则配置文件里写的启动脚本一般要存在,否则不能正常启动,提示如下:

mystart.service: Failed to execute command: No such file or directory
mystart.service: Failed at step EXEC spawning /home/latelee/mystart_pre.sh: No such file or directory

重启机器后,查看启动结果:

latelee@latelee-pc:~$ systemctl status mystart
● mystart.service - Start my demo after tty1 is ready , in case of interrupting plymouth, that cause cannot enter into tty1
     Loaded: loaded (/etc/systemd/system/mystart.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2024-11-21 12:01:33 CST; 1min 45s ago
   Main PID: 939 (node)
      Tasks: 7 (limit: 19233)
     Memory: 72.3M
     CGroup: /system.slice/mystart.service
             └─939 node /home/latelee/plat_test/node_server_test/server_test.js

11月 21 12:01:33 latelee-pc systemd[1]: Starting Start my demo after tty1 is ready , in case of interrupting plymouth, that cause cannot enter into tty1...
11月 21 12:01:33 latelee-pc systemd[1]: Started Start my demo after tty1 is ready , in case of interrupting plymouth, that cause cannot enter into tty1.
11月 21 12:01:34 latelee-pc mystart.sh[939]: listening on 5000

测试

在网页上保存参数后,网页提示:

Error: EACCES: permission denied, rename '/etc/network/interfaces.d/eth0_test.tmp' -> '/etc/network/interfaces.d/eth0_test' at Object.renameSync (node:fs:1026:3) at saveToFile (/home/latelee/plat_test/node_server_test/server_test.js:23:6) at testRename (/home/latelee/plat_test/node_server_test/server_test.js:33:5) at Object.handle (/home/latelee/plat_test/node_server_test/server_test.js:48:9) at next_layer (/home/latelee/plat_test/node_server_test/node_modules/express/lib/router/route.js:103:13) at Route.dispatch (/home/latelee/plat_test/node_server_test/node_modules/express/lib/router/route.js:107:5) at /home/latelee/plat_test/node_server_test/node_modules/express/lib/router/index.js:195:24 at Function.proto.process_params (/home/latelee/plat_test/node_server_test/node_modules/express/lib/router/index.js:251:12) at next (/home/latelee/plat_test/node_server_test/node_modules/express/lib/router/index.js:189:19) at next (/home/latelee/plat_test/node_server_test/node_modules/express/lib/router/index.js:166:38)

可以重现上述问题。

麒麟桌面操作系统安全机制略记

本文使用的操作系统是麒麟桌面版,该系统有kysec安全机制,默认启动,此时,不能执行自定义脚本、自定义程序,外部不能访问自定义端口。可以通过图形界面修改,或用setstatus设置。

$ setstatus
usage:  setstatus < disable | enable | softmode >
        setstatus < disable | enable | softmode > -p (if kysec status is not disabled)
        setstatus -f  < exectl | netctl >  < off | enforcing | warning > [-p]
        setstatus -f  < fpro | kmod | ppro | pblk | devctl | ipt>  < off | on > [-p]
        setstatus -f  < eperm >  < off | on > [-p]
        setstatus -f  < kid >  < off | partition | disable_privacy > [-p]

其中,-ppermanent,表示永久生效。第一行并列的disable | enable | softmode我有点想不通,disable enable已经是一事两面,符合结构化思维的分类原则了,但又多了个softmode,不过的确可以用softmode解决问题。

比如无法使用hwclock命令写硬件时间,可以如下设置:

$ sudo setstatus -f kid off -p

比如解决本文遇到的无法重命名的问题,可以如下设置:

$ sudo setstatus softmode -p

顺着这个思路,最终找到/etc/default/grub文件,可以将

GRUB_CMDLINE_LINUX_SECURITY="security=kysec"

改为

GRUB_CMDLINE_LINUX_SECURITY="security= "

即,把kysec这个字符,改为空格,注意是空格。然后执行sudo update-grub更新initrd镜像。不过动到内核的时候,还是小心为上。要长期测试系统是否稳定。

国内主流桌面版系统都有权限管理、安全措施,对于日常使用其实是足够的。由于笔者所涉及的是稍底层,且要动到操作系统的,因为不合适。至于为何不用嵌入式版,可能是因为当时从官站找镜像时,无意忽略掉,就用桌面版了。

小结

由特定语言编写的特定场景的问题引发出操作系统安全机制,搞了十几年Linux了,似乎是第一次遇到。主要是因为问题表现较奇特,如果是一刀切那样就好办,像这次,能够写入新文件,在终端执行正常,这种情况就是我的知识荒原了。虽然花费的时间较长,但加深了国产系统的一些认知,耗时有所值。

相关推荐
AiFlutter11 分钟前
Java实现简单的搜索引擎
java·搜索引擎·mybatis
意疏19 分钟前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
虚拟网络工程师24 分钟前
【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)
运维·服务器·网络·数据库·mariadb
BLEACH-heiqiyihu26 分钟前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
飞升不如收破烂~31 分钟前
Spring boot常用注解和作用
java·spring boot·后端
秦老师Q32 分钟前
Java基础第九章-Java集合框架(超详细)!!!
java·开发语言
计算机毕设源码qq-383653104133 分钟前
(附项目源码)Java开发语言,215 springboot 大学生爱心互助代购网站,计算机毕设程序开发+文案(LW+PPT)
java·开发语言·spring boot·mysql·课程设计
ashane131436 分钟前
Java list
java·windows·list
袁庭新43 分钟前
Cannal实现MySQL主从同步环境搭建
java·数据库·mysql·计算机·java程序员·袁庭新
无尽的大道44 分钟前
深入理解 Java 阻塞队列:使用场景、原理与性能优化
java·开发语言·性能优化