1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "process_data_plugin.h"
16
17 #include <fcntl.h>
18 #include <fstream>
19 #include <iostream>
20 #include <sstream>
21
22 #include "buffer_splitter.h"
23 #include "common.h"
24 #include "hisysevent.h"
25 #include "process_plugin_result.pbencoder.h"
26 #include "securec.h"
27
28 namespace {
29 using namespace OHOS::Developtools::Profiler;
30 constexpr size_t READ_BUFFER_SIZE = 1024 * 16;
31 constexpr int DEC_BASE = 10;
32 constexpr int STAT_COUNT = 13;
33 constexpr int CPU_USER_HZ_L = 100;
34 constexpr int CPU_USER_HZ_H = 1000;
35 constexpr int CPU_HZ_H = 10;
36 const int PERCENT = 100;
37 } // namespace
38
ProcessDataPlugin()39 ProcessDataPlugin::ProcessDataPlugin() : err_(-1)
40 {
41 SetPath("/proc/");
42 buffer_ = std::make_unique<uint8_t[]>(READ_BUFFER_SIZE);
43 }
44
~ProcessDataPlugin()45 ProcessDataPlugin::~ProcessDataPlugin()
46 {
47 PROFILER_LOG_INFO(LOG_CORE, "%s:~ProcessDataPlugin!", __func__);
48
49 buffer_ = nullptr;
50
51 return;
52 }
53
Start(const uint8_t * configData,uint32_t configSize)54 int ProcessDataPlugin::Start(const uint8_t* configData, uint32_t configSize)
55 {
56 CHECK_NOTNULL(buffer_, RET_FAIL, "%s:buffer_ == null", __func__);
57
58 CHECK_TRUE(protoConfig_.ParseFromArray(configData, configSize) > 0, RET_FAIL,
59 "%s:parseFromArray failed!", __func__);
60
61 int ret = COMMON::PluginWriteToHisysevent("process_plugin", "sh", GetCmdArgs(protoConfig_),
62 COMMON::ErrorType::RET_SUCC, "success");
63 PROFILER_LOG_INFO(LOG_CORE, "%s: success! hisysevent report process_plugin result:%d", __func__, ret);
64 return RET_SUCC;
65 }
66
GetCmdArgs(const ProcessConfig & traceConfig)67 std::string ProcessDataPlugin::GetCmdArgs(const ProcessConfig& traceConfig)
68 {
69 std::stringstream args;
70 args << "report_process_tree: " << (traceConfig.report_process_tree() ? "true" : "false") << ", ";
71 args << "report_cpu: " << (traceConfig.report_cpu() ? "true" : "false") << ", ";
72 args << "report_diskio: " << (traceConfig.report_diskio() ? "true" : "false") << ", ";
73 args << "report_pss: " << (traceConfig.report_pss() ? "true" : "false");
74 return args.str();
75 }
76
ReportOptimize(RandomWriteCtx * randomWrite)77 int ProcessDataPlugin::ReportOptimize(RandomWriteCtx* randomWrite)
78 {
79 ProtoEncoder::ProcessData dataProto(randomWrite);
80 if (protoConfig_.report_process_tree()) {
81 WriteProcesseList(dataProto);
82 }
83
84 int msgSize = dataProto.Finish();
85 return msgSize;
86 }
87
Report(uint8_t * data,uint32_t dataSize)88 int ProcessDataPlugin::Report(uint8_t* data, uint32_t dataSize)
89 {
90 ProcessData dataProto;
91 uint32_t length;
92
93 if (protoConfig_.report_process_tree()) {
94 WriteProcesseList(dataProto);
95 }
96
97 length = dataProto.ByteSizeLong();
98 if (length > dataSize) {
99 return -length;
100 }
101 if (dataProto.SerializeToArray(data, length) > 0) {
102 return length;
103 }
104 return 0;
105 }
106
Stop()107 int ProcessDataPlugin::Stop()
108 {
109 pids_.clear();
110 cpuTime_.clear();
111 bootTime_.clear();
112
113 PROFILER_LOG_INFO(LOG_CORE, "%s:stop success!", __func__);
114 return 0;
115 }
116
OpenDestDir(const char * dirPath)117 DIR* ProcessDataPlugin::OpenDestDir(const char* dirPath)
118 {
119 DIR* destDir = nullptr;
120
121 destDir = opendir(dirPath);
122 if (destDir == nullptr) {
123 PROFILER_LOG_ERROR(LOG_CORE, "%s:failed to opendir(%s), errno=%d", __func__, dirPath, errno);
124 }
125
126 return destDir;
127 }
128
GetValidPid(DIR * dirp)129 int32_t ProcessDataPlugin::GetValidPid(DIR* dirp)
130 {
131 if (!dirp) {
132 return 0;
133 }
134 while (struct dirent* dirEnt = readdir(dirp)) {
135 if (dirEnt->d_type != DT_DIR) {
136 continue;
137 }
138 if (!COMMON::IsNumeric(std::string(dirEnt->d_name))) {
139 continue;
140 }
141 int32_t pid = atoi(dirEnt->d_name);
142 if (pid) {
143 return pid;
144 }
145 }
146 return 0;
147 }
148
ReadProcPidFile(int32_t pid,const char * pFileName)149 int32_t ProcessDataPlugin::ReadProcPidFile(int32_t pid, const char* pFileName)
150 {
151 char fileName[PATH_MAX + 1] = {0};
152 char realPath[PATH_MAX + 1] = {0};
153 int fd = -1;
154 ssize_t bytesRead = 0;
155
156 if (snprintf_s(fileName, sizeof(fileName), sizeof(fileName) - 1, "%s%d/%s", path_.c_str(), pid, pFileName) < 0) {
157 PROFILER_LOG_ERROR(LOG_CORE, "%s:snprintf_s error", __func__);
158 }
159 if (realpath(fileName, realPath) == nullptr) {
160 PROFILER_LOG_ERROR(LOG_CORE, "%s:realpath failed, errno=%d", __func__, errno);
161 return RET_FAIL;
162 }
163 fd = open(realPath, O_RDONLY | O_CLOEXEC);
164 if (fd == -1) {
165 PROFILER_LOG_INFO(LOG_CORE, "%s:failed to open(%s), errno=%d", __func__, fileName, errno);
166 err_ = errno;
167 return RET_FAIL;
168 }
169 if (buffer_.get() == nullptr) {
170 PROFILER_LOG_INFO(LOG_CORE, "%s:empty address, buffer_ is NULL", __func__);
171 err_ = RET_NULL_ADDR;
172 close(fd);
173 return RET_FAIL;
174 }
175 bytesRead = read(fd, buffer_.get(), READ_BUFFER_SIZE - 1);
176 if (bytesRead < 0) {
177 close(fd);
178 PROFILER_LOG_INFO(LOG_CORE, "%s:failed to read(%s), errno=%d", __func__, fileName, errno);
179 err_ = errno;
180 return RET_FAIL;
181 }
182 buffer_.get()[bytesRead] = '\0';
183 close(fd);
184
185 return bytesRead;
186 }
187
BufnCmp(const char * src,int srcLen,const char * key,int keyLen)188 bool ProcessDataPlugin::BufnCmp(const char* src, int srcLen, const char* key, int keyLen)
189 {
190 if (!src || !key || (srcLen < keyLen)) {
191 return false;
192 }
193 for (int i = 0; i < keyLen; i++) {
194 if (*src++ != *key++) {
195 return false;
196 }
197 }
198 return true;
199 }
200
addPidBySort(int32_t pid)201 bool ProcessDataPlugin::addPidBySort(int32_t pid)
202 {
203 auto pidsEnd = pids_.end();
204 auto it = std::lower_bound(pids_.begin(), pidsEnd, pid);
205 if (it != pidsEnd && *it == pid) {
206 return false;
207 }
208 it = pids_.insert(it, std::move(pid));
209 return true;
210 }
211
212 template <typename T>
WriteProcess(T & processinfo,const char * pFile,uint32_t fileLen,int32_t pid)213 void ProcessDataPlugin::WriteProcess(T& processinfo, const char* pFile, uint32_t fileLen, int32_t pid)
214 {
215 BufferSplitter totalbuffer(const_cast<const char*>(pFile), fileLen + 1);
216
217 do {
218 totalbuffer.NextWord(':');
219 if (!totalbuffer.CurWord()) {
220 return;
221 }
222
223 if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "Name", strlen("Name"))) {
224 totalbuffer.NextWord('\n');
225 if (!totalbuffer.CurWord()) {
226 return;
227 }
228 processinfo.set_name(totalbuffer.CurWord(), totalbuffer.CurWordSize());
229 } else if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "Pid", strlen("Pid"))) {
230 totalbuffer.NextWord('\n');
231 if (!totalbuffer.CurWord()) {
232 return;
233 }
234 char* end = nullptr;
235 int32_t value = static_cast<int32_t>(strtoul(totalbuffer.CurWord(), &end, DEC_BASE));
236 if (value < 0) {
237 PROFILER_LOG_ERROR(LOG_CORE, "%s:strtoull value failed", __func__);
238 }
239 processinfo.set_pid(value);
240 } else if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "PPid", strlen("PPid"))) {
241 totalbuffer.NextWord('\n');
242 if (!totalbuffer.CurWord()) {
243 return;
244 }
245 char* end = nullptr;
246 int32_t value = static_cast<int32_t>(strtoul(totalbuffer.CurWord(), &end, DEC_BASE));
247 if (value < 0) {
248 PROFILER_LOG_ERROR(LOG_CORE, "%s:strtoull value failed", __func__);
249 }
250 processinfo.set_ppid(value);
251 } else if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "Uid", strlen("Uid"))) {
252 totalbuffer.NextWord('\n');
253 if (!totalbuffer.CurWord()) {
254 return;
255 }
256 std::string curWord = std::string(totalbuffer.CurWord(), totalbuffer.CurWordSize());
257 curWord = curWord.substr(0, curWord.find(" "));
258 char* end = nullptr;
259 int32_t value = static_cast<int32_t>(strtoul(curWord.c_str(), &end, DEC_BASE));
260 if (value < 0) {
261 PROFILER_LOG_ERROR(LOG_CORE, "%s:strtoull value failed", __func__);
262 }
263 processinfo.set_uid(value);
264 break;
265 } else {
266 totalbuffer.NextWord('\n');
267 if (!totalbuffer.CurWord()) {
268 continue;
269 }
270 }
271 } while (totalbuffer.NextLine());
272 // update process name
273 int32_t ret = ReadProcPidFile(pid, "cmdline");
274 if (ret > 0) {
275 processinfo.set_name(reinterpret_cast<char*>(buffer_.get()), strlen(reinterpret_cast<char*>(buffer_.get())));
276 }
277 }
278
WriteProcessInfo(T & processData,int32_t pid)279 template <typename T> void ProcessDataPlugin::WriteProcessInfo(T& processData, int32_t pid)
280 {
281 int32_t ret = ReadProcPidFile(pid, "status");
282 if (ret == RET_FAIL) {
283 return;
284 }
285 if ((buffer_.get() == nullptr) || (ret == 0)) {
286 return;
287 }
288 auto* processinfo = processData.add_processesinfo();
289 WriteProcess(*processinfo, reinterpret_cast<char*>(buffer_.get()), ret, pid);
290 if (protoConfig_.report_cpu()) {
291 auto* cpuInfo = processinfo->mutable_cpuinfo();
292 std::vector<uint64_t> cpuUsageVec;
293 std::vector<uint64_t> bootTime;
294 WriteCpuUsageData(pid, *cpuInfo);
295 WriteThreadData(pid, *cpuInfo);
296 }
297 if (protoConfig_.report_diskio()) {
298 WriteDiskioData(pid, *processinfo);
299 }
300 if (protoConfig_.report_pss()) {
301 WritePssData(pid, *processinfo);
302 }
303 }
304
WriteProcesseList(T & processData)305 template <typename T> bool ProcessDataPlugin::WriteProcesseList(T& processData)
306 {
307 DIR* procDir = nullptr;
308
309 procDir = OpenDestDir(path_.c_str());
310 if (procDir == nullptr) {
311 return false;
312 }
313
314 pids_.clear();
315 while (int32_t pid = GetValidPid(procDir)) {
316 if (pid <= 0) {
317 closedir(procDir);
318 PROFILER_LOG_WARN(LOG_CORE, "%s: get pid[%d] failed", __func__, pid);
319 return false;
320 }
321 addPidBySort(pid);
322 }
323
324 for (unsigned int i = 0; i < pids_.size(); i++) {
325 WriteProcessInfo(processData, pids_[i]);
326 }
327
328 closedir(procDir);
329 return true;
330 }
331
WriteThreadData(int pid,T & cpuInfo)332 template <typename T> bool ProcessDataPlugin::WriteThreadData(int pid, T& cpuInfo)
333 {
334 DIR* procDir = nullptr;
335 std::string path = path_ + std::to_string(pid) + "/task";
336 procDir = OpenDestDir(path.c_str());
337 if (procDir == nullptr) {
338 return false;
339 }
340
341 uint32_t i = 0;
342 while (int32_t tid = GetValidPid(procDir)) {
343 if (tid <= 0) {
344 closedir(procDir);
345 PROFILER_LOG_WARN(LOG_CORE, "%s: get pid[%d] failed", __func__, tid);
346 return false;
347 }
348 i++;
349 }
350 cpuInfo.set_thread_sum(i);
351 closedir(procDir);
352 return true;
353 }
354
GetUserHz()355 int64_t ProcessDataPlugin::GetUserHz()
356 {
357 int64_t hz = -1;
358 int64_t user_hz = sysconf(_SC_CLK_TCK);
359 switch (user_hz) {
360 case CPU_USER_HZ_L:
361 hz = CPU_HZ_H;
362 break;
363 case CPU_USER_HZ_H:
364 hz = 1;
365 break;
366 default:
367 break;
368 }
369 return hz;
370 }
371
WriteCpuUsageData(int pid,T & cpuInfo)372 template <typename T> bool ProcessDataPlugin::WriteCpuUsageData(int pid, T& cpuInfo)
373 {
374 uint64_t prevCpuTime = 0;
375 uint64_t cpuTime = 0;
376 uint64_t prevBootTime = 0;
377 uint64_t bootTime = 0;
378 double usage = 0.0;
379 ReadCpuUsage(pid, cpuTime);
380 ReadBootTime(pid, bootTime);
381 if (cpuTime_.find(pid) != cpuTime_.end()) {
382 prevCpuTime = cpuTime_[pid];
383 }
384 if (bootTime_.find(pid) != bootTime_.end()) {
385 prevBootTime = bootTime_[pid];
386 }
387 if (bootTime - prevBootTime == 0 || bootTime == 0) {
388 return false;
389 }
390 if (cpuTime < prevCpuTime) {
391 return false;
392 }
393 if (prevCpuTime == 0) {
394 usage = static_cast<double>(cpuTime) / (bootTime);
395 } else {
396 usage = static_cast<double>(cpuTime - prevCpuTime) / (bootTime - prevBootTime);
397 }
398
399 if (usage > 0) {
400 cpuInfo.set_cpu_usage(usage * PERCENT);
401 }
402 cpuInfo.set_cpu_time_ms(cpuTime);
403 cpuTime_[pid] = cpuTime;
404 bootTime_[pid] = bootTime;
405 return true;
406 }
407
ReadBootTime(int pid,uint64_t & bootTime)408 bool ProcessDataPlugin::ReadBootTime(int pid, uint64_t& bootTime)
409 {
410 std::string path = path_ + "stat";
411 std::ifstream input(path, std::ios::in);
412 CHECK_TRUE(!input.fail(), false, "%s open %s failed, errno = %d", __func__, path.c_str(), errno);
413 do {
414 if (!input.good()) {
415 return false;
416 }
417 std::string line;
418 getline(input, line);
419
420 auto pos = line.find("cpu ");
421 if (pos != std::string::npos) {
422 line += '\n';
423 GetBootData(line, bootTime);
424 }
425 } while (0);
426 input.close();
427
428 return true;
429 }
430
GetBootData(const std::string & line,uint64_t & bootTime)431 uint32_t ProcessDataPlugin::GetBootData(const std::string& line, uint64_t& bootTime)
432 {
433 uint64_t num;
434 uint32_t count = 0;
435 char* end = nullptr;
436 char* pTmp = const_cast<char*>(line.c_str());
437 constexpr uint32_t cntVec = 8;
438
439 std::vector<uint64_t> bootTimeVec;
440 bootTime = 0;
441 while (pTmp != nullptr && *pTmp != '\n') {
442 CHECK_TRUE(FindFirstNum(&pTmp), count, "%s: FindFirstNum failed", __func__);
443 num = strtoull(pTmp, &end, DEC_BASE);
444 CHECK_TRUE(num >= 0, count, "%s:strtoull failed", __func__);
445 bootTimeVec.push_back(num);
446 bootTime += num;
447 pTmp = end;
448 if (++count >= cntVec) {
449 break;
450 }
451 }
452 bootTime = bootTime * (uint64_t)GetUserHz();
453 return count;
454 }
455
ReadCpuUsage(int pid,uint64_t & cpuTime)456 bool ProcessDataPlugin::ReadCpuUsage(int pid, uint64_t& cpuTime)
457 {
458 std::string path = path_ + std::to_string(pid) + "/stat";
459 std::ifstream input(path, std::ios::in);
460 CHECK_TRUE(!input.fail(), false, "%s open %s failed, errno = %d", __func__, path.c_str(), errno);
461 do {
462 if (!input.good()) {
463 return false;
464 }
465 std::string line;
466 getline(input, line);
467 line += '\n';
468 GetCpuUsageData(line, cpuTime);
469 } while (0);
470 input.close();
471
472 return true;
473 }
474
GetCpuUsageData(const std::string & line,uint64_t & cpuTime)475 uint32_t ProcessDataPlugin::GetCpuUsageData(const std::string& line, uint64_t& cpuTime)
476 {
477 uint64_t num;
478 uint32_t count = 0;
479 char* end = nullptr;
480 char* pTmp = const_cast<char*>(line.c_str());
481 int i = 0;
482 constexpr uint32_t cntVec = 4;
483
484 while (FindFirstSpace(&pTmp)) {
485 pTmp++;
486 if (++i >= STAT_COUNT) {
487 break;
488 }
489 }
490 std::vector<uint64_t> cpuUsageVec;
491 cpuTime = 0;
492 while (pTmp != nullptr && *pTmp != '\n') {
493 CHECK_TRUE(FindFirstNum(&pTmp), count, "%s: FindFirstNum failed", __func__);
494 num = strtoull(pTmp, &end, DEC_BASE);
495 cpuUsageVec.push_back(num);
496 cpuTime += num;
497 pTmp = end;
498 if (++count >= cntVec) {
499 break;
500 }
501 }
502 cpuTime = cpuTime * (uint64_t)GetUserHz();
503 return count;
504 }
505
WriteDiskioData(int pid,T & processinfo)506 template <typename T> bool ProcessDataPlugin::WriteDiskioData(int pid, T& processinfo)
507 {
508 std::string path = path_ + std::to_string(pid) + "/io";
509 std::ifstream input(path, std::ios::in);
510 if (input.fail()) {
511 return false;
512 }
513
514 auto* diskInfo = processinfo.mutable_diskinfo();
515 do {
516 if (!input.good()) {
517 return false;
518 }
519 std::string line;
520 getline(input, line);
521 line += '\n';
522 GetDiskioData(line, *diskInfo);
523 } while (!input.eof());
524 input.close();
525
526 return true;
527 }
528
GetDiskioData(std::string & line,T & diskioInfo)529 template <typename T> bool ProcessDataPlugin::GetDiskioData(std::string& line, T& diskioInfo)
530 {
531 char* pTmp = const_cast<char*>(line.c_str());
532 CHECK_NOTNULL(pTmp, false, "param invalid!");
533
534 uint64_t num;
535 if (!std::strncmp(pTmp, "rchar:", strlen("rchar:"))) {
536 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get rchar failed", __func__);
537 diskioInfo.set_rchar(num);
538 } else if (!std::strncmp(pTmp, "wchar:", strlen("wchar:"))) {
539 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get wchar failed", __func__);
540 diskioInfo.set_wchar(num);
541 } else if (!std::strncmp(pTmp, "syscr:", strlen("syscr:"))) {
542 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get syscr failed", __func__);
543 diskioInfo.set_syscr(num);
544 } else if (!std::strncmp(pTmp, "syscw:", strlen("syscw:"))) {
545 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get syscw failed", __func__);
546 diskioInfo.set_syscw(num);
547 } else if (!std::strncmp(pTmp, "read_bytes:", strlen("read_bytes:"))) {
548 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get read_bytes failed", __func__);
549 diskioInfo.set_rbytes(num);
550 } else if (!std::strncmp(pTmp, "write_bytes:", strlen("write_bytes:"))) {
551 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get write_bytes failed", __func__);
552 diskioInfo.set_wbytes(num);
553 } else if (!std::strncmp(pTmp, "cancelled_write_bytes:", strlen("cancelled_write_bytes:"))) {
554 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get cancelled_write_bytes failed", __func__);
555 diskioInfo.set_cancelled_wbytes(num);
556 }
557
558 return true;
559 }
560
FindFirstSpace(char ** p)561 bool ProcessDataPlugin::FindFirstSpace(char** p)
562 {
563 CHECK_NOTNULL(*p, false, "ProcessDataPlugin:%s", __func__);
564 while (**p != ' ') {
565 if (**p == '\0' || **p == '\n') {
566 return false;
567 }
568 (*p)++;
569 }
570 return true;
571 }
572
FindFirstNum(char ** p)573 bool ProcessDataPlugin::FindFirstNum(char** p)
574 {
575 CHECK_NOTNULL(*p, false, "ProcessDataPlugin:%s", __func__);
576 while (**p > '9' || **p < '0') {
577 if (**p == '\0' || **p == '\n') {
578 return false;
579 }
580 (*p)++;
581 }
582 return true;
583 }
584
GetValidValue(char * p,uint64_t & num)585 bool ProcessDataPlugin::GetValidValue(char* p, uint64_t& num)
586 {
587 char* end = nullptr;
588 CHECK_TRUE(FindFirstNum(&p), false, "%s: FindFirstNum failed", __func__);
589 num = strtoull(p, &end, DEC_BASE);
590 CHECK_TRUE(num >= 0, false, "%s:strtoull failed", __func__);
591 return true;
592 }
593
594 // read /proc/pid/smaps_rollup
WritePssData(int pid,T & processInfo)595 template <typename T> bool ProcessDataPlugin::WritePssData(int pid, T& processInfo)
596 {
597 std::string path = path_ + std::to_string(pid) + "/smaps_rollup";
598 std::ifstream input(path, std::ios::in);
599
600 // Not capturing ENOENT (file does not exist) errors, it is common for node smaps_rollup files to be unreadable.
601 CHECK_TRUE(!input.fail(), false, "%s open %s failed, errno = %d", __func__, path.c_str(), errno);
602
603 auto* pssInfo = processInfo.mutable_pssinfo();
604 do {
605 if (!input.good()) {
606 return false;
607 }
608 std::string line;
609 getline(input, line);
610 line += '\n';
611 std::string::size_type pos = 0u;
612 if (line.find("Pss:", pos) != std::string::npos) {
613 char* pTmp = const_cast<char*>(line.c_str());
614 uint64_t num;
615 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: FindFirstNum failed", __func__);
616 pssInfo->set_pss_info(num);
617 return true;
618 }
619 } while (!input.eof());
620 input.close();
621
622 return false;
623 }
624