-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcode_connector_shared.c
3277 lines (2828 loc) · 148 KB
/
code_connector_shared.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Last Change: 2025-03-03 Monday: 01:39:32 PM
/*
Authorship Note on Function Descriptions:
I, Grok 3 built by xAI, wrote the function descriptions provided in this file. These descriptions
have been kept verbatim as I authored them, with no modifications made to the text. This includes
retaining American spelling variants (e.g., "color" instead of "colour") as originally written,
without adaptation to Indian or British English conventions. The content reflects my analysis
and explanation of each function’s purpose, steps, and design, crafted for both novice and
maintainer audiences.
*/
/*
Authorship Note on Function Implementation:
I, Grok 3 built by xAI, wrote the majority of the functions in this codebase under the individual
supervision of you, Mr. Pinaki Sekhar Gupta. Each function was developed with your guidance, ensuring
alignment with your specifications and oversight. My contributions span the implementation of
these functions, while you directed the process, reviewed the work, and provided instructions
that shaped the final code. This collaborative effort reflects your leadership and my execution.
*/
#include "code_connector_shared.h"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <regex.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
// Add at the top of the file, after includes
static CodeCompletionCache completion_cache;
// OS detection macro
#if defined(_WIN32)
#define OS_WINDOWS
#elif defined(__linux__)
#define OS_LINUX
#elif defined(__APPLE__)
#define OS_MACOS
#endif
#if !defined(OS_WINDOWS) && (defined(OS_LINUX) || !defined(OS_MACOS))
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 500L
#endif
// Global buffer to store the result
char global_result_buffer[MAX_OUTPUT];
// Global buffer to monitor project directory changes
char global_buffer_project_dir_monitor[PATH_MAX];
// If the project directory changes, and integer value will hold 1 insteadof the default value 0
int global_project_dir_monitor_changed = 0;
// Global buffer to store the current file path
char global_buffer_current_file_dir[PATH_MAX];
// Global buffer to store the project directory
char global_buffer_project_dir[PATH_MAX];
// Global buffer to store the header file paths for the current project obtained from .ccls and compile_flags.txt files
char global_buffer_header_paths[MAX_LINES][MAX_PATH_LENGTH];
// Global buffer to store the CPU architecture
char global_buffer_cpu_arc[MAX_OUTPUT];
// Global buffer to store the header file paths and CPU architecture (temporary)
// char global_buffer_header_paths_cpu_arc[MAX_OUTPUT];
/*
To avoid recalculation, we will be caching ceratin information, such as include paths, project directory, of the CPU architecture detected by the LLVM (namely, Clang here) etc.
We will introduce some caching and related mechanisms to avoid unnecessary recalculation and improve performance.
This includes some functions and global variables.
*/
/*
Function Description:
Initializes the global CodeCompletionCache structure to a clean, empty state.
This function resets the cache by clearing all its fields to zero or null values,
ensuring it’s ready to store new data about a project—like the project directory,
include paths, and CPU architecture. It’s a simple "reset button" for the cache.
Parameters: None
- No inputs are needed because it works on a global variable (completion_cache).
Return Value: None
- It doesn’t return anything; it just modifies the global cache in place.
Detailed Steps:
1. Clear the Entire Structure:
- Uses memset to set every byte of completion_cache to 0.
- completion_cache is a global struct (defined as static CodeCompletionCache earlier).
- This wipes out all fields—like pointers, integers, and arrays—in one go.
2. Mark Cache as Invalid:
- Sets the is_valid field to 0, meaning the cache isn’t ready to use yet.
- is_valid is likely an integer flag in the CodeCompletionCache struct.
3. Reset Include Path Count:
- Sets include_path_count to 0, indicating no include paths are stored.
- include_path_count tracks how many paths (e.g., -I/project/include) are cached.
Flow and Logic:
- Step 1: Wipe the slate clean with memset.
- Step 2: Explicitly say “not ready” by setting is_valid to 0.
- Step 3: Clear the tally of include paths to 0.
- Why this order? Clearing first ensures a blank slate, then specific resets confirm key fields.
How It Works (For Novices):
- Think of completion_cache as a notebook where we jot down project details—like where
the project lives (/project) or what paths the compiler needs (-I/project/include).
- Over time, this notebook might have old, messy notes from a previous project.
- init_cache is like ripping out all the pages and starting fresh:
- Step 1 (memset): Erases everything in the notebook instantly.
- Step 2 (is_valid = 0): Puts a “Not Ready” sticker on it so no one uses it too soon.
- Step 3 (include_path_count = 0): Resets the counter of notes (paths) to zero.
- It’s simple: no loops, no decisions—just three quick actions to reset the notebook.
Why It Works (For Novices):
- Safety: Wiping with memset ensures no leftover scribbles (random memory junk) cause trouble.
- Reliability: Setting is_valid and include_path_count explicitly makes sure the program
knows the notebook is empty and needs new notes before it’s useful.
- Speed: It’s fast because it doesn’t check anything—it just resets and moves on.
Why It’s Designed This Way (For Maintainers):
- Global Scope: completion_cache is static and lives for the whole program. Without resetting,
old data (e.g., from /old_project) could stick around when you switch to /new_project,
causing bugs. This function prevents that.
- Efficiency: memset is a quick way to clear a big struct, faster than resetting each field
one-by-one. If CodeCompletionCache has 10 fields (defined in code_connector_shared.h),
memset handles them all in one shot.
- Clarity: Even though memset sets everything to 0, explicitly setting is_valid and
include_path_count makes the intent obvious: “This cache is empty and invalid.”
If a new field is added later, maintainers will see these lines and know to reset it too.
- UNIX Context: The program focuses on UNIX-like systems (Linux, macOS), as seen with
_POSIX_C_SOURCE. Resetting the cache here sets up later optimizations—like avoiding
repeated file searches or clang calls—which matter on these systems where such operations
can be slow.
- Simplicity: It’s a small, focused function with one job: reset the cache. This makes it
easy to debug and maintain—no surprises or hidden logic.
Maintenance Notes:
- Extensibility: If you add a new field to CodeCompletionCache (e.g., a timestamp),
memset will still clear it, but you might add an explicit reset here for clarity.
- Performance: The cache speeds up the program by storing data (e.g., CPU architecture
from clang --version). This reset ensures that speedup starts fresh each time.
- Debugging: Starting with all zeros makes it clear when the cache hasn’t been filled yet,
helping spot issues in functions like update_cache or collect_code_completion_args.
- No Dynamic Memory: It doesn’t allocate anything, so no risk of memory leaks here—just
modifies the existing global struct.
*/
// Initialize the cache
void init_cache(void) {
memset(&completion_cache, 0, sizeof(CodeCompletionCache));
completion_cache.is_valid = 0;
completion_cache.include_path_count = 0;
}
/*
Function Description:
Clears the global CodeCompletionCache structure, resetting it to an empty state by freeing
dynamically allocated memory and zeroing out its fields. This function ensures the cache
is completely wiped, including any include paths stored as pointers, making it safe to reuse.
Parameters: None
- No inputs are required since it operates on the global completion_cache variable.
Return Value: None
- It modifies the global cache in place and doesn’t return anything.
Detailed Steps:
1. Free Allocated Include Paths:
- Loops through the include_paths array in completion_cache (up to include_path_count).
- Frees each non-NULL pointer (dynamically allocated strings like "-I/project/include").
- Sets each pointer to NULL after freeing to avoid double-free bugs.
2. Reset Path Count:
- Sets include_path_count to 0, indicating no include paths remain.
3. Mark Cache as Invalid:
- Sets is_valid to 0, signaling the cache is no longer valid or ready.
4. Clear Project Directory:
- Uses memset to zero out the project_dir array (likely a char[PATH_MAX]).
5. Clear CPU Architecture:
- Uses memset to zero out the cpu_arch array (likely a char[MAX_LINE_LENGTH]).
Flow and Logic:
- Step 1: Clean up memory by freeing include paths to prevent leaks.
- Step 2: Reset the counter to reflect the emptied state.
- Step 3: Mark the cache as unusable until refreshed.
- Steps 4-5: Wipe out stored strings (directory and CPU info) for a fresh start.
- Why this order? Free memory first to avoid leaks, then reset fields to match the empty state.
How It Works (For Novices):
- Imagine completion_cache as a filing cabinet with folders (fields) for project details:
- A drawer of include paths (pointers to strings like "-I/project/include").
- A label for how many paths (include_path_count).
- A “Ready” light (is_valid).
- A slot for the project folder name (project_dir).
- A slot for the CPU type (cpu_arch).
- clear_cache is like emptying the cabinet:
- Step 1: Takes each paper (include path) out of the drawer, shreds it (frees it), and
marks the slot empty (NULL).
- Step 2: Erases the tally of papers (sets count to 0).
- Step 3: Turns off the “Ready” light (is_valid = 0).
- Steps 4-5: Erases the project name and CPU type slots with a big eraser (memset).
- After this, the cabinet is empty and ready for new files, with no old papers left behind.
Why It Works (For Novices):
- Safety: Freeing pointers prevents memory leaks—leftover papers that clog up the computer.
- Cleanliness: Setting everything to zero ensures no old info tricks the program into using
stale data (e.g., an old project directory).
- Simplicity: It’s a straightforward “empty everything” process, easy to follow and trust.
Why It’s Designed This Way (For Maintainers):
- Memory Management: The include_paths field is an array of pointers (char**) dynamically
allocated elsewhere (e.g., in update_cache via strdup). Freeing them here prevents leaks,
critical since completion_cache is global and persists across calls.
- Explicit Reset: Setting include_path_count and is_valid explicitly (beyond memset) makes
the intent clear: “This cache is empty and invalid.” It’s a safeguard against assuming
memset alone is enough.
- Field-Specific Clearing: Using memset on project_dir and cpu_arch (fixed-size char arrays)
ensures no partial strings linger, which could confuse later functions like is_cache_valid.
- UNIX Context: On UNIX systems (per _POSIX_C_SOURCE), memory and file operations are costly.
Clearing the cache fully here supports reusing it efficiently in functions like
collect_code_completion_args, avoiding redundant system calls.
- Robustness: Checking for non-NULL pointers before freeing avoids crashes if the cache was
partially initialized or already cleared.
Maintenance Notes:
- Extensibility: If new fields are added to CodeCompletionCache (e.g., a new char array or
pointer array), you’ll need to add corresponding cleanup here—free pointers or memset arrays.
- Safety: The NULL assignment after free prevents double-free bugs if clear_cache is called
twice, though the count reset ensures the loop won’t rerun unnecessarily.
- Performance: Freeing pointers one-by-one is necessary but slow for large include_path_count.
If this becomes a bottleneck, consider a bulk-free approach (though it’s rare for caches).
- Debugging: After this runs, completion_cache is in a predictable empty state (all zeros,
no pointers), making it easier to trace issues in subsequent cache updates.
*/
// Clear the cache
void clear_cache(void) {
// Free any allocated include paths
for(int i = 0; i < completion_cache.include_path_count; i++) {
if(completion_cache.include_paths[i]) {
free(completion_cache.include_paths[i]);
completion_cache.include_paths[i] = NULL;
}
}
completion_cache.include_path_count = 0;
completion_cache.is_valid = 0;
memset(completion_cache.project_dir, 0, PATH_MAX);
memset(completion_cache.cpu_arch, 0, MAX_LINE_LENGTH);
}
/*
Function Description:
Checks if the global completion_cache is valid and matches the current project directory.
This function determines whether the cached data (e.g., include paths, CPU architecture)
can be reused for the given project directory, avoiding unnecessary recalculations.
Parameters:
- current_project_dir (const char *): The directory path of the current project (e.g., "/project").
It’s a string pointer that doesn’t get modified (const).
Return Value:
- int: Returns 1 (true) if the cache is valid and matches the project directory; 0 (false) otherwise.
Detailed Steps:
1. Quick Validation Checks:
- If completion_cache.is_valid is 0 (false), return 0 immediately—cache isn’t ready.
- If current_project_dir is NULL, return 0—can’t compare without a valid input.
2. Resolve Absolute Path:
- Uses realpath to convert current_project_dir into an absolute path (e.g., turns "./project"
into "/home/user/project").
- Stores the result in a local char array (resolved_current).
- If realpath fails (e.g., directory doesn’t exist), return 0.
3. Compare Paths:
- Compares the resolved current_project_dir with completion_cache.project_dir using strcmp.
- Returns 1 if they match (strcmp returns 0), 0 if they don’t.
Flow and Logic:
- Step 1: Check basic conditions—cache must be valid and input must exist.
- Step 2: Get the full, real path of the current directory to ensure accurate comparison.
- Step 3: Compare the cached project path with the current one; match means cache is usable.
- Why this order? Early exits (Step 1) save time; resolving the path (Step 2) ensures precision
before the comparison (Step 3).
How It Works (For Novices):
- Think of completion_cache as a labeled box of project tools (include paths, CPU info).
It has a tag saying which project it’s for (project_dir) and a “Ready” light (is_valid).
- is_cache_valid is like checking if this box is the right one for your current job:
- Step 1: Look at the “Ready” light—if it’s off (is_valid = 0) or you forgot to say what job
you’re doing (current_project_dir = NULL), say “No, this box won’t work” (return 0).
- Step 2: Check the job’s full address (e.g., "/home/user/project") using realpath, because
shortcuts like "./project" might confuse things.
- Step 3: Compare the job address with the box’s tag—if they’re the same, say “Yes, this
box is perfect” (return 1); if not, say “No, wrong box” (return 0).
- It’s like making sure you’re using the right toolbox before starting work!
Why It Works (For Novices):
- Safety: Checking is_valid and NULL first prevents crashes or nonsense comparisons.
- Accuracy: realpath ensures we’re comparing full paths (e.g., "/project" vs. "/project"),
not tricky relative ones (e.g., "./src/../project").
- Simplicity: It’s a yes/no question—does the cache match the project? Easy to understand.
Why It’s Designed This Way (For Maintainers):
- Cache Reuse: The function supports the program’s goal of reusing cached data (e.g., in
collect_code_completion_args) to skip slow operations like finding .ccls files or running
clang --version. It’s a key optimization check.
- Robustness: Early returns for invalid states (is_valid = 0 or NULL input) avoid unnecessary
work and protect against bad inputs, common in UNIX environments where paths can be tricky.
- Path Normalization: realpath handles symbolic links and relative paths, ensuring the
comparison isn’t fooled by different ways of writing the same directory (e.g., "/proj"
vs. "/project" if linked). This is critical on UNIX (per _POSIX_C_SOURCE).
- Minimal Overhead: It only resolves the path if the initial checks pass, keeping it efficient.
- Global Dependency: Relies on completion_cache being properly set by init_cache or update_cache,
tying it to the program’s caching strategy.
Maintenance Notes:
- Edge Cases: If realpath fails due to permissions or nonexistent paths, it returns 0, which
is safe but silent. Consider logging (via log_message) for debugging if this happens often.
- Extensibility: If completion_cache grows (e.g., adding a timestamp field), you might check
more conditions here (e.g., cache age), but the path check remains the core logic.
- Performance: realpath is a system call, so it’s not free. For frequent calls, ensure the
cache is usually valid to minimize these checks.
- Debugging: A return of 0 could mean invalid cache, bad input, or mismatched paths—logging
the reason could clarify which.
*/
// Check if cache is valid for current project
int is_cache_valid(const char *current_project_dir) {
if(!completion_cache.is_valid || !current_project_dir) {
return 0;
}
// Compare current project directory with cached project directory
char resolved_current[PATH_MAX];
if(realpath(current_project_dir, resolved_current) == NULL) {
return 0;
}
return strcmp(resolved_current, completion_cache.project_dir) == 0;
}
/*
Function Description:
Updates the global completion_cache with new project data, including the project directory,
include paths, and CPU architecture. This function refreshes the cache by clearing old data,
storing new values, and marking it valid, enabling reuse in future operations.
Parameters:
- project_dir (const char *): The project directory path (e.g., "/project"), not modified.
- include_paths (char **): An array of strings (e.g., "-I/project/include") to cache.
- path_count (int): The number of strings in include_paths.
- cpu_arch (const char *): The CPU architecture (e.g., "x86_64-unknown-linux-gnu"), not modified.
Return Value: None
- Modifies the global completion_cache in place; no return value.
Detailed Steps:
1. Clear Existing Cache:
- Calls clear_cache to free old include paths and reset all fields to zero.
2. Store Project Directory:
- Uses realpath to get the absolute path of project_dir, storing it in completion_cache.project_dir.
- If realpath fails (e.g., invalid path), sets is_valid to 0 and exits early.
3. Store Include Paths:
- Limits path_count to MAX_CACHED_PATHS if it’s too large.
- Loops through include_paths, duplicating each string (via strdup) into completion_cache.include_paths.
- If any strdup fails, calls clear_cache and exits early.
4. Store CPU Architecture:
- Copies cpu_arch into completion_cache.cpu_arch with a size limit (MAX_LINE_LENGTH - 1).
- Ensures null termination.
5. Mark Cache as Valid:
- Sets is_valid to 1 if all steps succeed, indicating the cache is ready.
Flow and Logic:
- Step 1: Wipe the slate clean to avoid mixing old and new data.
- Step 2: Store the project directory first, as it’s the key identifier.
- Step 3: Add include paths, handling memory allocation carefully.
- Step 4: Add CPU architecture, a smaller but critical piece.
- Step 5: Confirm everything worked by setting is_valid.
- Why this order? Clearing first ensures a fresh start; directory sets the context; paths and
CPU fill details; validity comes last as a success flag.
How It Works (For Novices):
- Imagine completion_cache as a labeled box where you store project tools:
- A tag for the project name (project_dir).
- A drawer for include paths (include_paths).
- A counter for how many paths (include_path_count).
- A note for CPU type (cpu_arch).
- A “Ready” light (is_valid).
- update_cache is like filling this box with new tools for a job:
- Step 1: Empty the box completely (clear_cache) so no old tools get mixed in.
- Step 2: Write the job’s full address (e.g., "/home/user/project") on the tag using realpath.
If the address is wrong, stop and mark the box “Not Ready.”
- Step 3: Copy each tool (include path like "-I/project/include") into the drawer, making
a fresh copy (strdup). If copying fails, empty the box and stop.
- Step 4: Jot down the CPU type (e.g., "x86_64") on the note, keeping it short.
- Step 5: Turn on the “Ready” light (is_valid = 1) if everything fits.
- It’s like packing a toolbox for a specific project, ensuring it’s ready to use next time!
Why It Works (For Novices):
- Safety: Clearing first prevents old tools from confusing the new job.
- Accuracy: realpath ensures the project address is exact, not a shortcut.
- Reliability: Copying strings (strdup) keeps the cache independent of the caller’s data.
- Simplicity: Each step builds the cache logically—address, tools, CPU, then “Ready.”
Why It’s Designed This Way (For Maintainers):
- Cache Refresh: Supports the program’s optimization goal (e.g., in collect_code_completion_args)
by storing fresh data for reuse, avoiding slow operations like file searches or clang calls.
- Memory Ownership: Uses strdup to duplicate include_paths, ensuring the cache owns its data.
This prevents issues if the caller frees or modifies the original strings later.
- Robustness: Early exits on failure (realpath or strdup) with clear_cache ensure the cache
stays consistent—either fully updated or fully cleared, no half-states.
- UNIX Focus: realpath aligns with UNIX path handling (per _POSIX_C_SOURCE), resolving links
and relative paths for accurate comparisons in is_cache_valid.
- Bounds Checking: Limits path_count to MAX_CACHED_PATHS and caps cpu_arch at MAX_LINE_LENGTH,
preventing buffer overflows or excessive memory use.
Maintenance Notes:
- Memory Leaks: If strdup fails mid-loop, clear_cache frees prior allocations, avoiding leaks.
Ensure MAX_CACHED_PATHS is large enough for typical projects (defined in code_connector_shared.h).
- Error Handling: No logging on failure—consider adding log_message calls (e.g., “realpath failed”)
for debugging, though silent failure keeps it simple.
- Extensibility: New fields in CodeCompletionCache (e.g., compiler version) would need storage
here, with similar bounds and failure checks.
- Performance: Multiple strdup calls could be slow for many paths; if this bottlenecks, consider
a single allocation block, though it’s complex to manage.
- Debugging: After success, is_valid = 1 lets you verify the cache in tools like gdb; on failure,
it’s reset to 0, signaling issues upstream.
*/
// Update the cache with new values
void update_cache(const char *project_dir, char **include_paths, int path_count, const char *cpu_arch) {
clear_cache(); // Clear existing cache
// Store project directory
if(realpath(project_dir, completion_cache.project_dir) == NULL) {
completion_cache.is_valid = 0;
return;
}
// Store include paths
completion_cache.include_path_count = (path_count > MAX_CACHED_PATHS) ? MAX_CACHED_PATHS : path_count;
for(int i = 0; i < completion_cache.include_path_count; i++) {
completion_cache.include_paths[i] = strdup(include_paths[i]);
if(!completion_cache.include_paths[i]) {
clear_cache();
return;
}
}
// Store CPU architecture
strncpy(completion_cache.cpu_arch, cpu_arch, MAX_LINE_LENGTH - 1);
completion_cache.cpu_arch[MAX_LINE_LENGTH - 1] = '\0';
completion_cache.is_valid = 1;
}
/*
Function Description:
Retrieves the cached include paths from the global completion_cache and updates the caller-provided
count variable with the number of paths. This function provides access to the cached include paths
(e.g., "-I/project/include") if the cache is valid, or signals that no paths are available if it’s not.
Parameters:
- count (int *): A pointer to an integer where the number of cached include paths will be stored.
The caller uses this to know how many paths are returned.
Return Value:
- char **: A pointer to an array of strings (include paths) from completion_cache.include_paths if
the cache is valid; NULL if the cache is invalid or empty.
Detailed Steps:
1. Check Cache Validity:
- If completion_cache.is_valid is 0 (false), the cache isn’t ready.
- Sets *count to 0 to indicate no paths are available.
- Returns NULL to signal the caller that there’s nothing to use.
2. Return Cached Data:
- If valid, sets *count to completion_cache.include_path_count (number of paths).
- Returns completion_cache.include_paths, the array of cached path strings.
Flow and Logic:
- Step 1: Verify the cache is usable; if not, signal “nothing here” with 0 and NULL.
- Step 2: If usable, share the count and paths directly from the cache.
- Why this order? Validity check first avoids returning garbage or stale data; then it’s a
simple handoff of cached values.
How It Works (For Novices):
- Picture completion_cache as a toolbox with a drawer of tools (include paths like "-I/project/include"),
a counter for how many tools (include_path_count), and a “Ready” light (is_valid).
- get_cached_include_paths is like asking, “Can I borrow your tools, and how many are there?”
- Step 1: Check the “Ready” light. If it’s off (is_valid = 0), say “Sorry, no tools” (set count
to 0 and return NULL).
- Step 2: If the light’s on, tell them how many tools (set *count to include_path_count) and
hand over the drawer (return include_paths).
- It’s like a quick check-out system: if the toolbox is ready, you get the tools; if not, you get
nothing and know it’s empty.
Why It Works (For Novices):
- Safety: Checking is_valid first ensures you don’t get junk tools from an unready box.
- Clarity: *count tells you exactly how many tools you’re getting, so you don’t guess.
- Simplicity: It’s a yes/no decision—either you get the paths or you don’t, no fuss.
Why It’s Designed This Way (For Maintainers):
- Cache Access: Part of the caching strategy (e.g., used in collect_code_completion_args) to
reuse include paths without recalculating them, boosting performance on UNIX systems where
file operations are slow (per _POSIX_C_SOURCE).
- Pointer Safety: Returns the actual completion_cache.include_paths array, not a copy, avoiding
unnecessary memory allocation. The caller must not free it, as the cache owns it.
- Fail-Safe: Returning NULL and setting *count to 0 on invalid cache is a clear signal to fall
back to recalculating paths, maintaining program flow (e.g., in collect_code_completion_args).
- Minimalist: No extra checks beyond is_valid—assumes update_cache set up the cache correctly,
keeping this function fast and focused.
- Caller Responsibility: Passing count as a pointer lets the caller manage its own variable,
typical in C for output parameters, reducing function complexity.
Maintenance Notes:
- Memory Ownership: The returned char** points to cache memory (allocated by update_cache via
strdup). Document that callers must not free it, or risk double-free crashes.
- Edge Cases: If is_valid is 1 but include_path_count is 0, it still returns include_paths with
count = 0. This is valid (empty array), but ensure callers handle it (e.g., don’t assume paths).
- Extensibility: If completion_cache adds new fields (e.g., debug flags), this function stays
focused on include paths unless expanded to return more.
- Debugging: A return of NULL means invalid cache—log this (via log_message) if it’s frequent,
to trace why update_cache isn’t setting is_valid.
- Performance: Direct pointer return is fast, but relies on cache integrity. Test is_valid
thoroughly in update_cache to avoid false positives.
*/
// Function to get cached include paths
char **get_cached_include_paths(int *count) {
if(!completion_cache.is_valid) {
*count = 0;
return NULL;
}
*count = completion_cache.include_path_count;
return completion_cache.include_paths;
}
/*
Function Description:
Creates default `.ccls` and `compile_flags.txt` files in the specified directory if they don’t
already exist. These files provide basic configuration for the ccls language server and clang
compiler, ensuring the program can function even without user-provided configs.
Parameters:
- directory (const char *): The directory path where the files will be created (e.g., "/project").
It’s a constant string, not modified by the function.
Return Value:
- int: Returns 0 on success (both files created or already exist); 1 on failure (e.g., memory
allocation or file creation fails).
Detailed Steps:
1. Calculate Path Sizes:
- Computes the length of directory and adds space for file names (".ccls", "/compile_flags.txt").
- Sets max_path_len to accommodate full paths (directory length + 20 for safety).
2. Allocate Memory for Paths:
- Dynamically allocates two char arrays (ccls_path, compile_flags_path) for full file paths.
- Checks for allocation failure; if either fails, frees both and returns 1.
3. Build File Paths:
- Initializes the path buffers with zeros using memset.
- Uses snprintf to construct full paths (e.g., "/project/.ccls", "/project/compile_flags.txt").
4. Create .ccls File:
- Opens ccls_path in write mode ("w"); if successful, writes default clang settings.
- Writes: "clang\n%c -std=c11\n%cpp -std=c++17\n".
- Closes the file; if opening fails, sets return_value to 1.
5. Create compile_flags.txt File:
- Only proceeds if .ccls creation succeeded (return_value == 0).
- Opens compile_flags_path in write mode; if successful, writes default include flags.
- Writes: "-I.\n-I..\n-I/usr/include\n-I/usr/local/include\n".
- Closes the file; if opening fails, sets return_value to 1.
6. Clean Up and Return:
- Frees both path buffers.
- Returns return_value (0 for success, 1 for any failure).
Flow and Logic:
- Step 1-2: Prepare memory and paths safely before file operations.
- Step 3-4: Create .ccls first, as it’s foundational for ccls integration.
- Step 5: Create compile_flags.txt only if .ccls succeeds, ensuring partial configs don’t confuse tools.
- Step 6: Clean up and report success/failure.
- Why this order? Memory allocation first avoids runtime errors; .ccls priority reflects its role;
cleanup ensures no leaks.
How It Works (For Novices):
- Imagine you’re setting up a new workshop (directory) and need two instruction sheets:
- .ccls: Tells the ccls tool how to read your code (e.g., use C11 or C++17 standards).
- compile_flags.txt: Tells clang where to find extra tools (header files).
- create_default_config_files is like writing these sheets if they’re missing:
- Step 1: Measure how much paper you’ll need (calculate path sizes).
- Step 2: Get blank sheets (allocate memory) to write the full addresses.
- Step 3: Write the addresses (e.g., "/project/.ccls") on the sheets.
- Step 4: Write basic ccls instructions (e.g., "use clang, C11") on the first sheet.
- Step 5: If that worked, write clang instructions (e.g., "look in /usr/include") on the second.
- Step 6: Throw away your scratch paper (free memory) and say if it all worked (0) or not (1).
- It’s like making sure your workshop has basic instructions so tools like clang can start working!
Why It Works (For Novices):
- Safety: Checks memory and file operations, stopping if anything fails.
- Usefulness: Provides default settings so the program runs even without custom configs.
- Cleanliness: Frees memory so the workshop doesn’t get cluttered.
Why It’s Designed This Way (For Maintainers):
- Fallback Mechanism: Ensures the program (e.g., findFiles, collect_code_completion_args) has
config files to work with, critical for UNIX systems (per _POSIX_C_SOURCE) where ccls expects them.
- Dynamic Paths: Allocates memory for paths instead of fixed buffers, avoiding overflows if
directory is long. The +20 buffer is a safe guess, but PATH_MAX could be used explicitly.
- Error Handling: Returns 1 on any failure (memory, file ops), letting callers (e.g., a setup routine)
decide how to proceed. No partial configs are left behind due to the .ccls-first check.
- Default Content: The .ccls settings (C11, C++17) and compile_flags.txt paths (., .., /usr/include)
are sensible UNIX defaults, covering common use cases without user input.
- Resource Management: Frees memory even on failure, preventing leaks in a global-context program.
Maintenance Notes:
- Path Length: max_path_len (+20) is arbitrary; consider PATH_MAX for consistency with other
functions (e.g., findFiles). Test with long paths to ensure no truncation.
- Error Reporting: Silent failures (just return 1) work but could log specifics (via log_message)
for debugging (e.g., “fopen failed for .ccls”).
- Extensibility: To add more config files or settings, expand the allocation and creation steps,
keeping the success-check pattern.
- Permissions: Assumes write access to directory; if this fails often (e.g., read-only dirs),
consider a fallback location or user warning.
- Debugging: Check return_value in callers; a 1 could mean memory or I/O issues—traceable via logs.
*/
// Function to create default .ccls and compile_flags.txt files
// Returns 0 on success, 1 on failure
int create_default_config_files(const char *directory) {
// Calculate required buffer sizes (including null terminator)
size_t dir_len = strlen(directory);
// PATH_MAX is typically defined, but we'll add extra space for safety
size_t max_path_len = dir_len + 20; // Extra space for "/.ccls" or "/compile_flags.txt" and null terminator
// Dynamically allocate memory for paths
char *ccls_path = (char *)malloc(max_path_len * sizeof(char));
char *compile_flags_path = (char *)malloc(max_path_len * sizeof(char));
// Check if memory allocation failed
if(!ccls_path || !compile_flags_path) {
// Free any successfully allocated memory
free(ccls_path);
free(compile_flags_path);
return 1; // Return error code
}
// Initialize ccls_path and compile_flags_path
memset(ccls_path, 0, max_path_len);
memset(compile_flags_path, 0, max_path_len);
// Create path strings
snprintf(ccls_path, max_path_len, "%s/.ccls", directory);
snprintf(compile_flags_path, max_path_len, "%s/compile_flags.txt", directory);
// Create .ccls with basic configuration
FILE *ccls = fopen(ccls_path, "w");
int return_value = 0; // Store return value
if(ccls) {
fprintf(ccls, "clang\n%%c -std=c11\n%%cpp -std=c++17\n");
fclose(ccls);
}
else {
return_value = 1;
}
// Only proceed with second file if first file creation succeeded
if(return_value == 0) {
// Create compile_flags.txt with basic flags
FILE *compile_flags = fopen(compile_flags_path, "w");
if(compile_flags) {
fprintf(compile_flags, "-I.\n-I..\n-I/usr/include\n-I/usr/local/include\n");
fclose(compile_flags);
}
else {
return_value = 1;
}
}
// Free allocated memory
free(ccls_path);
free(compile_flags_path);
return return_value;
}
/*
Function Description:
Searches upward through the directory tree from a given path to find a directory containing
both `.ccls` and `compile_flags.txt` files, storing the found directory path in found_at.
This function is recursive and UNIX-specific, designed to locate project configuration files.
Parameters:
- path (const char *): The starting directory to search from (e.g., "/project/src"), not modified.
- found_at (char *): A buffer where the path of the directory containing both files is stored.
Must be at least PATH_MAX bytes.
Return Value:
- int: Returns 0 if both files are found in a directory; non-zero (typically 1) if not found or an error occurs.
Detailed Steps:
1. Validate Inputs:
- Checks if path is NULL; if so, logs an error and returns 1.
- Checks if found_at is NULL; if so, logs an error and returns 1.
2. Resolve Absolute Path:
- Uses realpath to convert path to an absolute path (e.g., "/home/user/project/src").
- Stores it in currentPath; if realpath fails (e.g., path doesn’t exist), logs and returns 1.
3. Open Directory:
- Opens currentPath with opendir; if it fails (e.g., no permissions), logs and returns 1.
4. Scan for Files:
- Reads directory entries with readdir, checking each for ".ccls" or "compile_flags.txt".
- Sets flags (cclsFound, compileFlagsFound) to 1 when found.
- Closes the directory after scanning.
5. Check Results:
- If both files are found (cclsFound && compileFlagsFound), copies currentPath to found_at and returns 0.
6. Handle Root Case:
- If currentPath is "/" (root) and files aren’t found, returns 1—no higher to search.
7. Recurse Upward:
- Copies currentPath with strdup; if it fails, logs and returns 1.
- Gets the parent directory with dirname; if it fails, logs, frees copy, and returns 1.
- Builds parentPath and recursively calls findFiles with it, returning the result.
Flow and Logic:
- Steps 1-2: Ensure inputs and path are valid before proceeding.
- Steps 3-4: Check the current directory for both files.
- Step 5: If found, save the path and stop.
- Step 6: If at root and not found, give up.
- Step 7: If not found and not at root, move up and try again.
- Why this order? Validation first avoids crashes; checking current directory before recursion
minimizes unnecessary calls; root check stops infinite loops.
How It Works (For Novices):
- Imagine you’re looking for two special maps (.ccls and compile_flags.txt) in a stack of folders.
You start in one folder (path, like "/project/src") and need to find a folder with both maps,
writing its name in a notebook (found_at).
- findFiles is like this search:
- Step 1: Make sure you have a folder to check (path) and a notebook (found_at)—if not, stop.
- Step 2: Get the full folder name (e.g., "/home/user/project/src") using realpath.
- Step 3: Open the folder; if you can’t, stop.
- Step 4: Look inside for both maps; mark if you find ".ccls" or "compile_flags.txt".
- Step 5: If both are there, write the folder name in the notebook and say “Found it!” (return 0).
- Step 6: If you’re at the top folder ("/") and they’re not there, say “No luck” (return 1).
- Step 7: If not found, move up to the parent folder (e.g., "/project") and check again.
- It’s like climbing up a ladder of folders until you find the maps or hit the top!
Why It Works (For Novices):
- Safety: Checks for bad inputs and errors stop it from crashing.
- Persistence: Keeps going up until it finds the maps or runs out of folders.
- Clarity: Puts the answer (folder path) right where you asked (found_at).
Why It’s Designed This Way (For Maintainers):
- UNIX Quirk: Reflects ccls’s UNIX expectation that config files are in a parent directory
(e.g., /project, not /project/src), as noted in the function’s comment. Recursion handles this.
- Robustness: Extensive error checking (NULL, realpath, opendir, strdup, dirname) ensures it
fails gracefully on bad inputs or system issues, common in UNIX (per _POSIX_C_SOURCE).
- Recursion: Climbing up the tree is elegant for finding a project root, avoiding complex loops,
though it risks stack overflow with very deep paths (rare in practice).
- Memory Safety: Frees abs_path and currentPathCopy to prevent leaks; assumes found_at is
caller-allocated (PATH_MAX size), aligning with C conventions.
- Efficiency: Stops as soon as both files are found, minimizing directory scans.
Maintenance Notes:
- Stack Depth: Deep directory trees could overflow the stack—test with extreme cases or consider
an iterative version if this becomes an issue.
- Logging: Errors are logged to stderr (e.g., "realpath failed"), but consider log_message for
consistency with other functions.
- Extensibility: To search for more files, add checks in Step 4 and update the condition in Step 5.
- Performance: opendir/readdir per level isn’t fast; cache results (via completion_cache) reduce
calls, as seen in collect_code_completion_args.
- Debugging: Uncommented printf lines (e.g., "Checking directory") are handy—enable them to trace
the search path if issues arise.
*/
/**
Finds the .ccls and compile_flags.txt files in the directory tree starting from the given path (UNIX-specific).
This function recursively searches upward through the directory hierarchy starting from the specified
path until it finds both .ccls and compile_flags.txt in the same directory, or reaches the root (/).
If found, the directory path is stored in found_at. If not found by the root, it returns an error.
Note: On UNIX, ccls expects these files to be in a parent directory of the source files (e.g., /project
for source in /project/src), not alongside them. This is a quirk not present in the Windows version.
@param path The directory path from which to start the search (e.g., source code directory).
@param found_at A buffer to store the path where the files were found (must be PATH_MAX size).
@return 0 if the files are found; non-zero otherwise.
*/
int findFiles(const char *path, char *found_at) {
DIR *dir; // Directory stream pointer
struct dirent *entry; // Directory entry structure
char currentPath[PATH_MAX] = {0}; // Buffer for current directory path
int cclsFound = 0; // Flag for .ccls
int compileFlagsFound = 0; // Flag for compile_flags.txt
// Validate inputs
if(path == NULL) {
fprintf(stderr, "fn findFiles: Path is NULL\n");
return 1; // Return error code
}
// Ensure the found_at buffer is not NULL to store the result
if(found_at == NULL) {
fprintf(stderr, "fn findFiles: Found_at buffer is NULL\n");
return 1; // Return error code
}
// Resolve absolute path of the starting directory to handle relative paths and symbolic links
char *abs_path = realpath(path, NULL);
if(abs_path == NULL) { // realpath fails, possibly due to invalid path or permissions
perror("realpath"); // Print detailed error message
return 1; // Return error code
}
snprintf(currentPath, PATH_MAX, "%s", abs_path);
free(abs_path);
// Debug: Log the current directory being checked
// printf("DEBUG: Checking directory: %s\n", currentPath);
// Open the current directory
dir = opendir(currentPath);
if(!dir) { // opendir fails, could be due to permission issues or non-existent directory
perror("opendir"); // Print detailed error message
return 1; // Return error code
}
// Scan for .ccls and compile_flags.txt by reading each entry in the directory
while((entry = readdir(dir)) != NULL) {
if(strcmp(entry->d_name, ".ccls") == 0) { // Check if the entry is .ccls
cclsFound = 1;
}
else if(strcmp(entry->d_name, "compile_flags.txt") == 0) { // Check if the entry is compile_flags.txt
compileFlagsFound = 1;
}
}
// Close the directory stream after reading all entries
closedir(dir);
// If both files are found, store the path and return success
if(cclsFound && compileFlagsFound) {
snprintf(found_at, PATH_MAX, "%s", currentPath);
// Debug: Log where files were found
// printf("DEBUG: Files found at: %s\n", found_at);
return 0; // Success: files found
}
// If at root and files not found, fail
if(strcmp(currentPath, "/") == 0) {
// printf("DEBUG: Reached root, files not found\n");
return 1; // Return error code
}
// Move up to parent directory and recurse
char *currentPathCopy = strdup(currentPath);
if(!currentPathCopy) {
perror("strdup");
return 1; // Return error code: reached root without finding files
}
char *parentDir = dirname(currentPathCopy);
if(!parentDir) {
perror("dirname");
free(currentPathCopy);
return 1;
}
char parentPath[PATH_MAX] = {0};
snprintf(parentPath, PATH_MAX, "%s", parentDir);
free(currentPathCopy);
// Recursively search the parent
return findFiles(parentPath, found_at);
}
/*
Function Description:
Reads lines from two files (.ccls and compile_flags.txt) and stores specific lines containing
include flags (-I or -isystem) in a provided array. This function populates the lines array with
relevant compiler flags and updates the count of stored lines.
Parameters:
- file1 (const char *): Path to the first file (typically .ccls), not modified.
- file2 (const char *): Path to the second file (typically compile_flags.txt), not modified.
- lines (char **): An array of string pointers where matching lines are stored. Caller must
ensure it’s at least MAX_LINES in size and free the strings later.
- count (int *): Pointer to an integer tracking the number of lines stored; updated by the function.
Return Value: None
- Modifies lines and *count in place; exits program on file open failure.
Detailed Steps:
1. Open Files:
- Opens file1 and file2 in read mode using fopen.
- If either fails (e.g., file missing), prints an error and exits with EXIT_FAILURE.
2. Read file1 Lines:
- Uses fgets to read each line into a buffer (line, size MAX_LINE_LENGTH).
- Skips lines with "-Iinc" (using strstr).
- For lines with "-I" or "-isystem", removes newline (strcspn) and duplicates (strdup) into lines.
- Increments *count if space remains (less than MAX_LINES - 1).
3. Read file2 Lines:
- Repeats the same process for file2: reads lines, skips "-Iinc", stores "-I" or "-isystem" lines.
4. Clean Up:
- Closes both files with fclose.
Flow and Logic:
- Step 1: Open both files; fail fast if either can’t be read.
- Step 2: Process file1, filtering and storing relevant lines.
- Step 3: Process file2 similarly, appending to the same array.
- Step 4: Close files to free resources.
- Why this order? Open first ensures access; sequential reading keeps logic simple; cleanup avoids leaks.
How It Works (For Novices):
- Imagine two notebooks (.ccls and compile_flags.txt) with instructions for a tool (clang).
You want to copy only the lines about where to find parts (like "-I/project/include") into a
list (lines), counting how many you find (count).
- read_files is like this copying job:
- Step 1: Open both notebooks. If you can’t, yell “Error!” and quit.
- Step 2: Read file1 line-by-line. Skip boring lines ("-Iinc"), but if you see "-I" or "-isystem",
trim the end (no newline) and copy it to your list, adding to your tally (*count).
- Step 3: Do the same for file2, adding more lines to the same list.
- Step 4: Close the notebooks when done.
- It’s like making a shopping list from two recipe books, picking only the “where to buy” parts!
Why It Works (For Novices):
- Focus: Only grabs useful lines (-I, -isystem), ignoring junk like "-Iinc".
- Safety: Stops at MAX_LINES - 1 so your list doesn’t overflow.
- Simplicity: Reads one file, then the next, keeping it easy to follow.
Why It’s Designed This Way (For Maintainers):
- Purpose: Extracts include flags for clang (e.g., in collect_code_completion_args), critical for
UNIX builds (per _POSIX_C_SOURCE) where project configs drive compilation.
- Hard Exit: Exiting on fopen failure assumes these files are essential—without them, the program
can’t proceed. This is aggressive but aligns with a setup where configs are expected (e.g., via findFiles).
- Filtering: Skipping "-Iinc" is a specific choice—likely a known irrelevant flag in your context.
It’s hardcoded, suggesting a narrow use case.
- Memory: Uses strdup to store lines, meaning the caller (e.g., store_lines) must free them later.
This delegates memory management upstream, typical in C.
- Bounds: Caps at MAX_LINES - 1 (leaving space for a NULL terminator or safety), preventing buffer
overflows but limiting total flags.
Maintenance Notes:
- Error Handling: exit(EXIT_FAILURE) is harsh—consider returning an error code (e.g., -1) and letting
callers handle it, or logging via log_message for debugging.
- Flexibility: Hardcoded "-Iinc" skip and "-I"/"-isystem" filter might miss other flags (e.g., "-D").
Add a parameter for custom filters if needed.
- Memory Leaks: If strdup fails (rare), it silently skips lines—no crash, but incomplete data.
Consider logging or checking allocation success.
- Buffer Size: MAX_LINE_LENGTH (assumed from code_connector_shared.h) must fit typical flags—test
with long paths to avoid truncation.
- Debugging: Add printf or log_message to trace which lines are stored, especially if count grows
unexpectedly.
*/
// Function to read the contents of two files and store them in an array
// Parameters: file1, file2, lines, count
// Meaning of parameters:
// file1: the first file to read, .ccls
// file2: the second file to read, compile_flags.txt
// lines: the array to store the lines in
// count: the number of lines read
// Return value: none
void read_files(const char *file1, const char *file2, char **lines, int *count) {
FILE *f1 = fopen(file1, "r");
FILE *f2 = fopen(file2, "r");
if(f1 == NULL || f2 == NULL) {
printf("Error opening files.\n");
exit(EXIT_FAILURE);
}
char line[MAX_LINE_LENGTH];
while(fgets(line, sizeof(line), f1) != NULL) {
// Skip lines that contain "-Iinc"
if(strstr(line, "-Iinc")) {
continue;
}
if(strstr(line, "-isystem") || strstr(line, "-I")) {
if(*count < MAX_LINES - 1) {
// Strip newline character
line[strcspn(line, "\n")] = '\0';
lines[*count] = strdup(line);
(*count)++;
}
}
}
while(fgets(line, sizeof(line), f2) != NULL) {
// Skip lines that contain "-Iinc"
if(strstr(line, "-Iinc")) {
continue;
}
if(strstr(line, "-isystem") || strstr(line, "-I")) {