已合并
reinit log file heads with child PID after fork() #1127
zhangjie创建于 3月23日
reinit log file heads with child PID after fork() #1127
已合并
共 5 个文件变更+424-0
| @@ -867,6 +867,68 @@ LogStatus PlogFileMgrInit(void) | |||
| 867 | return LOG_SUCCESS; | 867 | return LOG_SUCCESS; |
| 868 | } | 868 | } |
| 869 | 869 | ||
| 870 | +/** | ||
| 871 | + * @brief : re-initialize log file heads with the child process's own PID | ||
| 872 | + * after fork(). Must be called in the child's atfork/fork handler | ||
| 873 | + * so that the child's log files are named with the child PID | ||
| 874 | + * rather than inheriting the parent's PID from g_plogFileList. | ||
| 875 | + * | ||
| 876 | + * Root cause of the bug this fixes: | ||
| 877 | + * PlogInitHostLogList() / PlogInitDeviceMaxFileNum() call ToolGetPid() at | ||
| 878 | + * *parent* init time and bake the result into list->pid and list->aucFileHead. | ||
| 879 | + * After fork() the child inherits g_plogFileList verbatim; without this | ||
| 880 | + * function, every filename the child generates still contains the parent PID | ||
| 881 | + * → multiple child processes all write into the same parent-PID-named file. | ||
| 882 | + */ | ||
| 883 | +void PlogReinitFileHeadsForChild(void) | ||
| 884 | +{ | ||
| 885 | + if (g_plogFileList == NULL) { | ||
| 886 | + return; | ||
| 887 | + } | ||
| 888 | + uint32_t childPid = (uint32_t)ToolGetPid(); | ||
| 889 | + | ||
| 890 | + /* Re-init host log file heads */ | ||
| 891 | + for (int32_t i = (int32_t)DEBUG_LOG; i < (int32_t)LOG_TYPE_NUM; i++) { | ||
| 892 | + PlogFileList *list = &(g_plogFileList->hostLogList[i]); | ||
| 893 | + list->pid = childPid; | ||
| 894 | + list->fileNum = 0; | ||
| 895 | + /* Clear current file name so next write creates a fresh child-PID file */ | ||
| 896 | + if ((list->aucFileName != NULL) && (list->currIndex < list->maxFileNum)) { | ||
| 897 | + (void)memset_s(list->aucFileName[list->currIndex], | ||
| 898 | + MAX_FILENAME_LEN + 1U, 0, MAX_FILENAME_LEN + 1U); | ||
| 899 | + } | ||
| 900 | + (void)PlogInitHostLogFileHead(list->aucFileHead, MAX_NAME_HEAD_LEN + 1U, childPid); | ||
| 901 | + } | ||
| 902 | + | ||
| 903 | + /* Re-init device log file heads */ | ||
| 904 | + for (uint32_t iType = 0; iType < (uint32_t)LOG_TYPE_NUM; iType++) { | ||
| 905 | + if (g_plogFileList->deviceLogList[iType] == NULL) { | ||
| 906 | + continue; | ||
| 907 | + } | ||
| 908 | + for (uint32_t idx = 0; idx < g_plogFileList->deviceNum; idx++) { | ||
| 909 | + PlogFileList *list = &(g_plogFileList->deviceLogList[iType][idx]); | ||
| 910 | + list->pid = childPid; | ||
| 911 | + list->fileNum = 0; | ||
| 912 | + if ((list->aucFileName != NULL) && (list->currIndex < list->maxFileNum)) { | ||
| 913 | + (void)memset_s(list->aucFileName[list->currIndex], | ||
| 914 | + MAX_FILENAME_LEN + 1U, 0, MAX_FILENAME_LEN + 1U); | ||
| 915 | + } | ||
| 916 | + (void)PlogInitDeviceLogFileHead(list->aucFileHead, MAX_NAME_HEAD_LEN + 1U, childPid); | ||
| 917 | + } | ||
| 918 | + } | ||
| 919 | + SELF_LOG_INFO("plog file heads reinitialized for child process, child_pid=%u.", childPid); | ||
| 920 | +} | ||
| 921 | + | ||
| 922 | +/** | ||
| 923 | + * @brief : return the internal file manager list pointer for unit-test access. | ||
| 924 | + * Production code must NOT call this function. | ||
| 925 | + * @return : pointer to g_plogFileList | ||
| 926 | + */ | ||
| 927 | +PlogFileMgrInfo *PlogGetFileMgrInfo(void) | ||
| 928 | +{ | ||
| 929 | + return g_plogFileList; | ||
| 930 | +} | ||
| 931 | + | ||
| 870 | void PlogFileMgrExit(void) | 932 | void PlogFileMgrExit(void) |
| 871 | { | 933 | { |
| 872 | ONE_ACT_WARN_LOG(g_plogFileList == NULL, return, "file list is null."); | 934 | ONE_ACT_WARN_LOG(g_plogFileList == NULL, return, "file list is null."); |
| @@ -48,6 +48,8 @@ typedef struct { // log file list paramter | |||
| 48 | 48 | ||
| 49 | LogStatus PlogFileMgrInit(void); | 49 | LogStatus PlogFileMgrInit(void); |
| 50 | void PlogFileMgrExit(void); | 50 | void PlogFileMgrExit(void); |
| 51 | +void PlogReinitFileHeadsForChild(void); | ||
| 52 | +PlogFileMgrInfo *PlogGetFileMgrInfo(void); | ||
| 51 | 53 | ||
| 52 | LogStatus PlogWriteDeviceLog(char *msg, const PlogDeviceLogInfo *info); | 54 | LogStatus PlogWriteDeviceLog(char *msg, const PlogDeviceLogInfo *info); |
| 53 | LogStatus PlogWriteHostLog(int32_t logType, char *msg, uint32_t len); | 55 | LogStatus PlogWriteHostLog(int32_t logType, char *msg, uint32_t len); |
| @@ -69,6 +69,9 @@ STATIC void PlogChildUnlock(void) | |||
| 69 | { | 69 | { |
| 70 | g_alogFlushTid = 0; | 70 | g_alogFlushTid = 0; |
| 71 | PlogUnlock(); | 71 | PlogUnlock(); |
| 72 | + /* Fix: reinitialize file heads with the child's own PID so the child's | ||
| 73 | + * logs land in a file named after the child PID, not the parent PID. */ | ||
| 74 | + PlogReinitFileHeadsForChild(); | ||
| 72 | } | 75 | } |
| 73 | 76 | ||
| 74 | STATIC void PlogAtForkCallback(int32_t type) | 77 | STATIC void PlogAtForkCallback(int32_t type) |
| @@ -256,6 +259,10 @@ STATIC void PlogForkCallback(void) | |||
| 256 | PlogUnlock(); | 259 | PlogUnlock(); |
| 257 | PlogCleanUpBuff(BUFFER_TYPE_SEND); | 260 | PlogCleanUpBuff(BUFFER_TYPE_SEND); |
| 258 | g_alogFlushTid = 0; | 261 | g_alogFlushTid = 0; |
| 262 | + /* Fix: also reinitialize file heads here for the LOG_FORK path so that | ||
| 263 | + * any caller going through alog.so's fork callback also gets the correct | ||
| 264 | + * child PID in its log filenames. */ | ||
| 265 | + PlogReinitFileHeadsForChild(); | ||
| 259 | } | 266 | } |
| 260 | 267 | ||
| 261 | static LogStatus PlogGetSyncEnv(int32_t *syncStatus) | 268 | static LogStatus PlogGetSyncEnv(int32_t *syncStatus) |
| @@ -45,6 +45,7 @@ set(libAlogSrc | |||
| 45 | ${CMAKE_CURRENT_SOURCE_DIR}/testcase/alog_host_perf_utest.cc | 45 | ${CMAKE_CURRENT_SOURCE_DIR}/testcase/alog_host_perf_utest.cc |
| 46 | ${CMAKE_CURRENT_SOURCE_DIR}/testcase/plog_buffer_utest.cc | 46 | ${CMAKE_CURRENT_SOURCE_DIR}/testcase/plog_buffer_utest.cc |
| 47 | ${CMAKE_CURRENT_SOURCE_DIR}/testcase/plog_host_log_utest.cc | 47 | ${CMAKE_CURRENT_SOURCE_DIR}/testcase/plog_host_log_utest.cc |
| 48 | + ${CMAKE_CURRENT_SOURCE_DIR}/testcase/plog_file_mgr_utest.cc | ||
| 48 | ${CMAKE_CURRENT_SOURCE_DIR}/stub/ascend_hal_stub.c | 49 | ${CMAKE_CURRENT_SOURCE_DIR}/stub/ascend_hal_stub.c |
| 49 | ${CMAKE_CURRENT_SOURCE_DIR}/stub/plog_stub.cc | 50 | ${CMAKE_CURRENT_SOURCE_DIR}/stub/plog_stub.cc |
| 50 | ${UT_SOURCE_PATH}/ep/stub/system_api_stub.c | 51 | ${UT_SOURCE_PATH}/ep/stub/system_api_stub.c |
| @@ -0,0 +1,352 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2025 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * @file plog_file_mgr_utest.cc | ||
| 13 | + * @brief Unit tests for plog_file_mgr.c, with focus on the fork() PID naming | ||
| 14 | + * bug fix introduced in PlogReinitFileHeadsForChild(). | ||
| 15 | + * | ||
| 16 | + * Bug summary: | ||
| 17 | + * After fork(), the child process inherits g_plogFileList verbatim from the | ||
| 18 | + * parent. Because PlogInitHostLogList() and PlogInitDeviceMaxFileNum() bake | ||
| 19 | + * the parent's PID into list->pid and list->aucFileHead at init time, the | ||
| 20 | + * child wrote logs into files named after the parent PID, causing all child | ||
| 21 | + * processes to collide on the same filename in the run/plog/ directory. | ||
| 22 | + * | ||
| 23 | + * Fix: | ||
| 24 | + * PlogReinitFileHeadsForChild() is called from PlogChildUnlock() (atfork | ||
| 25 | + * ATFORK_CHILD path) and PlogForkCallback() (alog.so LOG_FORK path). It | ||
| 26 | + * re-runs ToolGetPid() in the child context and updates every list->pid and | ||
| 27 | + * list->aucFileHead in both hostLogList and deviceLogList. | ||
| 28 | + */ | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +/* ── constants mirrored from plog_file_mgr.c ─────────────────────────────── */ | ||
| 44 | +static const char HOST_FILE_HEAD_PREFIX[] = "plog-"; /* PROC_HEAD + "-" */ | ||
| 45 | +static const char DEV_FILE_HEAD_PREFIX[] = "device-"; /* DEVICE_HEAD */ | ||
| 46 | + | ||
| 47 | +/* ── helper: check that aucFileHead encodes the expected pid ──────────────── */ | ||
| 48 | +static ::testing::AssertionResult FileHeadContainsPid(const char *fileHead, uint32_t pid) | ||
| 49 | +{ | ||
| 50 | + char expected[32] = {}; | ||
| 51 | + (void)snprintf_s(expected, sizeof(expected), sizeof(expected) - 1, "%u", pid); | ||
| 52 | + if (strstr(fileHead, expected) != nullptr) { | ||
| 53 | + return ::testing::AssertionSuccess(); | ||
| 54 | + } | ||
| 55 | + return ::testing::AssertionFailure() | ||
| 56 | + << "aucFileHead \"" << fileHead << "\" does not contain pid " << pid; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +/* ── test fixture ─────────────────────────────────────────────────────────── */ | ||
| 60 | +class PlogFileMgrUtest : public testing::Test { | ||
| 61 | +protected: | ||
| 62 | + static void SetUpTestCase() | ||
| 63 | + { | ||
| 64 | + /* Point the log root to PATH_ROOT so PlogFileMgrInit() succeeds. */ | ||
| 65 | + setenv("ASCEND_PROCESS_LOG_PATH", PATH_ROOT, 1); | ||
| 66 | + system("rm -rf " PATH_ROOT); | ||
| 67 | + system("mkdir -p " PATH_ROOT); | ||
| 68 | + system("echo [DBG][TEST] Start PlogFileMgr test suite"); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + static void TearDownTestCase() | ||
| 72 | + { | ||
| 73 | + system("rm -rf " PATH_ROOT); | ||
| 74 | + unsetenv("ASCEND_PROCESS_LOG_PATH"); | ||
| 75 | + system("echo [DBG][TEST] End PlogFileMgr test suite"); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + virtual void SetUp() | ||
| 79 | + { | ||
| 80 | + system("rm -rf " PATH_ROOT "/*"); | ||
| 81 | + ResetErrLog(); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + virtual void TearDown() | ||
| 85 | + { | ||
| 86 | + /* Always exit file manager so g_plogFileList is freed between tests. */ | ||
| 87 | + PlogFileMgrExit(); | ||
| 88 | + GlobalMockObject::verify(); | ||
| 89 | + } | ||
| 90 | +}; | ||
| 91 | + | ||
| 92 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 93 | + * TC-01: PlogReinitFileHeadsForChild() is safe when called before init, * | ||
| 94 | + * i.e., when g_plogFileList is NULL. Must not crash. * | ||
| 95 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 96 | +TEST_F(PlogFileMgrUtest, ReinitForChild_NullListSafe) | ||
| 97 | +{ | ||
| 98 | + /* g_plogFileList is NULL before PlogFileMgrInit() is called. */ | ||
| 99 | + ASSERT_EQ(nullptr, PlogGetFileMgrInfo()); | ||
| 100 | + /* Should return without crashing. */ | ||
| 101 | + PlogReinitFileHeadsForChild(); | ||
| 102 | + EXPECT_EQ(nullptr, PlogGetFileMgrInfo()); | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 106 | + * TC-02: After reinit, all hostLogList entries carry the child's PID in * | ||
| 107 | + * the pid field and in aucFileHead. * | ||
| 108 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 109 | +TEST_F(PlogFileMgrUtest, ReinitForChild_UpdatesAllHostLogPidAndFileHead) | ||
| 110 | +{ | ||
| 111 | + ASSERT_EQ(LOG_SUCCESS, PlogFileMgrInit()); | ||
| 112 | + PlogFileMgrInfo *fileList = PlogGetFileMgrInfo(); | ||
| 113 | + ASSERT_NE(nullptr, fileList); | ||
| 114 | + | ||
| 115 | + const uint32_t parentPid = (uint32_t)getpid(); | ||
| 116 | + /* Verify that after init the host log file heads contain the parent PID. */ | ||
| 117 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 118 | + EXPECT_EQ(parentPid, fileList->hostLogList[i].pid) | ||
| 119 | + << "hostLogList[" << i << "].pid should be parent pid after init"; | ||
| 120 | + EXPECT_TRUE(FileHeadContainsPid(fileList->hostLogList[i].aucFileHead, parentPid)) | ||
| 121 | + << "hostLogList[" << i << "].aucFileHead should contain parent pid after init"; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /* Simulate the child's ToolGetPid() returning a different PID. */ | ||
| 125 | + const uint32_t fakePid = parentPid + 12345U; | ||
| 126 | + MOCKER(ToolGetPid).stubs().will(returnValue((INT32)fakePid)); | ||
| 127 | + | ||
| 128 | + PlogReinitFileHeadsForChild(); | ||
| 129 | + fileList = PlogGetFileMgrInfo(); | ||
| 130 | + | ||
| 131 | + /* All host log entries must now carry the child PID. */ | ||
| 132 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 133 | + EXPECT_EQ(fakePid, fileList->hostLogList[i].pid) | ||
| 134 | + << "hostLogList[" << i << "].pid not updated after reinit"; | ||
| 135 | + | ||
| 136 | + const char *head = fileList->hostLogList[i].aucFileHead; | ||
| 137 | + /* Must start with "plog-" and contain the new PID. */ | ||
| 138 | + EXPECT_EQ(0, strncmp(head, HOST_FILE_HEAD_PREFIX, strlen(HOST_FILE_HEAD_PREFIX))) | ||
| 139 | + << "hostLogList[" << i << "].aucFileHead prefix wrong: " << head; | ||
| 140 | + EXPECT_TRUE(FileHeadContainsPid(head, fakePid)) | ||
| 141 | + << "hostLogList[" << i << "].aucFileHead should contain child pid"; | ||
| 142 | + /* Must NOT contain the old parent PID. */ | ||
| 143 | + EXPECT_FALSE(FileHeadContainsPid(head, parentPid)) | ||
| 144 | + << "hostLogList[" << i << "].aucFileHead still contains parent pid"; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + GlobalMockObject::verify(); | ||
| 148 | +} | ||
| 149 | + | ||
| 150 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 151 | + * TC-03: After reinit, all deviceLogList entries carry the child's PID in * | ||
| 152 | + * the pid field and in aucFileHead. * | ||
| 153 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 154 | +TEST_F(PlogFileMgrUtest, ReinitForChild_UpdatesAllDeviceLogPidAndFileHead) | ||
| 155 | +{ | ||
| 156 | + ASSERT_EQ(LOG_SUCCESS, PlogFileMgrInit()); | ||
| 157 | + PlogFileMgrInfo *fileList = PlogGetFileMgrInfo(); | ||
| 158 | + ASSERT_NE(nullptr, fileList); | ||
| 159 | + | ||
| 160 | + const uint32_t parentPid = (uint32_t)getpid(); | ||
| 161 | + const uint32_t fakePid = parentPid + 9999U; | ||
| 162 | + | ||
| 163 | + MOCKER(ToolGetPid).stubs().will(returnValue((INT32)fakePid)); | ||
| 164 | + | ||
| 165 | + PlogReinitFileHeadsForChild(); | ||
| 166 | + fileList = PlogGetFileMgrInfo(); | ||
| 167 | + | ||
| 168 | + for (uint32_t type = 0; type < (uint32_t)LOG_TYPE_NUM; type++) { | ||
| 169 | + ASSERT_NE(nullptr, fileList->deviceLogList[type]); | ||
| 170 | + for (uint32_t idx = 0; idx < fileList->deviceNum; idx++) { | ||
| 171 | + PlogFileList *list = &fileList->deviceLogList[type][idx]; | ||
| 172 | + EXPECT_EQ(fakePid, list->pid) | ||
| 173 | + << "deviceLogList[" << type << "][" << idx << "].pid not updated"; | ||
| 174 | + | ||
| 175 | + const char *head = list->aucFileHead; | ||
| 176 | + EXPECT_EQ(0, strncmp(head, DEV_FILE_HEAD_PREFIX, strlen(DEV_FILE_HEAD_PREFIX))) | ||
| 177 | + << "deviceLogList[" << type << "][" << idx << "].aucFileHead prefix wrong"; | ||
| 178 | + EXPECT_TRUE(FileHeadContainsPid(head, fakePid)) | ||
| 179 | + << "deviceLogList[" << type << "][" << idx << "] should contain child pid"; | ||
| 180 | + EXPECT_FALSE(FileHeadContainsPid(head, parentPid)) | ||
| 181 | + << "deviceLogList[" << type << "][" << idx << "] still contains parent pid"; | ||
| 182 | + } | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + GlobalMockObject::verify(); | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 189 | + * TC-04: After reinit, the current filename slot is cleared so the child's * | ||
| 190 | + * next write opens a fresh file rather than sharing the parent's. * | ||
| 191 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 192 | +TEST_F(PlogFileMgrUtest, ReinitForChild_ClearsCurrentFileNameSlot) | ||
| 193 | +{ | ||
| 194 | + ASSERT_EQ(LOG_SUCCESS, PlogFileMgrInit()); | ||
| 195 | + PlogFileMgrInfo *fileList = PlogGetFileMgrInfo(); | ||
| 196 | + ASSERT_NE(nullptr, fileList); | ||
| 197 | + | ||
| 198 | + /* Artificially set a non-empty filename so we can verify it gets cleared. */ | ||
| 199 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 200 | + PlogFileList *list = &fileList->hostLogList[i]; | ||
| 201 | + if ((list->aucFileName != nullptr) && (list->currIndex < list->maxFileNum)) { | ||
| 202 | + (void)strcpy_s(list->aucFileName[list->currIndex], MAX_FILENAME_LEN + 1U, | ||
| 203 | + "stale_parent_pid_name.log"); | ||
| 204 | + } | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | + const uint32_t fakePid = (uint32_t)getpid() + 7777U; | ||
| 208 | + MOCKER(ToolGetPid).stubs().will(returnValue((INT32)fakePid)); | ||
| 209 | + | ||
| 210 | + PlogReinitFileHeadsForChild(); | ||
| 211 | + fileList = PlogGetFileMgrInfo(); | ||
| 212 | + | ||
| 213 | + /* The current slot must be zeroed out so the child starts a new file. */ | ||
| 214 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 215 | + PlogFileList *list = &fileList->hostLogList[i]; | ||
| 216 | + if ((list->aucFileName != nullptr) && (list->currIndex < list->maxFileNum)) { | ||
| 217 | + EXPECT_EQ('\0', list->aucFileName[list->currIndex][0]) | ||
| 218 | + << "hostLogList[" << i << "] current filename slot not cleared after reinit"; | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + GlobalMockObject::verify(); | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 226 | + * TC-05: Multiple calls to PlogReinitFileHeadsForChild() are idempotent: * | ||
| 227 | + * the second call with the same (mocked) pid yields the same result. * | ||
| 228 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 229 | +TEST_F(PlogFileMgrUtest, ReinitForChild_IdempotentMultipleCalls) | ||
| 230 | +{ | ||
| 231 | + ASSERT_EQ(LOG_SUCCESS, PlogFileMgrInit()); | ||
| 232 | + | ||
| 233 | + const uint32_t fakePid = (uint32_t)getpid() + 5555U; | ||
| 234 | + MOCKER(ToolGetPid).stubs().will(returnValue((INT32)fakePid)); | ||
| 235 | + | ||
| 236 | + PlogReinitFileHeadsForChild(); | ||
| 237 | + PlogReinitFileHeadsForChild(); /* second call must not corrupt state */ | ||
| 238 | + | ||
| 239 | + PlogFileMgrInfo *fileList = PlogGetFileMgrInfo(); | ||
| 240 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 241 | + EXPECT_EQ(fakePid, fileList->hostLogList[i].pid); | ||
| 242 | + EXPECT_TRUE(FileHeadContainsPid(fileList->hostLogList[i].aucFileHead, fakePid)); | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + GlobalMockObject::verify(); | ||
| 246 | +} | ||
| 247 | + | ||
| 248 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 249 | + * TC-06: fork()-based integration test. * | ||
| 250 | + * Verifies that after a real fork() + PlogReinitFileHeadsForChild(), * | ||
| 251 | + * the child's g_plogFileList carries the child's own PID — not the * | ||
| 252 | + * parent's — in every host log list entry. * | ||
| 253 | + * * | ||
| 254 | + * exit(0) → g_plogFileList correctly updated with child PID (fix works) * | ||
| 255 | + * exit(1) → still contains parent PID (bug present) * | ||
| 256 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 257 | +TEST_F(PlogFileMgrUtest, ForkChild_ReinitGivesChildOwnPidInFileHead) | ||
| 258 | +{ | ||
| 259 | + ASSERT_EQ(LOG_SUCCESS, PlogFileMgrInit()); | ||
| 260 | + ASSERT_NE(nullptr, PlogGetFileMgrInfo()); | ||
| 261 | + | ||
| 262 | + const pid_t parentPid = getpid(); | ||
| 263 | + | ||
| 264 | + pid_t child = fork(); | ||
| 265 | + ASSERT_GE(child, 0) << "fork() failed: " << strerror(errno); | ||
| 266 | + | ||
| 267 | + if (child == 0) { | ||
| 268 | + /* ── child process ─────────────────────────────────────────────── */ | ||
| 269 | + const pid_t myPid = getpid(); | ||
| 270 | + | ||
| 271 | + /* Simulate what the real ATFORK_CHILD handler does. */ | ||
| 272 | + PlogReinitFileHeadsForChild(); | ||
| 273 | + | ||
| 274 | + PlogFileMgrInfo *childList = PlogGetFileMgrInfo(); | ||
| 275 | + bool ok = true; | ||
| 276 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 277 | + uint32_t storedPid = childList->hostLogList[i].pid; | ||
| 278 | + if (storedPid != (uint32_t)myPid) { | ||
| 279 | + ok = false; | ||
| 280 | + break; | ||
| 281 | + } | ||
| 282 | + const char *head = childList->hostLogList[i].aucFileHead; | ||
| 283 | + char expected[32] = {}; | ||
| 284 | + (void)snprintf_s(expected, sizeof(expected), sizeof(expected) - 1, "%u", (uint32_t)myPid); | ||
| 285 | + if (strstr(head, expected) == nullptr) { | ||
| 286 | + ok = false; | ||
| 287 | + break; | ||
| 288 | + } | ||
| 289 | + } | ||
| 290 | + /* exit(0) = fix works, exit(1) = bug still present */ | ||
| 291 | + _exit(ok ? 0 : 1); | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + /* ── parent: wait for child and check exit code ────────────────────── */ | ||
| 295 | + int wstatus = 0; | ||
| 296 | + waitpid(child, &wstatus, 0); | ||
| 297 | + ASSERT_TRUE(WIFEXITED(wstatus)) << "child did not exit normally"; | ||
| 298 | + EXPECT_EQ(0, WEXITSTATUS(wstatus)) | ||
| 299 | + << "Child process: PlogReinitFileHeadsForChild() did not update " | ||
| 300 | + "hostLogList with child PID — fork PID naming bug is still present"; | ||
| 301 | + | ||
| 302 | + /* Sanity-check: parent's list must still hold the parent's PID. */ | ||
| 303 | + PlogFileMgrInfo *parentList = PlogGetFileMgrInfo(); | ||
| 304 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 305 | + EXPECT_EQ((uint32_t)parentPid, parentList->hostLogList[i].pid) | ||
| 306 | + << "Parent's hostLogList[" << i << "].pid was modified by child " | ||
| 307 | + "(fork isolation violated?)"; | ||
| 308 | + } | ||
| 309 | +} | ||
| 310 | + | ||
| 311 | +/* ══════════════════════════════════════════════════════════════════════════ * | ||
| 312 | + * TC-07: Regression — without reinit, child would inherit the parent PID. * | ||
| 313 | + * This test documents the bug by showing what happens when reinit is * | ||
| 314 | + * NOT called, confirming the test environment is sound. * | ||
| 315 | + * ══════════════════════════════════════════════════════════════════════════ */ | ||
| 316 | +TEST_F(PlogFileMgrUtest, ForkChild_WithoutReinitInheritsParentPid_Regression) | ||
| 317 | +{ | ||
| 318 | + ASSERT_EQ(LOG_SUCCESS, PlogFileMgrInit()); | ||
| 319 | + ASSERT_NE(nullptr, PlogGetFileMgrInfo()); | ||
| 320 | + | ||
| 321 | + const pid_t parentPid = getpid(); | ||
| 322 | + | ||
| 323 | + pid_t child = fork(); | ||
| 324 | + ASSERT_GE(child, 0) << "fork() failed: " << strerror(errno); | ||
| 325 | + | ||
| 326 | + if (child == 0) { | ||
| 327 | + /* ── child process: intentionally does NOT call reinit ─────────── */ | ||
| 328 | + const pid_t myPid = getpid(); | ||
| 329 | + PlogFileMgrInfo *childList = PlogGetFileMgrInfo(); | ||
| 330 | + bool inherited = false; | ||
| 331 | + for (int i = (int)DEBUG_LOG; i < (int)LOG_TYPE_NUM; i++) { | ||
| 332 | + if (childList->hostLogList[i].pid == (uint32_t)parentPid && | ||
| 333 | + (uint32_t)myPid != (uint32_t)parentPid) { | ||
| 334 | + inherited = true; | ||
| 335 | + break; | ||
| 336 | + } | ||
| 337 | + } | ||
| 338 | + /* exit(0) = bug present (expected for regression doc), | ||
| 339 | + * exit(1) = somehow not inherited (unexpected) */ | ||
| 340 | + _exit(inherited ? 0 : 1); | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + int wstatus = 0; | ||
| 344 | + waitpid(child, &wstatus, 0); | ||
| 345 | + ASSERT_TRUE(WIFEXITED(wstatus)); | ||
| 346 | + /* The child exits 0 to signal the bug is present (no reinit → parent PID | ||
| 347 | + * inherited). If this expectation ever fails it means the OS or compiler | ||
| 348 | + * has somehow prevented the inheritance, which would be surprising. */ | ||
| 349 | + EXPECT_EQ(0, WEXITSTATUS(wstatus)) | ||
| 350 | + << "Regression: child should have inherited parent PID in g_plogFileList " | ||
| 351 | + "when PlogReinitFileHeadsForChild() is NOT called"; | ||
| 352 | +} | ||