1/* 2 * Copyright (c) 1998-2007 Matthijs Hollemans 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 * DEALINGS IN THE SOFTWARE. 21 */ 22 23 24#include "Grepper.h" 25 26#include <new> 27#include <stdio.h> 28#include <stdlib.h> 29#include <string.h> 30 31#include <Catalog.h> 32#include <Directory.h> 33#include <List.h> 34#include <Locale.h> 35#include <NodeInfo.h> 36#include <Path.h> 37#include <UTF8.h> 38 39#include "FileIterator.h" 40#include "Model.h" 41 42#undef B_TRANSLATION_CONTEXT 43#define B_TRANSLATION_CONTEXT "Grepper" 44 45 46using std::nothrow; 47 48// TODO: stippi: Check if this is a the best place to maintain a global 49// list of files and folders for node monitoring. It should probably monitor 50// every file that was grepped, as well as every visited (sub) folder. 51// For the moment I don't know the life cycle of the Grepper object. 52 53 54char* 55strdup_to_utf8(uint32 encode, const char* src, int32 length) 56{ 57 int32 srcLen = length; 58 int32 dstLen = 2 * srcLen; 59 // TODO: stippi: Why the duplicate copy? Why not just return 60 // dst (and allocate with malloc() instead of new)? Is 2 * srcLen 61 // enough space? Check return value of convert_to_utf8 and keep 62 // converting if it didn't fit? 63 char* dst = new (nothrow) char[dstLen + 1]; 64 if (dst == NULL) 65 return NULL; 66 int32 cookie = 0; 67 convert_to_utf8(encode, src, &srcLen, dst, &dstLen, &cookie); 68 dst[dstLen] = '\0'; 69 char* dup = strdup(dst); 70 delete[] dst; 71 if (srcLen != length) { 72 fprintf(stderr, "strdup_to_utf8(%ld, %ld) dst allocate smalled(%ld)\n", 73 encode, length, dstLen); 74 } 75 return dup; 76} 77 78 79char* 80strdup_from_utf8(uint32 encode, const char* src, int32 length) 81{ 82 int32 srcLen = length; 83 int32 dstLen = srcLen; 84 char* dst = new (nothrow) char[dstLen + 1]; 85 if (dst == NULL) 86 return NULL; 87 int32 cookie = 0; 88 convert_from_utf8(encode, src, &srcLen, dst, &dstLen, &cookie); 89 // TODO: See above. 90 dst[dstLen] = '\0'; 91 char* dup = strdup(dst); 92 delete[] dst; 93 if (srcLen != length) { 94 fprintf(stderr, "strdup_from_utf8(%ld, %ld) dst allocate " 95 "smalled(%ld)\n", encode, length, dstLen); 96 } 97 return dup; 98} 99 100 101Grepper::Grepper(const char* pattern, const Model* model, 102 const BHandler* target, FileIterator* iterator) 103 : fPattern(NULL), 104 fTarget(target), 105 fEscapeText(model->fEscapeText), 106 fCaseSensitive(model->fCaseSensitive), 107 fEncoding(model->fEncoding), 108 109 fIterator(iterator), 110 fThreadId(-1), 111 fMustQuit(false) 112{ 113 if (fEncoding > 0) { 114 char* src = strdup_from_utf8(fEncoding, pattern, strlen(pattern)); 115 _SetPattern(src); 116 free(src); 117 } else 118 _SetPattern(pattern); 119} 120 121 122Grepper::~Grepper() 123{ 124 Cancel(); 125 free(fPattern); 126 delete fIterator; 127} 128 129 130bool 131Grepper::IsValid() const 132{ 133 if (fIterator == NULL || !fIterator->IsValid()) 134 return false; 135 return fPattern != NULL; 136} 137 138 139void 140Grepper::Start() 141{ 142 Cancel(); 143 144 fMustQuit = false; 145 fThreadId = spawn_thread( 146 _SpawnThread, "_GrepperThread", B_NORMAL_PRIORITY, this); 147 148 resume_thread(fThreadId); 149} 150 151 152void 153Grepper::Cancel() 154{ 155 if (fThreadId < 0) 156 return; 157 158 fMustQuit = true; 159 int32 exitValue; 160 wait_for_thread(fThreadId, &exitValue); 161 fThreadId = -1; 162} 163 164 165// #pragma mark - private 166 167 168int32 169Grepper::_SpawnThread(void* cookie) 170{ 171 Grepper* self = static_cast<Grepper*>(cookie); 172 return self->_GrepperThread(); 173} 174 175 176int32 177Grepper::_GrepperThread() 178{ 179 BMessage message; 180 181 char fileName[B_PATH_NAME_LENGTH]; 182 char tempString[B_PATH_NAME_LENGTH]; 183 char command[B_PATH_NAME_LENGTH + 32]; 184 185 BPath tempFile; 186 sprintf(fileName, "/tmp/SearchText%ld", fThreadId); 187 tempFile.SetTo(fileName); 188 189 while (!fMustQuit && fIterator->GetNextName(fileName)) { 190 191 message.MakeEmpty(); 192 message.what = MSG_REPORT_FILE_NAME; 193 message.AddString("filename", fileName); 194 fTarget.SendMessage(&message); 195 196 message.MakeEmpty(); 197 message.what = MSG_REPORT_RESULT; 198 message.AddString("filename", fileName); 199 200 BEntry entry(fileName); 201 entry_ref ref; 202 entry.GetRef(&ref); 203 message.AddRef("ref", &ref); 204 205 if (!entry.Exists()) { 206 if (fIterator->NotifyNegatives()) 207 fTarget.SendMessage(&message); 208 continue; 209 } 210 211 if (!_EscapeSpecialChars(fileName, B_PATH_NAME_LENGTH)) { 212 sprintf(tempString, B_TRANSLATE("%s: Not enough room to escape " 213 "the filename."), fileName); 214 215 message.MakeEmpty(); 216 message.what = MSG_REPORT_ERROR; 217 message.AddString("error", tempString); 218 fTarget.SendMessage(&message); 219 continue; 220 } 221 222 sprintf(command, "grep -hn %s %s \"%s\" > \"%s\"", 223 fCaseSensitive ? "" : "-i", fPattern, fileName, tempFile.Path()); 224 225 int res = system(command); 226 227 if (res == 0 || res == 1) { 228 FILE *results = fopen(tempFile.Path(), "r"); 229 230 if (results != NULL) { 231 while (fgets(tempString, B_PATH_NAME_LENGTH, results) != 0) { 232 if (fEncoding > 0) { 233 char* tempdup = strdup_to_utf8(fEncoding, tempString, 234 strlen(tempString)); 235 message.AddString("text", tempdup); 236 free(tempdup); 237 } else 238 message.AddString("text", tempString); 239 } 240 241 if (message.HasString("text") || fIterator->NotifyNegatives()) 242 fTarget.SendMessage(&message); 243 244 fclose(results); 245 continue; 246 } 247 } 248 249 sprintf(tempString, B_TRANSLATE("%s: There was a problem running grep."), fileName); 250 251 message.MakeEmpty(); 252 message.what = MSG_REPORT_ERROR; 253 message.AddString("error", tempString); 254 fTarget.SendMessage(&message); 255 } 256 257 // We wait with removing the temporary file until after the 258 // entire search has finished, to prevent a lot of flickering 259 // if the Tracker window for /tmp/ might be open. 260 261 remove(tempFile.Path()); 262 263 message.MakeEmpty(); 264 message.what = MSG_SEARCH_FINISHED; 265 fTarget.SendMessage(&message); 266 267 return 0; 268} 269 270 271void 272Grepper::_SetPattern(const char* src) 273{ 274 if (src == NULL) 275 return; 276 277 if (!fEscapeText) { 278 fPattern = strdup(src); 279 return; 280 } 281 282 // We will simply guess the size of the memory buffer 283 // that we need. This should always be large enough. 284 fPattern = (char*)malloc((strlen(src) + 1) * 3 * sizeof(char)); 285 if (fPattern == NULL) 286 return; 287 288 const char* srcPtr = src; 289 char* dstPtr = fPattern; 290 291 // Put double quotes around the pattern, so separate 292 // words are considered to be part of a single string. 293 *dstPtr++ = '"'; 294 295 while (*srcPtr != '\0') { 296 char c = *srcPtr++; 297 298 // Put a backslash in front of characters 299 // that should be escaped. 300 if ((c == '.') || (c == ',') 301 || (c == '[') || (c == ']') 302 || (c == '?') || (c == '*') 303 || (c == '+') || (c == '-') 304 || (c == ':') || (c == '^') 305 || (c == '"') || (c == '`')) { 306 *dstPtr++ = '\\'; 307 } else if ((c == '\\') || (c == '$')) { 308 // Some characters need to be escaped 309 // with *three* backslashes in a row. 310 *dstPtr++ = '\\'; 311 *dstPtr++ = '\\'; 312 *dstPtr++ = '\\'; 313 } 314 315 // Note: we do not have to escape the 316 // { } ( ) < > and | characters. 317 318 *dstPtr++ = c; 319 } 320 321 *dstPtr++ = '"'; 322 *dstPtr = '\0'; 323} 324 325 326bool 327Grepper::_EscapeSpecialChars(char* buffer, ssize_t bufferSize) 328{ 329 char* copy = strdup(buffer); 330 char* start = buffer; 331 uint32 len = strlen(copy); 332 bool result = true; 333 for (uint32 count = 0; count < len; ++count) { 334 if (copy[count] == '"' || copy[count] == '$') 335 *buffer++ = '\\'; 336 if (buffer - start == bufferSize - 1) { 337 result = false; 338 break; 339 } 340 *buffer++ = copy[count]; 341 } 342 *buffer = '\0'; 343 free(copy); 344 return result; 345} 346 347