-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.c
More file actions
1137 lines (970 loc) · 34.6 KB
/
main.c
File metadata and controls
1137 lines (970 loc) · 34.6 KB
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
#include "os_interface.h"
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// --- STATS FILE ---
#define STATS_FILENAME "cryo.stats.json"
// --- CONFIGURATION DEFAULTS ---
#define MAX_TRACKED_APPS 7
#define CONFIG_FILENAME "cryo.conf"
// --- LOG FILE ---
#define LOG_FILENAME "cryo.log"
// --- PID FILE ---
#define PID_FILENAME "cryo.pid"
// --- STATE FILE (CRASH RECOVERY) ---
#define STATE_FILENAME "cryo.state"
// Whitelist Settings
#define WHITELIST_FILENAME "whitelist.txt"
#define MAX_WHITELIST_ITEMS 20
char user_whitelist[MAX_WHITELIST_ITEMS][MAX_PROC_NAME]; // 2D array of strings
int user_whitelist_count = 0;
// Runtime Flags
bool flag_dry_run = false; // If true, we observe but do not freeze
bool flag_energy_mode = false; // If true, only freeze when on battery
// --- ANSI COLORS ---
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m" // Freezing / Interface
#define COLOR_GREEN "\033[32m" // Thawing
#define COLOR_YELLOW "\033[33m" // Warnings
#define COLOR_CYAN "\033[36m" // Info / Stats
#define COLOR_BOLD "\033[1m" // Headers
// Runtime Configuration
int config_timeout = 10; // seconds
int config_min_memory = 50; // MB
int config_poll_rate_ms = 900; // milliseconds (Default 0.9s)
// Day 5 Features
bool config_auto_energy = true; // Auto-switch based on AC/Battery
// Session Statistics
int stats_frozen_count = 0;
uint64_t stats_ram_saved_mb = 0;
// --- CROSS-PLATFORM SLEEP ---
#ifdef _WIN32
#include <windows.h>
void sleep_ms(int ms) { Sleep(ms); }
#else
#include <unistd.h>
void sleep_ms(int ms) { usleep(ms * 1000); }
#endif
// --- DATA STRUCTURES ---
typedef struct {
int32_t pid;
char name[MAX_PROC_NAME];
time_t last_active_time;
time_t discovery_time; // Day 6: Grace Period
bool is_frozen;
bool valid;
bool is_throttled;
} AppState;
// --- PROTOTYPES ---
void write_log(const char *level, const char *message);
AppState history[MAX_TRACKED_APPS];
// --- LIVE STATS SYSTEM (JSON) ---
void update_stats_file(time_t start_time) {
FILE *f = fopen(STATS_FILENAME, "w");
if (f) {
// Industry Standard JSON Format
// Format: { "uptime": 123, "frozen_count": 5, "ram_saved_mb": 1024,
// "pressure": 45 }
time_t now = time(NULL);
long uptime = (long)difftime(now, start_time);
int pressure = os_get_memory_pressure();
fprintf(f, "{\n");
fprintf(f, " \"uptime\": %ld,\n", uptime);
fprintf(f, " \"frozen_count\": %d,\n", stats_frozen_count);
fprintf(f, " \"ram_saved_mb\": %llu,\n", stats_ram_saved_mb);
fprintf(f, " \"system_pressure\": %d,\n", pressure);
fprintf(f, " \"swap_used_mb\": %llu,\n",
os_get_swap_usage() / (1024 * 1024));
fprintf(f, " \"power_source\": \"%s\"\n",
os_is_on_ac_power() ? "AC" : "Battery");
fprintf(f, "}\n");
fclose(f);
}
}
// --- HELPER: INPUT CLEANING ---
void clear_input_buffer() {
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
}
// --- PID Manager ---
void create_pid_file() {
FILE *f = fopen(PID_FILENAME, "w");
if (f) {
fprintf(f, "%d", getpid());
fclose(f);
}
}
void remove_pid_file() { remove(PID_FILENAME); }
int get_running_pid() {
FILE *f = fopen(PID_FILENAME, "r");
if (!f)
return 0; // File doesn't exist
int pid = 0;
if (fscanf(f, "%d", &pid) != 1)
pid = 0;
fclose(f);
return pid;
}
// Check if a process is actually alive in the OS
bool is_process_running(int pid) {
if (pid <= 0)
return false;
// Signal 0 is a special null signal.
// it doesn't kill the process, but returns 0 if the process exists.
return (kill(pid, 0) == 0);
}
// --- FILE I/O HELPERS ---
void save_config() {
FILE *f = fopen(CONFIG_FILENAME, "w");
if (f == NULL) {
printf(COLOR_YELLOW "[WARN] Could not save configuration file." COLOR_RESET
"\n");
return;
}
fprintf(f, "%d %d %d", config_timeout, config_min_memory,
config_poll_rate_ms);
fclose(f);
printf(COLOR_CYAN "[DATA] Settings saved to '%s'" COLOR_RESET "\n",
CONFIG_FILENAME);
}
bool load_config() {
FILE *f = fopen(CONFIG_FILENAME, "r");
if (f == NULL)
return false;
// Try reading 3 values (new format)
if (fscanf(f, "%d %d %d", &config_timeout, &config_min_memory,
&config_poll_rate_ms) == 3) {
fclose(f);
return true;
}
// Fallback: Try reading 2 values (old format)
rewind(f);
if (fscanf(f, "%d %d", &config_timeout, &config_min_memory) == 2) {
config_poll_rate_ms = 900; // Default
fclose(f);
return true;
}
fclose(f);
fclose(f);
return false;
}
// --- STATE MANAGEMENT (CRASH RECOVERY) ---
void save_state(int pid) {
FILE *f = fopen(STATE_FILENAME, "a");
if (f) {
fprintf(f, "%d\n", pid);
fclose(f);
}
}
void remove_state(int pid) {
// Read all PIDs
FILE *f = fopen(STATE_FILENAME, "r");
if (!f)
return;
int pids[MAX_TRACKED_APPS * 2]; // Safety buffer
int count = 0;
int temp_pid;
while (fscanf(f, "%d", &temp_pid) == 1) {
if (temp_pid != pid && count < (MAX_TRACKED_APPS * 2)) {
pids[count++] = temp_pid;
}
}
fclose(f);
// Rewrite file
f = fopen(STATE_FILENAME, "w");
if (f) {
for (int i = 0; i < count; i++) {
fprintf(f, "%d\n", pids[i]);
}
fclose(f);
}
}
void recover_state() {
FILE *f = fopen(STATE_FILENAME, "r");
if (!f)
return;
printf(COLOR_YELLOW
"[RECOVERY] Checking for orphaned frozen apps..." COLOR_RESET "\n");
int pid;
int recovered_count = 0;
while (fscanf(f, "%d", &pid) == 1) {
if (pid > 0 && is_process_running(pid)) {
printf(COLOR_GREEN " > Thawing orphan PID %d" COLOR_RESET "\n", pid);
os_thaw_process(pid); // Thaw immediately
recovered_count++;
char log_msg[128];
snprintf(log_msg, sizeof(log_msg), "Recovered/Thawed Orphan PID %d", pid);
write_log("RECOVERY", log_msg);
}
}
fclose(f);
// Wipe the state file now that we are clean
remove(STATE_FILENAME);
if (recovered_count > 0) {
printf(COLOR_CYAN
"[RECOVERY] Restored %d apps from previous crash." COLOR_RESET "\n",
recovered_count);
} else {
printf("[RECOVERY] System Clean.\n");
}
}
// --- WHITELIST LOADER ---
void load_whitelist() {
FILE *f = fopen(WHITELIST_FILENAME, "r");
if (f == NULL) {
// Create a deafult file so the user knows about it
f = fopen(WHITELIST_FILENAME, "w");
if (f) {
fprintf(f, "Spotify\nDiscord\nActivity Monitor\n");
fclose(f);
printf(COLOR_CYAN "[DATA] Created deafult '%s'" COLOR_RESET "\n",
WHITELIST_FILENAME);
}
return;
}
user_whitelist_count = 0;
char line[MAX_PROC_NAME];
while (fgets(line, sizeof(line), f)) {
// clean up the line (remove newline and spaces)
line[strcspn(line, "\r\n")] = 0; // Remove newline
// skip empty lines or comments
if (strlen(line) < 2 || line[0] == '#')
continue;
if (user_whitelist_count < MAX_WHITELIST_ITEMS) {
strcpy(user_whitelist[user_whitelist_count], line);
user_whitelist_count++;
}
}
fclose(f);
printf(COLOR_CYAN "[DATA] Loaded %d VIP apps from '%s'" COLOR_RESET "\n",
user_whitelist_count, WHITELIST_FILENAME);
}
// --- CRITICAL SAFETY FILTER ---
// --- DEBUG SAFETY FILTER ---
bool is_critical_process(const char *name) {
// 1. HARDCODED SYSTEM SAFETY LIST
const char *blacklist[] = {
"Finder", "Dock", "Electron", "WindowServer", "loginwindow",
"kernel_task", "Cryo", "Terminal", "iTerm2", "Code",
"clang", "make", NULL};
for (int i = 0; blacklist[i] != NULL; i++) {
if (strstr(name, blacklist[i]) != NULL) {
// DEBUG PRINT: Tell us why it's blocked
// printf(COLOR_YELLOW "[DEBUG] Ignoring '%s' (Matches Blacklist: '%s')\n"
// COLOR_RESET, name, blacklist[i]);
return true;
}
}
// 2. USER WHITELIST (VIPs)
for (int i = 0; i < user_whitelist_count; i++) {
// Paranoid check for empty strings
if (strlen(user_whitelist[i]) < 1)
continue;
if (strstr(name, user_whitelist[i]) != NULL) {
// !!! THIS IS THE IMPORTANT LINE !!!
printf(COLOR_YELLOW
"[DEBUG] Ignoring '%s' (Matches Whitelist: '%s')\n" COLOR_RESET,
name, user_whitelist[i]);
return true;
}
}
return false;
}
// LOGGING SYSTEM
void write_log(const char *level, const char *message) {
FILE *f = fopen(LOG_FILENAME, "a"); // 'a' = append mode
if (f == NULL)
return;
// Current time logging
time_t now = time(NULL);
struct tm *t = localtime(&now);
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", t);
// write format: [TIME] [LEVEL] MESSAGE
fprintf(f, "[%s] [%s] %s\n", time_str, level, message);
fclose(f);
}
// NOTIFICATIONS
void send_notification(const char *title, const char *message) {
char command[512];
// construct AppleScript command to show notification
snprintf(command, sizeof(command),
"osascript -e 'display notification \"%s\" with title \"%s\"'",
message, title);
system(command);
}
// BUG FIXING FUNCTION
void perform_speculative_thaw() {
bool thawed_something = false;
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (history[i].valid && history[i].is_frozen) {
// Unfreeze everything so the user can enter
os_thaw_process(history[i].pid);
remove_state(history[i].pid); // PERSISTENCE
history[i].is_frozen = false;
// Reset timer
history[i].last_active_time = time(NULL);
thawed_something = true;
printf(COLOR_GREEN
"[SENTINEL] UI Struggle Detected! Emergency Thaw: %s" COLOR_RESET
"\n",
history[i].name);
// --- DAY 11: BLACK BOX LOGGING ---
char log_msg[128];
snprintf(log_msg, sizeof(log_msg), "Sentinel Emergency Thaw: %s",
history[i].name);
write_log("SENTINEL", log_msg);
}
}
// If we actually helped the user, tell them via notification
if (thawed_something) {
send_notification("Cryo Sentinel",
"Unlock complete. Apps thawed for access.");
}
}
// --- CORE LOGIC ---
void update_app_activity(int32_t pid) {
char name[MAX_PROC_NAME];
os_get_process_name(pid, name, MAX_PROC_NAME);
// SAFETY CHECK
if (is_critical_process(name)) {
return;
}
// Check existing
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (history[i].valid && history[i].pid == pid) {
history[i].last_active_time = time(NULL);
if (history[i].is_frozen) {
// GREEN for Thawing
printf(COLOR_GREEN
"[ACTION] Welcome back, %s (PID %d). Thawing..." COLOR_RESET
"\n",
history[i].name, pid);
os_thaw_process(pid);
remove_state(pid); // PERSISTENCE
history[i].is_frozen = false;
char log_msg[128];
snprintf(log_msg, sizeof(log_msg), "Thawed %s (User Active)",
history[i].name);
write_log("THAW", log_msg);
}
return;
}
}
// Add new (Smart Eviction)
static int next_slot = 0;
if (history[next_slot].valid && history[next_slot].is_frozen) {
// YELLOW for Warning
printf(COLOR_YELLOW "[WARN] History full! Evicting frozen app %s (PID %d). "
"Thawing first..." COLOR_RESET "\n",
history[next_slot].name, history[next_slot].pid);
os_thaw_process(history[next_slot].pid);
remove_state(history[next_slot].pid); // PERSISTENCE
history[next_slot].is_frozen = false;
}
// CYAN for Info
printf(COLOR_CYAN "[INFO] Tracking new app: %s (PID %d)" COLOR_RESET "\n",
name, pid);
history[next_slot].pid = pid;
strcpy(history[next_slot].name, name);
history[next_slot].last_active_time = time(NULL);
history[next_slot].discovery_time = time(NULL); // Day 6: Mark Birthday
history[next_slot].is_frozen = false;
history[next_slot].valid = true;
next_slot = (next_slot + 1) % MAX_TRACKED_APPS;
}
// --- CPU THROTTLE MANAGER ---
void manage_throttled_apps() {
bool active_throttles = false;
// Check if anyone needs throttling first to avoid useless sleeps
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (history[i].valid && history[i].is_throttled)
active_throttles = true;
}
if (!active_throttles)
return;
// PHASE 1: The Breath (100ms ON)
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (history[i].valid && history[i].is_throttled) {
os_thaw_process(history[i].pid);
}
}
sleep_ms(100);
// PHASE 2: The Hold (We don't sleep here, the main loop handles the rest)
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (history[i].valid && history[i].is_throttled) {
os_freeze_process(history[i].pid);
}
}
}
void check_for_idlers() {
time_t now = time(NULL);
int32_t active_pid = os_get_active_pid();
// --- DAY 5: ENVIRONMENT AWARENESS ---
bool is_ac = os_is_on_ac_power();
uint64_t swap_bytes = os_get_swap_usage();
int pressure = os_get_memory_pressure();
// Power Logic
int current_timeout = config_timeout;
if (config_auto_energy && !is_ac) {
current_timeout = 5; // Aggressive on Battery
flag_energy_mode = true;
} else {
flag_energy_mode = false;
}
// Swap Logic (Emergency Override)
bool swap_emergency = (swap_bytes > 1024 * 1024 * 1024); // > 1GB Swap
if (swap_emergency) {
// Freeze almost everything
pressure = 100; // Fake high pressure
}
// ------------------------------------
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (!history[i].valid)
continue;
if (history[i].pid == active_pid)
continue;
// Skip completely frozen apps unless they are throttled (we still manage
// throttled ones)
if (history[i].is_frozen && !history[i].is_throttled)
continue;
// 1. Check Memory Usage & Elastic Thresholds
uint64_t mem_bytes = os_get_memory_usage(history[i].pid);
double mem_mb = (double)mem_bytes / (1024 * 1024);
// Elastic threshold logic (Day 18)
int pressure = os_get_memory_pressure();
int dynamic_threshold = config_min_memory; // Default (e.g. 50MB)
if (pressure < 50) {
dynamic_threshold = 500; // Relaxed: Only freeze huge apps
} else if (pressure < 80) {
dynamic_threshold = config_min_memory * 2; // Moderate
} else {
dynamic_threshold = config_min_memory; // Strict (User setting)
}
// 2. The Gatekeeper
if (mem_mb < dynamic_threshold) {
// Uncomment below if you want to see debug logs for small apps
// printf("[IGNORE] %s is too small (%.1f MB)\n", history[i].name,
// mem_mb);
continue;
}
// --- THROTTLE DECISION ---
bool has_assertion = os_has_power_assertion(history[i].pid);
if (has_assertion) {
// If it has an assertion (Download/Audio), we don't freeze fully.
// Instead, we put it in THROTTLE mode.
if (!history[i].is_throttled) {
printf(COLOR_YELLOW "[THROTTLE] %s is busy (Assertion Detected). "
"Limiting CPU to 10%%." COLOR_RESET "\n",
history[i].name);
history[i].is_throttled = true;
// If it was already fully frozen, thaw it first so it can enter the
// cycle
if (history[i].is_frozen) {
os_thaw_process(history[i].pid);
history[i].is_frozen = false;
if (stats_frozen_count > 0)
stats_frozen_count--; // Adjust stats
}
}
// Skip the standard freeze logic
continue;
} else {
// Assertion is gone (Download finished)
if (history[i].is_throttled) {
printf(COLOR_GREEN
"[RELEASE] %s finished task. Resuming normal watch." COLOR_RESET
"\n",
history[i].name);
history[i].is_throttled = false;
os_thaw_process(history[i].pid); // Ensure it's fully awake
remove_state(history[i].pid); // PERSISTENCE (Just in case)
history[i].is_frozen = false;
}
}
// ---------------------------------------
// Day 6: CPU Safety Check
// If app is using > 10% CPU, it is working. Do not freeze.
// Note: Since our Mac impl returns 0.0 for now, this logic is prepared but
// inactive.
if (os_get_cpu_usage(history[i].pid) > 10.0) {
// printf("[BUSY] %s is using CPU. Skipping.\n", history[i].name);
continue;
}
// Day 6: Grace Period (30s)
// Give new apps 30 seconds to settle before we judge them.
if (difftime(now, history[i].discovery_time) < 30) {
continue;
}
// If currently throttled, skip standard freezing checks
if (history[i].is_throttled)
continue;
double seconds_inactive = difftime(now, history[i].last_active_time);
// 3. The Timeout
if (seconds_inactive > current_timeout) {
if (flag_dry_run) {
printf(COLOR_YELLOW "[DRY-RUN] Would have frozen %s (PID %d). Saving "
"%.0f MB." COLOR_RESET "\n",
history[i].name, history[i].pid, mem_mb);
// Reset timer so we don't spam the log every second
history[i].last_active_time = time(NULL);
continue; // Skip the actual freezing!
}
// RED for Freezing
printf(COLOR_RED
"[Interface] %s (PID %d) inactive for %.0fs. Freezing!" COLOR_RESET
"\n",
history[i].name, history[i].pid, seconds_inactive);
if (os_freeze_process(history[i].pid) == 0) {
history[i].is_frozen = true;
save_state(history[i].pid); // PERSISTENCE
// Update Statistics
stats_frozen_count++;
stats_ram_saved_mb += (uint64_t)mem_mb;
// CYAN for Score
printf(COLOR_CYAN
" (Score: %d freezes | +%.0f MB saved)" COLOR_RESET "\n",
stats_frozen_count, mem_mb);
// Send Notification
char msg[128];
snprintf(msg, sizeof(msg), "Froze %s (+%.0f MB RAM)", history[i].name,
mem_mb);
send_notification("Cryo Interface", msg);
// --- DAY 11: BLACK BOX LOGGING ---
write_log("FREEZE", msg);
}
}
}
}
// --- SIGNAL HANDLER ---
void handle_exit(int sig) {
printf("\n\n");
printf(COLOR_BOLD "========================================\n");
printf(" SESSION REPORT 📊\n");
printf("========================================\n" COLOR_RESET);
printf(" Apps Frozen: %d\n", stats_frozen_count);
printf(" RAM Reclaimed: %llu MB\n", stats_ram_saved_mb);
printf(COLOR_BOLD "========================================\n" COLOR_RESET);
printf(" Cleaning up...\n\n");
// Thaw every process we are tracking
for (int i = 0; i < MAX_TRACKED_APPS; i++) {
if (history[i].valid && history[i].is_frozen) {
printf(COLOR_GREEN "[RESTORE] Emergency Thaw: %s (PID %d)" COLOR_RESET
"\n",
history[i].name, history[i].pid);
os_thaw_process(history[i].pid);
history[i].is_frozen = false;
}
}
printf("[DONE] All Processes Restored. Exiting safely. Bye!\n\n");
write_log("SYSTEM", "ENGINE STOPPED.");
remove_pid_file();
remove(STATE_FILENAME); // Clean exit = clear state
exit(0);
}
// HOT RELOAD
void handle_reload(int sig) {
// Reload whitelist
load_whitelist();
printf(COLOR_CYAN
"\n[SIGNAL] Received SIGHUP. Reloading configuration..." COLOR_RESET
"\n");
write_log("SYSTEM", "Reloading Configuration (Hot Swap)");
}
// we cant easily change the timeout logic mid-loop,
// but the variables config_timeout and config_min_memory
// update instantly for the NEXT check.
// --- Daemonizer ---
void daemonize() {
#ifdef _WIN32
printf("Daemon mode not yet supported on Windows.\n");
exit(1);
#else
// 1. Fork off the parent process
pid_t pid = fork();
// An error occurred
if (pid < 0)
exit(EXIT_FAILURE);
// Success: Let the parent terminate
if (pid > 0)
exit(EXIT_SUCCESS);
// 2. On success: The child process becomes the session leader
if (setsid() < 0)
exit(EXIT_FAILURE);
// 3. Catch, Ignore and Handle Signals
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
// 4. Fork off for the second time (Safety best practice)
pid = fork();
if (pid < 0)
exit(EXIT_FAILURE);
if (pid > 0)
exit(EXIT_SUCCESS);
// 5. Write the NEW child PID to the file so we can stop it later
create_pid_file();
// 6. Close all standard file descriptors
// We cannot print to the terminal anymore!
freopen("/dev/null", "r", stdin);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
// From now on, only write_log() and send_notification() will work.
#endif
}
void print_status_report() {
int pid = get_running_pid();
bool running = (pid > 0 && is_process_running(pid));
printf("\n" COLOR_BOLD "========================================\n");
printf(" Cryo STATUS REPORT 📊\n");
printf("========================================\n" COLOR_RESET);
if (running) {
printf(" State: " COLOR_GREEN
"● RUNNING (Daemon Active)" COLOR_RESET "\n");
printf(" PID: %d\n", pid);
// Read the live stats (JSON Parsing Helper)
FILE *f = fopen(STATS_FILENAME, "r");
if (f) {
char buffer[1024];
int count = 0;
long uptime = 0;
uint64_t ram = 0;
int pressure = 0;
// Very simple JSON parser for our specific format
while (fgets(buffer, sizeof(buffer), f)) {
if (strstr(buffer, "\"uptime\":"))
sscanf(buffer, " \"uptime\": %ld,", &uptime);
if (strstr(buffer, "\"frozen_count\":"))
sscanf(buffer, " \"frozen_count\": %d,", &count);
if (strstr(buffer, "\"ram_saved_mb\":"))
sscanf(buffer, " \"ram_saved_mb\": %llu,", &ram);
if (strstr(buffer, "\"system_pressure\":"))
sscanf(buffer, " \"system_pressure\": %d", &pressure);
}
int hours = (int)uptime / 3600;
int minutes = ((int)uptime % 3600) / 60;
printf(" Uptime: %dh %dm\n", hours, minutes);
printf(" Apps Frozen: %d\n", count);
printf(" RAM Saved: " COLOR_CYAN "%llu MB" COLOR_RESET "\n", ram);
printf(" Sys Pressure:" COLOR_YELLOW " %d%%" COLOR_RESET "\n",
pressure);
fclose(f);
} else {
printf(" Stats: " COLOR_YELLOW "Waiting for update..." COLOR_RESET
"\n");
}
// Show Current Config
printf("----------------------------------------\n");
printf(" Timeout: %ds\n", config_timeout);
printf(" RAM Limit: %d MB\n", config_min_memory);
} else {
printf(" State: " COLOR_RED "● STOPPED" COLOR_RESET "\n");
printf(" To start: ./Cryo --daemon\n");
}
}
void get_plist_path(char *buffer, size_t size) {
const char *home = getenv("HOME");
snprintf(buffer, size, "%s/Library/LaunchAgents/com.cryo.agent.plist", home);
}
void install_startup() {
char exe_path[PATH_MAX];
char plist_path[PATH_MAX];
// 1. Get where Cryo is currently sitting
if (getcwd(exe_path, sizeof(exe_path)) == NULL)
return;
// we need the full path to the binary
// NOTE: Assumes you are running this from the folder containing the
// executable
strcat(exe_path, "/Cryo");
get_plist_path(plist_path, sizeof(plist_path));
printf(" > Target: %s\n", plist_path);
printf(" > Binary: %s\n", exe_path);
// 2. write the LaunchAgent XML file
FILE *f = fopen(plist_path, "w");
if (!f) {
printf(COLOR_RED "[ERROR] Could not write plist file!" COLOR_RESET "\n");
return;
}
fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
fprintf(f, "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n");
fprintf(f, "<plist version=\"1.0\">\n");
fprintf(f, "<dict>\n");
fprintf(f, " <key>Label</key>\n");
fprintf(f, " <string>com.cryo.agent</string>\n");
fprintf(f, " <key>ProgramArguments</key>\n");
fprintf(f, " <array>\n");
fprintf(f, " <string>%s</string>\n", exe_path);
fprintf(f, " <string>--daemon</string>\n"); // Always run as ghost
fprintf(f, " </array>\n");
fprintf(f, " <key>RunAtLoad</key>\n");
fprintf(f, " <true/>\n");
fprintf(f, " <key>StandardOutPath</key>\n");
fprintf(f, " <string>/tmp/cryo.out.log</string>\n");
fprintf(f, " <key>StandardErrorPath</key>\n");
fprintf(f, " <string>/tmp/cryo.err.log</string>\n");
fprintf(f, "</dict>\n");
fprintf(f, "</plist>\n");
fclose(f);
// 3. tell macOS to load it immediately
char command[512];
snprintf(command, sizeof(command), "launchctl load %s", plist_path);
system(command);
printf(COLOR_GREEN
"[SUCCESS] Cryo will now start automatically at login!" COLOR_RESET
"\n");
printf(COLOR_YELLOW
"(Do not move the Cryo file, or startup will fail!)" COLOR_RESET "\n");
}
void remove_startup() {
char plist_path[PATH_MAX];
get_plist_path(plist_path, sizeof(plist_path));
// 1. unload from macOS
char command[512];
snprintf(command, sizeof(command), "launchctl unload %s", plist_path);
system(command);
// 2. delete the file
remove(plist_path);
printf(COLOR_CYAN "[INFO] Startup removed. Cryo is manual only." COLOR_RESET
"\n");
}
// --- MAIN LOOP ---
int main(int argc, char *argv[]) {
time_t session_start_time = time(NULL);
// 1. PARSE ARGUMENTS
bool force_setup = false;
bool run_as_daemon = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
printf("\nCryo Usage:\n");
printf(" ./Cryo Run normally\n");
printf(" ./Cryo --help Show this message\n");
printf(" ./Cryo --setup Force configuration menu\n");
printf(" ./Cryo --dry-run Safe mode (No freezing)\n");
printf(" ./Cryo --daemon Run in background (no terminal output)\n");
printf(" ./Cryo --reload Reload configuration and whitelist\n");
printf(" ./Cryo --status Show current status report\n");
printf(" ./Cryo --install-startup Setup Cryo to start at login\n");
printf(" ./Cryo --remove-startup Remove Cryo from login items\n");
printf(" ./Cryo --energy-mode Enable Energy Mode (Freeze only on "
"Battery)\n");
printf(" ./Cryo --stop Kill the background daemon.\n\n");
return 0;
} else if (strcmp(argv[i], "--allow") == 0 && i + 1 < argc) {
// Day 6: CLI Whitelist Add
FILE *f = fopen(WHITELIST_FILENAME, "a");
if (f) {
fprintf(f, "\n%s\n", argv[i + 1]);
fclose(f);
printf(COLOR_GREEN "[SUCCESS] Added '%s' to whitelist." COLOR_RESET
"\n",
argv[i + 1]);
} else {
printf(COLOR_RED "[ERROR] Could not write to whitelist." COLOR_RESET
"\n");
}
return 0;
} else if (strcmp(argv[i], "--remove") == 0 && i + 1 < argc) {
// Day 6: CLI Whitelist Remove (Simple Re-write)
// Note: For a robust impl, we'd read all lines, filter, and write back.
// For now, we will just tell the user to edit the file manually as
// removal is complex in C.
printf("To remove '%s', please edit 'whitelist.txt' manually.\n",
argv[i + 1]);
return 0;
} else if (strcmp(argv[i], "--install-startup") == 0) {
install_startup();
return 0;
} else if (strcmp(argv[i], "--remove-startup") == 0) {
remove_startup();
return 0;
} else if (strcmp(argv[i], "--status") == 0) {
print_status_report();
return 0;
} else if (strcmp(argv[i], "--energy-mode") == 0) {
flag_energy_mode = true;
printf(
COLOR_GREEN
"[FLAG] Energy Mode: ENABLED (Freezing only on Battery)" COLOR_RESET
"\n");
} else if (strcmp(argv[i], "--reload") == 0) {
int pid = get_running_pid();
if (pid > 0 && is_process_running(pid)) {
printf("Reloading Cryo Daemon (PID %d)...\n", pid);
kill(pid, SIGHUP);
} else {
printf("Cryo is not running.\n");
}
return 0;
} else if (strcmp(argv[i], "--stop") == 0) {
int pid = get_running_pid();
if (pid > 0 && is_process_running(pid)) {
printf("Stopping Cryo Daemon (PID %d)...\n", pid);
kill(pid, SIGINT);
// wait a tiny bit for cleanup
sleep_ms(500);
} else {
printf("Cryo is not running (or PID file missing.)\n");
}
return 0;
} else if (strcmp(argv[i], "--daemon") == 0) {
run_as_daemon = true;
} else if (strcmp(argv[i], "--setup") == 0)
force_setup = true;
else if (strcmp(argv[i], "--dry-run") == 0) {
flag_dry_run = true;
printf(COLOR_YELLOW "[FLAG] Dry Run Mode: ENABLED" COLOR_RESET "\n");
}
}
// before starting, check if already running
recover_state(); // CRASH RECOVERY CHECK
int existing_pid = get_running_pid();
if (existing_pid > 0 && is_process_running(existing_pid)) {
printf(COLOR_RED "[ERROR] Cryo is already running (PID %d)!" COLOR_RESET
"\n",
existing_pid);
printf("Use './Cryo --stop' first.\n");
return 1;
}
// If we are not a daemon, write the PID now.
// If we are a daemon, it will be written after daemonizing.
if (!run_as_daemon) {
create_pid_file();
}
printf("\n" COLOR_BOLD "========================================\n");
printf(" Cryo - AUTO CONFIGURATION\n");
printf("========================================\n" COLOR_RESET);
// 2. CONFIGURATION
if (!force_setup && load_config()) {
printf(" > Mode: AUTOMATIC (Loaded from 'cryo.conf')\n");
} else {
if (force_setup)
printf(" > Mode: FORCED SETUP\n");
else
printf(" > Mode: FIRST RUN SETUP\n");
printf("----------------------------------------\n");
int input_val;
printf("[1] Enter Freeze Timeout (Seconds) [Default: 10]: ");
if (scanf("%d", &input_val) == 1 && input_val > 0)
config_timeout = input_val;