diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4ea8508 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +CC = kos32-gcc +LD = kos32-ld + +HOME_DIR = $(shell echo ~) +TOOLCHAIN_DIR = $(abspath /home/autobuild/tools/win32/) +CONTRIB_DIR = $(abspath $(HOME_DIR)/kolibrios/contrib) +SDK_DIR = $(CONTRIB_DIR)/sdk +C_LAYER = $(CONTRIB_DIR)/C_Layer + +CFLAGS = -fno-ident -fomit-frame-pointer -U__WIN32__ -U_Win32 -U_WIN32 -U__MINGW32__ -UWIN32 -I $(SDK_DIR)/sources/newlib/libc/include +LDFLAGS = -static -S -nostdlib -T app.lds --subsystem native -L $(TOOLCHAIN_DIR)/lib -L $TOOLCHAIN_DIR/mingw32/lib -L $(SDK_DIR)/lib + +INCLUDES = -I$(SDK_DIR)/sources/newlib/libc/include -DNDEBUG -I.. +LIBPATH = -L $(SDK_DIR)/lib -L $(TOOLCHAIN_DIR)/mingw32/lib + +SOURCES := lib/microtar/src/microtar.c lib/ccsv/src/ccsv.c \ + src/kospm_tar.c src/kospm_list.c src/kospm_common.c \ + src/kospm_package.c src/kospm_db.c src/kospm.c + +OBJECTS := $(SOURCES:.c=.o) + +default: libkospm.a + +libkospm.a: $(OBJECTS) Makefile + ar -crs libkospm.a $(OBJECTS) + +%.o : %.c Makefile $(SOURCES) + $(CC) $(CFLAGS) $(INCLUDES) -o $@ $< diff --git a/include/kospm.h b/include/kospm.h new file mode 100644 index 0000000..d539e6d --- /dev/null +++ b/include/kospm.h @@ -0,0 +1,30 @@ +/* + * kospm.h + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#include "kospm_db.h" +#include "kospm_package.h" + +int kospm_install(kospm_db_t *db, char *pkg_file); + +int kospm_remove(kospm_db_t *db, char *pkg_name); + +int kospm_query(kospm_db_t *db, char *pkg_name); diff --git a/include/kospm_common.h b/include/kospm_common.h new file mode 100644 index 0000000..f997b60 --- /dev/null +++ b/include/kospm_common.h @@ -0,0 +1,54 @@ +/* + * kospm_common.h + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#ifndef KOSPM_CSV +#define KOSPM_CSV + +#include +#include +#include "../lib/ccsv.h" +#include "kospm_list.h" + +ccsv_reader_options _kospm_csv_reader_options = { + .delim = ',', + .quote_char = '"', + .skip_initial_space = 1, + .skip_empty_lines = 1, + .skip_comments = 1 +}; + +ccsv_reader_options _kospm_csv_writer_options = { + .delim = ',', + .quote_char = '"' +}; + + +typedef struct { + int col_number; + char *col_name; +} kospm_csv_header_column; + +kospm_list_t* _kospm_csv_header_get(FILE* file); + +int _kospm_csv_col_get(kospm_list_t* col_list, char* col_name); + +#endif diff --git a/include/kospm_db.h b/include/kospm_db.h new file mode 100644 index 0000000..b61ec46 --- /dev/null +++ b/include/kospm_db.h @@ -0,0 +1,73 @@ +/* + * kospm_db.h + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#ifndef KOSPM_DB +#define KOSPM_DB + +#include +#include "kospm_list.h" +#include "kospm_package.h" + +#define DB_FOLDER "/kospm/db" +#define METADATA_TABLE "/packages.csv" +#define FILES_TABLE "/files.csv" + +char **_kospm_db_metadata_table_header; +char **_kospm_db_files_table_header; + +typedef struct kospm_db { + char *db_path; + FILE* metadata_table; + FILE* files_table; + kospm_list_t *packages; + kospm_list_t *files; +} kospm_db_t; + +kospm_db_t* kospm_db_init(char *root_dir); + +kospm_db_t* kospm_db_open(char *root_dir); + +int kospm_db_save(kospm_db_t *db); + +int kospm_db_close(kospm_db_t *db); + +int kospm_db_package_add(kospm_db_t *db, kospm_package_t *package); + +kospm_package_t* kospm_db_package_query(kospm_db_t *db, char *pkg_name); + +int kospm_db_package_remove(kospm_db_t *db, char *pkg_name); + +int kospm_db_file_add(kospm_db_t *db, char *pkg_name, kospm_package_file_t *file); + +kospm_package_t* kospm_db_file_query(kospm_db_t *db, char *file_name); + +int kospm_db_file_remove(kospm_db_t *db, char *filename); + +int kospm_db_packages_add(kospm_db_t *db, kospm_package_list_t *package_list); + +int kospm_db_files_add(kospm_db_t *db, kospm_list_t *file_list); + +int kospm_db_packagelist_save(kospm_db_t *db); + +int kospm_db_filelist_save(kospm_db_t *db); + +#endif diff --git a/include/kospm_list.h b/include/kospm_list.h new file mode 100644 index 0000000..5d858df --- /dev/null +++ b/include/kospm_list.h @@ -0,0 +1,42 @@ +/* + * kospm_list.h + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#ifndef KOSPM_LIST +#define KOSPM_LIST + +#include "stddef.h" + +typedef struct { + void *data; + kospm_list_t *prev; + kospm_list_t *next; + } kospm_list_t; + +kospm_list_t* kospm_list_new(void *data); + +void kospm_list_add(kospm_list_t **list, void *data); + +void kospm_list_del_by_value(kospm_list_t **list, void *data, int (*cmp)(void *, void *)); + +void kospm_list_free(kospm_list_t *list); + +#endif diff --git a/include/kospm_package.h b/include/kospm_package.h new file mode 100644 index 0000000..0269ecc --- /dev/null +++ b/include/kospm_package.h @@ -0,0 +1,56 @@ +/* + * kospm_package.h + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#ifndef KOSPM_PKG +#define KOSPM_PKG + +#include +#include "kospm_list.h" +#define PKGINFO "PKGINFO.CSV" +#define FILES "FILES.CSV" +#define CACHE "/tmp0/1/kospm/cache/" + +typedef struct { + char pkg_name[64]; + char *file_name; +} kospm_package_file_t; + +typedef struct { + char pkg_name[64]; + char pkg_version[16]; + char *pkg_description; +} kospm_package_t; + +typedef struct { + kospm_list_t *packages; + kospm_list_t *files; +} kospm_package_list_t; + +kospm_list_t* kospm_package_metadata_get(FILE* fp); + +kospm_list_t* kospm_package_files_get(FILE* fp); + +kospm_package_list_t* kospm_package_list_open(char *pkg_file); + +int kospm_package_list_close(kospm_package_list_t *package_list); + +#endif diff --git a/include/kospm_tar.h b/include/kospm_tar.h new file mode 100644 index 0000000..67fac3f --- /dev/null +++ b/include/kospm_tar.h @@ -0,0 +1,31 @@ +/* + * kospm_tar.h + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#ifndef KOSPM_TAR +#define KOSPM_TAR + +#include "../lib/microtar/src/microtar.h" + +int tar_unpack(const char *tar_name, + const char *src_file, const char *dest_file); + +#endif diff --git a/kos_build.sh b/kos_build.sh new file mode 100644 index 0000000..80b7770 --- /dev/null +++ b/kos_build.sh @@ -0,0 +1,3 @@ +#SHS + +/kolibrios/develop/tcc/tcc -r -o libkospm.o lib/microtar/src/microtar.c lib/ccsv/src/ccsv.c src/kospm_tar.c src/kospm_list.c src/kospm_common.c src/kospm_package.c src/kospm_db.c src/kospm.c diff --git a/kospm.geany b/kospm.geany new file mode 100644 index 0000000..76a58f1 --- /dev/null +++ b/kospm.geany @@ -0,0 +1,50 @@ +[editor] +line_wrapping=false +line_break_column=72 +auto_continue_multiline=true + +[file_prefs] +final_new_line=true +ensure_convert_new_lines=false +strip_trailing_spaces=false +replace_tabs=false + +[indentation] +indent_width=4 +indent_type=1 +indent_hard_tab_width=8 +detect_indent=false +detect_indent_width=false +indent_mode=2 + +[project] +name=kospm +base_path=/home/kexa/Документы/npoekt/kospm +description= + +[long line marker] +long_line_behaviour=1 +long_line_column=72 + +[files] +current_page=0 +FILE_NAME_0=1194;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fsrc%2Fkospm.c;0;4 +FILE_NAME_1=4136;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fsrc%2Fkospm_db.c;0;4 +FILE_NAME_2=961;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fsrc%2Fkospm_list.c;0;4 +FILE_NAME_3=3586;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fsrc%2Fkospm_package.c;0;4 +FILE_NAME_4=1069;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fsrc%2Fkospm_tar.c;0;4 +FILE_NAME_5=861;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Finclude%2Fkospm.h;0;4 +FILE_NAME_6=1948;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Finclude%2Fkospm_db.h;0;4 +FILE_NAME_7=1205;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Finclude%2Fkospm_list.h;0;4 +FILE_NAME_8=1498;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Finclude%2Fkospm_package.h;0;4 +FILE_NAME_9=990;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Finclude%2Fkospm_tar.h;0;4 +FILE_NAME_10=198;Sh;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fkos_build.sh;0;4 +FILE_NAME_11=907;Make;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2FMakefile;0;4 +FILE_NAME_12=9813;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Flib%2Fccsv%2Finclude%2Fccsv.h;0;4 +FILE_NAME_13=0;C++;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Flib%2Fmicrotar%2Fsrc%2Fmicrotar.h;0;4 +FILE_NAME_14=17;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Fsrc%2Fkospm_common.c;0;4 +FILE_NAME_15=1269;C++;0;EUTF-8;1;1;1;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Finclude%2Fkospm_common.h;0;4 +FILE_NAME_16=2814;C;0;EUTF-8;1;1;0;%2Fhome%2Fkexa%2FДокументы%2Fnpoekt%2Fkospm%2Flib%2Fccsv%2Fsrc%2Fccsv.c;0;4 + +[VTE] +last_dir=/home/kexa diff --git a/lib/ccsv/.gitignore b/lib/ccsv/.gitignore new file mode 100644 index 0000000..7aa4469 --- /dev/null +++ b/lib/ccsv/.gitignore @@ -0,0 +1,58 @@ +/.vscode +/.vs + +tests/write_test.c +rough*.c + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/lib/ccsv/LICENSE b/lib/ccsv/LICENSE new file mode 100644 index 0000000..8dd313f --- /dev/null +++ b/lib/ccsv/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Ayush Tripathy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/ccsv/README.md b/lib/ccsv/README.md new file mode 100644 index 0000000..bfe9357 --- /dev/null +++ b/lib/ccsv/README.md @@ -0,0 +1,117 @@ +# ccsv +Fast, flexible, easy-to-use CSV reading, writing library for C + +For full documentation, see the [docs](https://github.com/Ayush-Tripathy/ccsv/tree/main/docs) + +## Usage +### Create a reader object + +```c +ccsv_reader *reader = ccsv_init_reader(NULL); // NULL for default options +``` + +### Create a reader object with custom options + +```c +ccsv_reader_options options = { + .delim = ',', + .quote_char = '"', + .skip_initial_space = 0, + .skip_empty_lines = 1, + .skip_comments = 1}; + +// Initialize the reader with the options +ccsv_reader *reader = ccsv_init_reader(&options); +``` + + +### Read a row with + +```c +CSVRow *row = read_row(fp, reader); // Will return NULL if all rows are read +``` + +`fp` is a FILE pointer to the CSV file you want to read + +`reader` is a ccsv_reader object + +### Get the number of fields in a row with + +```c +int row_len = row->fields_count; +``` + +### Get a field from a row with + +```c +char *field = row->fields[0]; // 0 for the first field +``` + +### Free the memory allocated to a row with + +```c +free_row(row); +``` + +### Free the memory allocated to the reader with + +```c +free(reader); +``` + + + +## Example + +```c +#include +#include + +#include "ccsv.h" + +int main(void) +{ + FILE *fp = fopen("../../ign.csv", "r"); // Specify the path to your file + + if (fp == NULL) + { + printf("Error opening file\n"); + exit(1); + } + + // Reader object + ccsv_reader *reader = ccsv_init_reader(NULL); // NULL for default options + + CSVRow *row; + + // Read each row and print each field + while ((row = read_row(fp, reader)) != NULL) + { + int row_len = row->fields_count; // Get number of fields in the row + for (int i = 0; i < row_len; i++) + { + printf("%d.Field: %s\n", i + 1, row->fields[i]); // Print each field + } + printf("\n"); + free_row(row); // Free the memory allocated to the row + } + printf("\n\nRows read: %d\n", reader->rows_read); // Print number of rows read + + free(reader); // Free the memory allocated to the reader + fclose(fp); + + return 0; +} +``` + +#### You can find more examples in the `examples` folder + +Compile with `make ./example_file_name` + + +For full documentation, see the [docs](https://github.com/Ayush-Tripathy/ccsv/tree/main/docs) + +## License +This project is licensed under the MIT License. See the [LICENSE](LICENSE) + +## Feel free to contribute! diff --git a/lib/ccsv/docs/ccsv.pdf b/lib/ccsv/docs/ccsv.pdf new file mode 100644 index 0000000..4c68ac6 Binary files /dev/null and b/lib/ccsv/docs/ccsv.pdf differ diff --git a/lib/ccsv/examples/count_rf.c b/lib/ccsv/examples/count_rf.c new file mode 100644 index 0000000..a2f30c7 --- /dev/null +++ b/lib/ccsv/examples/count_rf.c @@ -0,0 +1,46 @@ +#include +#include +#include + +#include "ccsv.h" + +int main(int argc, char *argv[]) +{ + clock_t start = clock(); + + if (argc != 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + char *filename = argv[1]; + + ccsv_reader_options options = {.skip_initial_space = 1, .skip_empty_lines = 0}; + + ccsv_reader *reader = ccsv_open(filename, CCSV_READER, "r", &options, NULL); + if (reader == NULL || ccsv_is_error(reader, NULL)) + { + fprintf(stderr, "Error initializing CSV reader\n"); + return 1; + } + + ccsv_row *row; + + size_t fields_count = 0; + while ((row = ccsv_next(reader)) != NULL) + { + fields_count += row->fields_count; + ccsv_free_row(row); + } + + printf("%s: %d rows, %ld fields\n", argv[1], reader->rows_read, fields_count); + + ccsv_close(reader); + + clock_t end = clock(); + double time_spent = (double)(end - start) / CLOCKS_PER_SEC; + printf("Time taken: %lf seconds\n", time_spent); + + return 0; +} diff --git a/lib/ccsv/examples/cpy.c b/lib/ccsv/examples/cpy.c new file mode 100644 index 0000000..856f1f6 --- /dev/null +++ b/lib/ccsv/examples/cpy.c @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "ccsv.h" + +int main(int argc, char *argv[]) +{ + clock_t start = clock(); + + if (argc != 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + char *filename = argv[1]; + + ccsv_reader *reader = ccsv_init_reader(NULL, NULL); + if (reader == NULL || reader == (void *)CCSV_ERNOMEM) + { + fprintf(stderr, "Error initializing CSV reader\n"); + return 1; + } + + FILE *file = fopen(filename, "r"); + if (file == NULL) + { + fprintf(stderr, "Error opening file\n"); + free(reader); + return 1; + } + + FILE *output = fopen("output.csv", "w+"); + if (output == NULL) + { + fprintf(stderr, "Error opening file\n"); + free(reader); + fclose(file); + return 1; + } + + ccsv_writer *writer = ccsv_init_writer(NULL, NULL); + + ccsv_row *row; + + size_t fields_count = 0; + while ((row = read_row(file, reader)) != NULL) + { + fields_count += row->fields_count; + write_row(output, writer, *row); + ccsv_free_row(row); + } + + printf("%s - %d rows, %ld fields\n", argv[1], reader->rows_read, fields_count); + printf("Output written to output.csv\n"); + + fclose(file); + fclose(output); + free(reader); + free(writer); + + clock_t end = clock(); + double time_spent = (double)(end - start) / CLOCKS_PER_SEC; + printf("Time taken: %lf seconds\n", time_spent); + + return 0; +} diff --git a/lib/ccsv/examples/cpy_new.c b/lib/ccsv/examples/cpy_new.c new file mode 100644 index 0000000..a4b0b68 --- /dev/null +++ b/lib/ccsv/examples/cpy_new.c @@ -0,0 +1,52 @@ +#include +#include + +#include "ccsv.h" + +int main(int argc, char **argv) +{ + clock_t start = clock(); + + if (argc != 3) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + char *source = argv[1]; + char *destination = argv[2]; + + ccsv_reader *reader = ccsv_open(source, CCSV_READER, "r", NULL, NULL); + if (reader == NULL) + { + fprintf(stderr, "Error initializing CSV reader\n"); + return 1; + } + + ccsv_writer *writer = ccsv_open(destination, CCSV_WRITER, "w+", NULL, NULL); + if (writer == NULL) + { + fprintf(stderr, "Error initializing CSV writer\n"); + return 1; + } + + ccsv_row *row; + while ((row = ccsv_next(reader)) != NULL) + { + ccsv_write(writer, *row); + ccsv_free_row(row); + } + + printf("Rows read: %d\n", reader->rows_read); + + printf("CSV file written to %s\n", destination); + + ccsv_close(reader); + ccsv_close(writer); + + clock_t end = clock(); + double time_spent = (double)(end - start) / CLOCKS_PER_SEC; + printf("Time taken: %lf seconds\n", time_spent); + + return 0; +} \ No newline at end of file diff --git a/lib/ccsv/examples/handling_memory_allocation_failure.c b/lib/ccsv/examples/handling_memory_allocation_failure.c new file mode 100644 index 0000000..fe5ea0b --- /dev/null +++ b/lib/ccsv/examples/handling_memory_allocation_failure.c @@ -0,0 +1,57 @@ +#include +#include + +#include "../include/ccsv.h" + +int main(void) +{ + FILE *csv_file = fopen("../../ign.csv", "r"); + if (csv_file == NULL) + { + perror("Error opening file"); + return 1; + } + + ccsv_reader_options options = { + .delim = ',', // Example delimiter, change according to your CSV file + .quote_char = '"', + .skip_initial_space = 0, + // Add other options if necessary + }; + + ccsv_reader *reader = ccsv_init_reader(&options, NULL); + if (reader == NULL || ccsv_is_error(reader, NULL)) + { + fprintf(stderr, "Error initializing CSV reader\n"); + fclose(csv_file); + return 1; + } + + ccsv_row *row; + short err_status; + while (1) + { + row = read_row(csv_file, reader); + if (row == NULL && ccsv_is_error(reader, &err_status)) + { + if (err_status == CCSV_ERNOMEM) + fprintf(stderr, "Memory allocation failure while reading row\n"); + break; + } + + int fields_count = row->fields_count; + for (int i = 0; i < fields_count; ++i) + { + printf("%s\t", row->fields[i]); + } + printf("\n"); + + ccsv_free_row(row); + } + printf("\n\nRows read: %d\n", reader->rows_read); + + fclose(csv_file); + free(reader); + + return 0; +} diff --git a/lib/ccsv/examples/makefile b/lib/ccsv/examples/makefile new file mode 100644 index 0000000..ab2471e --- /dev/null +++ b/lib/ccsv/examples/makefile @@ -0,0 +1,8 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -I../include + +%: %.c + $(CC) $(CFLAGS) -o $@.out $< ../src/ccsv.c + +clean: + rm -f *.out diff --git a/lib/ccsv/examples/print_each_row.c b/lib/ccsv/examples/print_each_row.c new file mode 100644 index 0000000..e2d7b4e --- /dev/null +++ b/lib/ccsv/examples/print_each_row.c @@ -0,0 +1,50 @@ +#include +#include + +#include "../include/ccsv.h" + +int main(void) +{ + + // ** There is new way to read rows in ccsv v0.5.0 ** + // ** Check examples/print_each_row_ccsv_v0.5.c ** + + FILE *fp = fopen("../../ign.csv", "r"); // Specify the path to your file + + if (fp == NULL) + { + printf("Error opening file\n"); + exit(1); + } + + // Reader object + ccsv_reader *reader = ccsv_init_reader(NULL, NULL); // NULL for default options + /* + Default options: + delim = ',' + quote_char = '"' + skip_initial_space = 0 + skip_empty_lines = 0 + skip_comments = 0 + */ + + ccsv_row *row; + + // Read each row and print each field + while ((row = read_row(fp, reader)) != NULL) + { + int row_len = row->fields_count; // Get number of fields in the row + for (int i = 0; i < row_len; i++) + { + printf("%d.Field: %s\n", i + 1, row->fields[i]); // Print each field + } + printf("\n"); + ccsv_free_row(row); // Free the memory allocated to the row + } + printf("\n\nRows read: %d\n", reader->rows_read); // Print number of rows read + + free(reader); // Free the memory allocated to the reader + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/lib/ccsv/examples/print_each_row_ccsv_v0.5.c b/lib/ccsv/examples/print_each_row_ccsv_v0.5.c new file mode 100644 index 0000000..539d678 --- /dev/null +++ b/lib/ccsv/examples/print_each_row_ccsv_v0.5.c @@ -0,0 +1,41 @@ +#include +#include + +#include "../include/ccsv.h" + +int main(void) +{ + ccsv_reader_options options = { + .delim = ',', + .quote_char = '"', + .skip_comments = 1, + .skip_initial_space = 0, + .skip_empty_lines = 0, + }; + // Reader object + ccsv_reader *reader = ccsv_open("../../comments.csv", CCSV_READER, "r", &options, NULL); // NULL for default options + if (reader == NULL) + { + fprintf(stderr, "Error initializing CSV reader\n"); + return 1; + } + + ccsv_row *row; + + // Read each row and print each field + while ((row = ccsv_next(reader)) != NULL) + { + int row_len = row->fields_count; // Get number of fields in the row + for (int i = 0; i < row_len; i++) + { + printf("%d.Field: %s\n", i + 1, row->fields[i]); // Print each field + } + printf("\n"); + ccsv_free_row(row); // Free the memory allocated to the row + } + printf("\n\nRows read: %d\n", reader->rows_read); // Print number of rows read + + ccsv_close(reader); // Close the reader + + return 0; +} \ No newline at end of file diff --git a/lib/ccsv/examples/print_headers.c b/lib/ccsv/examples/print_headers.c new file mode 100644 index 0000000..927c41d --- /dev/null +++ b/lib/ccsv/examples/print_headers.c @@ -0,0 +1,35 @@ +#include +#include + +#include "../include/ccsv.h" + +int main(void) +{ + FILE *fp = fopen("../../ign.csv", "r"); // Specify the path to your file + + if (fp == NULL) + { + printf("Error opening file\n"); + exit(1); + } + + // ** There is new way to read rows in ccsv v0.5.0 ** + // ** Check examples/print_each_row_ccsv_v0.5.c ** + + // Reader object + ccsv_reader *reader = ccsv_init_reader(NULL, NULL); // NULL for default options + + ccsv_row *row = read_row(fp, reader); + + int row_len = row->fields_count; // Get number of fields in the row + for (int i = 0; i < row_len; i++) + { + printf("%d.Field: %s\n", i + 1, row->fields[i]); // Print each field + } + + ccsv_free_row(row); // Free the memory allocated to the row + free(reader); // Free the memory allocated to the reader + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/lib/ccsv/examples/with_reader_options.c b/lib/ccsv/examples/with_reader_options.c new file mode 100644 index 0000000..223c204 --- /dev/null +++ b/lib/ccsv/examples/with_reader_options.c @@ -0,0 +1,56 @@ +#include +#include + +#include "../include/ccsv.h" + +int main(void) +{ + FILE *fp = fopen("../../ign.csv", "r"); // Specify the path to your file + + if (fp == NULL) + { + printf("Error opening file\n"); + exit(1); + } + + /* + ------- Way 1 ------- + ccsv_reader_options *options = (ccsv_reader_options *)malloc(sizeof(ccsv_reader_options)); + options->delim = ','; + options->quote_char = '"'; + options->skip_initial_space = 0; + */ + + /* OR */ + + ccsv_reader_options options = { + .delim = ',', + .quote_char = '"', + .skip_initial_space = 0, + .skip_empty_lines = 1, + .skip_comments = 1}; + + // Reader object + ccsv_reader *reader = ccsv_init_reader(&options, NULL); // NULL for default options + // free(options); /* If you used Way 1 */ + + ccsv_row *row; + + // Read each row and print each field + while ((row = read_row(fp, reader)) != NULL) + { + int row_len = row->fields_count; // Get number of fields in the row + for (int i = 0; i < row_len; i++) + { + printf("%s\t", row->fields[i]); // Print each field + } + printf("\n"); + ccsv_free_row(row); // Free the memory allocated to the row + } + printf("\n\nRows read: %d\n", reader->rows_read); // Print number of rows read + + free(reader); // Free the memory allocated to the reader + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/lib/ccsv/examples/writer_macros.c b/lib/ccsv/examples/writer_macros.c new file mode 100644 index 0000000..620844a --- /dev/null +++ b/lib/ccsv/examples/writer_macros.c @@ -0,0 +1,50 @@ +#include +#include + +#include "ccsv.h" + +int main(void) +{ + // Initialize ccsv_writer_options + ccsv_writer_options options = { + .delim = ',', + .quote_char = '"' + // Add other options if necessary + }; + + // Initialize the writer + ccsv_writer *writer = ccsv_init_writer(&options, NULL); + if (writer == NULL) + { + fprintf(stderr, "Error initializing CSV writer\n"); + return 1; + } + + FILE *file = fopen("output.csv", "w+"); + if (file == NULL) + { + fprintf(stderr, "Error opening file\n"); + free(writer); + return 1; + } + + CCSV_WRITE_ROW_START(file, writer); // Write row start + CCSV_WRITE_FIELD(file, writer, "hi"); // Write field + CCSV_WRITE_FIELD(file, writer, "hello, world!"); // Write field + CCSV_WRITE_FIELD(file, writer, "\"escapedword\""); // Write field + CCSV_WRITE_ROW_END(file, writer, NULL); // Write row end + + short err_status; + if (ccsv_is_error(writer, &err_status)) + { + fprintf(stderr, "Error writing CSV row from string: %s\n", ccsv_get_status_message(err_status)); + fclose(file); + free(writer); + return 1; + } + + fclose(file); + free(writer); + + return 0; +} diff --git a/lib/ccsv/examples/writing_from_CSVRow_struct.c b/lib/ccsv/examples/writing_from_CSVRow_struct.c new file mode 100644 index 0000000..725fa65 --- /dev/null +++ b/lib/ccsv/examples/writing_from_CSVRow_struct.c @@ -0,0 +1,64 @@ +#include +#include + +#include "ccsv.h" + +int main(void) +{ + // Initialize ccsv_writer_options + ccsv_writer_options options = { + .delim = ',', + .quote_char = '"' + // Add other options if necessary + }; + + // Initialize the writer + ccsv_writer *writer = ccsv_init_writer(&options, NULL); + if (writer == NULL) + { + fprintf(stderr, "Error initializing CSV writer\n"); + return 1; + } + + ccsv_reader *reader = ccsv_init_reader(NULL, NULL); + + FILE *dest_file = fopen("output.csv", "a+"); + if (dest_file == NULL) + { + fprintf(stderr, "Error opening file\n"); + free(writer); + return 1; + } + FILE *source_file = fopen("../../ign.csv", "r"); + if (source_file == NULL) + { + fprintf(stderr, "Error opening file\n"); + free(writer); + return 1; + } + + ccsv_row *row = read_row(source_file, reader); + write_row(dest_file, writer, *row); // Pass the value of the row pointer + ccsv_free_row(row); // Free the row + + row = read_row(source_file, reader); // Read the next row + write_row(dest_file, writer, *row); // Write row to file + ccsv_free_row(row); // Free the row + + if (ccsv_is_error(writer, NULL)) + { + fprintf(stderr, "Error writing CSV row.\n"); + fclose(dest_file); + fclose(source_file); + free(reader); + free(writer); + return 1; + } + + fclose(dest_file); + fclose(source_file); + free(reader); + free(writer); + + return 0; +} diff --git a/lib/ccsv/examples/writing_from_array.c b/lib/ccsv/examples/writing_from_array.c new file mode 100644 index 0000000..55f99ad --- /dev/null +++ b/lib/ccsv/examples/writing_from_array.c @@ -0,0 +1,47 @@ +#include +#include +#include "../include/ccsv.h" + +int main(void) +{ + // Initialize ccsv_writer_options + ccsv_writer_options options = { + .delim = ',', + .quote_char = '"' + // Add other options if necessary + }; + + // Initialize the writer + ccsv_writer *writer = ccsv_init_writer(&options, NULL); + if (writer == NULL) + { + fprintf(stderr, "Error initializing CSV writer\n"); + return 1; + } + + FILE *file = fopen("output.csv", "a+"); + if (file == NULL) + { + fprintf(stderr, "Error opening file\n"); + free(writer); + return 1; + } + + char *row_string[] = {"hi", "hello", "hello, world!", "\"escapedword\"", "hola", "bonjour"}; + + write_row_from_array(file, writer, row_string, ARRAY_LEN(row_string)); /* Write row to file */ + + short err_status; + if (ccsv_is_error(writer, &err_status)) + { + fprintf(stderr, "Error writing CSV row from string: %s\n", ccsv_get_status_message(err_status)); + fclose(file); + free(writer); + return 1; + } + + fclose(file); + free(writer); + + return 0; +} diff --git a/lib/ccsv/include/ccsv.h b/lib/ccsv/include/ccsv.h new file mode 100644 index 0000000..8a5f936 --- /dev/null +++ b/lib/ccsv/include/ccsv.h @@ -0,0 +1,442 @@ +// File: ccsv.h + +// Created: 2023 by Ayush Tripathy +// github.com/Ayush-Tripathy + +/* + * CCSV - A CSV parser and writer library for C. + * Version: 0.1 + * + * For full documentation, see the README.md file. + */ + +/* + MIT License + + Copyright (c) 2023 Ayush Tripathy + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#include +#include + +#define CCSV_VERSION 0.1f + +#ifdef __cplusplus +extern "C" +{ +#endif + +// File sizes +#define CCSV_LARGE_FILE_SIZE 524288000ULL /* 500 MiB */ +#define CCSV_MED_FILE_SIZE 20971520ULL /* 20 MiB */ + +// Buffer sizes +#define CCSV_HIGH_BUFFER_SIZE 65536 /* 64 KiB */ +#define CCSV_MED_BUFFER_SIZE 16384 /* 16 KiB */ +#define CCSV_LOW_BUFFER_SIZE 2048 /* 2 KiB */ + +#define CCSV_BUFFER_SIZE 8096 +#define MAX_FIELD_SIZE 256 + +// Default values +#define CCSV_DELIMITER 0x2c +#define CCSV_QUOTE_CHAR 0x22 +#define CCSV_CR 0x0d +#define CCSV_LF 0x0a +#define CCSV_SPACE 0x20 +#define CCSV_TAB 0x09 +#define CCSV_COMMENT_CHAR 0x23 +#define CCSV_NULL_CHAR 0x00 + +#define DEFAULT_DELIMITER CCSV_DELIMITER +#define DEFAULT_QUOTE_CHAR CCSV_QUOTE_CHAR +#define DEFAULT_ESCAPE_CHAR CCSV_QUOTE_CHAR +#define DEFAULT_COMMENT_CHAR CCSV_COMMENT_CHAR + +#define TOTAL_ERROR_MESSAGES 7 + +// Return codes +#define CCSV_SUCCESS 0 +#define CCSV_ERROR -1 +#define CCSV_ERNOMEM -2 +#define CCSV_ERINVALID -3 +#define CCSV_ERNULLFP -6 /* File pointer is NULL */ +#define CCSV_ERMODE -7 /* Invalid mode */ +#define CCSV_EROPEN -8 /* Error opening file */ +#define CCSV_ERINVOBJTYPE -9 /* Invalid object type */ +#define CCSV_ERNULLROW -10 /* Row is NULL */ +#define CCSV_ERBUFNTALLOC -11 /* Buffer not allocated */ + +#define WRITE_SUCCESS CCSV_SUCCESS +#define WRITE_STARTED 1 +#define WRITE_ENDED 2 +#define WRITE_ERNOTSTARTED -4 /* Writer not started */ +#define WRITE_ERNOMEM CCSV_ERNOMEM +#define WRITE_ERINVALID CCSV_ERINVALID +#define WRITE_ERALWRITING -5 /* Already writing field */ + +// Object types +#define CCSV_READER 21 +#define CCSV_WRITER 22 + +#define ARRAY_LEN(array) sizeof(array) / sizeof(array[0]) + +// Writer Macros +/* Start new row */ +#define CCSV_WRITE_ROW_START(fp, writer) _write_row_start(fp, writer) + +/* Write field */ +#define CCSV_WRITE_FIELD(fp, writer, string) \ + if (writer->__state == WRITER_ROW_START) \ + { \ + writer->__state = WRITER_WRITING_FIELD; \ + } \ + else \ + { \ + fputc(writer->__delim, fp); \ + } \ + _write_field(fp, writer, string); + +/* End row, with an additional field */ +#define CCSV_WRITE_ROW_END(fp, writer, last_field) \ + if (last_field) \ + { \ + _write_field(fp, writer, last_field); \ + } \ + _write_row_end(fp, writer); + + typedef enum State + { + FIELD_START, /* Start of field */ + FIELD_NOT_STARTED, /* Spaces before field start */ + FIELD_END, /* End of field */ + FIELD_STARTED, /* Inside field */ + INSIDE_QUOTED_FIELD, /* Inside quoted field */ + MAY_BE_ESCAPED /* Quote char detected inside quoted field */ + } State; + + typedef enum WriterState + { + WRITER_NOT_STARTED, /* Writer not started */ + WRITER_ROW_START, /* Writer setup done */ + WRITER_WRITING_FIELD, /* Field writing started */ + WRITER_ROW_END /* Row writing ended */ + } WriterState; + + typedef struct ccsv_reader_options + { + char delim; + char quote_char; + char comment_char; + char escape_char; + int skip_initial_space; + int skip_empty_lines; + int skip_comments; + } ccsv_reader_options; + + typedef struct ccsv_reader + { + int rows_read; + char __delim; + char __quote_char; + char __comment_char; + char __escape_char; + int __skip_initial_space; + int __skip_empty_lines; + int __skip_comments; + char *__buffer; + size_t __buffer_pos; + size_t __buffer_size; + bool __buffer_allocated; + FILE *__fp; + short status; + short object_type; + } ccsv_reader; + + typedef struct ccsv_row + { + char **fields; + int fields_count; + } ccsv_row; + + typedef struct ccsv_writer_options + { + char delim; + char quote_char; + char escape_char; + } ccsv_writer_options; + + typedef struct ccsv_writer + { + char __delim; + char __quote_char; + char __escape_char; + WriterState __state; + FILE *__fp; + short write_status; + short object_type; + } ccsv_writer; + + // Public functions ------------------------------------------------------------------------ + + /* -------- General -------- */ + + /* + * This function opens a file and attaches it with specified object. + * + * returns: + * void*: pointer to the object + */ + void *ccsv_open(const char *filename, short object_type, const char *mode, void *options, short *status); + + /* + * This function closes the ccsv object (reader or writer). + * + * params: + * obj: pointer to the object + */ + void ccsv_close(void *obj); + + /* + * This function returns the status message for the given status code. + * + * params: + * status: status code + * + * returns: + * char*: pointer to the status message + */ + const char *ccsv_get_status_message(short status); + + /* + * This function returns if error occurred in the object. + * + * params: + * obj: pointer to the object + * + * returns: + * int: 1, if error occurred + * int: 0, if no error occurred + */ + int ccsv_is_error(void *obj, short *status); + + /* -------- Reader -------- */ + + /* + * This function initializes the parser with the given parameters, and + * returns a pointer to the parser. + * + * params: + * options: pointer to the reader options struct + * + * returns: + * ccsv_reader*: pointer to the reader + */ + ccsv_reader *ccsv_init_reader(ccsv_reader_options *options, short *status); + + /* + * This function reads a row from the file pointer, and returns a pointer + * to CSVRow struct. + * + * params: + * fp: file pointer + * parser: pointer to the parser + * + * returns: + * CSVRow*: pointer to the CSVRow struct + */ + ccsv_row *read_row(FILE *fp, ccsv_reader *parser); + + /* + * This function reads a row from reader, and returns a pointer + * to CSVRow struct. + * + * params: + * reader: pointer to the reader + * + * returns: + * CSVRow*: pointer to the CSVRow struct + */ + ccsv_row *ccsv_next(ccsv_reader *reader); + + /* + * This function frees the memory allocated to the CSVRow struct. + * + * params: + * row: pointer to the CSVRow struct + */ + void ccsv_free_row(ccsv_row *row); + + /* -------- Writer -------- */ + + /* + * This function initializes the writer with the given parameters, and + * returns a pointer to the writer. + * + * params: + * options: pointer to the writer options struct + * + * returns: + * ccsv_writer*: pointer to the writer + * + */ + ccsv_writer *ccsv_init_writer(ccsv_writer_options *options, short *status); + + /* + * This function writes a row (from CSVRow struct) to the file pointer. + */ + int ccsv_write(ccsv_writer *writer, ccsv_row row); + + /* + * This function writes a row (from string array) to the file pointer. + * + * params: + * writer: pointer to the writer + * row_string: pointer to the row string + * + * returns: + * int: 0, if successful + * CSV_ERNOMEM, if memory allocation failed + */ + int ccsv_write_from_array(ccsv_writer *writer, char **fields, int fields_len); + + /* + * This function writes a row (from CSVRow struct) to the file pointer. + * + * params: + * fp: file pointer + * writer: pointer to the writer + * row: CSVRow struct + */ + int write_row(FILE *fp, ccsv_writer *writer, ccsv_row row); + + /* + *This function writes a row (from string array) to the file pointer. + * + * params: + * fp: file pointer + * writer: pointer to the writer + * row_string: pointer to the row string + * + * returns: + * int: 0, if successful + * CSV_ERNOMEM, if memory allocation failed + */ + int write_row_from_array(FILE *fp, ccsv_writer *writer, char **fields, int row_len); + + // Private functions ----------------------------------------------------------------------- + + /* + * This function returns the object type of the object. + * + * params: + * obj: pointer to the object + * + * returns: + * int: object type + */ + int _get_object_type(void *obj); + + /* + * This function reads a row from the file pointer, and returns a pointer to CSVRow struct. + * + * params: + * fp: file pointer + * reader: pointer to the reader + * + * returns: + * CSVRow*: pointer to the CSVRow struct + */ + ccsv_row *_read_row(FILE *fp, ccsv_reader *reader); + + /* + * This function reads a row from the file pointer, and returns a pointer to CSVRow struct. + * + * params: + * fp: file pointer + * reader: pointer to the reader + * + * returns: + * CSVRow*: pointer to the CSVRow struct + */ + ccsv_row *_next(FILE *fp, ccsv_reader *reader); + + /* + * This functions checks if the reader buffer is empty. + */ + int _is_buffer_empty(ccsv_reader *reader); + + /* + *This function frees multiple pointers. + * + * params: + * num: number of pointers to free + * ...: pointers to free + * + */ + void _free_multiple(int num, ...); + + /* + * This function writes a field to the file pointer. + * + * params: + * fp: file pointer + * writer: pointer to the writer + * string: pointer to the string + * string_len: length of the string + * string_pos: pointer to the position of the string + * + * returns: + * size_t: number of characters written + */ + int _write_field(FILE *fp, ccsv_writer *writer, const char *string); + + /* + *This function writes a row start to the file pointer. + * + * params: + * fp: file pointer + * writer: pointer to the writer + * + * returns: + * int: WRITE_STARTED, if successful + * int: WRITE_ERALWRITING, if already writing field + */ + int _write_row_start(FILE *fp, ccsv_writer *writer); + + /* + * This function writes a row end to the file pointer. + * + * params: + * fp: file pointer + * writer: pointer to the writer + * + * returns: + * int: WRITE_ENDED, if successful + * int: WRITE_ERNOTSTARTED, if writer not started + */ + int _write_row_end(FILE *fp, ccsv_writer *writer); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/ccsv/src/ccsv.c b/lib/ccsv/src/ccsv.c new file mode 100644 index 0000000..505a01e --- /dev/null +++ b/lib/ccsv/src/ccsv.c @@ -0,0 +1,1179 @@ +// File: ccsv.c + +// Created: 2023 by Ayush Tripathy +// github.com/Ayush-Tripathy + +/* + * This library provides functions to handle reading, writing csv files. + */ + +/* + MIT License + + Copyright (c) 2023 Ayush Tripathy + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +// ccsv header file +#include "ccsv.h" + + const char *status_messages[] = { + "Success", + "Error", + "Memory allocation failure.", + "Malformed CSV file.", + "Not started writing, CSV_WRITE_ROW_START() not called.", + "Already writing field, CSV_WRITE_ROW_START() already called."}; + + const char *ccsv_get_status_message(short status) + { + if (status > 0) + return status_messages[0]; + + if (status < -5) + return NULL; + return status_messages[-1 * status]; + } + + int _get_object_type(void *obj) + { + if (obj == NULL) + return CCSV_NULL_CHAR; + + if (((ccsv_reader *)obj)->object_type == CCSV_READER) + return CCSV_READER; + + if (((ccsv_writer *)obj)->object_type == CCSV_WRITER) + return CCSV_WRITER; + + return CCSV_NULL_CHAR; + } + + int ccsv_is_error(void *obj, short *status) + { + if (obj == NULL) + return 0; + + if (_get_object_type(obj) == CCSV_READER) + { + short __status = ((ccsv_reader *)obj)->status; + if (status != NULL) + *status = __status; + return __status < 0; + } + + if (_get_object_type(obj) == CCSV_WRITER) + { + short __status = ((ccsv_writer *)obj)->write_status; + if (status != NULL) + *status = __status; + return __status < 0; + } + + return 0; + } + +/* Reader */ + +// These macros should be used only in read_row() function +#define ADD_FIELD(field) \ + field[field_pos++] = CCSV_NULL_CHAR; \ + fields_count++; \ + fields = (char **)realloc(fields, sizeof(char *) * fields_count); \ + if (fields == NULL) \ + { \ + _free_multiple(3, field, row_string, row); \ + reader->status = CCSV_ERNOMEM; \ + return NULL; \ + } \ + fields[fields_count - 1] = field; + +#define GROW_FIELD_BUFFER_IF_NEEDED(field, field_size, field_pos) \ + if (field_pos > field_size - 1) \ + { \ + field_size += MAX_FIELD_SIZE; \ + field = (char *)realloc(field, field_size + 1); \ + if (field == NULL) \ + { \ + _free_multiple(3, fields, row_string, row); \ + reader->status = CCSV_ERNOMEM; \ + return NULL; \ + } \ + } + +#define GROW_ROW_BUFFER_IF_NEEDED(row_string, row_len, row_pos) \ + if (row_pos > row_len - 1) \ + { \ + row_string_size += reader->__buffer_size; \ + row_string = (char *)realloc(row_string, row_string_size + 1); \ + if (row_string == NULL) \ + { \ + _free_multiple(2, fields, row); \ + reader->status = CCSV_ERNOMEM; \ + return NULL; \ + } \ + } + +#define RETURN_IF_WRITE_ERROR(writer, desired_status) \ + if (writer->write_status != desired_status) \ + return writer->write_status; + + ccsv_reader *ccsv_init_reader(ccsv_reader_options *options, short *status) + { + char delim, quote_char, comment_char, escape_char; + int skip_initial_space, skip_empty_lines, skip_comments; + if (options == NULL) + { + delim = DEFAULT_DELIMITER; + quote_char = DEFAULT_QUOTE_CHAR; + comment_char = DEFAULT_COMMENT_CHAR; + escape_char = DEFAULT_ESCAPE_CHAR; + skip_initial_space = 0; + skip_empty_lines = 0; + skip_comments = 0; + } + else + { + // It is not mandatory to pass all options to options struct + // So check if the option is passed or not, if not then use the default value + if (options->delim == CCSV_NULL_CHAR) + delim = DEFAULT_DELIMITER; + + else + delim = options->delim; + + if (options->quote_char == CCSV_NULL_CHAR) + quote_char = DEFAULT_QUOTE_CHAR; + + else + quote_char = options->quote_char; + + if (options->comment_char == CCSV_NULL_CHAR) + comment_char = DEFAULT_COMMENT_CHAR; + + else + comment_char = options->comment_char; + + if (options->escape_char == CCSV_NULL_CHAR) + escape_char = DEFAULT_ESCAPE_CHAR; + + else + escape_char = options->escape_char; + + if (options->skip_initial_space == CCSV_NULL_CHAR) + skip_initial_space = 0; + + else + skip_initial_space = options->skip_initial_space; + + if (options->skip_empty_lines == CCSV_NULL_CHAR) + skip_empty_lines = 0; + + else + skip_empty_lines = options->skip_empty_lines; + + if (options->skip_comments == CCSV_NULL_CHAR) + skip_comments = 0; + + else + skip_comments = options->skip_comments; + } + + // Parser + ccsv_reader *parser = (ccsv_reader *)malloc(sizeof(ccsv_reader)); + if (parser == NULL) + { + if (status != NULL) + *status = CCSV_ERNOMEM; + return NULL; + } + parser->__delim = delim; + parser->__quote_char = quote_char; + parser->__comment_char = comment_char; + parser->__escape_char = escape_char; + parser->__skip_initial_space = skip_initial_space; + parser->__skip_empty_lines = skip_empty_lines; + parser->__skip_comments = skip_comments; + + parser->__fp = NULL; + + parser->rows_read = 0; + parser->status = CCSV_SUCCESS; + parser->object_type = CCSV_READER; + + return parser; + } + + void ccsv_free_row(ccsv_row *row) + { + const int fields_count = row->fields_count; + for (int i = 0; i < fields_count; i++) + { + free(row->fields[i]); + } + free(row->fields); + free(row); + } + + void _free_multiple(int num, ...) + { + va_list args; + va_start(args, num); + + for (int i = 0; i < num; ++i) + { + void *ptr = va_arg(args, void *); + free(ptr); + } + + va_end(args); + } + + void *ccsv_open(const char *filename, short object_type, const char *mode, void *options, short *status) + { + if (filename == NULL) + return NULL; + + if (object_type != CCSV_READER && object_type != CCSV_WRITER) + { + if (status != NULL) + *status = CCSV_ERINVOBJTYPE; + return NULL; + } + + if (strcmp(mode, "r") != 0 && + strcmp(mode, "rb") != 0 && + strcmp(mode, "r+") != 0 && + strcmp(mode, "rb+") != 0 && + strcmp(mode, "w+") != 0 && + strcmp(mode, "a+") != 0) + { + if (status != NULL) + *status = CCSV_ERMODE; + return NULL; + } + + if (object_type == CCSV_READER) + { + short init_status; + +#ifdef __cplusplus + ccsv_reader_options *reader_options = reinterpret_cast(options); + ccsv_reader *reader = ccsv_init_reader(reader_options, &init_status); +#else + options = (ccsv_reader_options *)options; + ccsv_reader *reader = ccsv_init_reader(options, &init_status); +#endif + + if (init_status == CCSV_ERNOMEM || reader == NULL) + { + if (status != NULL) + *status = CCSV_ERNOMEM; + return NULL; + } + FILE *fp = fopen(filename, mode); + if (fp == NULL) + { + if (status != NULL) + *status = CCSV_EROPEN; + return NULL; + } + + if (!reader->__buffer_allocated) + { + size_t buffer_size = CCSV_BUFFER_SIZE; + + size_t file_size; + + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (file_size >= CCSV_LARGE_FILE_SIZE) + buffer_size = CCSV_HIGH_BUFFER_SIZE; + else if (file_size >= CCSV_MED_FILE_SIZE) + buffer_size = CCSV_MED_BUFFER_SIZE; + else + buffer_size = CCSV_LOW_BUFFER_SIZE; + + reader->__buffer = (char *)malloc(buffer_size + 1); + if (reader->__buffer == NULL) + { + free(reader); + if (status != NULL) + *status = CCSV_ERNOMEM; + return NULL; + } + reader->__buffer[0] = CCSV_NULL_CHAR; + + reader->__buffer_size = buffer_size; + reader->__buffer_allocated = true; + } + reader->__fp = fp; + reader->object_type = object_type; + + return reader; + } + else if (object_type == CCSV_WRITER) + { + FILE *fp = fopen(filename, mode); + if (fp == NULL) + { + if (status != NULL) + *status = CCSV_EROPEN; + return NULL; + } + + short init_status; + +#ifdef __cplusplus + ccsv_writer_options *writer_options = reinterpret_cast(options); + ccsv_writer *writer = ccsv_init_writer(writer_options, &init_status); +#else + options = (ccsv_writer_options *)options; + ccsv_writer *writer = ccsv_init_writer(options, &init_status); +#endif + if (init_status == CCSV_ERNOMEM || writer == NULL) + { + if (status != NULL) + *status = CCSV_ERNOMEM; + return NULL; + } + writer->__fp = fp; + writer->object_type = object_type; + + return writer; + } + return NULL; + } + + void ccsv_close(void *obj) + { + if (obj == NULL) + return; + + if (_get_object_type(obj) == CCSV_READER) + { + ccsv_reader *reader = (ccsv_reader *)obj; + fclose(reader->__fp); + free(reader); + } + else if (_get_object_type(obj) == CCSV_WRITER) + { + ccsv_writer *writer = (ccsv_writer *)obj; + fclose(writer->__fp); + free(writer); + } + else + { + return; + } + } + + ccsv_row *ccsv_next(ccsv_reader *reader) + { + if (reader == NULL) + return NULL; + + if (reader->__buffer == NULL) + { + reader->status = CCSV_ERBUFNTALLOC; + return NULL; + } + + if (reader->__fp == NULL) + { + reader->status = CCSV_ERNULLFP; + return NULL; + } + + return _next(reader->__fp, reader); + } + + ccsv_row *read_row(FILE *fp, ccsv_reader *reader) + { + if (fp == NULL) + { + reader->status = CCSV_ERNULLFP; + return NULL; + } + return _read_row(fp, reader); + } + + ccsv_row *_read_row(FILE *fp, ccsv_reader *reader) + { + ccsv_row *row = (ccsv_row *)malloc(sizeof(ccsv_row)); + if (row == NULL) + { + reader->status = CCSV_ERNOMEM; + return NULL; + } + + const char DELIM = reader->__delim; + const char QUOTE_CHAR = reader->__quote_char; + const char COMMENT_CHAR = reader->__comment_char; + const int SKIP_INITIAL_SPACE = reader->__skip_initial_space; + const int SKIP_EMPTY_LINES = reader->__skip_empty_lines; + const int SKIP_COMMENTS = reader->__skip_comments; + + State state = FIELD_START; + + char *row_string = (char *)malloc(CCSV_BUFFER_SIZE + 1); + if (row_string == NULL) + { + _free_multiple(1, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + size_t row_string_size = CCSV_BUFFER_SIZE; + size_t row_pos = 0; + + char **fields = (char **)malloc(sizeof(char *)); + if (fields == NULL) + { + _free_multiple(2, row_string, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + size_t fields_count = 0; + + char *field = (char *)malloc(MAX_FIELD_SIZE + 1); + if (field == NULL) + { + _free_multiple(3, row_string, fields, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + size_t field_size = MAX_FIELD_SIZE; + size_t field_pos = 0; + + int inside_quotes = 0; + + size_t row_len; + + readfile: + if (fgets(row_string, CCSV_BUFFER_SIZE, fp) == NULL) + { + /* If fields is not empty then return the row */ + if (fields_count > 0 || field_pos > 0) + { + /* Add the last field */ + + /* + * If fields_count > 0: + * This happens when the function holding the values of last row + * but yet to return the last row + * + * if field_pos > 0: + * This only happens when there is a single element in the last row and also + * there is no line after the current line + * So we need to add the only field of the last row + */ + ADD_FIELD(field); + goto end; + } + + _free_multiple(4, row_string, field, fields, row); + return NULL; + } + + row_len = strlen(row_string); + row_pos = 0; + + while (1) + { + char c = row_string[row_pos]; + switch (state) + { + case FIELD_START: + if (c == QUOTE_CHAR) + { + /* Start of quoted field */ + state = INSIDE_QUOTED_FIELD; + } + else if (SKIP_INITIAL_SPACE && c == CCSV_SPACE) + { + /* Skip initial spaces */ + state = FIELD_NOT_STARTED; + } + else if (c == DELIM || c == CCSV_CR || c == CCSV_LF) + { + /* Return empty field or empty row */ + state = FIELD_END; + row_pos--; + } + else + { + state = FIELD_STARTED; + field[field_pos++] = c; + } + break; + + case INSIDE_QUOTED_FIELD: + inside_quotes = 1; + if (c == QUOTE_CHAR) + { + /* Might be the end of the field, or it might be a escaped quote */ + state = MAY_BE_ESCAPED; + } + else + { + state = FIELD_STARTED; + field[field_pos++] = c; + } + break; + + case FIELD_NOT_STARTED: + if (c == QUOTE_CHAR) + { + /* Start of quoted field */ + state = INSIDE_QUOTED_FIELD; + } + else if (c == DELIM) + { + /* Return empty field */ + row_pos--; + state = FIELD_END; + } + else if (c == CCSV_SPACE) + { + /* + * Skip initial spaces, will only get to this point if + * skip_initial_spaces = 1 + */ + state = FIELD_NOT_STARTED; + } + else + { + /* Start of non-quoted field */ + state = FIELD_STARTED; + field[field_pos++] = c; + } + break; + + case FIELD_STARTED: + GROW_FIELD_BUFFER_IF_NEEDED(field, field_size, field_pos); + GROW_ROW_BUFFER_IF_NEEDED(row_string, row_len, row_pos); + + if (c == QUOTE_CHAR && inside_quotes) + { + /* Might be the end of the field, or it might be a escaped quote */ + state = MAY_BE_ESCAPED; + } + else if (c == QUOTE_CHAR && !inside_quotes) + { + /* Add the quote char if not at the start of field */ + state = FIELD_STARTED; + field[field_pos++] = c; + } + else if ((c == DELIM && !inside_quotes) || + ((c == CCSV_LF || c == CCSV_CR) && !inside_quotes) || + (c == CCSV_NULL_CHAR)) + { + /* End of field */ + state = FIELD_END; + row_pos--; + } + else + { + /* Add the character to the field */ + state = FIELD_STARTED; + field[field_pos++] = c; + } + break; + + case MAY_BE_ESCAPED: + if (c == QUOTE_CHAR) + { + /* Escaped quote */ + state = FIELD_STARTED; + field[field_pos++] = c; + } + else + { + /* End of field */ + inside_quotes = 0; + state = FIELD_STARTED; + row_pos--; + } + + break; + + case FIELD_END: + state = FIELD_START; + if (SKIP_EMPTY_LINES && + fields_count == 0 && + field_pos == 0 && + (c == CCSV_CR || c == CCSV_LF || c == CCSV_NULL_CHAR) && + !inside_quotes) + { + /* Do not return empty lines, parse again */ + goto readfile; + } + else if (SKIP_COMMENTS && + fields_count == 0 && + field_pos > 0 && + field[0] == COMMENT_CHAR && + !inside_quotes) + { + /* Do not return comment lines, parse again */ + field_pos = 0; + goto readfile; + } + else + { + ADD_FIELD(field); + } + field = (char *)malloc(MAX_FIELD_SIZE + 1); + field_size = MAX_FIELD_SIZE; + if (field == NULL) + { + _free_multiple(3, fields, row_string, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + field_pos = 0; + + if (c == CCSV_CR || c == CCSV_LF || c == CCSV_NULL_CHAR) + { + free(field); + goto end; + } + break; + + default: + break; + } + + row_pos++; + + if (row_pos > row_len - 1) + { + goto readfile; + } + } + + end: + row->fields = fields; + row->fields_count = fields_count; + free(row_string); + + reader->rows_read++; + reader->status = CCSV_SUCCESS; + return row; + } + +#define IS_TERMINATOR(c) (c == CCSV_CR || c == CCSV_LF || c == CCSV_NULL_CHAR) + + ccsv_row *_next(FILE *fp, ccsv_reader *reader) + { + ccsv_row *row = (ccsv_row *)malloc(sizeof(ccsv_row)); + if (row == NULL) + { + reader->status = CCSV_ERNOMEM; + return NULL; + } + + const char DELIM = reader->__delim; + const char QUOTE_CHAR = reader->__quote_char; + const char COMMENT_CHAR = reader->__comment_char; + const char ESCAPE_CHAR = reader->__escape_char; + const int SKIP_INITIAL_SPACE = reader->__skip_initial_space; + const int SKIP_EMPTY_LINES = reader->__skip_empty_lines; + const int SKIP_COMMENTS = reader->__skip_comments; + + size_t buffer_size = reader->__buffer_size; + + State state = FIELD_START; + + char *row_string = reader->__buffer; + size_t row_pos = 0; + + char **fields = (char **)malloc(sizeof(char *)); + if (fields == NULL) + { + _free_multiple(2, row_string, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + + size_t fields_count = 0; + + char *field = (char *)malloc(MAX_FIELD_SIZE + 1); + if (field == NULL) + { + _free_multiple(3, row_string, fields, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + + size_t field_size = MAX_FIELD_SIZE; + size_t field_pos = 0; + + size_t bytes_read; + readfile: + + /* Checking buffer */ + if (_is_buffer_empty(reader)) + { + bytes_read = fread(reader->__buffer, sizeof(char), buffer_size, fp); + + if (bytes_read <= 0) + { + if (fields_count > 0 || field_pos > 0) + { + /* Add the last field */ + + /* + * If fields_count > 0: + * This happens when the function holding the values of last row + * but yet to return the last row + * + * if field_pos > 0: + * This only happens when there is a single element in the last row and also + * there is no line after the current line + * So we need to add the only field of the last row + */ + ADD_FIELD(field); + goto end; + } + + _free_multiple(4, row_string, field, fields, row); + return NULL; + } + + reader->__buffer[bytes_read] = CCSV_NULL_CHAR; + reader->__buffer_size = bytes_read; + reader->__buffer_pos = 0; + row_pos = 0; + row_string = reader->__buffer; + + if (IS_TERMINATOR(row_string[row_pos]) && state == FIELD_START) + row_pos++; + } + else + { + row_string = reader->__buffer; + bytes_read = reader->__buffer_size; + row_pos = reader->__buffer_pos; + } + + for (; row_pos < bytes_read;) + { + char c = row_string[row_pos++]; + + switch (state) + { + case FIELD_START: + if (c == QUOTE_CHAR) + state = INSIDE_QUOTED_FIELD; /* Start of quoted field */ + else if (SKIP_INITIAL_SPACE && c == CCSV_SPACE) + state = FIELD_NOT_STARTED; /* Skip initial spaces */ + else if (c == DELIM || IS_TERMINATOR(c)) + { + state = FIELD_END; /* Empty field or empty row */ + row_pos--; + } + else + { + state = FIELD_STARTED; + field[field_pos++] = c; + } + break; + + case INSIDE_QUOTED_FIELD: + GROW_FIELD_BUFFER_IF_NEEDED(field, field_size, field_pos); + if (c == QUOTE_CHAR) + state = MAY_BE_ESCAPED; /* Might be the end of the field, or it might be a escaped quote */ + else if (c == ESCAPE_CHAR) + { + field[field_pos++] = c; /* Escaped escape character */ + row_pos++; + } + else + field[field_pos++] = c; + + break; + + case MAY_BE_ESCAPED: + if (c == QUOTE_CHAR) + { + state = INSIDE_QUOTED_FIELD; /* Escaped quote */ + field[field_pos++] = c; + } + else if (c == DELIM || IS_TERMINATOR(c)) + { + state = FIELD_END; /* End of field */ + row_pos--; + } + else + { + state = FIELD_STARTED; + field[field_pos++] = c; + } + + break; + + case FIELD_NOT_STARTED: + if (c == QUOTE_CHAR) + state = INSIDE_QUOTED_FIELD; /* Start of quoted field */ + else if (c == DELIM) + { + state = FIELD_END; /* Return empty field */ + row_pos--; + } + else if (c == CCSV_SPACE) + state = FIELD_NOT_STARTED; /* Skip initial spaces, will only get to this point if skip_initial_spaces = 1 */ + else + { + state = FIELD_STARTED; + field[field_pos++] = c; /* Start of non-quoted field */ + } + break; + + case FIELD_STARTED: + GROW_FIELD_BUFFER_IF_NEEDED(field, field_size, field_pos); + + if (c == DELIM || IS_TERMINATOR(c)) + { + state = FIELD_END; /* End of field */ + row_pos--; + } + else + field[field_pos++] = c; /* Add the character to the field */ + break; + + case FIELD_END: + state = FIELD_START; + + if (SKIP_EMPTY_LINES && + fields_count == 0 && + field_pos == 0 && + IS_TERMINATOR(c)) + { + /* Do not return empty lines, parse again */ + reader->__buffer_pos = row_pos; + goto readfile; + } + else if (SKIP_COMMENTS && + fields_count == 0 && + field_pos > 0 && + field[0] == COMMENT_CHAR) + { + /* Do not return comment lines, parse again */ + field_pos = 0; + reader->__buffer_pos = row_pos + 1; + goto readfile; + } + else + { + ADD_FIELD(field); + } + + field = (char *)malloc(MAX_FIELD_SIZE + 1); + field_size = MAX_FIELD_SIZE; + if (field == NULL) + { + _free_multiple(3, fields, row_string, row); + reader->status = CCSV_ERNOMEM; + return NULL; + } + field_pos = 0; + + if (IS_TERMINATOR(c)) /* CR or LF */ + { + + if (IS_TERMINATOR(row_string[row_pos])) /* CRLF */ + row_pos++; + + free(field); + goto end; + } + break; + + default: + break; + } + } + + // This point is reached only when for loop is completed fully + if (row_pos > bytes_read - 1) + { + reader->__buffer[0] = CCSV_NULL_CHAR; /* Reset the buffer */ + goto readfile; + } + + end: + row->fields = fields; + row->fields_count = fields_count; + + if (row_pos > bytes_read - 1) + reader->__buffer[0] = CCSV_NULL_CHAR; /* Reset the buffer */ + else + reader->__buffer_pos = row_pos; + + reader->rows_read++; + reader->status = CCSV_SUCCESS; + return row; + } + + int _is_buffer_empty(ccsv_reader *reader) + { + return reader->__buffer[0] == CCSV_NULL_CHAR; + } + + /* Writer */ + + ccsv_writer *ccsv_init_writer(ccsv_writer_options *options, short *status) + { + char delim, quote_char, escape_char; + WriterState state = WRITER_NOT_STARTED; + if (options == NULL) + { + delim = DEFAULT_DELIMITER; + quote_char = DEFAULT_QUOTE_CHAR; + escape_char = DEFAULT_ESCAPE_CHAR; + } + else + { + // It is not mandatory to pass all options to options struct + // So check if the option is passed or not, if not then use the default value + if (options->delim == CCSV_NULL_CHAR) + delim = DEFAULT_DELIMITER; + + else + delim = options->delim; + + if (options->quote_char == CCSV_NULL_CHAR) + quote_char = DEFAULT_QUOTE_CHAR; + + else + quote_char = options->quote_char; + + if (options->escape_char == CCSV_NULL_CHAR) + escape_char = DEFAULT_ESCAPE_CHAR; + + else + escape_char = options->escape_char; + } + + // Writer + ccsv_writer *writer = (ccsv_writer *)malloc(sizeof(ccsv_writer)); + if (writer == NULL) + { + if (status != NULL) + *status = CCSV_ERNOMEM; + return NULL; + } + writer->__delim = delim; + writer->__quote_char = quote_char; + writer->__escape_char = escape_char; + writer->__state = state; + + writer->write_status = WRITER_NOT_STARTED; + writer->object_type = CCSV_WRITER; + + return writer; + } + + int ccsv_write(ccsv_writer *writer, ccsv_row row) + { + if (writer == NULL) + return WRITE_ERNOTSTARTED; + + if (writer->__fp == NULL) + return CCSV_ERNULLFP; + + return write_row(writer->__fp, writer, row); + } + + int ccsv_write_from_array(ccsv_writer *writer, char **fields, int fields_len) + { + if (writer == NULL) + return WRITE_ERNOTSTARTED; + + if (writer->__fp == NULL) + return CCSV_ERNULLFP; + + return write_row_from_array(writer->__fp, writer, fields, fields_len); + } + + int write_row(FILE *fp, ccsv_writer *writer, ccsv_row row) + { + const int fields_count = row.fields_count; + char **fields = row.fields; + return (write_row_from_array(fp, writer, fields, fields_count)); + } + + int write_row_from_array(FILE *fp, ccsv_writer *writer, char **fields, int row_len) + { + CCSV_WRITE_ROW_START(fp, writer); + RETURN_IF_WRITE_ERROR(writer, WRITE_STARTED); + + for (int i = 0; i < row_len; i++) + { + const char *field = fields[i]; + CCSV_WRITE_FIELD(fp, writer, field); + RETURN_IF_WRITE_ERROR(writer, WRITE_SUCCESS); + } + CCSV_WRITE_ROW_END(fp, writer, NULL); + RETURN_IF_WRITE_ERROR(writer, WRITE_ENDED); + + writer->write_status = WRITE_SUCCESS; + return WRITE_SUCCESS; + } + + int _write_row_start(FILE *fp, ccsv_writer *writer) + { + long file_size; + char last_char; + + switch (writer->__state) + { + case WRITER_NOT_STARTED: + writer->__state = WRITER_ROW_START; /* Start writing row */ + + /* Move the file pointer to the end to get the file size */ + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + + /* Move the pointer to the penultimate position */ + fseek(fp, -1, SEEK_END); + + last_char = fgetc(fp); + + if (last_char != CCSV_LF && last_char != CCSV_CR && file_size > 0) + { + fputc(CCSV_CR, fp); + fputc(CCSV_LF, fp); + } + + /* Rewind the file pointer */ + fseek(fp, -file_size, SEEK_END); + break; + + case WRITER_ROW_END: + writer->__state = WRITER_ROW_START; /* Start writing row */ + break; + + case WRITER_WRITING_FIELD: + case WRITER_ROW_START: + writer->__state = WRITER_ROW_END; + writer->write_status = WRITE_ERALWRITING; /* Already writing field */ + return WRITE_ERALWRITING; + + default: + break; + } + + writer->write_status = WRITE_STARTED; + return WRITE_STARTED; + } + + int _write_row_end(FILE *fp, ccsv_writer *writer) + { + switch (writer->__state) + { + case WRITER_NOT_STARTED: + writer->__state = WRITER_ROW_END; + writer->write_status = WRITE_ERNOTSTARTED; /* Not started writing, CSV_WRITE_ROW_START() not called */ + return WRITE_ERNOTSTARTED; + + case WRITER_ROW_START: + case WRITER_WRITING_FIELD: + writer->__state = WRITER_ROW_END; + fputc(CCSV_CR, fp); + fputc(CCSV_LF, fp); + break; + + case WRITER_ROW_END: + writer->__state = WRITER_NOT_STARTED; /* Reset the state */ + break; + + default: + break; + } + + writer->write_status = WRITE_ENDED; + return WRITE_ENDED; + } + + int _write_field(FILE *fp, ccsv_writer *writer, const char *string) + { + WriterState state = writer->__state; + if (state != WRITER_ROW_START && state != WRITER_WRITING_FIELD) + { + /* Not started writing, CSV_WRITE_ROW_START() not called */ + writer->write_status = WRITE_ERNOTSTARTED; + return WRITE_ERNOTSTARTED; + } + + const char DELIM = writer->__delim; + const char QUOTE_CHAR = writer->__quote_char; + const char ESCAPE_CHAR = writer->__escape_char; + + int inside_quotes = 0; + + size_t string_len = strlen(string); + + char ch; + for (size_t i = 0; i < string_len; i++) + { + ch = string[i]; + if (ch == DELIM || ch == QUOTE_CHAR || ch == CCSV_CR || ch == CCSV_LF) + { + inside_quotes = 1; + break; + } + } + + if (inside_quotes) + { + fputc(QUOTE_CHAR, fp); + for (size_t i = 0; i < string_len; i++) + { + ch = string[i]; + /* Escape the quote character */ + if (ch == QUOTE_CHAR) + fputc(ESCAPE_CHAR, fp); + + fputc(ch, fp); + } + fputc(QUOTE_CHAR, fp); + } + else + fputs(string, fp); + + writer->write_status = WRITE_SUCCESS; + return WRITE_SUCCESS; + } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/ccsv/tests/Makefile b/lib/ccsv/tests/Makefile new file mode 100644 index 0000000..ab2471e --- /dev/null +++ b/lib/ccsv/tests/Makefile @@ -0,0 +1,8 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -I../include + +%: %.c + $(CC) $(CFLAGS) -o $@.out $< ../src/ccsv.c + +clean: + rm -f *.out diff --git a/lib/ccsv/tests/test_ccsv.c b/lib/ccsv/tests/test_ccsv.c new file mode 100644 index 0000000..220a4d5 --- /dev/null +++ b/lib/ccsv/tests/test_ccsv.c @@ -0,0 +1,9 @@ +#include +#include + +#include "ccsv.h" + +int main(void) +{ + return 0; +} \ No newline at end of file diff --git a/lib/microtar/LICENSE b/lib/microtar/LICENSE new file mode 100644 index 0000000..7e3bf17 --- /dev/null +++ b/lib/microtar/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 rxi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/microtar/README.md b/lib/microtar/README.md new file mode 100644 index 0000000..42acf49 --- /dev/null +++ b/lib/microtar/README.md @@ -0,0 +1,99 @@ +# microtar +A lightweight tar library written in ANSI C + + +## Basic Usage +The library consists of `microtar.c` and `microtar.h`. These two files can be +dropped into an existing project and compiled along with it. + + +#### Reading +```c +mtar_t tar; +mtar_header_t h; +char *p; + +/* Open archive for reading */ +mtar_open(&tar, "test.tar", "r"); + +/* Print all file names and sizes */ +while ( (mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) { + printf("%s (%d bytes)\n", h.name, h.size); + mtar_next(&tar); +} + +/* Load and print contents of file "test.txt" */ +mtar_find(&tar, "test.txt", &h); +p = calloc(1, h.size + 1); +mtar_read_data(&tar, p, h.size); +printf("%s", p); +free(p); + +/* Close archive */ +mtar_close(&tar); +``` + +#### Writing +```c +mtar_t tar; +const char *str1 = "Hello world"; +const char *str2 = "Goodbye world"; + +/* Open archive for writing */ +mtar_open(&tar, "test.tar", "w"); + +/* Write strings to files `test1.txt` and `test2.txt` */ +mtar_write_file_header(&tar, "test1.txt", strlen(str1)); +mtar_write_data(&tar, str1, strlen(str1)); +mtar_write_file_header(&tar, "test2.txt", strlen(str2)); +mtar_write_data(&tar, str2, strlen(str2)); + +/* Finalize -- this needs to be the last thing done before closing */ +mtar_finalize(&tar); + +/* Close archive */ +mtar_close(&tar); +``` + + +## Error handling +All functions which return an `int` will return `MTAR_ESUCCESS` if the operation +is successful. If an error occurs an error value less-than-zero will be +returned; this value can be passed to the function `mtar_strerror()` to get its +corresponding error string. + + +## Wrapping a stream +If you want to read or write from something other than a file, the `mtar_t` +struct can be manually initialized with your own callback functions and a +`stream` pointer. + +All callback functions are passed a pointer to the `mtar_t` struct as their +first argument. They should return `MTAR_ESUCCESS` if the operation succeeds +without an error, or an integer below zero if an error occurs. + +After the `stream` field has been set, all required callbacks have been set and +all unused fields have been zeroset the `mtar_t` struct can be safely used with +the microtar functions. `mtar_open` *should not* be called if the `mtar_t` +struct was initialized manually. + +#### Reading +The following callbacks should be set for reading an archive from a stream: + +Name | Arguments | Description +--------|------------------------------------------|--------------------------- +`read` | `mtar_t *tar, void *data, unsigned size` | Read data from the stream +`seek` | `mtar_t *tar, unsigned pos` | Set the position indicator +`close` | `mtar_t *tar` | Close the stream + +#### Writing +The following callbacks should be set for writing an archive to a stream: + +Name | Arguments | Description +--------|------------------------------------------------|--------------------- +`write` | `mtar_t *tar, const void *data, unsigned size` | Write data to the stream + + +## License +This library is free software; you can redistribute it and/or modify it under +the terms of the MIT license. See [LICENSE](LICENSE) for details. diff --git a/lib/microtar/src/microtar.c b/lib/microtar/src/microtar.c new file mode 100644 index 0000000..4b89776 --- /dev/null +++ b/lib/microtar/src/microtar.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2017 rxi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "microtar.h" + +typedef struct { + char name[100]; + char mode[8]; + char owner[8]; + char group[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char type; + char linkname[100]; + char _padding[255]; +} mtar_raw_header_t; + + +static unsigned round_up(unsigned n, unsigned incr) { + return n + (incr - n % incr) % incr; +} + + +static unsigned checksum(const mtar_raw_header_t* rh) { + unsigned i; + unsigned char *p = (unsigned char*) rh; + unsigned res = 256; + for (i = 0; i < offsetof(mtar_raw_header_t, checksum); i++) { + res += p[i]; + } + for (i = offsetof(mtar_raw_header_t, type); i < sizeof(*rh); i++) { + res += p[i]; + } + return res; +} + + +static int tread(mtar_t *tar, void *data, unsigned size) { + int err = tar->read(tar, data, size); + tar->pos += size; + return err; +} + + +static int twrite(mtar_t *tar, const void *data, unsigned size) { + int err = tar->write(tar, data, size); + tar->pos += size; + return err; +} + + +static int write_null_bytes(mtar_t *tar, int n) { + int i, err; + char nul = '\0'; + for (i = 0; i < n; i++) { + err = twrite(tar, &nul, 1); + if (err) { + return err; + } + } + return MTAR_ESUCCESS; +} + + +static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) { + unsigned chksum1, chksum2; + + /* If the checksum starts with a null byte we assume the record is NULL */ + if (*rh->checksum == '\0') { + return MTAR_ENULLRECORD; + } + + /* Build and compare checksum */ + chksum1 = checksum(rh); + sscanf(rh->checksum, "%o", &chksum2); + if (chksum1 != chksum2) { + return MTAR_EBADCHKSUM; + } + + /* Load raw header into header */ + sscanf(rh->mode, "%o", &h->mode); + sscanf(rh->owner, "%o", &h->owner); + sscanf(rh->size, "%o", &h->size); + sscanf(rh->mtime, "%o", &h->mtime); + h->type = rh->type; + strcpy(h->name, rh->name); + strcpy(h->linkname, rh->linkname); + + return MTAR_ESUCCESS; +} + + +static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) { + unsigned chksum; + + /* Load header into raw header */ + memset(rh, 0, sizeof(*rh)); + sprintf(rh->mode, "%o", h->mode); + sprintf(rh->owner, "%o", h->owner); + sprintf(rh->size, "%o", h->size); + sprintf(rh->mtime, "%o", h->mtime); + rh->type = h->type ? h->type : MTAR_TREG; + strcpy(rh->name, h->name); + strcpy(rh->linkname, h->linkname); + + /* Calculate and write checksum */ + chksum = checksum(rh); + sprintf(rh->checksum, "%06o", chksum); + rh->checksum[7] = ' '; + + return MTAR_ESUCCESS; +} + + +const char* mtar_strerror(int err) { + switch (err) { + case MTAR_ESUCCESS : return "success"; + case MTAR_EFAILURE : return "failure"; + case MTAR_EOPENFAIL : return "could not open"; + case MTAR_EREADFAIL : return "could not read"; + case MTAR_EWRITEFAIL : return "could not write"; + case MTAR_ESEEKFAIL : return "could not seek"; + case MTAR_EBADCHKSUM : return "bad checksum"; + case MTAR_ENULLRECORD : return "null record"; + case MTAR_ENOTFOUND : return "file not found"; + } + return "unknown error"; +} + + +static int file_write(mtar_t *tar, const void *data, unsigned size) { + unsigned res = fwrite(data, 1, size, tar->stream); + return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL; +} + +static int file_read(mtar_t *tar, void *data, unsigned size) { + unsigned res = fread(data, 1, size, tar->stream); + return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL; +} + +static int file_seek(mtar_t *tar, unsigned offset) { + int res = fseek(tar->stream, offset, SEEK_SET); + return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL; +} + +static int file_close(mtar_t *tar) { + fclose(tar->stream); + return MTAR_ESUCCESS; +} + + +int mtar_open(mtar_t *tar, const char *filename, const char *mode) { + int err; + mtar_header_t h; + + /* Init tar struct and functions */ + memset(tar, 0, sizeof(*tar)); + tar->write = file_write; + tar->read = file_read; + tar->seek = file_seek; + tar->close = file_close; + + /* Assure mode is always binary */ + if ( strchr(mode, 'r') ) mode = "rb"; + if ( strchr(mode, 'w') ) mode = "wb"; + if ( strchr(mode, 'a') ) mode = "ab"; + /* Open file */ + tar->stream = fopen(filename, mode); + if (!tar->stream) { + return MTAR_EOPENFAIL; + } + /* Read first header to check it is valid if mode is `r` */ + if (*mode == 'r') { + err = mtar_read_header(tar, &h); + if (err != MTAR_ESUCCESS) { + mtar_close(tar); + return err; + } + } + + /* Return ok */ + return MTAR_ESUCCESS; +} + + +int mtar_close(mtar_t *tar) { + return tar->close(tar); +} + + +int mtar_seek(mtar_t *tar, unsigned pos) { + int err = tar->seek(tar, pos); + tar->pos = pos; + return err; +} + + +int mtar_rewind(mtar_t *tar) { + tar->remaining_data = 0; + tar->last_header = 0; + return mtar_seek(tar, 0); +} + + +int mtar_next(mtar_t *tar) { + int err, n; + mtar_header_t h; + /* Load header */ + err = mtar_read_header(tar, &h); + if (err) { + return err; + } + /* Seek to next record */ + n = round_up(h.size, 512) + sizeof(mtar_raw_header_t); + return mtar_seek(tar, tar->pos + n); +} + + +int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h) { + int err; + mtar_header_t header; + /* Start at beginning */ + err = mtar_rewind(tar); + if (err) { + return err; + } + /* Iterate all files until we hit an error or find the file */ + while ( (err = mtar_read_header(tar, &header)) == MTAR_ESUCCESS ) { + if ( !strcmp(header.name, name) ) { + if (h) { + *h = header; + } + return MTAR_ESUCCESS; + } + mtar_next(tar); + } + /* Return error */ + if (err == MTAR_ENULLRECORD) { + err = MTAR_ENOTFOUND; + } + return err; +} + + +int mtar_read_header(mtar_t *tar, mtar_header_t *h) { + int err; + mtar_raw_header_t rh; + /* Save header position */ + tar->last_header = tar->pos; + /* Read raw header */ + err = tread(tar, &rh, sizeof(rh)); + if (err) { + return err; + } + /* Seek back to start of header */ + err = mtar_seek(tar, tar->last_header); + if (err) { + return err; + } + /* Load raw header into header struct and return */ + return raw_to_header(h, &rh); +} + + +int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) { + int err; + /* If we have no remaining data then this is the first read, we get the size, + * set the remaining data and seek to the beginning of the data */ + if (tar->remaining_data == 0) { + mtar_header_t h; + /* Read header */ + err = mtar_read_header(tar, &h); + if (err) { + return err; + } + /* Seek past header and init remaining data */ + err = mtar_seek(tar, tar->pos + sizeof(mtar_raw_header_t)); + if (err) { + return err; + } + tar->remaining_data = h.size; + } + /* Read data */ + err = tread(tar, ptr, size); + if (err) { + return err; + } + tar->remaining_data -= size; + /* If there is no remaining data we've finished reading and seek back to the + * header */ + if (tar->remaining_data == 0) { + return mtar_seek(tar, tar->last_header); + } + return MTAR_ESUCCESS; +} + + +int mtar_write_header(mtar_t *tar, const mtar_header_t *h) { + mtar_raw_header_t rh; + /* Build raw header and write */ + header_to_raw(&rh, h); + tar->remaining_data = h->size; + return twrite(tar, &rh, sizeof(rh)); +} + + +int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) { + mtar_header_t h; + /* Build header */ + memset(&h, 0, sizeof(h)); + strcpy(h.name, name); + h.size = size; + h.type = MTAR_TREG; + h.mode = 0664; + /* Write header */ + return mtar_write_header(tar, &h); +} + + +int mtar_write_dir_header(mtar_t *tar, const char *name) { + mtar_header_t h; + /* Build header */ + memset(&h, 0, sizeof(h)); + strcpy(h.name, name); + h.type = MTAR_TDIR; + h.mode = 0775; + /* Write header */ + return mtar_write_header(tar, &h); +} + + +int mtar_write_data(mtar_t *tar, const void *data, unsigned size) { + int err; + /* Write data */ + err = twrite(tar, data, size); + if (err) { + return err; + } + tar->remaining_data -= size; + /* Write padding if we've written all the data for this file */ + if (tar->remaining_data == 0) { + return write_null_bytes(tar, round_up(tar->pos, 512) - tar->pos); + } + return MTAR_ESUCCESS; +} + + +int mtar_finalize(mtar_t *tar) { + /* Write two NULL records */ + return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2); +} diff --git a/lib/microtar/src/microtar.h b/lib/microtar/src/microtar.h new file mode 100644 index 0000000..f4a62d1 --- /dev/null +++ b/lib/microtar/src/microtar.h @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2017 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See `microtar.c` for details. + */ + +#ifndef MICROTAR_H +#define MICROTAR_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#define MTAR_VERSION "0.1.0" + +enum { + MTAR_ESUCCESS = 0, + MTAR_EFAILURE = -1, + MTAR_EOPENFAIL = -2, + MTAR_EREADFAIL = -3, + MTAR_EWRITEFAIL = -4, + MTAR_ESEEKFAIL = -5, + MTAR_EBADCHKSUM = -6, + MTAR_ENULLRECORD = -7, + MTAR_ENOTFOUND = -8 +}; + +enum { + MTAR_TREG = '0', + MTAR_TLNK = '1', + MTAR_TSYM = '2', + MTAR_TCHR = '3', + MTAR_TBLK = '4', + MTAR_TDIR = '5', + MTAR_TFIFO = '6' +}; + +typedef struct { + unsigned mode; + unsigned owner; + unsigned size; + unsigned mtime; + unsigned type; + char name[100]; + char linkname[100]; +} mtar_header_t; + + +typedef struct mtar_t mtar_t; + +struct mtar_t { + int (*read)(mtar_t *tar, void *data, unsigned size); + int (*write)(mtar_t *tar, const void *data, unsigned size); + int (*seek)(mtar_t *tar, unsigned pos); + int (*close)(mtar_t *tar); + void *stream; + unsigned pos; + unsigned remaining_data; + unsigned last_header; +}; + + +const char* mtar_strerror(int err); + +int mtar_open(mtar_t *tar, const char *filename, const char *mode); +int mtar_close(mtar_t *tar); + +int mtar_seek(mtar_t *tar, unsigned pos); +int mtar_rewind(mtar_t *tar); +int mtar_next(mtar_t *tar); +int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h); +int mtar_read_header(mtar_t *tar, mtar_header_t *h); +int mtar_read_data(mtar_t *tar, void *ptr, unsigned size); + +int mtar_write_header(mtar_t *tar, const mtar_header_t *h); +int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size); +int mtar_write_dir_header(mtar_t *tar, const char *name); +int mtar_write_data(mtar_t *tar, const void *data, unsigned size); +int mtar_finalize(mtar_t *tar); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/kospm.c b/src/kospm.c new file mode 100644 index 0000000..dba9ffa --- /dev/null +++ b/src/kospm.c @@ -0,0 +1,37 @@ +/* + * kospm.c + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#include "../include/kospm.h" + +int kospm_install(char *root_dir, char *pkg_file){ + kospm_db_t *db = kospm_db_open(root_dir); + kospm_package_list_t *package_list = kospm_package_list_open(pkg_file); + + kospm_db_packages_add(db, package_list->packages); + kospm_db_files_add(db, package_list->files); + + kospm_package_list_close(package_list); + kospm_db_save(db); + kospm_db_close(db); + + return 0; +} diff --git a/src/kospm_common.c b/src/kospm_common.c new file mode 100644 index 0000000..60e64a1 --- /dev/null +++ b/src/kospm_common.c @@ -0,0 +1,36 @@ +#include +#include "../include/kospm_common.h" + +kospm_list_t* _kospm_csv_header_get(FILE *file){ + ccsv_reader *reader = ccsv_init_reader(&_kospm_csv_reader_options); + kospm_list_t *header = (kospm_list_t*)malloc(sizeof(kospm_list_t)); + ccsv_row *row; + kospm_csv_header_column *head_elem; + + fseek(fp, 0, SEEK_SET); + if ((row = read_row(fp, reader)) != NULL){ + for (int i = 0; i < row->fields_count; i++){ + head_elem->col_number = i; + head_elem->col_name = row->fields[i]; + + kospm_list_add(header, head_elem); + } + } + + ccsv_free_row(row); + free(reader); + + return header; +} + +int _kospm_csv_col_get(kospm_list_t* col_list, char* col_name){ + kospm_list_t *current = col_list; + + while(strcmp(current->data->col_name, col_name) || current!=NULL){ + current=current->next; + } + + if(current==NULL){return -1;} + + return current->data->col_num; +} diff --git a/src/kospm_db.c b/src/kospm_db.c new file mode 100644 index 0000000..e527b13 --- /dev/null +++ b/src/kospm_db.c @@ -0,0 +1,149 @@ +/* + * kospm_db.c + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#include +#include +#include "../include/kospm_db.h" +#include "../include/kospm_common.h" + +int _kospm_db_metadata_list_write(FILE* metadata_table, kospm_list_t *metadata_list){ + kospm_list_t *metadata_col_list = _kospm_csv_header_get(db->metadata_table); + kospm_list_t *current = metadata_list; + + short *write_status; + ccsv_writer *writer = ccsv_init_writer(&_kospm_csv_writer_options, write_status); + + char **metadata = malloc(12*sizeof(char*)); + + while(*current!=NULL){ + metadata[_kospm_csv_col_get(metadata_col_list, "name")] = current->data->pkg_name; + metadata[_kospm_csv_col_get(metadata_col_list, "version")] = current->data->pkg_version; + metadata[_kospm_csv_col_get(metadata_col_list, "description")] = current->data->pkg_description; + + write_row_from_array(metadata_table, writer, metadata, 12); + } + + free(metadata); + + return 0; +} + +int _kospm_db_files_list_write(FILE* files_table, kospm_list_t *file_list){ + kospm_list_t *files_col_list = _kospm_csv_header_get(db->files_table); + kospm_list_t *current = file_list; + + short *write_status; + ccsv_writer *writer = ccsv_init_writer(&_kospm_csv_writer_options, write_status); + + char **file = malloc(4*sizeof(char*)); + + while(*current!=NULL){ + file[_kospm_csv_col_get(files_col_list, "pkgname")] = current->data->pkg_name; + file[_kospm_csv_col_get(files_col_list, "filename")] = current->data->file_name; + + write_row_from_array(metadata_table, writer, metadata, 4); + } + + free(metadata); + + return 0; +} + +kospm_db_t* kospm_db_init(char *root_dir){ + kospm_db_t *db = (kospm_db_t*)malloc(sizeof(kospm_db_t)); + db->path = strcat(root_dir, DB_FOLDER); + + db->metadata_table = fopen(strcat(db->path, METADATA_TABLE), "a+"); + db->files_table = fopen(strcat(db->path, FILES_TABLE), "a+"); + + fprintf(metadata_table, "name, version, description"); + fprintf(files_table, "pkgname, filename"); + + freopen(strcat(db->path, METADATA_TABLE), "r+", db->metadata_table); + freopen(strcat(db->path, FILES_TABLE), "r+", db->files_table); + return db; +} + +kospm_db_t* kospm_db_open(char *root_dir){ + kospm_db_t *db = (kospm_db_t*)malloc(sizeof(kospm_db_t)); + + db->path = strcat(root_dir, DB_FOLDER); + db->metadata_table = fopen(strcat(db->path, METADATA_TABLE), "r+"); + db->files_table = fopen(strcat(db->path, FILES_TABLE), "r+"); + db->packages = kospm_package_metadata_get(metadata_table); + db->files = kospm_package_files_get(files_table); + + return db; +} + +int kospm_db_save(kospm_db_t *db){ + _kospm_db_metadata_list_write(db->metadata_table, db->packages); + _kospm_db_files_list_write(db->files_table, db->files); + + return 0; +} + +int kospm_db_close(kospm_db_t *db){ + kospm_list_free(db->files); + kospm_list_free(db->packages); + + fclose(db->files_table); + fclose(db->metadata_table); + + free(db); + + return 0; +} + +int kospm_db_package_add(kospm_db_t *db, kospm_package_t *package){ + kospm_list_add(db->packages, package); + + return 0; +} + +int kospm_db_packages_add(kospm_db_t *db, kospm_list_t *package_list){ + kospm_list_t *current = package_list; + while(*current!=NULL){ + kospm_list_add(db->packages, current->data); + + *current = package_list->next; + } + + return 0; +} + +int kospm_db_file_add(kospm_db_t *db, char *pkg_name, kospm_package_file_t *file){ + kospm_list_add(db->files, file); + + return 0; +} + +int kospm_db_files_add(kospm_db_t *db, kospm_list_t *file_list){ + kospm_list_t *current = file_list; + while(*current!=NULL){ + kospm_list_add(db->files, current->data); + + *current = package_list->next; + } + + return 0; +} diff --git a/src/kospm_list.c b/src/kospm_list.c new file mode 100644 index 0000000..452ae3b --- /dev/null +++ b/src/kospm_list.c @@ -0,0 +1,73 @@ +/* + * kospm_list.c + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#include "../include/kospm_list.h" + +kospm_list_t* kospm_list_new(void *data) { + kospm_list_t *node = (kospm_list_t*)malloc(sizeof(kospm_list_t)); + node->data = data; + node->next = NULL; + node->prev = NULL; + return node; +} + +void kospm_list_add(kospm_list_t **list, void *data) { + kospm_list_t *new_node = kospm_list_new(data); + if (*list == NULL) { + *list = new_node; + } else { + kospm_list_t *current = *list; + while (current->next != NULL) { + current = current->next; + } + current->next = new_node; + new_node->prev = current; + } +} + + +void kospm_list_del_by_value(kospm_list_t **list, void *data, int (*cmp)(void *, void *)) { + kospm_list_t *current = *list; + while (current != NULL) { + if (cmp(current->data, data) == 0) { + if (current->prev != NULL) { + current->prev->next = current->next; + } else { + *list = current->next; + } + if (current->next != NULL) { + current->next->prev = current->prev; + } + free(current); + break; + } + current = current->next; + } +} + +void kospm_list_free(kospm_list_t *list) { + while (list != NULL) { + kospm_list_t *temp = list; + list = list->next; + free(temp); + } +} diff --git a/src/kospm_package.c b/src/kospm_package.c new file mode 100644 index 0000000..71abba6 --- /dev/null +++ b/src/kospm_package.c @@ -0,0 +1,116 @@ +/* + * kospm_package.c + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ + +#include +#include +#include "../lib/microtar/src/microtar.h" +#include "../include/kospm_tar.h" +#include "../lib/ccsv/include/ccsv.h" +#include "../include/kospm_package.h" +#include "../include/kospm_common.h" + + + +kospm_list_t* kospm_package_files_get(FILE* fp){ + kospm_list_t *file_list = (kospm_list_t*)malloc(sizeof(kospm_list_t)); + kospm_package_file_t *file; + char *files_path = strcat(CACHE, FILES); + char *files_fields[] = {"pkgname", "filename"}; + short *read_status; + ccsv_reader *reader = ccsv_init_reader(&_kospm_csv_reader_options, read_status); + kospm_list_t *files_col_list = _kospm_csv_header_get(fp); + + ccsv_row *row; + while ((row = read_row(fp, reader)) != NULL) { + file = (kospm_package_file_t*)malloc(sizeof(kospm_package_file_t)); + + file->pkg_name = row->fields[_kospm_csv_col_number_get(files_col_list, files_fields[0])]; + file->file_name = row->fields[_kospm_csv_col_number_get(files_col_list, files_fields[1])]; + + kospm_list_add(file_list, file); + } + + kospm_list_free(files_col_list); + ccsv_free_row(row); + free(reader); + + return file_list; +} + +kospm_list_t* kospm_package_metadata_get(FILE* fp){ + kospm_list_t *metadata_list = (kospm_list_t*)malloc(sizeof(kospm_list_t)); + kospm_package_t *package; + char *pkginfo_path = strcat(CACHE, PKGINFO); + char *metadata_fields[] = {"name", "version", "description"}; + short *read_status; + ccsv_reader *reader = ccsv_init_reader(&_kospm_csv_reader_options, read_status); + kospm_list_t *metadata_col_list = _kospm_csv_header_get(fp); + + ccsv_row *row; + while ((row = read_row(fp, reader)) != NULL) { + package = (kospm_package_t*)malloc(sizeof(kospm_package_t)); + + package->pkg_name = row.fields[_kospm_csv_col_get(metadata_col_list, metadata_fields[0])]; + package->pkg_version = row.fields[_kospm_csv_col_get(metadata_col_list, metadata_fields[1])]; + package->pkg_description = row.fields[_kospm_csv_col_get(metadata_col_list, metadata_fields[2])]; + + kospm_list_add(metadata_list, package); + } + + kospm_list_free(metadata_col_list); + ccsv_free_row(row); + free(reader); + + return metadata_list; +} + +kospm_package_list_t *kospm_package_list_open(char *pkg_file){ + kospm_package_list_t *package_list = (kospm_package_list_t*)malloc(sizeof(kospm_package_list_t)); + FILE* metadata_fp; + FILE* files_fp; + char *pkginfo_path = strcat(CACHE, PKGINFO); + char *files_path = strcat(CACHE, FILES); + + tar_unpack(pkg_file, PKGINFO, pkginfo_path); + tar_unpack(pkg_file, FILES, files_path); + metadata_fp = fopen(pkginfo_path, "r"); + files_fp = fopen(files_path, "r"); + + package_list->packages = kospm_package_metadata_get(metadata_fp); + + package_list->files = kospm_package_files_get(files_fp); + + free(reader); + fclose(metadata_fp); + fclose(files_fp); + + return package_list; +} + +int kospm_package_list_close(kospm_package_list_t *package_list){ + kospm_list_free(package_list->packages); + kospm_list_free(package_list->files); + free(package_list); + + return 0; +} diff --git a/src/kospm_tar.c b/src/kospm_tar.c new file mode 100644 index 0000000..1f2f2e6 --- /dev/null +++ b/src/kospm_tar.c @@ -0,0 +1,53 @@ +/* + * kospm_tar.c + * + * Copyright 2024 keXa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + */ +#include +#include "../include/kospm_tar.h" + +int tar_unpack(const char *tar_name, const char *src_file, + const char *dest_file){ + mtar_t tar; + mtar_header_t header; + + if(mtar_open(tar, tar_name, "r") != MTAR_ESUCCESS){return 1;} + + if (mtar_find(&tar, file_to_extract, &header) != MTAR_ESUCCESS) { + return 1; + } + + FILE *output_file = fopen(header.name, "wb"); + if (!output_file) { + mtar_close(&tar); + return 1; + } + + char *buffer = malloc(header.size); + if (mtar_read_data(&tar, buffer, header.size) == MTAR_ESUCCESS) { + fwrite(buffer, 1, header.size, output_file); + } + free(buffer); + fclose(output_file); + + mtar_close(&tar); + + return 0; +}