mirror of
https://github.com/yuanyuanxiang/SimpleRemoter.git
synced 2026-02-18 04:13:08 +08:00
281 lines
9.3 KiB
C++
281 lines
9.3 KiB
C++
#pragma once
|
||
#include <dirent.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/statvfs.h>
|
||
#include <iconv.h>
|
||
#include <cstring>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <fstream>
|
||
#include <sstream>
|
||
|
||
// ============== Linux 文件管理(参考 Windows 端 client/FileManager.cpp)==============
|
||
// 最小实现:仅支持浏览文件(驱动器列表 + 目录文件列表)
|
||
|
||
class FileManager : public IOCPManager
|
||
{
|
||
public:
|
||
FileManager(IOCPClient* client)
|
||
: m_client(client)
|
||
{
|
||
// 与 Windows 端一致:构造时立即发送驱动器列表
|
||
SendDriveList();
|
||
}
|
||
|
||
~FileManager() {}
|
||
|
||
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||
{
|
||
if (!szBuffer || ulLength == 0) return;
|
||
|
||
switch (szBuffer[0]) {
|
||
case COMMAND_LIST_FILES:
|
||
SendFilesList((char*)szBuffer + 1);
|
||
break;
|
||
case COMMAND_DELETE_FILE:
|
||
case COMMAND_DELETE_DIRECTORY:
|
||
// TODO: unlink / recursive rmdir
|
||
SendToken(TOKEN_DELETE_FINISH);
|
||
break;
|
||
case COMMAND_DOWN_FILES:
|
||
case COMMAND_CONTINUE:
|
||
case COMMAND_STOP:
|
||
// TODO: 文件上传(客户端→服务端)
|
||
SendToken(TOKEN_TRANSFER_FINISH);
|
||
break;
|
||
case COMMAND_CREATE_FOLDER:
|
||
// TODO: mkdir -p
|
||
SendToken(TOKEN_CREATEFOLDER_FINISH);
|
||
break;
|
||
case COMMAND_RENAME_FILE:
|
||
// TODO: rename()
|
||
SendToken(TOKEN_RENAME_FINISH);
|
||
break;
|
||
case COMMAND_SET_TRANSFER_MODE:
|
||
// 无需回复,仅存储模式
|
||
break;
|
||
case COMMAND_FILE_SIZE:
|
||
// 服务端想上传文件到本地 → 回复跳过
|
||
RejectIncomingFile();
|
||
break;
|
||
case COMMAND_FILE_DATA:
|
||
// 已拒绝,正常不会收到;若收到则忽略
|
||
break;
|
||
default:
|
||
Mprintf("[FileManager] Unhandled command: %d\n", (int)szBuffer[0]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private:
|
||
IOCPClient* m_client;
|
||
|
||
// ---- 发送单字节 Token 回复 ----
|
||
void SendToken(BYTE token)
|
||
{
|
||
if (!m_client) return;
|
||
m_client->Send2Server((char*)&token, 1);
|
||
}
|
||
|
||
// ---- 拒绝服务端上传文件:回复 TOKEN_DATA_CONTINUE,偏移 low = -1(跳过)----
|
||
void RejectIncomingFile()
|
||
{
|
||
if (!m_client) return;
|
||
BYTE buf[9] = {};
|
||
buf[0] = (BYTE)TOKEN_DATA_CONTINUE;
|
||
DWORD skip = (DWORD)-1;
|
||
memcpy(buf + 5, &skip, sizeof(DWORD)); // dwSizeLow = 0xFFFFFFFF
|
||
m_client->Send2Server((char*)buf, sizeof(buf));
|
||
}
|
||
|
||
// ---- iconv 编码转换 ----
|
||
static std::string iconvConvert(const std::string& input, const char* from, const char* to)
|
||
{
|
||
if (input.empty()) return input;
|
||
iconv_t cd = iconv_open(to, from);
|
||
if (cd == (iconv_t)-1) return input;
|
||
|
||
size_t inLeft = input.size();
|
||
size_t outLeft = inLeft * 4; // 最大膨胀 4 倍
|
||
std::string output(outLeft, '\0');
|
||
|
||
char* inPtr = const_cast<char*>(input.data());
|
||
char* outPtr = &output[0];
|
||
|
||
size_t ret = iconv(cd, &inPtr, &inLeft, &outPtr, &outLeft);
|
||
iconv_close(cd);
|
||
|
||
if (ret == (size_t)-1) return input; // 转换失败,原样返回
|
||
output.resize(output.size() - outLeft);
|
||
return output;
|
||
}
|
||
|
||
// 服务端发来的 GBK 路径 → UTF-8(用于 opendir 等系统调用)
|
||
static std::string gbkToUtf8(const std::string& gbk)
|
||
{
|
||
return iconvConvert(gbk, "GBK", "UTF-8");
|
||
}
|
||
|
||
// 本地 UTF-8 文件名 → GBK(发送给服务端显示)
|
||
static std::string utf8ToGbk(const std::string& utf8)
|
||
{
|
||
return iconvConvert(utf8, "UTF-8", "GBK");
|
||
}
|
||
|
||
// ---- Windows 路径 → Linux 路径 ----
|
||
// 服务端发送 "/:\home\user" 格式,需转换为 "/home/user"
|
||
static std::string winPathToLinux(const char* winPath)
|
||
{
|
||
std::string p(winPath);
|
||
// 1. 反斜杠 → 正斜杠
|
||
for (auto& c : p) {
|
||
if (c == '\\') c = '/';
|
||
}
|
||
// 2. 去掉 "X:/" 前缀(X 为驱动器字母,Linux 下为 '/')
|
||
if (p.size() >= 3 && p[1] == ':' && p[2] == '/') {
|
||
p = p.substr(3);
|
||
} else if (p.size() >= 2 && p[1] == ':') {
|
||
p = p.substr(2);
|
||
}
|
||
// 3. 确保以 / 开头
|
||
if (p.empty() || p[0] != '/') {
|
||
p = "/" + p;
|
||
}
|
||
// 4. 合并连续斜杠
|
||
std::string result;
|
||
result.reserve(p.size());
|
||
for (size_t i = 0; i < p.size(); i++) {
|
||
if (i > 0 && p[i] == '/' && p[i - 1] == '/')
|
||
continue;
|
||
result += p[i];
|
||
}
|
||
return result.empty() ? "/" : result;
|
||
}
|
||
|
||
// ---- Unix time_t → Windows FILETIME(100ns since 1601-01-01)----
|
||
static uint64_t unixToFiletime(time_t t)
|
||
{
|
||
return (uint64_t)t * 10000000ULL + 116444736000000000ULL;
|
||
}
|
||
|
||
// ---- 读取根分区文件系统类型(从 /proc/mounts)----
|
||
static std::string getRootFsType()
|
||
{
|
||
std::ifstream f("/proc/mounts");
|
||
std::string line;
|
||
while (std::getline(f, line)) {
|
||
std::istringstream iss(line);
|
||
std::string dev, mp, fs;
|
||
if (iss >> dev >> mp >> fs) {
|
||
if (mp == "/") return fs;
|
||
}
|
||
}
|
||
return "ext4";
|
||
}
|
||
|
||
// ---- 发送驱动器列表(Linux:只发送根分区 /)----
|
||
void SendDriveList()
|
||
{
|
||
if (!m_client) return;
|
||
|
||
BYTE buf[256];
|
||
buf[0] = (BYTE)TOKEN_DRIVE_LIST;
|
||
DWORD offset = 1;
|
||
|
||
// 驱动器字母: '/'
|
||
buf[offset] = '/';
|
||
// 驱动器类型: DRIVE_FIXED = 3
|
||
buf[offset + 1] = 3;
|
||
|
||
// 磁盘大小(MB)
|
||
unsigned long totalMB = 0, freeMB = 0;
|
||
struct statvfs sv;
|
||
if (statvfs("/", &sv) == 0) {
|
||
unsigned long long totalBytes = (unsigned long long)sv.f_blocks * sv.f_frsize;
|
||
unsigned long long freeBytes = (unsigned long long)sv.f_bfree * sv.f_frsize;
|
||
totalMB = (unsigned long)(totalBytes / 1024 / 1024);
|
||
freeMB = (unsigned long)(freeBytes / 1024 / 1024);
|
||
}
|
||
memcpy(buf + offset + 2, &totalMB, sizeof(unsigned long));
|
||
memcpy(buf + offset + 6, &freeMB, sizeof(unsigned long));
|
||
|
||
// 卷标名(类型描述)
|
||
const char* typeName = "Linux";
|
||
int typeNameLen = strlen(typeName) + 1;
|
||
memcpy(buf + offset + 10, typeName, typeNameLen);
|
||
|
||
// 文件系统名
|
||
std::string fsType = getRootFsType();
|
||
int fsNameLen = fsType.size() + 1;
|
||
memcpy(buf + offset + 10 + typeNameLen, fsType.c_str(), fsNameLen);
|
||
|
||
offset += 10 + typeNameLen + fsNameLen;
|
||
|
||
m_client->Send2Server((char*)buf, offset);
|
||
Mprintf("[FileManager] SendDriveList: %u bytes (total=%luMB free=%luMB fs=%s)\n",
|
||
(unsigned)offset, totalMB, freeMB, fsType.c_str());
|
||
}
|
||
|
||
// ---- 列出目录文件,构造 TOKEN_FILE_LIST 二进制数据 ----
|
||
void SendFilesList(const char* path)
|
||
{
|
||
if (!m_client) return;
|
||
|
||
std::string linuxPath = winPathToLinux(gbkToUtf8(path).c_str());
|
||
Mprintf("[FileManager] SendFilesList: '%s' -> '%s'\n", path, linuxPath.c_str());
|
||
|
||
std::vector<uint8_t> buf;
|
||
buf.reserve(64 * 1024);
|
||
buf.push_back((uint8_t)TOKEN_FILE_LIST);
|
||
|
||
DIR* dir = opendir(linuxPath.c_str());
|
||
if (!dir) {
|
||
Mprintf("[FileManager] opendir failed: %s (errno=%d)\n", linuxPath.c_str(), errno);
|
||
m_client->Send2Server((char*)buf.data(), (ULONG)buf.size());
|
||
return;
|
||
}
|
||
|
||
struct dirent* ent;
|
||
while ((ent = readdir(dir)) != nullptr) {
|
||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
|
||
continue;
|
||
|
||
// 完整路径用于 stat()
|
||
std::string fullPath = linuxPath;
|
||
if (fullPath.back() != '/') fullPath += '/';
|
||
fullPath += ent->d_name;
|
||
|
||
struct stat st;
|
||
if (lstat(fullPath.c_str(), &st) != 0)
|
||
continue;
|
||
|
||
// 文件属性:0x10 = 目录,0x00 = 文件(对应 FILE_ATTRIBUTE_DIRECTORY)
|
||
uint8_t isDir = S_ISDIR(st.st_mode) ? 0x10 : 0x00;
|
||
buf.push_back(isDir);
|
||
|
||
// 文件名(UTF-8 → GBK,null 结尾)
|
||
std::string gbkName = utf8ToGbk(ent->d_name);
|
||
buf.insert(buf.end(), gbkName.begin(), gbkName.end());
|
||
buf.push_back(0);
|
||
|
||
// 文件大小:高 4 字节 + 低 4 字节
|
||
uint64_t fileSize = (uint64_t)st.st_size;
|
||
DWORD sizeHigh = (DWORD)(fileSize >> 32);
|
||
DWORD sizeLow = (DWORD)(fileSize & 0xFFFFFFFF);
|
||
const uint8_t* pH = (const uint8_t*)&sizeHigh;
|
||
const uint8_t* pL = (const uint8_t*)&sizeLow;
|
||
buf.insert(buf.end(), pH, pH + sizeof(DWORD));
|
||
buf.insert(buf.end(), pL, pL + sizeof(DWORD));
|
||
|
||
// 最后修改时间:Windows FILETIME 格式(8 字节)
|
||
uint64_t ft = unixToFiletime(st.st_mtime);
|
||
const uint8_t* pFT = (const uint8_t*)&ft;
|
||
buf.insert(buf.end(), pFT, pFT + 8);
|
||
}
|
||
closedir(dir);
|
||
|
||
m_client->Send2Server((char*)buf.data(), (ULONG)buf.size());
|
||
Mprintf("[FileManager] SendFilesList: %u bytes\n", (unsigned)buf.size());
|
||
}
|
||
};
|