├── assets ├── uneeded │ ├── log.png │ ├── logo.png │ └── ascii_art.sh └── ccc.conf ├── src ├── ccc.h ├── mem.h ├── cleaner.h ├── config.h ├── ini.h ├── cleaner.c ├── error.h ├── log.c ├── log.h ├── ini.c ├── mem.c ├── config.c └── ccc.c ├── .gitignore ├── LICENSE ├── README.md └── Makefile /assets/uneeded/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssleert/ccc/HEAD/assets/uneeded/log.png -------------------------------------------------------------------------------- /assets/uneeded/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssleert/ccc/HEAD/assets/uneeded/logo.png -------------------------------------------------------------------------------- /src/ccc.h: -------------------------------------------------------------------------------- 1 | #ifndef __CCC_H__ 2 | #define __CCC_H__ 3 | 4 | #include 5 | 6 | #define BUFSIZE 4096 7 | 8 | #define DropCachesFile "/proc/sys/vm/drop_caches" 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /assets/ccc.conf: -------------------------------------------------------------------------------- 1 | [Files] 2 | Lock = /tmp/ccc.lock 3 | 4 | [Log] 5 | Silent = false 6 | 7 | [Options] 8 | Sync = true 9 | 10 | [Levels] # percents 11 | First = 15 12 | Second = 10 13 | Third = 5 14 | 15 | [Timeouts] # seconds 16 | Check = 10 17 | 18 | [Error] 19 | MaxAmount = 10 20 | -------------------------------------------------------------------------------- /src/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef __RAM_H__ 2 | #define __RAM_H__ 3 | 4 | #include 5 | 6 | #include "error.h" 7 | 8 | #define MemInfoFile "/proc/meminfo" 9 | 10 | typedef struct { 11 | size_t Val; 12 | Error Err; 13 | } MemSizeError; 14 | 15 | MemSizeError MemGetTotal(void); 16 | MemSizeError MemGetFree(void); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/cleaner.h: -------------------------------------------------------------------------------- 1 | #ifndef __CLEANER_H__ 2 | #define __CLEANER_H__ 3 | 4 | #include "error.h" 5 | 6 | typedef enum { 7 | CLEANER_LEVEL_Low = 1, 8 | CLEANER_LEVEL_Middle, 9 | CLEANER_LEVEL_High, 10 | } CLEANER_LEVEL; 11 | #define CLEANER_LEVEL_LEN 3 12 | 13 | Error CleanerDropCaches(const char FileName[], CLEANER_LEVEL level); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | #include 5 | #include 6 | 7 | #include "ccc.h" 8 | #include "error.h" 9 | #include "log.h" 10 | 11 | #define ConfigFileDef "/usr/local/etc/ccc.conf" 12 | 13 | extern char ConfigLockFile[BUFSIZE]; 14 | extern bool ConfigLogSilent; 15 | extern bool ConfigOptionsSync; 16 | extern int32_t ConfigLevelsFirst; 17 | extern int32_t ConfigLevelsSecond; 18 | extern int32_t ConfigLevelsThird; 19 | extern size_t ConfigTimeoutsCheck; 20 | extern int32_t ConfigErrorMaxAmount; 21 | 22 | Error ConfigLoad(const char ConfigFile[]); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # result binary and objects 55 | ccc 56 | obj/ 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Simon Ryabinkov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ini.h: -------------------------------------------------------------------------------- 1 | // Super simple stack-only ini parser 2 | // for embedded systems in C 3 | // 2023 Simon Ryabinkov 4 | 5 | #ifndef __INI_H__ 6 | #define __INI_H__ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define __INI_H_BUFSIZE 4096 14 | 15 | // func that was called on all values in .ini file 16 | typedef int32_t(IniCallBack)(const char Section[], const char Key[], 17 | const char Val[]); 18 | 19 | typedef enum { 20 | INI_LEXEME_Section = 1, 21 | INI_LEXEME_Key, 22 | INI_LEXEME_Value, 23 | INI_LEXEME_Comment 24 | } INI_LEXEME; 25 | #define INI_LEXEME_LEN 4 26 | 27 | static inline bool IniCheckValue(const char Section[], const char Key[], 28 | const char NeededSection[], 29 | const char NeededKey[]) { 30 | return (strcmp(Section, NeededSection) == 0 && strcmp(Key, NeededKey) == 0); 31 | } 32 | 33 | // Stream - opened file stream 34 | // Func - return non-zero to stop, error is passed back through function 35 | // return - 0 on success 36 | int32_t IniLoad(FILE *Stream, IniCallBack *Func); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/cleaner.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "cleaner.h" 8 | 9 | static inline char GetCharForLevel(CLEANER_LEVEL Level) { 10 | assert(Level >= 1 && Level <= CLEANER_LEVEL_LEN); 11 | 12 | return Level + '0'; 13 | } 14 | 15 | Error CleanerDropCaches(const char FileName[], CLEANER_LEVEL Level) { 16 | assert(FileName != NULL); 17 | assert(Level >= 1 && Level <= CLEANER_LEVEL_LEN); 18 | 19 | Error Err = ErrorNo(); 20 | int32_t DropFile = 0; 21 | { 22 | errno = 0; 23 | DropFile = open(FileName, O_WRONLY); 24 | if (DropFile == -1) { 25 | Err = ErrorNew("Cant open %s file for writing: %s", FileName, 26 | strerror(errno)); 27 | goto Cleanup; 28 | } 29 | 30 | char Line[] = {GetCharForLevel(Level), '\n'}; 31 | ssize_t Written = write(DropFile, Line, sizeof(Line)); 32 | if (Written == -1) { 33 | Err = ErrorNew("Error on writting to %s file. Check for file access", 34 | FileName); 35 | goto Cleanup; 36 | } 37 | } 38 | Cleanup: 39 | if (DropFile != 0) { 40 | close(DropFile); 41 | } 42 | return Err; 43 | } 44 | -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | #ifndef _ERROR_H_ 2 | #define _ERROR_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // i really tooooo lazy to google murmurhash 11 | static inline size_t djb2Hash(const char Str[]) { 12 | size_t hash = 5381; 13 | const char *ch = Str; 14 | 15 | for (; *ch != '\0'; ++ch) { 16 | hash = ((hash << 5) + hash) + *ch; 17 | } 18 | return hash; 19 | } 20 | 21 | #define __ERROR_H_BUFSIZE 4096 22 | 23 | typedef struct { 24 | char What[__ERROR_H_BUFSIZE]; 25 | size_t Hash; 26 | } Error; 27 | 28 | static inline Error ErrorNo(void) { return (Error){.What = "", .Hash = 0}; } 29 | 30 | static inline Error ErrorNew(const char What[], ...) { 31 | assert(What != NULL); 32 | assert(strnlen(What, __ERROR_H_BUFSIZE) < __ERROR_H_BUFSIZE); 33 | 34 | va_list Args; 35 | va_start(Args, What); 36 | 37 | Error Err; 38 | 39 | vsnprintf(Err.What, __ERROR_H_BUFSIZE - 1, What, Args); 40 | Err.What[__ERROR_H_BUFSIZE - 1] = '\0'; 41 | 42 | Err.Hash = djb2Hash(What); 43 | 44 | va_end(Args); 45 | return Err; 46 | } 47 | 48 | static inline bool ErrorIs(Error *Err) { 49 | assert(Err != NULL); 50 | return !(Err->Hash == 0); 51 | } 52 | 53 | static inline bool ErrorEquals(Error *ErrFirst, Error *ErrSecond) { 54 | assert(ErrFirst != NULL); 55 | assert(ErrSecond != NULL); 56 | 57 | return ErrFirst->Hash == ErrSecond->Hash; 58 | } 59 | 60 | static inline char *ErrorWhat(Error *Err) { 61 | assert(Err != NULL); 62 | return Err->What; 63 | } 64 | 65 | #endif /* _ERROR_H_ */ 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | ### **`Linux` cache dropper in `C` without any alloc🎈** 7 | 8 |
9 |
10 | 11 | 12 | 13 | # Description 📖 14 | A few days ago I noticed that while **playing** a game my `OS` is **caching** all the `RAM` and then frantically **trying** to cram a **new game level** into the *few hundred free migabytes*, at *this point* the **game** is a bit **slow** and it's a bit annoying. So I *wrote* a **small** but very **dumb** `solution` that **just works**. 15 | 16 | In **fact**, I'm really *surprised* at *how much* such a **dumb** decision could **fix** the **situation**. I don't **fully** *understand* why this **happens**, so if you **do**, *please open* an ``issue`` and **help** me out. 17 | 18 | At this point `ссс` is in a very early stage of development so feel free to open an **issue** with your problems and suggestions. 19 | 20 | > **Note** that this is only **compatible** with the `linux` kernel at this time. 21 | 22 |
23 | 24 | # Installation ☁️ 25 | From source 26 | ``` 27 | git clone https://github.com/ssleert/ccc.git 28 | cd ./ccc 29 | sudo make install 30 | ``` 31 | 32 | # Configuration ⚙️ 33 | ```fish 34 | /usr/local/etc/ccc.conf 35 | ``` 36 | 37 |
38 | config file example 39 | 40 | ```ini 41 | [Files] 42 | Lock = /tmp/ccc.lock 43 | 44 | [Log] 45 | Silent = false 46 | 47 | [Options] 48 | Sync = true 49 | 50 | [Levels] # percents 51 | First = 15 52 | Second = 10 53 | Third = 5 54 | 55 | [Timeouts] # seconds 56 | Check = 10 57 | 58 | [Error] 59 | MaxAmount = 10 60 | 61 | ``` 62 | 63 |
64 | 65 | # Contribute 66 | Before contributing, please run 67 | ```fish 68 | make format 69 | ``` 70 | 71 |
72 |
73 | 74 | ### made with 🫀 by `sfome` 75 | 76 |
77 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEBUG ?= 0 2 | STATIC ?= 0 3 | UNAME := $(uname) 4 | 5 | 6 | CC ?= gcc 7 | CFLAGS ?= -std=gnu17 -Wall -Wextra -Wpedantic \ 8 | -Wformat=2 -Wno-unused-parameter -Wshadow \ 9 | -Wwrite-strings -Wstrict-prototypes -Wold-style-definition \ 10 | -Wredundant-decls -Wnested-externs -Wmissing-include-dirs \ 11 | -Wno-format-nonliteral # -Wno-incompatible-pointer-types-discards-qualifiers 12 | ifeq ($(CC),gcc) 13 | CFLAGS += -Wjump-misses-init -Wlogical-op 14 | endif 15 | 16 | CFLAGS_RELEASE = $(CFLAGS) -O3 -DNDEBUG -DNTRACE 17 | ifeq ($(CC),gcc) 18 | CFLAGS_RELEASE += -flto 19 | endif 20 | ifeq ($(STATIC),1) 21 | CFLAGS_RELEASE += -static 22 | endif 23 | 24 | CFLAGS_DEBUG = $(CFLAGS) -O0 -g3 -fstack-protector -ftrapv -fwrapv 25 | CFLAGS_DEBUG += -fsanitize=address,undefined 26 | 27 | PREFIX ?= /usr/local 28 | BINDIR ?= $(PREFIX)/bin 29 | CONFDIR ?= $(PREFIX)/etc 30 | ASSETSPATH ?= ./assets/ 31 | INSTALL ?= install -s 32 | 33 | SRCDIR ?= src 34 | OBJDIR ?= obj 35 | 36 | PROG = ccc 37 | CONFIG = $(PROG).conf 38 | 39 | CFILES != ls $(SRCDIR)/*.c 40 | COBJS = ${CFILES:.c=.o} 41 | COBJS := $(subst $(SRCDIR), $(OBJDIR), $(COBJS)) 42 | 43 | ifeq ($(DEBUG),1) 44 | _CFLAGS := $(CFLAGS_DEBUG) 45 | else 46 | _CFLAGS := $(CFLAGS_RELEASE) 47 | endif 48 | 49 | all: prepare $(PROG) 50 | prepare: $(OBJDIR) 51 | 52 | $(PROG): $(COBJS) 53 | $(CC) $^ -o $@ $(LDFLAGS) $(_CFLAGS) 54 | 55 | $(OBJDIR)/%.o: $(SRCDIR)/%.c $(SRCDIR)/%.h 56 | $(CC) $(_CFLAGS) -c $< -o $@ 57 | 58 | $(OBJDIR): 59 | mkdir $(OBJDIR) 60 | 61 | format: 62 | clang-format -i ./src/* 63 | 64 | install: all 65 | mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(CONFDIR) 66 | $(INSTALL) $(PROG) $(DESTDIR)$(BINDIR) 67 | cp $(ASSETSPATH)$(CONFIG) $(DESTDIR)$(CONFDIR) 68 | 69 | uninstall: 70 | rm $(DESTDIR)$(BINDIR)/$(PROG) 71 | rm $(DESTDIR)$(CONFDIR)/$(CONFIG) 72 | 73 | clean: 74 | rm -rf $(PROG) $(OBJDIR) 75 | 76 | .PHONY: all install uninstall clean 77 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | LOG_VERBOSITY LogMaxVerbosity = LOG_VERBOSITY_Trace; 9 | bool LogColored = true; 10 | bool LogAddNewLine = true; 11 | bool LogAddDate = false; 12 | 13 | static const char *LogVerbosityStrings[] = { 14 | "why you're watching inside binary?", 15 | "[TRACE]", 16 | "[INFO] ", 17 | "[WARN] ", 18 | "[ERROR]", 19 | "[FATAL]", 20 | }; 21 | 22 | static const char *LogVerbosityStringsColored[] = { 23 | "sfome on the swag", "[\033[0;34mTRACE\033[1;0m]", 24 | "[\033[0;32mINFO\033[1;0m] ", "[\033[0;33mWARN\033[1;0m] ", 25 | "[\033[0;31mERROR\033[1;0m]", "[\033[0;31mFATAL\033[1;0m]", 26 | }; 27 | 28 | static inline struct tm *LogCurrentTime(void) { 29 | time_t t = time(NULL); 30 | return localtime(&t); 31 | } 32 | 33 | void LogFlog(LOG_VERBOSITY Verbosity, FILE *Stream, size_t Line, 34 | const char Filename[], const char Fmt[], ...) { 35 | assert(Verbosity >= 0 && Verbosity <= LOG_VERBOSITY_LEN); 36 | assert(Stream != NULL); 37 | assert(Filename != NULL); 38 | assert(Fmt != NULL); 39 | assert(strnlen(Fmt, __LOG_H_BUFSIZE) < __LOG_H_BUFSIZE); 40 | 41 | if (LogMaxVerbosity == LOG_VERBOSITY_None || 42 | Verbosity == LOG_VERBOSITY_None) { 43 | return; 44 | } 45 | if (Verbosity < LogMaxVerbosity) { 46 | return; 47 | } 48 | 49 | char TimeBuffer[64]; 50 | char StringBuffer[__LOG_H_BUFSIZE + 256]; 51 | char *StringPointer = StringBuffer; 52 | bool LocalLogColored = 53 | (Stream == stdout || Stream == stderr) ? LogColored : false; 54 | 55 | strftime(TimeBuffer, sizeof(TimeBuffer), 56 | (LogAddDate) ? "%Y-%m-%d %H:%M:%S" : "%H:%M:%S", LogCurrentTime()); 57 | 58 | StringPointer += 59 | sprintf(StringPointer, "%s %s %s:%zu: ", TimeBuffer, 60 | (LocalLogColored) ? LogVerbosityStringsColored[Verbosity] 61 | : LogVerbosityStrings[Verbosity], 62 | Filename, Line); 63 | 64 | va_list Args; 65 | va_start(Args, Fmt); 66 | size_t w = vsnprintf(StringPointer, __LOG_H_BUFSIZE - 1, Fmt, Args); 67 | va_end(Args); 68 | 69 | StringPointer += (w >= __LOG_H_BUFSIZE) ? __LOG_H_BUFSIZE - 1 : w; 70 | 71 | if (LogAddNewLine) { 72 | StringPointer[0] = '\n'; 73 | StringPointer[1] = '\0'; 74 | } 75 | 76 | fputs(StringBuffer, Stream); 77 | return; 78 | } 79 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOG_H_ 2 | #define _LOG_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define __LOG_H_BUFSIZE 4096 10 | 11 | typedef enum { 12 | LOG_VERBOSITY_None, 13 | LOG_VERBOSITY_Trace, 14 | LOG_VERBOSITY_Info, 15 | LOG_VERBOSITY_Warn, 16 | LOG_VERBOSITY_Error, 17 | LOG_VERBOSITY_Fatal, 18 | } LOG_VERBOSITY; 19 | #define LOG_VERBOSITY_LEN 5 20 | 21 | extern LOG_VERBOSITY LogMaxVerbosity; 22 | extern bool LogColored; 23 | extern bool LogAddNewLine; 24 | extern bool LogAddDate; 25 | 26 | void LogFlog(LOG_VERBOSITY Verbosity, FILE *Stream, size_t Line, 27 | const char Filename[], const char Fmt[], ...); 28 | 29 | #define LogTrace(...) \ 30 | LogFlog(LOG_VERBOSITY_Trace, stderr, __LINE__, __FILE__, __VA_ARGS__) 31 | #define LogInfo(...) \ 32 | LogFlog(LOG_VERBOSITY_Info, stdout, __LINE__, __FILE__, __VA_ARGS__) 33 | #define LogWarn(...) \ 34 | LogFlog(LOG_VERBOSITY_Warn, stderr, __LINE__, __FILE__, __VA_ARGS__) 35 | #define LogErr(...) \ 36 | LogFlog(LOG_VERBOSITY_Error, stderr, __LINE__, __FILE__, __VA_ARGS__) 37 | #define LogFatal(...) \ 38 | do { \ 39 | LogFlog(LOG_VERBOSITY_Fatal, stderr, __LINE__, __FILE__, __VA_ARGS__); \ 40 | abort(); \ 41 | } while (false); 42 | 43 | #define FlogTrace(File, ...) \ 44 | LogFlog(LOG_VERBOSITY_Trace, File, __LINE__, __FILE__, __VA_ARGS__) 45 | #define FlogInfo(File, ...) \ 46 | LogFlog(LOG_VERBOSITY_Info, File, __LINE__, __FILE__, __VA_ARGS__) 47 | #define FlogWarn(File, ...) \ 48 | LogFlog(LOG_VERBOSITY_Warn, File, __LINE__, __FILE__, __VA_ARGS__) 49 | #define FlogErr(File, ...) \ 50 | LogFlog(LOG_VERBOSITY_Error, File, __LINE__, __FILE__, __VA_ARGS__) 51 | #define FlogFatal(File, ...) \ 52 | do { \ 53 | LogFlog(LOG_VERBOSITY_Fatal, File, __LINE__, __FILE__, __VA_ARGS__); \ 54 | fflush(File); \ 55 | abort(); \ 56 | } while (false) 57 | 58 | #endif /* _LOG_H_ */ 59 | -------------------------------------------------------------------------------- /src/ini.c: -------------------------------------------------------------------------------- 1 | #include "ini.h" 2 | 3 | #include 4 | #include 5 | 6 | // return pointer to truncated string 7 | static inline char *StrTruncate(char s[]) { 8 | if (s[0] == '\0') { 9 | return s; 10 | } 11 | char *Str = s; 12 | for (; *Str == ' '; ++Str) 13 | ; 14 | char *StrEnd = Str + strlen(Str) - 1; 15 | for (; *StrEnd == ' ' && StrEnd > Str; --StrEnd) 16 | ; 17 | StrEnd[1] = '\0'; 18 | return Str; 19 | } 20 | 21 | int32_t IniLoad(FILE *Stream, IniCallBack *Func) { 22 | assert(Stream != NULL && "file stream is NULL"); 23 | assert(Func != NULL && "callback func is NULL"); 24 | 25 | char Section[__INI_H_BUFSIZE + 1]; 26 | size_t SectionIdx = 0; 27 | char Key[__INI_H_BUFSIZE + 1]; 28 | size_t KeyIdx = 0; 29 | char Value[__INI_H_BUFSIZE + 1]; 30 | size_t ValueIdx = 0; 31 | 32 | INI_LEXEME Lexeme = INI_LEXEME_Key; 33 | INI_LEXEME PrevLexeme = Lexeme; 34 | size_t CallBackCount = 0; 35 | 36 | while (!feof(Stream)) { 37 | char ch = fgetc(Stream); 38 | switch (ch) { 39 | case '#': 40 | case ';': 41 | PrevLexeme = Lexeme; 42 | Lexeme = INI_LEXEME_Comment; 43 | continue; 44 | case '[': 45 | Lexeme = INI_LEXEME_Section; 46 | SectionIdx = 0; 47 | *Section = '\0'; 48 | continue; 49 | case ']': 50 | Lexeme = INI_LEXEME_Key; 51 | Section[SectionIdx] = '\0'; 52 | continue; 53 | case '=': 54 | if (Lexeme == INI_LEXEME_Value) { 55 | goto ValueHandle; 56 | } 57 | Lexeme = INI_LEXEME_Value; 58 | Key[KeyIdx] = '\0'; 59 | KeyIdx = 0; 60 | continue; 61 | case '\n': 62 | Value[ValueIdx] = '\0'; 63 | if (Lexeme == INI_LEXEME_Value || 64 | (Lexeme == INI_LEXEME_Comment && PrevLexeme == INI_LEXEME_Value)) { 65 | size_t CallBackResult = 66 | Func(StrTruncate(Section), StrTruncate(Key), StrTruncate(Value)); 67 | if (CallBackResult != 0) { 68 | return CallBackResult; 69 | } 70 | CallBackCount++; 71 | } 72 | Lexeme = INI_LEXEME_Key; 73 | KeyIdx = 0; 74 | ValueIdx = 0; 75 | continue; 76 | default: 77 | ValueHandle: 78 | switch (Lexeme) { 79 | case INI_LEXEME_Section: 80 | if (SectionIdx == __INI_H_BUFSIZE) { 81 | continue; 82 | } 83 | Section[SectionIdx] = ch; 84 | SectionIdx++; 85 | continue; 86 | case INI_LEXEME_Key: 87 | if (KeyIdx == __INI_H_BUFSIZE) { 88 | continue; 89 | } 90 | Key[KeyIdx] = ch; 91 | KeyIdx++; 92 | continue; 93 | case INI_LEXEME_Value: 94 | if (ValueIdx == __INI_H_BUFSIZE) { 95 | continue; 96 | } 97 | Value[ValueIdx] = ch; 98 | ValueIdx++; 99 | continue; 100 | default: 101 | continue; 102 | } 103 | } 104 | } 105 | 106 | // ini file is incorrect 107 | if (CallBackCount < 1) { 108 | return 1; 109 | } 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /src/mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ccc.h" 10 | #include "mem.h" 11 | 12 | // get line from file without heap allocation 13 | // returns Len if buffer fully used 14 | // and -1 on EOF 15 | static inline ssize_t GetLine(char Str[], size_t Len, FILE *Stream) { 16 | assert(Str != NULL); 17 | assert(Stream != NULL); 18 | 19 | ssize_t Idx = 0; 20 | while (true) { 21 | if (feof(Stream)) { 22 | Idx = -1; 23 | break; 24 | } 25 | if ((size_t)Idx == Len) { 26 | break; 27 | } 28 | char ch = fgetc(Stream); 29 | if (ch == '\n') { 30 | break; 31 | } 32 | Str[Idx] = ch; 33 | Idx++; 34 | } 35 | 36 | Str[Len - 1] = '\0'; 37 | return Idx; 38 | } 39 | 40 | // we cant get error here 41 | static inline size_t GetSizeFromMemStr(const char Str[]) { 42 | assert(Str != NULL); 43 | 44 | char Integer[BUFSIZE] = {0}; 45 | size_t IntegerIdx = 0; 46 | 47 | for (size_t i = 0; Str[i] != '\0'; ++i) { 48 | if (isdigit(Str[i])) { 49 | Integer[IntegerIdx] = Str[i]; 50 | IntegerIdx++; 51 | } 52 | } 53 | 54 | char *endptr = NULL; 55 | size_t Result = strtoumax(Integer, &endptr, 10); 56 | 57 | return Result; 58 | } 59 | 60 | MemSizeError MemGetTotal(void) { 61 | MemSizeError Result = {0}; 62 | 63 | FILE *MemFile = NULL; 64 | { 65 | 66 | errno = 0; 67 | MemFile = fopen(MemInfoFile, "r"); 68 | if (MemFile == NULL) { 69 | Result.Err = 70 | ErrorNew("Error with %s file open: %s", MemInfoFile, strerror(errno)); 71 | goto Cleanup; 72 | } 73 | 74 | char Line[BUFSIZE] = {0}; 75 | ssize_t Readed = GetLine(Line, sizeof(Line), MemFile); 76 | if (Readed == -1) { 77 | Result.Err = 78 | ErrorNew("Error with total line reading from %s file", MemInfoFile); 79 | goto Cleanup; 80 | } 81 | 82 | Result.Val = GetSizeFromMemStr(Line); 83 | } 84 | Cleanup: 85 | if (MemFile != NULL) { 86 | fclose(MemFile); 87 | } 88 | return Result; 89 | } 90 | 91 | MemSizeError MemGetFree(void) { 92 | MemSizeError Result = {0}; 93 | 94 | FILE *MemFile = NULL; 95 | { 96 | 97 | errno = 0; 98 | MemFile = fopen(MemInfoFile, "r"); 99 | if (MemFile == NULL) { 100 | Result.Err = 101 | ErrorNew("Error with %s file open: %s", MemInfoFile, strerror(errno)); 102 | goto Cleanup; 103 | } 104 | 105 | char Line[BUFSIZE] = {0}; 106 | // skip total mem line 107 | (void)GetLine(Line, sizeof(Line), MemFile); 108 | 109 | ssize_t Readed = GetLine(Line, sizeof(Line), MemFile); 110 | if (Readed == -1) { 111 | Result.Err = 112 | ErrorNew("Error with free line reading from %s file", MemInfoFile); 113 | goto Cleanup; 114 | } 115 | 116 | Result.Val = GetSizeFromMemStr(Line); 117 | } 118 | Cleanup: 119 | if (MemFile != NULL) { 120 | fclose(MemFile); 121 | } 122 | return Result; 123 | } 124 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | #include "ini.h" 6 | #include "log.h" 7 | 8 | char ConfigLockFile[BUFSIZE] = "/tmp/ccc.lock"; 9 | bool ConfigLogSilent = false; 10 | bool ConfigOptionsSync = true; 11 | int32_t ConfigLevelsFirst = 15; 12 | int32_t ConfigLevelsSecond = 10; 13 | int32_t ConfigLevelsThird = 5; 14 | size_t ConfigTimeoutsCheck = 10; 15 | int32_t ConfigErrorMaxAmount = 10; 16 | 17 | static int32_t IniCallBackFunc(const char Section[], const char Key[], 18 | const char Val[]) { 19 | assert(Section != NULL); 20 | assert(Key != NULL); 21 | assert(Val != NULL); 22 | if (IniCheckValue(Section, Key, "Files", "Lock")) { 23 | if (Val[0] == '\0') { 24 | LogErr("Error with LogFile. string is empty"); 25 | return 0; 26 | } 27 | strncpy(ConfigLockFile, Val, sizeof(ConfigLockFile) - 1); 28 | ConfigLockFile[sizeof(ConfigLockFile) - 1] = '\0'; 29 | 30 | LogInfo("ConfigLockFile = '%s'", ConfigLockFile); 31 | return 0; 32 | } 33 | 34 | if (IniCheckValue(Section, Key, "Log", "Silent")) { 35 | ConfigLogSilent = (strncmp(Val, "true", BUFSIZE - 1) == 0); 36 | 37 | LogInfo("ConfigLogSilent = %s", ConfigLogSilent ? "true" : "false"); 38 | return 0; 39 | } 40 | 41 | if (IniCheckValue(Section, Key, "Options", "Sync")) { 42 | ConfigOptionsSync = (strncmp(Val, "true", BUFSIZE - 1) == 0); 43 | 44 | LogInfo("ConfigOptionsSync = %s", ConfigOptionsSync ? "true" : "false"); 45 | return 0; 46 | } 47 | 48 | if (IniCheckValue(Section, Key, "Levels", "First")) { 49 | int8_t Level = atoi(Val); 50 | if (Level <= 0 || Level >= 100) { 51 | LogErr("Error with LevelsFirst. incorrect value: %d", Level); 52 | return 0; 53 | } 54 | 55 | ConfigLevelsFirst = Level; 56 | LogInfo("ConfigLevelsFirst = %d", ConfigLevelsFirst); 57 | return 0; 58 | } 59 | 60 | if (IniCheckValue(Section, Key, "Levels", "Second")) { 61 | int8_t Level = atoi(Val); 62 | if (Level <= 0 || Level >= 100) { 63 | LogErr("Error with LevelsSecond. incorrect value: %d", Level); 64 | return 0; 65 | } 66 | 67 | ConfigLevelsSecond = Level; 68 | LogInfo("ConfigLevelsSecond = %d", ConfigLevelsSecond); 69 | return 0; 70 | } 71 | 72 | if (IniCheckValue(Section, Key, "Levels", "Third")) { 73 | int8_t Level = atoi(Val); 74 | if (Level <= 0 || Level >= 100) { 75 | LogErr("Error with LevelsThird. incorrect value: %d", Level); 76 | return 0; 77 | } 78 | 79 | ConfigLevelsThird = Level; 80 | LogInfo("ConfigLevelsThird = %d", ConfigLevelsThird); 81 | return 0; 82 | } 83 | 84 | if (IniCheckValue(Section, Key, "Timeouts", "Check")) { 85 | int32_t Check = atoi(Val); 86 | if (Check <= 0) { 87 | LogErr("Error with TimeoutsCheck. incorrect value: %d sec", Check); 88 | return 0; 89 | } 90 | 91 | ConfigTimeoutsCheck = Check; 92 | LogInfo("ConfigTimeoutsCheck = %d", ConfigTimeoutsCheck); 93 | return 0; 94 | } 95 | 96 | if (IniCheckValue(Section, Key, "Error", "MaxAmount")) { 97 | int32_t Amount = atoi(Val); 98 | if (Amount <= 0) { 99 | LogErr("Error with ErrorMaxAmount. incorrect value: %d", Amount); 100 | return 0; 101 | } 102 | 103 | ConfigErrorMaxAmount = Amount; 104 | LogInfo("ConfigErrorMaxAmount = %d", ConfigErrorMaxAmount); 105 | return 0; 106 | } 107 | 108 | return 0; 109 | } 110 | 111 | Error ConfigLoad(const char ConfigFileStr[]) { 112 | assert(ConfigFileStr != NULL); 113 | 114 | Error Err = ErrorNo(); 115 | FILE *ConfigFile = NULL; 116 | { 117 | const char *FileStr = 118 | (ConfigFileStr[0] == '\0') ? ConfigFileDef : ConfigFileStr; 119 | 120 | errno = 0; 121 | ConfigFile = fopen(FileStr, "r"); 122 | if (ConfigFile == NULL) { 123 | Err = ErrorNew("Config File %s open error: %s", FileStr, strerror(errno)); 124 | goto Cleanup; 125 | } 126 | 127 | int32_t IniParsingResult = IniLoad(ConfigFile, IniCallBackFunc); 128 | if (IniParsingResult == 1) { 129 | Err = ErrorNew("Config file doesn't finded"); 130 | goto Cleanup; 131 | } 132 | if (IniParsingResult != 0) { 133 | Err = ErrorNew("Ini parsing error: %d code", IniParsingResult); 134 | goto Cleanup; 135 | } 136 | } 137 | Cleanup: 138 | if (ConfigFile != NULL) { 139 | fclose(ConfigFile); 140 | } 141 | return Err; 142 | } 143 | -------------------------------------------------------------------------------- /assets/uneeded/ascii_art.sh: -------------------------------------------------------------------------------- 1 | echo -e "$(tput bold)  2 |  ______     ______     ______     3 | /\  ___\   /\  ___\   /\  ___\    4 | \ \ \____  \ \ \____  \ \ \____   5 |  \ \_____\  \ \_____\  \ \_____\  6 |   \/_____/   \/_____/   \/_____/  7 |                                     8 | $(tput sgr0)" 9 | -------------------------------------------------------------------------------- /src/ccc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ccc.h" 12 | #include "cleaner.h" 13 | #include "config.h" 14 | #include "error.h" 15 | #include "log.h" 16 | #include "mem.h" 17 | 18 | // private 19 | FILE *CccLockFile = NULL; 20 | 21 | // -c 22 | // -a [switch output colors] 23 | // -s [silent mode] 24 | static const char *Options = "c:as"; 25 | static const int32_t ExitSignals[] = { 26 | SIGTERM, SIGINT, SIGQUIT, SIGKILL, SIGHUP, 27 | }; 28 | 29 | static inline void PrintHelp(void) { 30 | puts(" ccc - simple cache cleaner"); 31 | puts(" -c "); 32 | puts(" -a [switch output colors]"); 33 | } 34 | 35 | static inline int32_t GetCleanLevelByPercent(int32_t Percent) { 36 | assert(Percent >= 0 && Percent <= 100); 37 | 38 | int32_t Result = 0; 39 | if (Percent <= ConfigLevelsFirst) 40 | Result++; 41 | if (Percent <= ConfigLevelsSecond) 42 | Result++; 43 | if (Percent <= ConfigLevelsThird) 44 | Result++; 45 | return Result; 46 | } 47 | 48 | static Error Init(const char ConfigFileStr[], bool SilentLog) { 49 | assert(ConfigFileStr != NULL); 50 | 51 | if (geteuid() != 0) { 52 | return ErrorNew("App needs root"); 53 | } 54 | 55 | if (stat(ConfigFileStr, &(struct stat){0}) != 0) { 56 | LogWarn("Config file '%s' doesnt finded.", ConfigFileStr); 57 | } else { 58 | Error Err = ConfigLoad(ConfigFileStr); 59 | if (ErrorIs(&Err)) { 60 | return ErrorNew("Config error: %s", ErrorWhat(&Err)); 61 | } 62 | LogTrace("Config File Parsed."); 63 | } 64 | 65 | if (SilentLog == false) { 66 | LogMaxVerbosity = 67 | ConfigLogSilent ? LOG_VERBOSITY_Warn : LOG_VERBOSITY_Trace; 68 | } else { 69 | LogMaxVerbosity = LOG_VERBOSITY_Warn; 70 | } 71 | 72 | if (stat(ConfigLockFile, &(struct stat){0}) == 0) { 73 | return ErrorNew("Lock file accured. ccc already working"); 74 | } 75 | LogTrace("Lock file checked."); 76 | 77 | errno = 0; 78 | FILE *check = fopen(DropCachesFile, "w"); 79 | if (check == NULL) { 80 | return ErrorNew("Cant open %s file for writing: %s", DropCachesFile, 81 | strerror(errno)); 82 | } 83 | fclose(check); 84 | LogTrace("DropCachesFile checked."); 85 | 86 | errno = 0; 87 | CccLockFile = fopen(ConfigLockFile, "w"); 88 | if (CccLockFile == NULL) { 89 | return ErrorNew("Cant open %s lock file: %s", ConfigLockFile, 90 | strerror(errno)); 91 | } 92 | fputs("Why do u check lock file?", CccLockFile); 93 | fflush(CccLockFile); 94 | LogTrace("Lock file created."); 95 | 96 | return ErrorNo(); 97 | } 98 | 99 | static void DeInit(int Signal) { 100 | if (CccLockFile != NULL) { 101 | fclose(CccLockFile); 102 | LogTrace("Lock file closed."); 103 | remove(ConfigLockFile); 104 | LogTrace("Lock file removed."); 105 | } 106 | fflush(stdout); 107 | fflush(stderr); 108 | LogFatal("DeInit called. Received signal: %d.", Signal); 109 | } 110 | 111 | static void RegDeInit(void) { 112 | for (size_t i = 0; i < sizeof(ExitSignals) / sizeof(ExitSignals[0]); ++i) { 113 | signal(ExitSignals[i], &DeInit); 114 | } 115 | } 116 | 117 | int main(int argc, char *argv[]) { 118 | // thx jcs for https://no-color.org/ 119 | char *NoColor = getenv("NO_COLOR"); 120 | LogColored = (NoColor != NULL && NoColor[0] != '\0') ? false : true; 121 | 122 | char ConfigFileStr[BUFSIZE] = "/usr/local/etc/ccc.conf"; 123 | bool SilentLog = false; 124 | 125 | // flag parsing 126 | while (true) { 127 | int32_t r = getopt(argc, argv, Options); 128 | if (r == -1) 129 | break; 130 | switch (r) { 131 | case 'c': { 132 | size_t OptArgLen = strnlen(optarg, BUFSIZE); 133 | if (OptArgLen > sizeof(ConfigFileStr) - 1) { 134 | LogFatal("Stack Buffer overwrited " 135 | "on ConfigFile. %d > %d", 136 | OptArgLen, sizeof(optarg)); 137 | } 138 | strncpy(ConfigFileStr, optarg, sizeof(ConfigFileStr) - 1); 139 | ConfigFileStr[sizeof(ConfigFileStr) - 1] = '\0'; 140 | break; 141 | } 142 | case 'a': { 143 | LogColored = !LogColored; 144 | break; 145 | } 146 | case 's': { 147 | LogMaxVerbosity = LOG_VERBOSITY_Warn; 148 | SilentLog = true; 149 | break; 150 | } 151 | default: { 152 | PrintHelp(); 153 | return 1; 154 | } 155 | } 156 | } 157 | 158 | LogInfo("ccc started."); 159 | LogTrace("Config file - '%s'.", ConfigFileStr); 160 | 161 | RegDeInit(); 162 | LogTrace("DeInit registered."); 163 | 164 | Error Err = Init(ConfigFileStr, SilentLog); 165 | if (ErrorIs(&Err)) { 166 | LogFatal("Error on Init: %s.", ErrorWhat(&Err)); 167 | } 168 | LogTrace("Initialization completed."); 169 | 170 | MemSizeError TotalMem = MemGetTotal(); 171 | if (ErrorIs(&TotalMem.Err)) { 172 | LogFatal("Error with MemGetTotal(): %s.", ErrorWhat(&Err)); 173 | } 174 | MemSizeError FreeMem = MemGetFree(); 175 | if (ErrorIs(&FreeMem.Err)) { 176 | LogFatal("Error with MemGetTotal(): %s.", ErrorWhat(&Err)); 177 | } 178 | 179 | LogInfo("Total mem: %.2fmib.", (double)TotalMem.Val / 1024); 180 | LogInfo("Free mem: %.2fmib.", (double)FreeMem.Val / 1024); 181 | 182 | // count errors before stop 183 | int32_t ErrorCounter = 0; 184 | 185 | // main loop 186 | LogInfo("ccc started normaly."); 187 | while (true) { 188 | FreeMem = MemGetFree(); 189 | if (ErrorIs(&FreeMem.Err)) { 190 | LogErr("Error with MemGetTotal(): %s.", ErrorWhat(&Err)); 191 | ErrorCounter++; 192 | } 193 | 194 | int32_t Percent = ((double)FreeMem.Val / TotalMem.Val) * 100; 195 | int32_t CleanLevel = GetCleanLevelByPercent(Percent); 196 | LogInfo("Free memory amount: %.2fmib or %d%% and CleanLevel = %d", 197 | (double)FreeMem.Val / 1024, Percent, CleanLevel); 198 | 199 | if (CleanLevel > 0) { 200 | if (ConfigOptionsSync) { 201 | sync(); 202 | LogInfo("sync() called."); 203 | } 204 | Err = CleanerDropCaches(DropCachesFile, CleanLevel); 205 | if (ErrorIs(&Err)) { 206 | LogErr("Error with CleanerDropCaches(): %s.", ErrorWhat(&Err)); 207 | ErrorCounter++; 208 | } 209 | LogInfo("Caches dropped."); 210 | } 211 | 212 | fflush(stdout); 213 | fflush(stderr); 214 | int32_t Slept = sleep(ConfigTimeoutsCheck); 215 | if (Slept > 0) { 216 | LogErr("Not all time slept. time left: %ds", Slept); 217 | ErrorCounter++; 218 | } 219 | 220 | if (ErrorCounter >= ConfigErrorMaxAmount) { 221 | LogFatal("ErrorCounter is to big. exiting"); 222 | } 223 | } 224 | 225 | return 0; 226 | } 227 | --------------------------------------------------------------------------------