You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

运行于LocalService账户的Windows服务无法读取非LOCAL_SERVICE所有的文件

Windows服务读取配置文件失败权限问题咨询

问题描述

使用C++结合Win32 API开发了一款Windows服务程序,程序进入正式服务运行模式前,会尝试读取指定绝对路径下的配置文件,读取操作失败会导致程序直接退出。经多轮调试排查,初步怀疑故障原因与文件所有权权限配置有关,现需可行解决方案,优先提供PowerShell脚本实现的文件所有权修改方案。

已排查上下文信息

  • 主程序逻辑:读取配置文件失败时直接退出,核心代码如下:
int main()
{
  std::string cfg_file_name = config::get_config(config::comm_config_file);
  if (cfg.read(cfg_file_name) < 0)
  {
    events::start_log(cfg.log_path, cfg.log_spdlog_level);
    SPDLOG_CRITICAL("Cannot read: " + cfg_file_name);
    return 1;
  }
  • get_config()函数逻辑:调用Win32 API获取可执行文件所在路径(即配置文件存放路径),将其与配置文件名拼接得到绝对路径,实现代码如下:
std::string config::get_config(const std::string& config_name)
{
#ifdef _MSC_VER
  std::string s = config::get_executable_path();

  // 日志启动前的调试输出,默认写入C:\Windows\System32\comm.txt
  std::ofstream ofs("comm.txt");
  ofs << "GetModuleFileName: " << s << std::endl;

  TCHAR buf[MAX_PATH];
  GetCurrentDirectory(MAX_PATH, buf);
  ofs << "GetCurrentDirectory: " << buf << std::endl;

  // 将进程当前目录修改为可执行文件所在路径
  if (SetCurrentDirectory(s.c_str()) == 0)
  {
    ofs << "SetCurrentDirectory failed" << std::endl;
  }

  GetCurrentDirectory(MAX_PATH, buf);
  ofs << "GetCurrentDirectory after set: " << buf << std::endl;

  s += config_name;
  ofs.close();
  return s;
#else
  return config_name;
#endif
}
  • 服务创建命令:通过PowerShell脚本调用sc.exe创建服务,执行命令如下:
sc.exe create _comm_ftp_server binPath= "$install_dir\ftp_server.exe" start= auto obj= "NT AUTHORITY\LocalService" password= " "
  • 基础读写能力验证:编写的测试服务可正常写入并读取自身创建的文件,证明服务本身具备基础文件读写能力,测试服务核心代码如下:
int main(int argc, char* argv[])
{
  std::string path = config::get_executable_path();
  cfg.log_path = path;
  events::start_log(cfg.log_path, "trace", true);

  // 服务程序通过SERVICE_TABLE_ENTRY结构声明可启动的服务,指定服务名和服务主函数指针
  // 服务主函数调用StartServiceCtrlDispatcher连接服务控制管理器(SCM)
  SERVICE_TABLE_ENTRY service_table[] =
  {
      { (LPSTR)service_name, (LPSERVICE_MAIN_FUNCTION)service_main },
      { NULL, NULL }
  };

  if (StartServiceCtrlDispatcher(service_table))
  {
    return 0;
  }
  else
  {
    return 1;
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
//service_main 服务主函数
/////////////////////////////////////////////////////////////////////////////////////////////////////

void WINAPI service_main(DWORD argc, LPTSTR* argv)
{
  service_handle = RegisterServiceCtrlHandler(service_name, service_handler);
  if (service_handle == NULL)
  {
    return;
  }

  service_stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (service_stop_event == NULL)
  {
    return;
  }

  report_status(SERVICE_START_PENDING);

  report_status(SERVICE_RUNNING);
  SPDLOG_INFO("service running..." + std::to_string(current_state));

  HANDLE thread_service = 0;
  thread_service = CreateThread(NULL, 0, service_thread, NULL, 0, NULL);
  WaitForSingleObject(thread_service, INFINITE);

  /////////////////////////////////////////////////////////////////////////////////////////////////////
  // 收到服务关闭请求
  /////////////////////////////////////////////////////////////////////////////////////////////////////

  CloseHandle(thread_service);

  report_status(SERVICE_STOP_PENDING);
  SPDLOG_INFO("service stop pending..." + std::to_string(current_state));
  CloseHandle(service_stop_event);

  report_status(SERVICE_STOPPED);
  SPDLOG_INFO("service stopped..." + std::to_string(current_state));
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
//service_thread 服务工作线程
/////////////////////////////////////////////////////////////////////////////////////////////////////

DWORD WINAPI service_thread(LPVOID lpParam)
{
  std::string path = cfg.log_path;
  SPDLOG_INFO("service started in..." + cfg.log_path);
  path += "\\test.txt";

  size_t i = 0;
  while (WaitForSingleObject(service_stop_event, 0) != WAIT_OBJECT_0)
  {
    write_txt_file(path, "writing...#" + std::to_string(i));
    i++;
    Sleep(10000);
    read_txt_file(path);
  }
  return ERROR_SUCCESS;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
//write_txt_file 文件写入函数
/////////////////////////////////////////////////////////////////////////////////////////////////////

void write_txt_file(const std::string& file_name, const std::string& input)
{
  FILE* f = fopen(file_name.c_str(), "a+");
  fprintf(f, "%s\n", input.c_str());
  fclose(f);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
//read_txt_file 文件读取函数
/////////////////////////////////////////////////////////////////////////////////////////////////////

void read_txt_file(const std::string& file_name)
{
  std::ifstream ifs;
  ifs.open(file_name);
  if (!ifs.is_open())
  {
    SPDLOG_ERROR("Cannot open: " + file_name);
    return;
  }
  std::string line;
  while (std::getline(ifs, line))
  {
    SPDLOG_INFO("Line: " + line);
  }
  ifs.close();
}
  • 权限差异验证:在Windows资源管理器中查看测试服务写入的文件属性,可见文件所有者为LOCAL_SERVICE;而待读取的目标配置文件所有者为Administrators组,因此判定文件所有权是导致读取失败的核心原因。
  • 配置文件读取逻辑:使用std::ifstream以默认只读模式打开文件,实现代码如下:
int config::config_t::read(const std::string& fname)
{
  try
  {
    std::ifstream ifs(fname);
    ifs >> configuration_json;
    ifs.close();
    from_json(configuration_json, *this);
  }
  catch (const std::exception& e)
  {
    SPDLOG_ERROR(e.what());
    return -1;
  }
  return 0;
}

待解决问题

  • 如何通过PowerShell脚本修改目标配置文件的所有权/权限,使服务可正常读取该文件
  • 是否可在创建服务时配置对应权限,使其能够读取目标文件

内容的提问来源于stack exchange,提问作者Pedro Vicente

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:48:22