1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <frameworks/base/core/proto/android/os/incident.pb.h> 18 19 #include <map> 20 #include <set> 21 #include <sstream> 22 #include <string> 23 24 #ifndef FALLTHROUGH_INTENDED 25 #define FALLTHROUGH_INTENDED [[fallthrough]] 26 #endif 27 28 using namespace android; 29 using namespace android::os; 30 using namespace google::protobuf; 31 using namespace google::protobuf::io; 32 using namespace google::protobuf::internal; 33 using namespace std; 34 35 /** 36 * Implementation details: 37 * This binary auto generates .cpp files for incident and incidentd. 38 * 39 * When argument "incident" is specified, it generates incident_section.cpp file. 40 * 41 * When argument "incidentd" is specified, it generates section_list.cpp file. 42 * 43 * In section_list.cpp file, it generates a SECTION_LIST array and a PRIVACY_POLICY_LIST array. 44 * For SECTION_LIST, it generates Section.h classes only for proto fields with section option enabled. 45 * For PRIVACY_POLICY_LIST, it generates Privacy.h classes only for proto fields with privacy option enabled. 46 * 47 * For Privacy struct, it is possible to have self recursion definitions since protobuf is defining "classes" 48 * So the logic to handle it becomes very complicated when Privacy tag of a message contains a list of Privacies 49 * of its sub-messages. The code also handles multiple depth of self recursion fields. 50 * 51 * For example here is a one level self recursion message WindowManager: 52 * message WindowState { 53 * string state = 1 [(privacy).dest = LOCAL]; 54 * int32 display_id = 2; 55 * repeated WindowState child_windows = 3; 56 * } 57 * 58 * message WindowManager { 59 * WindowState my_window = 1; 60 * } 61 * 62 * When generating Privacy options for WindowManager, this tool will generate cpp syntax source code: 63 * 64 * #include "section_list.h" 65 * ... 66 * Privacy WindowState__state { 1, 9, NULL, LOCAL, NULL }; // first two integers are values for field id and proto type. 67 * Privacy WindowState__child_windows { 3, 11, NULL, UNSET, NULL }; // reserved for WindowState_LIST 68 * Privacy* WindowState__MSG__UNSET[] = { 69 * &WindowState_state, 70 * // display id is default, nothing is generated. 71 * &WindowState_child_windows, 72 * NULL // terminator of the array 73 * }; 74 * Privacy WindowState__my_window { 1, 11, WindowState__MSG__UNSET, UNSET, NULL }; 75 * 76 * createList() { 77 * ... 78 * WindowState_child_windows.children = WindowState__MSG_UNSET; // point to its own definition after the list is defined. 79 * ... 80 * } 81 * 82 * const Privacy** PRIVACY_POLICY_LIST = createList(); 83 * const int PRIVACY_POLICY_COUNT = 1; 84 * 85 * Privacy Value Inheritance rules: 86 * 1. Both field and message can be tagged with DESTINATION: LOCAL(L), EXPLICIT(E), AUTOMATIC(A). 87 * 2. Primitives inherits containing message's tag unless defined explicitly. 88 * 3. Containing message's tag doesn't apply to message fields, even when unset (in this case, uses its default message tag). 89 * 4. Message field tag overrides its default message tag. 90 * 5. UNSET tag defaults to EXPLICIT. 91 */ 92 93 // The assignments will be called when constructs PRIVACY_POLICY_LIST, has to be global variable 94 vector<string> gSelfRecursionAssignments; 95 96 static inline void emptyline() { 97 printf("\n"); 98 } 99 100 static void generateHead(const char* header) { 101 printf("// Auto generated file. Do not modify\n"); 102 emptyline(); 103 printf("#include \"%s.h\"\n", header); 104 emptyline(); 105 } 106 107 // ======================== incident_sections ============================= 108 static bool generateIncidentSectionsCpp(Descriptor const* descriptor) 109 { 110 generateHead("incident_sections"); 111 112 map<string,FieldDescriptor const*> sections; 113 int N; 114 N = descriptor->field_count(); 115 for (int i=0; i<N; i++) { 116 const FieldDescriptor* field = descriptor->field(i); 117 sections[field->name()] = field; 118 } 119 120 printf("IncidentSection const INCIDENT_SECTIONS[] = {\n"); 121 N = sections.size(); 122 int i = 0; 123 for (map<string,FieldDescriptor const*>::const_iterator it = sections.begin(); 124 it != sections.end(); it++, i++) { 125 const FieldDescriptor* field = it->second; 126 printf(" { %d, \"%s\" }", field->number(), field->name().c_str()); 127 if (i != N-1) { 128 printf(",\n"); 129 } else { 130 printf("\n"); 131 } 132 } 133 printf("};\n"); 134 135 printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); 136 137 return true; 138 } 139 140 // ========================= section_list =================================== 141 static void splitAndPrint(const string& args) { 142 size_t base = 0; 143 size_t found; 144 while (true) { 145 found = args.find_first_of(' ', base); 146 if (found != base) { 147 string arg = args.substr(base, found - base); 148 printf(" \"%s\",", arg.c_str()); 149 } 150 if (found == args.npos) break; 151 base = found + 1; 152 } 153 } 154 155 static string replaceAll(const string& fieldName, const char oldC, const string& newS) { 156 if (fieldName.find_first_of(oldC) == fieldName.npos) return fieldName.c_str(); 157 size_t pos = 0, idx = 0; 158 char* res = new char[fieldName.size() * newS.size() + 1]; // assign a larger buffer 159 while (pos != fieldName.size()) { 160 char cur = fieldName[pos++]; 161 if (cur != oldC) { 162 res[idx++] = cur; 163 continue; 164 } 165 166 for (size_t i=0; i<newS.size(); i++) { 167 res[idx++] = newS[i]; 168 } 169 } 170 res[idx] = '\0'; 171 string result(res); 172 delete [] res; 173 return result; 174 } 175 176 static inline void printPrivacy(const string& name, const FieldDescriptor* field, const string& children, 177 const Destination dest, const string& patterns, const string& comments = "") { 178 printf("Privacy %s = { %d, %d, %s, %d, %s };%s\n", name.c_str(), field->number(), field->type(), 179 children.c_str(), dest, patterns.c_str(), comments.c_str()); 180 } 181 182 // Get Custom Options ================================================================================ 183 static inline SectionFlags getSectionFlags(const FieldDescriptor* field) { 184 return field->options().GetExtension(section); 185 } 186 187 static inline PrivacyFlags getPrivacyFlags(const FieldDescriptor* field) { 188 return field->options().GetExtension(privacy); 189 } 190 191 static inline PrivacyFlags getPrivacyFlags(const Descriptor* descriptor) { 192 return descriptor->options().GetExtension(msg_privacy); 193 } 194 195 // Get Destinations =================================================================================== 196 static inline Destination getMessageDest(const Descriptor* descriptor, const Destination overridden) { 197 return overridden != DEST_UNSET ? overridden : getPrivacyFlags(descriptor).dest(); 198 } 199 200 // Returns field's own dest, when it is a message field, uses its message default tag if unset. 201 static inline Destination getFieldDest(const FieldDescriptor* field) { 202 Destination fieldDest = getPrivacyFlags(field).dest(); 203 return field->type() != FieldDescriptor::TYPE_MESSAGE ? fieldDest : 204 getMessageDest(field->message_type(), fieldDest); 205 } 206 207 // Converts Destination to a string. 208 static inline string getDestString(const Destination dest) { 209 switch (dest) { 210 case DEST_AUTOMATIC: return "AUTOMATIC"; 211 case DEST_LOCAL: return "LOCAL"; 212 case DEST_EXPLICIT: return "EXPLICIT"; 213 // UNSET is considered EXPLICIT by default. 214 case DEST_UNSET: return "EXPLICIT"; 215 default: return "UNKNOWN"; 216 } 217 } 218 219 // Get Names =========================================================================================== 220 static inline string getFieldName(const FieldDescriptor* field) { 221 // replace . with double underscores to avoid name conflicts since fields use snake naming convention 222 return replaceAll(field->full_name(), '.', "__"); 223 } 224 225 226 static inline string getMessageName(const Descriptor* descriptor, const Destination overridden) { 227 // replace . with one underscore since messages use camel naming convention 228 return replaceAll(descriptor->full_name(), '.', "_") + "__MSG__" + 229 to_string(getMessageDest(descriptor, overridden)); 230 } 231 232 // IsDefault ============================================================================================ 233 // Returns true if a field is default. Default is defined as this field has same dest as its containing message. 234 // For message fields, it only looks at its field tag and own default message tag, doesn't recursively go deeper. 235 static inline bool isDefaultField(const FieldDescriptor* field, const Destination containerDest) { 236 Destination fieldDest = getFieldDest(field); 237 if (field->type() != FieldDescriptor::TYPE_MESSAGE) { 238 return fieldDest == containerDest || (fieldDest == DEST_UNSET); 239 } else { 240 return fieldDest == containerDest || 241 (containerDest == DEST_UNSET && fieldDest == DEST_EXPLICIT) || 242 (containerDest == DEST_EXPLICIT && fieldDest == DEST_UNSET); 243 } 244 } 245 246 static bool isDefaultMessageImpl(const Descriptor* descriptor, const Destination dest, set<string>* parents) { 247 const int N = descriptor->field_count(); 248 const Destination messageDest = getMessageDest(descriptor, dest); 249 parents->insert(descriptor->full_name()); 250 for (int i=0; i<N; ++i) { 251 const FieldDescriptor* field = descriptor->field(i); 252 const Destination fieldDest = getFieldDest(field); 253 // If current field is not default, return false immediately 254 if (!isDefaultField(field, messageDest)) return false; 255 switch (field->type()) { 256 case FieldDescriptor::TYPE_MESSAGE: 257 // if self recursion, don't go deep. 258 if (parents->find(field->message_type()->full_name()) != parents->end()) break; 259 // if is a default message, just continue 260 if (isDefaultMessageImpl(field->message_type(), fieldDest, parents)) break; 261 // sub message is not default, so this message is always not default 262 return false; 263 case FieldDescriptor::TYPE_STRING: 264 if (getPrivacyFlags(field).patterns_size() != 0) return false; 265 break; 266 default: 267 break; 268 } 269 } 270 parents->erase(descriptor->full_name()); 271 return true; 272 } 273 274 // Recursively look at if this message is default, meaning all its fields and sub-messages 275 // can be described by the same dest. 276 static bool isDefaultMessage(const Descriptor* descriptor, const Destination dest) { 277 set<string> parents; 278 return isDefaultMessageImpl(descriptor, dest, &parents); 279 } 280 281 // =============================================================================================================== 282 static bool numberInOrder(const FieldDescriptor* f1, const FieldDescriptor* f2) { 283 return f1->number() < f2->number(); 284 } 285 286 // field numbers are possibly out of order, sort them here. 287 static vector<const FieldDescriptor*> sortFields(const Descriptor* descriptor) { 288 vector<const FieldDescriptor*> fields; 289 fields.reserve(descriptor->field_count()); 290 for (int i=0; i<descriptor->field_count(); i++) { 291 fields.push_back(descriptor->field(i)); 292 } 293 std::sort(fields.begin(), fields.end(), numberInOrder); 294 return fields; 295 } 296 297 // This function looks for privacy tags of a message type and recursively its sub-messages. 298 // It generates Privacy objects for each non-default fields including non-default sub-messages. 299 // And if the message has Privacy objects generated, it returns a list of them. 300 // Returns false if the descriptor doesn't have any non default privacy flags set, including its submessages 301 static bool generatePrivacyFlags(const Descriptor* descriptor, const Destination overridden, 302 map<string, bool> &variableNames, set<string>* parents) { 303 const string messageName = getMessageName(descriptor, overridden); 304 const Destination messageDest = getMessageDest(descriptor, overridden); 305 306 if (variableNames.find(messageName) != variableNames.end()) { 307 bool hasDefault = variableNames[messageName]; 308 return !hasDefault; // if has default, then don't generate privacy flags. 309 } 310 // insert the message type name so sub-message will figure out if self-recursion occurs 311 parents->insert(messageName); 312 313 // sort fields based on number, iterate though them and generate sub flags first 314 vector<const FieldDescriptor*> fieldsInOrder = sortFields(descriptor); 315 bool hasDefaultFlags[fieldsInOrder.size()]; 316 for (size_t i=0; i<fieldsInOrder.size(); i++) { 317 const FieldDescriptor* field = fieldsInOrder[i]; 318 const string fieldName = getFieldName(field); 319 const Destination fieldDest = getFieldDest(field); 320 321 if (variableNames.find(fieldName) != variableNames.end()) { 322 hasDefaultFlags[i] = variableNames[fieldName]; 323 continue; 324 } 325 hasDefaultFlags[i] = isDefaultField(field, messageDest); 326 327 string fieldMessageName; 328 PrivacyFlags p = getPrivacyFlags(field); 329 switch (field->type()) { 330 case FieldDescriptor::TYPE_MESSAGE: 331 fieldMessageName = getMessageName(field->message_type(), fieldDest); 332 if (parents->find(fieldMessageName) != parents->end()) { // Self-Recursion proto definition 333 if (hasDefaultFlags[i]) { 334 hasDefaultFlags[i] = isDefaultMessage(field->message_type(), fieldDest); 335 } 336 if (!hasDefaultFlags[i]) { 337 printPrivacy(fieldName, field, "NULL", fieldDest, "NULL", 338 " // self recursion field of " + fieldMessageName); 339 // generate the assignment and used to construct createList function later on. 340 gSelfRecursionAssignments.push_back(fieldName + ".children = " + fieldMessageName); 341 } 342 } else if (generatePrivacyFlags(field->message_type(), p.dest(), variableNames, parents)) { 343 if (variableNames.find(fieldName) == variableNames.end()) { 344 printPrivacy(fieldName, field, fieldMessageName, fieldDest, "NULL"); 345 } 346 hasDefaultFlags[i] = false; 347 } else if (!hasDefaultFlags[i]) { 348 printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); 349 } 350 break; 351 case FieldDescriptor::TYPE_STRING: 352 if (p.patterns_size() != 0) { // if patterns are specified 353 if (hasDefaultFlags[i]) break; 354 printf("const char* %s_patterns[] = {\n", fieldName.c_str()); 355 for (int j=0; j<p.patterns_size(); j++) { 356 // generated string needs to escape backslash too, duplicate it to allow escape again. 357 printf(" \"%s\",\n", replaceAll(p.patterns(j), '\\', "\\\\").c_str()); 358 } 359 printf(" NULL };\n"); 360 printPrivacy(fieldName, field, "NULL", fieldDest, fieldName + "_patterns"); 361 break; 362 } 363 FALLTHROUGH_INTENDED; 364 // else treat string field as primitive field and goes to default 365 default: 366 if (!hasDefaultFlags[i]) printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); 367 } 368 // Don't generate a variable twice 369 if (!hasDefaultFlags[i]) variableNames[fieldName] = false; 370 } 371 372 bool allDefaults = true; 373 for (size_t i=0; i<fieldsInOrder.size(); i++) { 374 allDefaults &= hasDefaultFlags[i]; 375 } 376 377 parents->erase(messageName); // erase the message type name when exit the message. 378 variableNames[messageName] = allDefaults; // store the privacy tags of the message here to avoid overhead. 379 380 if (allDefaults) return false; 381 382 emptyline(); 383 int policyCount = 0; 384 printf("Privacy* %s[] = {\n", messageName.c_str()); 385 for (size_t i=0; i<fieldsInOrder.size(); i++) { 386 const FieldDescriptor* field = fieldsInOrder[i]; 387 if (hasDefaultFlags[i]) continue; 388 printf(" &%s,\n", getFieldName(field).c_str()); 389 policyCount++; 390 } 391 printf(" NULL };\n"); 392 emptyline(); 393 return true; 394 } 395 396 static bool generateSectionListCpp(Descriptor const* descriptor) { 397 generateHead("section_list"); 398 399 // generate namespaces 400 printf("namespace android {\n"); 401 printf("namespace os {\n"); 402 printf("namespace incidentd {\n"); 403 404 // generates SECTION_LIST 405 printf("// Generate SECTION_LIST.\n\n"); 406 407 printf("const Section* SECTION_LIST[] = {\n"); 408 for (int i=0; i<descriptor->field_count(); i++) { 409 const FieldDescriptor* field = descriptor->field(i); 410 411 if (field->type() != FieldDescriptor::TYPE_MESSAGE && 412 field->type() != FieldDescriptor::TYPE_STRING && 413 field->type() != FieldDescriptor::TYPE_BYTES) { 414 continue; 415 } 416 417 const SectionFlags s = getSectionFlags(field); 418 if (s.userdebug_and_eng_only()) { 419 printf("#if ALLOW_RESTRICTED_SECTIONS\n"); 420 } 421 422 switch (s.type()) { 423 case SECTION_NONE: 424 continue; 425 case SECTION_FILE: 426 printf(" new FileSection(%d, \"%s\"),\n", field->number(), s.args().c_str()); 427 break; 428 case SECTION_COMMAND: 429 printf(" new CommandSection(%d,", field->number()); 430 splitAndPrint(s.args()); 431 printf(" NULL),\n"); 432 break; 433 case SECTION_DUMPSYS: 434 printf(" new DumpsysSection(%d, ", field->number()); 435 splitAndPrint(s.args()); 436 printf(" NULL),\n"); 437 break; 438 case SECTION_LOG: 439 printf(" new LogSection(%d, %s),\n", field->number(), s.args().c_str()); 440 break; 441 case SECTION_GZIP: 442 printf(" new GZipSection(%d,", field->number()); 443 splitAndPrint(s.args()); 444 printf(" NULL),\n"); 445 break; 446 case SECTION_TOMBSTONE: 447 printf(" new TombstoneSection(%d, \"%s\"),\n", field->number(), 448 s.args().c_str()); 449 break; 450 } 451 if (s.userdebug_and_eng_only()) { 452 printf("#endif\n"); 453 } 454 } 455 printf(" NULL };\n"); 456 457 emptyline(); 458 printf("// =============================================================================\n"); 459 emptyline(); 460 461 // generates PRIVACY_POLICY_LIST 462 printf("// Generate PRIVACY_POLICY_LIST.\n\n"); 463 map<string, bool> variableNames; 464 set<string> parents; 465 vector<const FieldDescriptor*> fieldsInOrder = sortFields(descriptor); 466 vector<bool> skip(fieldsInOrder.size()); 467 const Destination incidentDest = getPrivacyFlags(descriptor).dest(); 468 469 for (size_t i=0; i<fieldsInOrder.size(); i++) { 470 const FieldDescriptor* field = fieldsInOrder[i]; 471 const string fieldName = getFieldName(field); 472 const Destination fieldDest = getFieldDest(field); 473 printf("\n// Incident Report Section: %s (%d)\n", field->name().c_str(), field->number()); 474 if (field->type() != FieldDescriptor::TYPE_MESSAGE) { 475 printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); 476 continue; 477 } 478 479 skip[i] = true; 480 const string fieldMessageName = getMessageName(field->message_type(), fieldDest); 481 // generate privacy flags for each section. 482 if (generatePrivacyFlags(field->message_type(), incidentDest, variableNames, &parents)) { 483 printPrivacy(fieldName, field, fieldMessageName, fieldDest, "NULL"); 484 } else if (fieldDest == incidentDest) { 485 printf("// default %s: fieldDest=%d incidentDest=%d\n", fieldName.c_str(), 486 getFieldDest(field), incidentDest); 487 continue; // don't create a new privacy if the value is default. 488 } else { 489 printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); 490 } 491 skip[i] = false; 492 } 493 494 // generate final PRIVACY_POLICY_LIST 495 emptyline(); 496 int policyCount = 0; 497 if (gSelfRecursionAssignments.empty()) { 498 printf("Privacy* privacyArray[] = {\n"); 499 for (size_t i=0; i<fieldsInOrder.size(); i++) { 500 if (skip[i]) continue; 501 printf(" &%s,\n", getFieldName(fieldsInOrder[i]).c_str()); 502 policyCount++; 503 } 504 printf("};\n\n"); 505 printf("const Privacy** PRIVACY_POLICY_LIST = const_cast<const Privacy**>(privacyArray);\n\n"); 506 printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount); 507 } else { 508 for (size_t i=0; i<fieldsInOrder.size(); i++) { 509 if (!skip[i]) policyCount++; 510 } 511 512 printf("static const Privacy** createList() {\n"); 513 for (size_t i=0; i<gSelfRecursionAssignments.size(); ++i) { 514 printf(" %s;\n", gSelfRecursionAssignments[i].c_str()); 515 } 516 printf(" Privacy** privacyArray = (Privacy**)malloc(%d * sizeof(Privacy**));\n", policyCount); 517 policyCount = 0; // reset 518 for (size_t i=0; i<fieldsInOrder.size(); i++) { 519 if (skip[i]) continue; 520 printf(" privacyArray[%d] = &%s;\n", policyCount++, getFieldName(fieldsInOrder[i]).c_str()); 521 } 522 printf(" return const_cast<const Privacy**>(privacyArray);\n"); 523 printf("}\n\n"); 524 printf("const Privacy** PRIVACY_POLICY_LIST = createList();\n\n"); 525 printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount); 526 } 527 528 printf("} // incidentd\n"); 529 printf("} // os\n"); 530 printf("} // android\n"); 531 return true; 532 } 533 534 // ================================================================================ 535 static string replace_string(const string& str, const char replace, const char with) 536 { 537 string result(str); 538 const int N = result.size(); 539 for (int i=0; i<N; i++) { 540 if (result[i] == replace) { 541 result[i] = with; 542 } 543 } 544 return result; 545 } 546 547 static void generateCsv(Descriptor const* descriptor, const string& indent, set<string>* parents, const Destination containerDest = DEST_UNSET) { 548 DebugStringOptions options; 549 options.include_comments = true; 550 for (int i=0; i<descriptor->field_count(); i++) { 551 const FieldDescriptor* field = descriptor->field(i); 552 const Destination fieldDest = getFieldDest(field); 553 stringstream text; 554 if (field->type() == FieldDescriptor::TYPE_MESSAGE) { 555 text << field->message_type()->name(); 556 } else { 557 text << field->type_name(); 558 } 559 text << " " << field->name(); 560 text << " (PRIVACY="; 561 if (isDefaultField(field, containerDest)) { 562 text << getDestString(containerDest); 563 } else { 564 text << getDestString(fieldDest); 565 } 566 text << ")"; 567 printf("%s%s,\n", indent.c_str(), replace_string(text.str(), '\n', ' ').c_str()); 568 if (field->type() == FieldDescriptor::TYPE_MESSAGE && 569 parents->find(field->message_type()->full_name()) == parents->end()) { 570 parents->insert(field->message_type()->full_name()); 571 generateCsv(field->message_type(), indent + ",", parents, fieldDest); 572 parents->erase(field->message_type()->full_name()); 573 } 574 } 575 } 576 577 // ================================================================================ 578 int main(int argc, char const *argv[]) 579 { 580 if (argc < 2) return 1; 581 const char* module = argv[1]; 582 583 Descriptor const* descriptor = IncidentProto::descriptor(); 584 585 if (strcmp(module, "incident") == 0) { 586 return !generateIncidentSectionsCpp(descriptor); 587 } 588 if (strcmp(module, "incidentd") == 0 ) { 589 return !generateSectionListCpp(descriptor); 590 } 591 // Generates Csv Format of proto definition for each section. 592 if (strcmp(module, "csv") == 0 && argc > 2) { 593 int sectionId = atoi(argv[2]); 594 for (int i=0; i<descriptor->field_count(); i++) { 595 const FieldDescriptor* field = descriptor->field(i); 596 if (strcmp(field->name().c_str(), argv[2]) == 0 597 || field->number() == sectionId) { 598 set<string> parents; 599 printf("%s\n", field->name().c_str()); 600 generateCsv(field->message_type(), "", &parents, getFieldDest(field)); 601 break; 602 } 603 } 604 // Returns failure if csv is enabled to prevent Android building with it. 605 // It doesn't matter if this command runs manually. 606 return 1; 607 } 608 // Returns failure if not called by the whitelisted modules 609 return 1; 610 } 611