2 * Copyright (c) 1998-2007 Matthijs Hollemans
3 * Copyright (c) 2008-2017, Haiku Inc.
4 * Distributed under the terms of the MIT license.
8 * Stephan Aßmus <superstippi@gmx.de>
19 #include <sys/select.h>
23 #include <Directory.h>
32 #include "FileIterator.h"
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "Grepper"
39 const char* kEOFTag
= "//EOF";
45 strdup_to_utf8(uint32 encode
, const char* src
, int32 length
)
47 int32 srcLen
= length
;
48 int32 dstLen
= 2 * srcLen
;
49 // TODO: stippi: Why the duplicate copy? Why not just return
50 // dst (and allocate with malloc() instead of new)? Is 2 * srcLen
51 // enough space? Check return value of convert_to_utf8 and keep
52 // converting if it didn't fit?
53 char* dst
= new (nothrow
) char[dstLen
+ 1];
57 convert_to_utf8(encode
, src
, &srcLen
, dst
, &dstLen
, &cookie
);
59 char* dup
= strdup(dst
);
61 if (srcLen
!= length
) {
62 fprintf(stderr
, "strdup_to_utf8(%" B_PRId32
", %" B_PRId32
63 ") dst allocate smalled(%" B_PRId32
")\n", encode
, length
, dstLen
);
70 strdup_from_utf8(uint32 encode
, const char* src
, int32 length
)
72 int32 srcLen
= length
;
73 int32 dstLen
= srcLen
;
74 char* dst
= new (nothrow
) char[dstLen
+ 1];
78 convert_from_utf8(encode
, src
, &srcLen
, dst
, &dstLen
, &cookie
);
81 char* dup
= strdup(dst
);
83 if (srcLen
!= length
) {
84 fprintf(stderr
, "strdup_from_utf8(%" B_PRId32
", %" B_PRId32
85 ") dst allocate smalled(%" B_PRId32
")\n", encode
, length
, dstLen
);
91 Grepper::Grepper(const char* pattern
, const Model
* model
,
92 const BHandler
* target
, FileIterator
* iterator
)
95 fRegularExpression(model
->fRegularExpression
),
96 fCaseSensitive(model
->fCaseSensitive
),
97 fEncoding(model
->fEncoding
),
105 char* src
= strdup_from_utf8(fEncoding
, pattern
, strlen(pattern
));
109 _SetPattern(pattern
);
122 Grepper::IsValid() const
124 if (fIterator
== NULL
|| !fIterator
->IsValid())
126 return fPattern
!= NULL
;
136 fRunnerThreadId
= spawn_thread(
137 _SpawnRunnerThread
, "Grep runner", B_NORMAL_PRIORITY
, this);
139 resume_thread(fRunnerThreadId
);
146 if (fRunnerThreadId
< 0)
151 wait_for_thread(fRunnerThreadId
, &exitValue
);
152 fRunnerThreadId
= -1;
156 // #pragma mark - private
160 Grepper::_SpawnWriterThread(void* cookie
)
162 Grepper
* self
= static_cast<Grepper
*>(cookie
);
163 return self
->_WriterThread();
168 Grepper::_WriterThread()
171 char fileName
[B_PATH_NAME_LENGTH
*2];
174 printf("paths_writer started.\n");
176 while (!fMustQuit
&& fIterator
->GetNextName(fileName
)) {
179 message
.what
= MSG_REPORT_FILE_NAME
;
180 message
.AddString("filename", fileName
);
182 BEntry
entry(fileName
);
185 if (!entry
.Exists()) {
186 if (fIterator
->NotifyNegatives()) {
187 message
.what
= MSG_REPORT_RESULT
;
188 message
.AddRef("ref", &ref
);
189 fTarget
.SendMessage(&message
);
194 if (!_EscapeSpecialChars(fileName
, sizeof(fileName
))) {
195 char tempString
[B_PATH_NAME_LENGTH
+ 32];
196 sprintf(tempString
, B_TRANSLATE("%s: Not enough room to escape "
197 "the filename."), fileName
);
199 message
.what
= MSG_REPORT_ERROR
;
200 message
.AddString("error", tempString
);
201 fTarget
.SendMessage(&message
);
205 // file exists, send it to xargs
206 write(fXargsInput
, fileName
, strlen(fileName
));
207 write(fXargsInput
, "\n", 1);
208 // printf(">>>>>> %s\n", fileName);
210 fTarget
.SendMessage(&message
);
215 write(fXargsInput
, kEOFTag
, strlen(kEOFTag
));
216 write(fXargsInput
, "\n", 1);
219 printf("paths_writer stopped (%d paths).\n", count
);
226 Grepper::_SpawnRunnerThread(void* cookie
)
228 Grepper
* self
= static_cast<Grepper
*>(cookie
);
229 return self
->_RunnerThread();
234 Grepper::_RunnerThread()
237 char fileName
[B_PATH_NAME_LENGTH
];
239 const char* argv
[32];
241 argv
[argc
++] = "xargs";
243 // can't use yet the --null mode due to pipe issue
244 // the xargs stdin input pipe closure is not detected
245 // by xargs. Instead, we use eof-string mode
247 // argv[argc++] = "--null";
249 argv
[argc
++] = kEOFTag
;
251 // Enable parallel mode
252 // Retrieve cpu count for to parallel xargs via -P argument
254 system_info sys_info
;
255 get_system_info(&sys_info
);
256 snprintf(cpuCount
, sizeof(cpuCount
), "%" B_PRIu32
, sys_info
.cpu_count
);
258 argv
[argc
++] = cpuCount
;
260 // grep command driven by xargs dispatcher
261 argv
[argc
++] = "grep";
262 argv
[argc
++] = "-n"; // need matching line(s) number(s)
263 argv
[argc
++] = "-H"; // need filename prefix
264 if (! fCaseSensitive
)
266 if (! fRegularExpression
)
267 argv
[argc
++] = "-F"; // no a regexp: force fixed string,
268 argv
[argc
++] = fPattern
;
271 // prepare xargs to run with stdin, stdout and stderr pipes
273 int oldStdIn
, oldStdOut
, oldStdErr
;
274 oldStdIn
= dup(STDIN_FILENO
);
275 oldStdOut
= dup(STDOUT_FILENO
);
276 oldStdErr
= dup(STDERR_FILENO
);
279 if (pipe(fds
) != 0) {
281 message
.what
= MSG_REPORT_ERROR
;
282 message
.AddString("error",
283 B_TRANSLATE("Failed to open input pipe!"));
284 fTarget
.SendMessage(&message
);
287 dup2(fds
[0], STDIN_FILENO
);
289 fXargsInput
= fds
[1]; // write to in, appears on command's stdin
291 if (pipe(fds
) != 0) {
294 message
.what
= MSG_REPORT_ERROR
;
295 message
.AddString("error",
296 B_TRANSLATE("Failed to open output pipe!"));
297 fTarget
.SendMessage(&message
);
300 dup2(fds
[1], STDOUT_FILENO
);
302 int out
= fds
[0]; // read from out, taken from command's stdout
304 if (pipe(fds
) != 0) {
308 message
.what
= MSG_REPORT_ERROR
;
309 message
.AddString("error",
310 B_TRANSLATE("Failed to open errors pipe!"));
311 fTarget
.SendMessage(&message
);
314 dup2(fds
[1], STDERR_FILENO
);
316 int err
= fds
[0]; // read from err, taken from command's stderr
319 thread_id xargsThread
= load_image(argc
, argv
,
320 const_cast<const char**>(environ
));
321 // xargsThread is suspended after loading
323 // restore our previous stdin, stdout and stderr
327 close(STDOUT_FILENO
);
330 close(STDERR_FILENO
);
334 if (xargsThread
< B_OK
) {
339 message
.what
= MSG_REPORT_ERROR
;
340 message
.AddString("error",
341 B_TRANSLATE("Failed to start xargs program!"));
342 fTarget
.SendMessage(&message
);
346 // Listen on xargs's stdout and stderr via select()
348 for (int i
= 0; i
< argc
; i
++) {
349 printf("%s ", argv
[i
]);
353 int fdl
[2] = { out
, err
};
355 for (int i
= 0; i
< 2; i
++) {
361 struct timeval timeout
= { 0, 100000 };
362 char line
[B_PATH_NAME_LENGTH
* 2];
364 FILE* output
= fdopen(out
, "r");
365 FILE* errors
= fdopen(err
, "r");
367 char currentFileName
[B_PATH_NAME_LENGTH
];
368 currentFileName
[0] = '\0';
369 bool canReadOutput
, canReadErrors
;
370 canReadOutput
= canReadErrors
= true;
372 thread_id writerThread
= spawn_thread(_SpawnWriterThread
,
373 "Grep writer", B_LOW_PRIORITY
, this);
374 set_thread_priority(xargsThread
, B_LOW_PRIORITY
);
376 // we're ready, let's go!
377 resume_thread(xargsThread
);
378 resume_thread(writerThread
);
380 while (!fMustQuit
&& (canReadOutput
|| canReadErrors
)) {
383 FD_SET(out
, &readSet
);
386 FD_SET(err
, &readSet
);
389 int result
= select(maxfd
+ 1, &readSet
, NULL
, NULL
, &timeout
);
390 if (result
== -1 && errno
== EINTR
)
393 // timeout, but meanwhile fMustQuit was changed maybe...
399 message
.what
= MSG_REPORT_ERROR
;
400 message
.AddString("error", strerror(errno
));
401 fTarget
.SendMessage(&message
);
405 if (canReadOutput
&& FD_ISSET(out
, &readSet
)) {
406 if (fgets(line
, sizeof(line
), output
) != NULL
) {
410 sscanf(line
, "%[^\n:]:%d:%n", fileName
, &lineNumber
, &textPos
);
411 // printf("sscanf(\"%s\") -> %s %d %d\n", line, fileName,
412 // lineNumber, textPos);
414 if (strcmp(fileName
, currentFileName
) != 0) {
415 fTarget
.SendMessage(&message
);
417 strncpy(currentFileName
, fileName
,
418 sizeof(currentFileName
));
421 message
.what
= MSG_REPORT_RESULT
;
422 message
.AddString("filename", fileName
);
424 BEntry
entry(fileName
);
427 message
.AddRef("ref", &ref
);
430 char* text
= &line
[strlen(fileName
)+1];
431 // printf("[%s] %s", fileName, text);
433 char* tempdup
= strdup_to_utf8(fEncoding
, text
,
435 message
.AddString("text", tempdup
);
438 message
.AddString("text", text
);
440 message
.AddInt32("line", lineNumber
);
443 canReadOutput
= false;
446 if (canReadErrors
&& FD_ISSET(err
, &readSet
)) {
447 if (fgets(line
, sizeof(line
), errors
) != NULL
) {
448 // printf("ERROR: %s", line);
449 if (message
.HasString("text"))
450 fTarget
.SendMessage(&message
);
451 currentFileName
[0] = '\0';
454 message
.what
= MSG_REPORT_ERROR
;
455 message
.AddString("error", line
);
456 fTarget
.SendMessage(&message
);
458 canReadErrors
= false;
463 // send last pending message, if any
464 if (message
.HasString("text"))
465 fTarget
.SendMessage(&message
);
476 wait_for_thread(xargsThread
, &exitValue
);
477 wait_for_thread(writerThread
, &exitValue
);
480 message
.what
= MSG_SEARCH_FINISHED
;
481 fTarget
.SendMessage(&message
);
488 Grepper::_SetPattern(const char* src
)
493 fPattern
= strdup(src
);
498 Grepper::_EscapeSpecialChars(char* buffer
, ssize_t bufferSize
)
500 char* copy
= strdup(buffer
);
501 char* start
= buffer
;
502 uint32 len
= strlen(copy
);
504 for (uint32 count
= 0; count
< len
; ++count
) {
505 if (copy
[count
] == '\'' || copy
[count
] == '\\'
506 || copy
[count
] == ' ' || copy
[count
] == '\n'
507 || copy
[count
] == '"')
509 if (buffer
- start
== bufferSize
- 1) {
513 *buffer
++ = copy
[count
];